Query Monitor - Version 3.8.0

Version Description

Download this release

Release Info

Developer johnbillion
Plugin Icon 128x128 Query Monitor
Version 3.8.0
Comparing to
See all releases

Code changes from version 3.7.1 to 3.8.0

Files changed (91) hide show
  1. assets/query-monitor-dark.css +7 -6
  2. assets/query-monitor.css +7 -6
  3. assets/query-monitor.js +49 -43
  4. classes/Activation.php +26 -1
  5. classes/Backtrace.php +270 -117
  6. classes/CLI.php +10 -1
  7. classes/Collector.php +139 -12
  8. classes/Collectors.php +40 -1
  9. classes/Dispatcher.php +87 -5
  10. classes/Dispatchers.php +33 -1
  11. classes/Hook.php +11 -4
  12. classes/Output.php +17 -1
  13. classes/Plugin.php +25 -1
  14. classes/QM.php +48 -0
  15. classes/QueryMonitor.php +57 -23
  16. classes/Timer.php +86 -12
  17. classes/Util.php +162 -38
  18. classes/debug_bar.php +15 -0
  19. classes/debug_bar_panel.php +37 -2
  20. collectors/admin.php +33 -17
  21. collectors/assets.php +93 -38
  22. collectors/assets_scripts.php +14 -1
  23. collectors/assets_styles.php +11 -1
  24. collectors/block_editor.php +97 -18
  25. collectors/cache.php +17 -7
  26. collectors/caps.php +97 -57
  27. collectors/conditionals.php +13 -3
  28. collectors/db_callers.php +11 -1
  29. collectors/db_components.php +11 -1
  30. collectors/db_dupes.php +18 -9
  31. collectors/db_queries.php +93 -44
  32. collectors/debug_bar.php +35 -4
  33. collectors/environment.php +118 -59
  34. collectors/hooks.php +20 -8
  35. collectors/http.php +131 -46
  36. collectors/languages.php +64 -50
  37. collectors/logger.php +110 -21
  38. collectors/overview.php +25 -4
  39. collectors/php_errors.php +161 -77
  40. collectors/raw_request.php +25 -13
  41. collectors/redirects.php +32 -6
  42. collectors/request.php +31 -9
  43. collectors/theme.php +256 -64
  44. collectors/timing.php +91 -29
  45. collectors/transients.php +51 -30
  46. composer.json +77 -0
  47. dispatchers/AJAX.php +23 -1
  48. dispatchers/Html.php +206 -77
  49. dispatchers/REST.php +14 -1
  50. dispatchers/REST_Envelope.php +15 -4
  51. dispatchers/Redirect.php +15 -1
  52. dispatchers/WP_Die.php +35 -21
  53. output/Headers.php +7 -0
  54. output/Html.php +93 -25
  55. output/Raw.php +4 -5
  56. output/headers/overview.php +12 -2
  57. output/headers/php_errors.php +21 -11
  58. output/headers/redirects.php +12 -2
  59. output/html/admin.php +14 -1
  60. output/html/assets.php +54 -18
  61. output/html/assets_scripts.php +17 -3
  62. output/html/assets_styles.php +17 -3
  63. output/html/block_editor.php +40 -17
  64. output/html/caps.php +27 -10
  65. output/html/conditionals.php +26 -5
  66. output/html/db_callers.php +19 -2
  67. output/html/db_components.php +19 -2
  68. output/html/db_dupes.php +23 -2
  69. output/html/db_queries.php +93 -41
  70. output/html/debug_bar.php +16 -2
  71. output/html/environment.php +28 -13
  72. output/html/headers.php +30 -5
  73. output/html/hooks.php +23 -6
  74. output/html/http.php +36 -44
  75. output/html/languages.php +23 -2
  76. output/html/logger.php +54 -13
  77. output/html/overview.php +112 -44
  78. output/html/php_errors.php +47 -22
  79. output/html/request.php +24 -7
  80. output/html/theme.php +30 -10
  81. output/html/timing.php +24 -7
  82. output/html/transients.php +21 -4
  83. output/raw/cache.php +13 -2
  84. output/raw/conditionals.php +11 -0
  85. output/raw/db_queries.php +38 -10
  86. output/raw/http.php +14 -3
  87. output/raw/logger.php +13 -2
  88. output/raw/transients.php +12 -1
  89. query-monitor.php +5 -7
  90. readme.txt +24 -19
  91. wp-content/db.php +35 -29
assets/query-monitor-dark.css CHANGED
@@ -234,6 +234,7 @@ body.admin-color-light #wp-admin-bar-query-monitor:not(.qm-all-clear):not(:hover
234
  background: #23282d !important;
235
  border-top: 1px solid #50626f !important;
236
  bottom: 0 !important;
 
237
  direction: ltr !important;
238
  display: none;
239
  left: 0 !important;
@@ -942,7 +943,7 @@ body.admin-color-light #wp-admin-bar-query-monitor:not(.qm-all-clear):not(:hover
942
  #query-monitor-main .qm button.qm-filter-info:before {
943
  left: unset !important;
944
  right: 2px !important;
945
- content: "" !important;
946
  visibility: visible !important;
947
  }
948
  #query-monitor-main .qm a.qm-external-link:after,
@@ -955,14 +956,14 @@ body.admin-color-light #wp-admin-bar-query-monitor:not(.qm-all-clear):not(:hover
955
  visibility: visible !important;
956
  }
957
  #query-monitor-main .qm button.qm-filter-trigger:after {
958
- content: "" !important;
959
  }
960
  #query-monitor-main .qm a.qm-edit-link:after {
961
- content: "" !important;
962
  }
963
  #query-monitor-main .qm a.qm-external-link:after,
964
  #query-monitor-main .qm a.qm-link:after {
965
- content: "" !important;
966
  }
967
  #query-monitor-main #qm-ajax-errors {
968
  display: none;
@@ -1060,7 +1061,7 @@ body.admin-color-light #wp-admin-bar-query-monitor:not(.qm-all-clear):not(:hover
1060
  width: 16px !important;
1061
  }
1062
  #query-monitor-main .qm .qm-sortable-column .qm-sort-arrow::before {
1063
- content: "" !important;
1064
  position: absolute !important;
1065
  right: 0 !important;
1066
  top: 4px !important;
@@ -1073,7 +1074,7 @@ body.admin-color-light #wp-admin-bar-query-monitor:not(.qm-all-clear):not(:hover
1073
  color: #0073aa !important;
1074
  }
1075
  #query-monitor-main .qm .qm-sortable-column.qm-sorted-asc .qm-sort-arrow::before {
1076
- content: "" !important;
1077
  }
1078
  #query-monitor-main .qm button:focus,
1079
  #query-monitor-main .qm a:focus,
234
  background: #23282d !important;
235
  border-top: 1px solid #50626f !important;
236
  bottom: 0 !important;
237
+ contain: layout paint;
238
  direction: ltr !important;
239
  display: none;
240
  left: 0 !important;
943
  #query-monitor-main .qm button.qm-filter-info:before {
944
  left: unset !important;
945
  right: 2px !important;
946
+ content: "\f534" !important;
947
  visibility: visible !important;
948
  }
949
  #query-monitor-main .qm a.qm-external-link:after,
956
  visibility: visible !important;
957
  }
958
  #query-monitor-main .qm button.qm-filter-trigger:after {
959
+ content: "\f536" !important;
960
  }
961
  #query-monitor-main .qm a.qm-edit-link:after {
962
+ content: "\f464" !important;
963
  }
964
  #query-monitor-main .qm a.qm-external-link:after,
965
  #query-monitor-main .qm a.qm-link:after {
966
+ content: "\f504" !important;
967
  }
968
  #query-monitor-main #qm-ajax-errors {
969
  display: none;
1061
  width: 16px !important;
1062
  }
1063
  #query-monitor-main .qm .qm-sortable-column .qm-sort-arrow::before {
1064
+ content: "\f140" !important;
1065
  position: absolute !important;
1066
  right: 0 !important;
1067
  top: 4px !important;
1074
  color: #0073aa !important;
1075
  }
1076
  #query-monitor-main .qm .qm-sortable-column.qm-sorted-asc .qm-sort-arrow::before {
1077
+ content: "\f142" !important;
1078
  }
1079
  #query-monitor-main .qm button:focus,
1080
  #query-monitor-main .qm a:focus,
assets/query-monitor.css CHANGED
@@ -234,6 +234,7 @@ body.admin-color-light #wp-admin-bar-query-monitor:not(.qm-all-clear):not(:hover
234
  background: #fff !important;
235
  border-top: 1px solid #aaa !important;
236
  bottom: 0 !important;
 
237
  direction: ltr !important;
238
  display: none;
239
  left: 0 !important;
@@ -942,7 +943,7 @@ body.admin-color-light #wp-admin-bar-query-monitor:not(.qm-all-clear):not(:hover
942
  #query-monitor-main .qm button.qm-filter-info:before {
943
  left: unset !important;
944
  right: 2px !important;
945
- content: "" !important;
946
  visibility: visible !important;
947
  }
948
  #query-monitor-main .qm a.qm-external-link:after,
@@ -955,14 +956,14 @@ body.admin-color-light #wp-admin-bar-query-monitor:not(.qm-all-clear):not(:hover
955
  visibility: visible !important;
956
  }
957
  #query-monitor-main .qm button.qm-filter-trigger:after {
958
- content: "" !important;
959
  }
960
  #query-monitor-main .qm a.qm-edit-link:after {
961
- content: "" !important;
962
  }
963
  #query-monitor-main .qm a.qm-external-link:after,
964
  #query-monitor-main .qm a.qm-link:after {
965
- content: "" !important;
966
  }
967
  #query-monitor-main #qm-ajax-errors {
968
  display: none;
@@ -1060,7 +1061,7 @@ body.admin-color-light #wp-admin-bar-query-monitor:not(.qm-all-clear):not(:hover
1060
  width: 16px !important;
1061
  }
1062
  #query-monitor-main .qm .qm-sortable-column .qm-sort-arrow::before {
1063
- content: "" !important;
1064
  position: absolute !important;
1065
  right: 0 !important;
1066
  top: 4px !important;
@@ -1073,7 +1074,7 @@ body.admin-color-light #wp-admin-bar-query-monitor:not(.qm-all-clear):not(:hover
1073
  color: #0073aa !important;
1074
  }
1075
  #query-monitor-main .qm .qm-sortable-column.qm-sorted-asc .qm-sort-arrow::before {
1076
- content: "" !important;
1077
  }
1078
  #query-monitor-main .qm button:focus,
1079
  #query-monitor-main .qm a:focus,
234
  background: #fff !important;
235
  border-top: 1px solid #aaa !important;
236
  bottom: 0 !important;
237
+ contain: layout paint;
238
  direction: ltr !important;
239
  display: none;
240
  left: 0 !important;
943
  #query-monitor-main .qm button.qm-filter-info:before {
944
  left: unset !important;
945
  right: 2px !important;
946
+ content: "\f534" !important;
947
  visibility: visible !important;
948
  }
949
  #query-monitor-main .qm a.qm-external-link:after,
956
  visibility: visible !important;
957
  }
958
  #query-monitor-main .qm button.qm-filter-trigger:after {
959
+ content: "\f536" !important;
960
  }
961
  #query-monitor-main .qm a.qm-edit-link:after {
962
+ content: "\f464" !important;
963
  }
964
  #query-monitor-main .qm a.qm-external-link:after,
965
  #query-monitor-main .qm a.qm-link:after {
966
+ content: "\f504" !important;
967
  }
968
  #query-monitor-main #qm-ajax-errors {
969
  display: none;
1061
  width: 16px !important;
1062
  }
1063
  #query-monitor-main .qm .qm-sortable-column .qm-sort-arrow::before {
1064
+ content: "\f140" !important;
1065
  position: absolute !important;
1066
  right: 0 !important;
1067
  top: 4px !important;
1074
  color: #0073aa !important;
1075
  }
1076
  #query-monitor-main .qm .qm-sortable-column.qm-sorted-asc .qm-sort-arrow::before {
1077
+ content: "\f142" !important;
1078
  }
1079
  #query-monitor-main .qm button:focus,
1080
  #query-monitor-main .qm a:focus,
assets/query-monitor.js CHANGED
@@ -21,10 +21,10 @@ var QM_i18n = {
21
  number = parseFloat( number );
22
 
23
  var num_float = number.toFixed( decimals ),
24
- num_int = Math.floor( number ),
25
- num_str = num_int.toString(),
26
- fraction = num_float.substring( num_float.indexOf( '.' ) + 1, num_float.length ),
27
- o = '';
28
 
29
  if ( num_str.length > 3 ) {
30
  for ( i = num_str.length; i > 3; i -= 3 ) {
@@ -48,18 +48,18 @@ var QM_i18n = {
48
  if ( window.jQuery ) {
49
 
50
  jQuery( function($) {
51
- var toolbarHeight = $('#wpadminbar').length ? $('#wpadminbar').outerHeight() : 0;
52
- var minheight = 100;
53
- var maxheight = ( $(window).height() - toolbarHeight );
54
- var minwidth = 300;
55
- var maxwidth = $(window).width();
56
- var container = $('#query-monitor-main');
57
- var body = $('body');
58
- var body_margin = body.css('margin-bottom');
59
- var container_height_key = 'qm-container-height';
60
- var container_pinned_key = 'qm-' + ( $('body').hasClass('wp-admin') ? 'admin' : 'front' ) + '-container-pinned';
61
  var container_position_key = 'qm-container-position';
62
- var container_width_key = 'qm-container-width';
63
 
64
  if ( container.hasClass('qm-peek') ) {
65
  minheight = 27;
@@ -145,12 +145,12 @@ if ( window.jQuery ) {
145
 
146
  if ( selected_menu.length ) {
147
  var selected_menu_top = selected_menu.position().top - 27;
148
- var menu_height = $('#qm-panel-menu').height();
149
- var menu_scroll = $('#qm-panel-menu').scrollTop();
150
  selected_menu.closest('#qm-panel-menu > ul > li').addClass('qm-current-menu');
151
 
152
  var selected_menu_off_bottom = ( selected_menu_top > ( menu_height ) );
153
- var selected_menu_off_top = ( selected_menu_top < 0 );
154
 
155
  if ( selected_menu_off_bottom || selected_menu_off_top ) {
156
  $('#qm-panel-menu').scrollTop( selected_menu_top + menu_scroll - ( menu_height / 2 ) + ( selected_menu.outerHeight() / 2 ) );
@@ -217,13 +217,13 @@ if ( window.jQuery ) {
217
  container.find('.qm-filter').on('change',function(e){
218
 
219
  var filter = $(this).attr('data-filter'),
220
- table = $(this).closest('table'),
221
- tr = table.find('tbody tr[data-qm-' + filter + ']'),
222
  // Escape the following chars with a backslash before passing into jQ selectors: [ ] ( ) ' " \
223
- val = $(this).val().replace(/[[\]()'"\\]/g, "\\$&"),
224
- total = tr.removeClass('qm-hide-' + filter).length,
225
  hilite = $(this).attr('data-highlight'),
226
- time = 0;
227
 
228
  key = $(this).attr('id');
229
  if ( val ) {
@@ -270,7 +270,7 @@ if ( window.jQuery ) {
270
  });
271
 
272
  container.find('.qm-filter').each(function () {
273
- var key = $(this).attr('id');
274
  var value = localStorage.getItem( key );
275
  if ( value !== null ) {
276
  // Escape the following chars with a backslash before passing into jQ selectors: [ ] ( ) ' " \
@@ -284,7 +284,7 @@ if ( window.jQuery ) {
284
 
285
  container.find('.qm-filter-trigger').on('click',function(e){
286
  var filter = $(this).data('qm-filter'),
287
- value = $(this).data('qm-value'),
288
  target = $(this).data('qm-target');
289
  $('#qm-' + target).find('.qm-filter').not('[data-filter="' + filter + '"]').val('').removeClass('qm-highlight').trigger('change');
290
  $('#qm-' + target).find('[data-filter="' + filter + '"]').val(value).addClass('qm-highlight').trigger('change');
@@ -294,9 +294,9 @@ if ( window.jQuery ) {
294
  });
295
 
296
  container.find('.qm-toggle').on('click',function(e){
297
- var el = $(this);
298
  var currentState = el.attr('aria-expanded');
299
- var newState = 'true';
300
  if (currentState === 'true') {
301
  newState = 'false';
302
  }
@@ -323,7 +323,7 @@ if ( window.jQuery ) {
323
  container.find('.qm-highlighter').on('mouseenter',function(e){
324
 
325
  var subject = $(this).data('qm-highlight');
326
- var table = $(this).closest('table');
327
 
328
  if ( ! subject ) {
329
  return;
@@ -408,7 +408,7 @@ if ( window.jQuery ) {
408
  } );
409
 
410
  $('.qm-auth').on('click',function(e){
411
- var state = $('#qm-settings').data('qm-state');
412
  var action = ( 'off' === state ? 'on' : 'off' );
413
 
414
  $.ajax(qm_l10n.ajaxurl,{
@@ -435,7 +435,7 @@ if ( window.jQuery ) {
435
  editorSuccessIndicator.hide();
436
 
437
  $('.qm-editor-button').on('click',function(e){
438
- var state = $('#qm-settings').data('qm-state');
439
  var editor = $('#qm-editor-select').val();
440
 
441
  $.ajax(qm_l10n.ajaxurl,{
@@ -468,8 +468,8 @@ if ( window.jQuery ) {
468
  event.stopPropagation();
469
 
470
  resizerHeight = $(this).outerHeight() - 1;
471
- startY = container.outerHeight() + ( event.clientY || event.originalEvent.targetTouches[0].pageY );
472
- startX = container.outerWidth() + ( event.clientX || event.originalEvent.targetTouches[0].pageX );
473
 
474
  if ( ! container.hasClass('qm-show-right') ) {
475
  $(document).on('mousemove touchmove', qm_do_resizer_drag_vertical);
@@ -537,11 +537,11 @@ if ( window.jQuery ) {
537
  }
538
 
539
  $(window).on('resize', function(){
540
- var h = container.height();
541
- var w = container.width();
542
 
543
  maxheight = ( $(window).height() - toolbarHeight );
544
- maxwidth = $(window).width();
545
 
546
  if ( h < minheight ) {
547
  container.height( minheight );
@@ -616,21 +616,21 @@ if ( window.jQuery ) {
616
  * Author: Gajus Kuizinas <g.kuizinas@anuary.com>
617
  */
618
  (function ($) {
619
- $.qm = $.qm || {};
620
  $.qm.tableSort = function (settings) {
621
  // @param object columns NodeList table colums.
622
  // @param integer row_width defines the number of columns per row.
623
  var table_to_array = function (columns, row_width) {
624
  columns = Array.prototype.slice.call(columns, 0);
625
 
626
- var rows = [];
627
  var row_index = 0;
628
 
629
  for (var i = 0, j = columns.length; i < j; i += row_width) {
630
  var row = [];
631
 
632
  for (var k = 0; k < row_width; k++) {
633
- var e = columns[i + k];
634
  var data = e.dataset.qmSortWeight;
635
 
636
  if (data === undefined) {
@@ -658,7 +658,7 @@ if ( window.jQuery ) {
658
  var table = $(this);
659
 
660
  table.find('.qm-sortable-column').on('click', function (e) {
661
- var desc = ! $(this).hasClass('qm-sorted-desc');
662
  var index = $(this).index();
663
 
664
  table.find('thead th').removeClass('qm-sorted-asc qm-sorted-desc').attr('aria-sort','none');
@@ -670,8 +670,8 @@ if ( window.jQuery ) {
670
  }
671
 
672
  table.find('tbody').each(function () {
673
- var tbody = $(this);
674
- var rows = this.rows;
675
  var columns = this.querySelectorAll('th,td');
676
 
677
  if (this.data_matrix === undefined) {
@@ -722,6 +722,7 @@ if ( window.jQuery ) {
722
 
723
  window.addEventListener('load', function() {
724
  var main = document.getElementById( 'query-monitor-main' );
 
725
  var broken = document.getElementById( 'qm-broken' );
726
  var menu_item = document.getElementById( 'wp-admin-bar-query-monitor' );
727
 
@@ -750,7 +751,12 @@ window.addEventListener('load', function() {
750
  }
751
 
752
  if ( ! main ) {
753
- // QM's output has disappeared
754
- console.error( 'QM error from JS: QM output does not exist' );
 
 
 
 
 
755
  }
756
  } );
21
  number = parseFloat( number );
22
 
23
  var num_float = number.toFixed( decimals ),
24
+ num_int = Math.floor( number ),
25
+ num_str = num_int.toString(),
26
+ fraction = num_float.substring( num_float.indexOf( '.' ) + 1, num_float.length ),
27
+ o = '';
28
 
29
  if ( num_str.length > 3 ) {
30
  for ( i = num_str.length; i > 3; i -= 3 ) {
48
  if ( window.jQuery ) {
49
 
50
  jQuery( function($) {
51
+ var toolbarHeight = $('#wpadminbar').length ? $('#wpadminbar').outerHeight() : 0;
52
+ var minheight = 100;
53
+ var maxheight = ( $(window).height() - toolbarHeight );
54
+ var minwidth = 300;
55
+ var maxwidth = $(window).width();
56
+ var container = $('#query-monitor-main');
57
+ var body = $('body');
58
+ var body_margin = body.css('margin-bottom');
59
+ var container_height_key = 'qm-container-height';
60
+ var container_pinned_key = 'qm-' + ( $('body').hasClass('wp-admin') ? 'admin' : 'front' ) + '-container-pinned';
61
  var container_position_key = 'qm-container-position';
62
+ var container_width_key = 'qm-container-width';
63
 
64
  if ( container.hasClass('qm-peek') ) {
65
  minheight = 27;
145
 
146
  if ( selected_menu.length ) {
147
  var selected_menu_top = selected_menu.position().top - 27;
148
+ var menu_height = $('#qm-panel-menu').height();
149
+ var menu_scroll = $('#qm-panel-menu').scrollTop();
150
  selected_menu.closest('#qm-panel-menu > ul > li').addClass('qm-current-menu');
151
 
152
  var selected_menu_off_bottom = ( selected_menu_top > ( menu_height ) );
153
+ var selected_menu_off_top = ( selected_menu_top < 0 );
154
 
155
  if ( selected_menu_off_bottom || selected_menu_off_top ) {
156
  $('#qm-panel-menu').scrollTop( selected_menu_top + menu_scroll - ( menu_height / 2 ) + ( selected_menu.outerHeight() / 2 ) );
217
  container.find('.qm-filter').on('change',function(e){
218
 
219
  var filter = $(this).attr('data-filter'),
220
+ table = $(this).closest('table'),
221
+ tr = table.find('tbody tr[data-qm-' + filter + ']'),
222
  // Escape the following chars with a backslash before passing into jQ selectors: [ ] ( ) ' " \
223
+ val = $(this).val().replace(/[[\]()'"\\]/g, "\\$&"),
224
+ total = tr.removeClass('qm-hide-' + filter).length,
225
  hilite = $(this).attr('data-highlight'),
226
+ time = 0;
227
 
228
  key = $(this).attr('id');
229
  if ( val ) {
270
  });
271
 
272
  container.find('.qm-filter').each(function () {
273
+ var key = $(this).attr('id');
274
  var value = localStorage.getItem( key );
275
  if ( value !== null ) {
276
  // Escape the following chars with a backslash before passing into jQ selectors: [ ] ( ) ' " \
284
 
285
  container.find('.qm-filter-trigger').on('click',function(e){
286
  var filter = $(this).data('qm-filter'),
287
+ value = $(this).data('qm-value'),
288
  target = $(this).data('qm-target');
289
  $('#qm-' + target).find('.qm-filter').not('[data-filter="' + filter + '"]').val('').removeClass('qm-highlight').trigger('change');
290
  $('#qm-' + target).find('[data-filter="' + filter + '"]').val(value).addClass('qm-highlight').trigger('change');
294
  });
295
 
296
  container.find('.qm-toggle').on('click',function(e){
297
+ var el = $(this);
298
  var currentState = el.attr('aria-expanded');
299
+ var newState = 'true';
300
  if (currentState === 'true') {
301
  newState = 'false';
302
  }
323
  container.find('.qm-highlighter').on('mouseenter',function(e){
324
 
325
  var subject = $(this).data('qm-highlight');
326
+ var table = $(this).closest('table');
327
 
328
  if ( ! subject ) {
329
  return;
408
  } );
409
 
410
  $('.qm-auth').on('click',function(e){
411
+ var state = $('#qm-settings').data('qm-state');
412
  var action = ( 'off' === state ? 'on' : 'off' );
413
 
414
  $.ajax(qm_l10n.ajaxurl,{
435
  editorSuccessIndicator.hide();
436
 
437
  $('.qm-editor-button').on('click',function(e){
438
+ var state = $('#qm-settings').data('qm-state');
439
  var editor = $('#qm-editor-select').val();
440
 
441
  $.ajax(qm_l10n.ajaxurl,{
468
  event.stopPropagation();
469
 
470
  resizerHeight = $(this).outerHeight() - 1;
471
+ startY = container.outerHeight() + ( event.clientY || event.originalEvent.targetTouches[0].pageY );
472
+ startX = container.outerWidth() + ( event.clientX || event.originalEvent.targetTouches[0].pageX );
473
 
474
  if ( ! container.hasClass('qm-show-right') ) {
475
  $(document).on('mousemove touchmove', qm_do_resizer_drag_vertical);
537
  }
538
 
539
  $(window).on('resize', function(){
540
+ var h = container.height();
541
+ var w = container.width();
542
 
543
  maxheight = ( $(window).height() - toolbarHeight );
544
+ maxwidth = $(window).width();
545
 
546
  if ( h < minheight ) {
547
  container.height( minheight );
616
  * Author: Gajus Kuizinas <g.kuizinas@anuary.com>
617
  */
618
  (function ($) {
619
+ $.qm = $.qm || {};
620
  $.qm.tableSort = function (settings) {
621
  // @param object columns NodeList table colums.
622
  // @param integer row_width defines the number of columns per row.
623
  var table_to_array = function (columns, row_width) {
624
  columns = Array.prototype.slice.call(columns, 0);
625
 
626
+ var rows = [];
627
  var row_index = 0;
628
 
629
  for (var i = 0, j = columns.length; i < j; i += row_width) {
630
  var row = [];
631
 
632
  for (var k = 0; k < row_width; k++) {
633
+ var e = columns[i + k];
634
  var data = e.dataset.qmSortWeight;
635
 
636
  if (data === undefined) {
658
  var table = $(this);
659
 
660
  table.find('.qm-sortable-column').on('click', function (e) {
661
+ var desc = ! $(this).hasClass('qm-sorted-desc');
662
  var index = $(this).index();
663
 
664
  table.find('thead th').removeClass('qm-sorted-asc qm-sorted-desc').attr('aria-sort','none');
670
  }
671
 
672
  table.find('tbody').each(function () {
673
+ var tbody = $(this);
674
+ var rows = this.rows;
675
  var columns = this.querySelectorAll('th,td');
676
 
677
  if (this.data_matrix === undefined) {
722
 
723
  window.addEventListener('load', function() {
724
  var main = document.getElementById( 'query-monitor-main' );
725
+ var ceased = document.getElementById( 'query-monitor-ceased' );
726
  var broken = document.getElementById( 'qm-broken' );
727
  var menu_item = document.getElementById( 'wp-admin-bar-query-monitor' );
728
 
751
  }
752
 
753
  if ( ! main ) {
754
+ if ( ceased ) {
755
+ // QM was ceased
756
+ console.info( 'QM: collection and output was ceased' );
757
+ } else {
758
+ // QM's output has disappeared
759
+ console.error( 'QM error from JS: QM output does not exist' );
760
+ }
761
  }
762
  } );
classes/Activation.php CHANGED
@@ -7,6 +7,9 @@
7
 
8
  class QM_Activation extends QM_Plugin {
9
 
 
 
 
10
  protected function __construct( $file ) {
11
 
12
  # PHP version handling
@@ -16,7 +19,7 @@ class QM_Activation extends QM_Plugin {
16
  }
17
 
18
  # Filters
19
- add_filter( 'pre_update_option_active_plugins', array( $this, 'filter_active_plugins' ) );
20
  add_filter( 'pre_update_site_option_active_sitewide_plugins', array( $this, 'filter_active_sitewide_plugins' ) );
21
 
22
  # Activation and deactivation
@@ -28,6 +31,10 @@ class QM_Activation extends QM_Plugin {
28
 
29
  }
30
 
 
 
 
 
31
  public function activate( $sitewide = false ) {
32
  $db = WP_CONTENT_DIR . '/db.php';
33
  $create_symlink = defined( 'QM_DB_SYMLINK' ) ? QM_DB_SYMLINK : true;
@@ -44,6 +51,9 @@ class QM_Activation extends QM_Plugin {
44
 
45
  }
46
 
 
 
 
47
  public function deactivate() {
48
  $admins = QM_Util::get_admins();
49
 
@@ -59,6 +69,10 @@ class QM_Activation extends QM_Plugin {
59
 
60
  }
61
 
 
 
 
 
62
  public function filter_active_plugins( $plugins ) {
63
 
64
  // this needs to run on the cli too
@@ -76,6 +90,10 @@ class QM_Activation extends QM_Plugin {
76
 
77
  }
78
 
 
 
 
 
79
  public function filter_active_sitewide_plugins( $plugins ) {
80
 
81
  if ( empty( $plugins ) ) {
@@ -98,6 +116,9 @@ class QM_Activation extends QM_Plugin {
98
 
99
  }
100
 
 
 
 
101
  public function php_notice() {
102
  ?>
103
  <div id="qm_php_notice" class="notice notice-error">
@@ -115,6 +136,10 @@ class QM_Activation extends QM_Plugin {
115
  <?php
116
  }
117
 
 
 
 
 
118
  public static function init( $file = null ) {
119
 
120
  static $instance = null;
7
 
8
  class QM_Activation extends QM_Plugin {
9
 
10
+ /**
11
+ * @param string $file
12
+ */
13
  protected function __construct( $file ) {
14
 
15
  # PHP version handling
19
  }
20
 
21
  # Filters
22
+ add_filter( 'pre_update_option_active_plugins', array( $this, 'filter_active_plugins' ) );
23
  add_filter( 'pre_update_site_option_active_sitewide_plugins', array( $this, 'filter_active_sitewide_plugins' ) );
24
 
25
  # Activation and deactivation
31
 
32
  }
33
 
34
+ /**
35
+ * @param bool $sitewide
36
+ * @return void
37
+ */
38
  public function activate( $sitewide = false ) {
39
  $db = WP_CONTENT_DIR . '/db.php';
40
  $create_symlink = defined( 'QM_DB_SYMLINK' ) ? QM_DB_SYMLINK : true;
51
 
52
  }
53
 
54
+ /**
55
+ * @return void
56
+ */
57
  public function deactivate() {
58
  $admins = QM_Util::get_admins();
59
 
69
 
70
  }
71
 
72
+ /**
73
+ * @param array<int, string> $plugins
74
+ * @return array<int, string>
75
+ */
76
  public function filter_active_plugins( $plugins ) {
77
 
78
  // this needs to run on the cli too
90
 
91
  }
92
 
93
+ /**
94
+ * @param array<string, int> $plugins
95
+ * @return array<string, int>
96
+ */
97
  public function filter_active_sitewide_plugins( $plugins ) {
98
 
99
  if ( empty( $plugins ) ) {
116
 
117
  }
118
 
119
+ /**
120
+ * @return void
121
+ */
122
  public function php_notice() {
123
  ?>
124
  <div id="qm_php_notice" class="notice notice-error">
136
  <?php
137
  }
138
 
139
+ /**
140
+ * @param string $file
141
+ * @return self
142
+ */
143
  public static function init( $file = null ) {
144
 
145
  static $instance = null;
classes/Backtrace.php CHANGED
@@ -5,83 +5,134 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  if ( ! class_exists( 'QM_Backtrace' ) ) {
11
  class QM_Backtrace {
12
 
 
 
 
13
  protected static $ignore_class = array(
14
- 'wpdb' => true,
15
- 'QueryMonitor' => true,
16
- 'W3_Db' => true,
17
- 'Debug_Bar_PHP' => true,
18
- 'WP_Hook' => true,
 
19
  );
 
 
 
 
20
  protected static $ignore_method = array();
 
 
 
 
21
  protected static $ignore_func = array(
22
- 'include_once' => true,
23
- 'require_once' => true,
24
- 'include' => true,
25
- 'require' => true,
26
  'call_user_func_array' => true,
27
- 'call_user_func' => true,
28
- 'trigger_error' => true,
29
- '_doing_it_wrong' => true,
30
  '_deprecated_argument' => true,
31
- '_deprecated_file' => true,
32
  '_deprecated_function' => true,
33
- 'dbDelta' => true,
34
  );
 
 
 
 
 
35
  protected static $show_args = array(
36
- 'do_action' => 1,
37
- 'apply_filters' => 1,
38
- 'do_action_ref_array' => 1,
39
- 'apply_filters_ref_array' => 1,
40
- 'get_template_part' => 2,
 
 
41
  'get_extended_template_part' => 2,
42
- 'load_template' => 'dir',
43
- 'dynamic_sidebar' => 1,
44
- 'get_header' => 1,
45
- 'get_sidebar' => 1,
46
- 'get_footer' => 1,
47
- 'class_exists' => 2,
48
- 'current_user_can' => 3,
49
- 'user_can' => 4,
50
- 'current_user_can_for_blog' => 4,
51
- 'author_can' => 4,
 
 
 
 
 
52
  );
 
 
 
 
 
 
 
 
 
53
  protected static $filtered = false;
54
- protected $trace = null;
55
- protected $filtered_trace = null;
56
- protected $calling_line = 0;
57
- protected $calling_file = '';
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  public function __construct( array $args = array(), array $trace = null ) {
60
  $this->trace = ( null === $trace ) ? debug_backtrace( false ) : $trace;
61
 
62
- $args = array_merge( array(
63
- 'ignore_current_filter' => true,
64
- 'ignore_frames' => 0,
 
 
 
65
  ), $args );
66
 
67
- $this->ignore( 1 ); # Self-awareness
68
-
69
- /**
70
- * If error_handler() is in the trace, QM fails later when it tries
71
- * to get $lowest['file'] in get_filtered_trace()
72
- */
73
- if ( 'error_handler' === $this->trace[0]['function'] ) {
74
- $this->ignore( 1 );
75
- }
76
-
77
- if ( $args['ignore_frames'] ) {
78
- $this->ignore( $args['ignore_frames'] );
79
- }
80
- if ( $args['ignore_current_filter'] ) {
81
- $this->ignore_current_filter();
82
- }
83
-
84
- foreach ( $this->trace as $k => $frame ) {
85
  if ( ! isset( $frame['args'] ) ) {
86
  continue;
87
  }
@@ -98,11 +149,12 @@ class QM_Backtrace {
98
  } else {
99
  unset( $frame['args'] );
100
  }
101
-
102
- $this->trace[ $k ] = $frame;
103
  }
104
  }
105
 
 
 
 
106
  public function get_stack() {
107
 
108
  $trace = $this->get_filtered_trace();
@@ -112,6 +164,9 @@ class QM_Backtrace {
112
 
113
  }
114
 
 
 
 
115
  public function get_caller() {
116
 
117
  $trace = $this->get_filtered_trace();
@@ -120,18 +175,25 @@ class QM_Backtrace {
120
 
121
  }
122
 
 
 
 
123
  public function get_component() {
 
 
 
124
 
125
  $components = array();
126
 
127
- foreach ( $this->trace as $frame ) {
128
  $component = self::get_frame_component( $frame );
129
 
130
  if ( $component ) {
131
  if ( 'plugin' === $component->type ) {
132
  // If the component is a plugin then it can't be anything else,
133
  // so short-circuit and return early.
134
- return $component;
 
135
  }
136
 
137
  $components[ $component->type ] = $component;
@@ -140,50 +202,72 @@ class QM_Backtrace {
140
 
141
  foreach ( QM_Util::get_file_dirs() as $type => $dir ) {
142
  if ( isset( $components[ $type ] ) ) {
143
- return $components[ $type ];
 
144
  }
145
  }
146
 
147
- # This should not happen
148
-
 
 
 
149
  }
150
 
 
 
 
 
 
 
 
151
  public static function get_frame_component( array $frame ) {
152
- try {
153
 
154
- if ( isset( $frame['class'] ) ) {
155
- if ( ! class_exists( $frame['class'], false ) ) {
156
- return null;
157
- }
158
- if ( ! method_exists( $frame['class'], $frame['function'] ) ) {
159
- return null;
160
- }
161
- $ref = new ReflectionMethod( $frame['class'], $frame['function'] );
162
- $file = $ref->getFileName();
163
- } elseif ( isset( $frame['function'] ) && function_exists( $frame['function'] ) ) {
164
- $ref = new ReflectionFunction( $frame['function'] );
165
- $file = $ref->getFileName();
166
- } elseif ( isset( $frame['file'] ) ) {
167
- $file = $frame['file'];
168
- } else {
169
  return null;
170
  }
171
-
172
- return QM_Util::get_file_component( $file );
173
-
174
- } catch ( ReflectionException $e ) {
 
 
 
 
 
 
 
175
  return null;
176
  }
 
 
 
 
 
 
177
  }
178
 
 
 
 
179
  public function get_trace() {
180
  return $this->trace;
181
  }
182
 
 
 
 
 
 
183
  public function get_display_trace() {
184
  return $this->get_filtered_trace();
185
  }
186
 
 
 
 
187
  public function get_filtered_trace() {
188
 
189
  if ( ! isset( $this->filtered_trace ) ) {
@@ -192,13 +276,13 @@ class QM_Backtrace {
192
  $trace = array_values( array_filter( $trace ) );
193
 
194
  if ( empty( $trace ) && ! empty( $this->trace ) ) {
195
- $lowest = $this->trace[0];
196
- $file = QM_Util::standard_dir( $lowest['file'], '' );
197
  $lowest['calling_file'] = $lowest['file'];
198
  $lowest['calling_line'] = $lowest['line'];
199
- $lowest['function'] = $file;
200
- $lowest['display'] = $file;
201
- $lowest['id'] = $file;
202
  unset( $lowest['class'], $lowest['args'], $lowest['type'] );
203
  $trace[0] = $lowest;
204
  }
@@ -211,6 +295,53 @@ class QM_Backtrace {
211
 
212
  }
213
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  public function ignore( $num ) {
215
  for ( $i = 0; $i < $num; $i++ ) {
216
  unset( $this->trace[ $i ] );
@@ -219,16 +350,10 @@ class QM_Backtrace {
219
  return $this;
220
  }
221
 
222
- public function ignore_current_filter() {
223
-
224
- if ( isset( $this->trace[2] ) && isset( $this->trace[2]['function'] ) ) {
225
- if ( in_array( $this->trace[2]['function'], array( 'apply_filters', 'do_action' ), true ) ) {
226
- $this->ignore( 3 ); # Ignore filter and action callbacks
227
- }
228
- }
229
-
230
- }
231
-
232
  public function filter_trace( array $frame ) {
233
 
234
  if ( ! self::$filtered && function_exists( 'did_action' ) && did_action( 'plugins_loaded' ) ) {
@@ -241,7 +366,7 @@ class QM_Backtrace {
241
  * @param bool[] $ignore_class Array of class names to ignore. The array keys are class names to ignore,
242
  * the array values are whether to ignore the class or not (usually true).
243
  */
244
- self::$ignore_class = apply_filters( 'qm/trace/ignore_class', self::$ignore_class );
245
 
246
  /**
247
  * Filters which class methods to ignore when constructing user-facing call stacks.
@@ -261,7 +386,17 @@ class QM_Backtrace {
261
  * @param bool[] $ignore_func Array of function names to ignore. The array keys are function names to ignore,
262
  * the array values are whether to ignore the function or not (usually true).
263
  */
264
- self::$ignore_func = apply_filters( 'qm/trace/ignore_func', self::$ignore_func );
 
 
 
 
 
 
 
 
 
 
265
 
266
  /**
267
  * Filters the number of argument values to show for the given function name when constructing user-facing
@@ -273,53 +408,71 @@ class QM_Backtrace {
273
  * array keys are function names, the array values are either integers or
274
  * "dir" to specifically treat the function argument as a directory path.
275
  */
276
- self::$show_args = apply_filters( 'qm/trace/show_args', self::$show_args );
277
 
278
  self::$filtered = true;
279
 
280
  }
281
 
282
  $return = $frame;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
 
284
  if ( isset( $frame['class'] ) ) {
285
- if ( isset( self::$ignore_class[ $frame['class'] ] ) ) {
286
  $return = null;
287
- } elseif ( isset( self::$ignore_method[ $frame['class'] ][ $frame['function'] ] ) ) {
288
  $return = null;
289
  } elseif ( 0 === strpos( $frame['class'], 'QM' ) ) {
290
  $return = null;
291
  } else {
292
- $return['id'] = $frame['class'] . $frame['type'] . $frame['function'] . '()';
293
  $return['display'] = QM_Util::shorten_fqn( $frame['class'] . $frame['type'] . $frame['function'] ) . '()';
294
  }
295
  } else {
296
- if ( isset( self::$ignore_func[ $frame['function'] ] ) ) {
297
  $return = null;
298
- } elseif ( isset( self::$show_args[ $frame['function'] ] ) ) {
299
- $show = self::$show_args[ $frame['function'] ];
300
 
301
  if ( 'dir' === $show ) {
302
  if ( isset( $frame['args'][0] ) ) {
303
  $arg = QM_Util::standard_dir( $frame['args'][0], '' );
304
- $return['id'] = $frame['function'] . '()';
305
  $return['display'] = QM_Util::shorten_fqn( $frame['function'] ) . "('{$arg}')";
306
  }
307
  } else {
308
- $args = array();
309
- for ( $i = 0; $i < $show; $i++ ) {
310
- if ( isset( $frame['args'][ $i ] ) ) {
311
- if ( is_string( $frame['args'][ $i ] ) ) {
312
- $args[] = '\'' . $frame['args'][ $i ] . '\'';
313
- } else {
314
- $args[] = QM_Util::display_variable( $frame['args'][ $i ] );
 
 
 
 
315
  }
316
  }
 
 
317
  }
318
- $return['id'] = $frame['function'] . '()';
319
- $return['display'] = QM_Util::shorten_fqn( $frame['function'] ) . '(' . implode( ',', $args ) . ')';
320
  }
321
  } else {
322
- $return['id'] = $frame['function'] . '()';
323
  $return['display'] = QM_Util::shorten_fqn( $frame['function'] ) . '()';
324
  }
325
  }
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  if ( ! class_exists( 'QM_Backtrace' ) ) {
13
  class QM_Backtrace {
14
 
15
+ /**
16
+ * @var array<string, bool>
17
+ */
18
  protected static $ignore_class = array(
19
+ 'wpdb' => true,
20
+ 'hyperdb' => true,
21
+ 'QueryMonitor' => true,
22
+ 'W3_Db' => true,
23
+ 'Debug_Bar_PHP' => true,
24
+ 'WP_Hook' => true,
25
  );
26
+
27
+ /**
28
+ * @var array<string, bool>
29
+ */
30
  protected static $ignore_method = array();
31
+
32
+ /**
33
+ * @var array<string, bool>
34
+ */
35
  protected static $ignore_func = array(
36
+ 'include_once' => true,
37
+ 'require_once' => true,
38
+ 'include' => true,
39
+ 'require' => true,
40
  'call_user_func_array' => true,
41
+ 'call_user_func' => true,
42
+ 'trigger_error' => true,
43
+ '_doing_it_wrong' => true,
44
  '_deprecated_argument' => true,
45
+ '_deprecated_file' => true,
46
  '_deprecated_function' => true,
47
+ 'dbDelta' => true,
48
  );
49
+
50
+ /**
51
+ * @var array<string, int|string>
52
+ * @phpstan-var array<string, positive-int|'dir'>
53
+ */
54
  protected static $show_args = array(
55
+ 'do_action' => 1,
56
+ 'apply_filters' => 1,
57
+ 'do_action_ref_array' => 1,
58
+ 'apply_filters_ref_array' => 1,
59
+ 'get_query_template' => 1,
60
+ 'resolve_block_template' => 1,
61
+ 'get_template_part' => 2,
62
  'get_extended_template_part' => 2,
63
+ 'ai_get_template_part' => 2,
64
+ 'load_template' => 'dir',
65
+ 'dynamic_sidebar' => 1,
66
+ 'get_header' => 1,
67
+ 'get_sidebar' => 1,
68
+ 'get_footer' => 1,
69
+ 'get_option' => 1,
70
+ 'update_option' => 1,
71
+ 'get_transient' => 1,
72
+ 'set_transient' => 1,
73
+ 'class_exists' => 2,
74
+ 'current_user_can' => 3,
75
+ 'user_can' => 4,
76
+ 'current_user_can_for_blog' => 4,
77
+ 'author_can' => 4,
78
  );
79
+
80
+ /**
81
+ * @var array<string, bool>
82
+ */
83
+ protected static $ignore_hook = array();
84
+
85
+ /**
86
+ * @var bool
87
+ */
88
  protected static $filtered = false;
 
 
 
 
89
 
90
+ /**
91
+ * @var array<string, mixed[]>
92
+ */
93
+ protected $args = array();
94
+
95
+ /**
96
+ * @var mixed[]|null
97
+ */
98
+ protected $trace = null;
99
+
100
+ /**
101
+ * @var mixed[]|null
102
+ */
103
+ protected $filtered_trace = null;
104
+
105
+ /**
106
+ * @var int
107
+ */
108
+ protected $calling_line = 0;
109
+
110
+ /**
111
+ * @var string
112
+ */
113
+ protected $calling_file = '';
114
+
115
+ /**
116
+ * @var stdClass|null
117
+ */
118
+ protected $component = null;
119
+
120
+ /**
121
+ * @param array<string, mixed[]> $args
122
+ * @param mixed[] $trace
123
+ */
124
  public function __construct( array $args = array(), array $trace = null ) {
125
  $this->trace = ( null === $trace ) ? debug_backtrace( false ) : $trace;
126
 
127
+ $this->args = array_merge( array(
128
+ 'ignore_class' => array(),
129
+ 'ignore_method' => array(),
130
+ 'ignore_func' => array(),
131
+ 'ignore_hook' => array(),
132
+ 'show_args' => array(),
133
  ), $args );
134
 
135
+ foreach ( $this->trace as & $frame ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  if ( ! isset( $frame['args'] ) ) {
137
  continue;
138
  }
149
  } else {
150
  unset( $frame['args'] );
151
  }
 
 
152
  }
153
  }
154
 
155
+ /**
156
+ * @return array<int, string>
157
+ */
158
  public function get_stack() {
159
 
160
  $trace = $this->get_filtered_trace();
164
 
165
  }
166
 
167
+ /**
168
+ * @return mixed[]
169
+ */
170
  public function get_caller() {
171
 
172
  $trace = $this->get_filtered_trace();
175
 
176
  }
177
 
178
+ /**
179
+ * @return stdClass
180
+ */
181
  public function get_component() {
182
+ if ( isset( $this->component ) ) {
183
+ return $this->component;
184
+ }
185
 
186
  $components = array();
187
 
188
+ foreach ( $this->get_filtered_trace() as $frame ) {
189
  $component = self::get_frame_component( $frame );
190
 
191
  if ( $component ) {
192
  if ( 'plugin' === $component->type ) {
193
  // If the component is a plugin then it can't be anything else,
194
  // so short-circuit and return early.
195
+ $this->component = $component;
196
+ return $this->component;
197
  }
198
 
199
  $components[ $component->type ] = $component;
202
 
203
  foreach ( QM_Util::get_file_dirs() as $type => $dir ) {
204
  if ( isset( $components[ $type ] ) ) {
205
+ $this->component = $components[ $type ];
206
+ return $this->component;
207
  }
208
  }
209
 
210
+ return (object) array(
211
+ 'type' => 'unknown',
212
+ 'name' => __( 'Unknown', 'query-monitor' ),
213
+ 'context' => 'unknown',
214
+ );
215
  }
216
 
217
+ /**
218
+ * Attempts to determine the component responsible for a given frame.
219
+ *
220
+ * @param mixed[] $frame A single frame from a trace.
221
+ * @return stdClass|null A stdClass object (ouch) representing the component, or null if
222
+ * the component cannot be determined.
223
+ */
224
  public static function get_frame_component( array $frame ) {
225
+ try {
226
 
227
+ if ( isset( $frame['class'] ) ) {
228
+ if ( ! class_exists( $frame['class'], false ) ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
229
  return null;
230
  }
231
+ if ( ! method_exists( $frame['class'], $frame['function'] ) ) {
232
+ return null;
233
+ }
234
+ $ref = new ReflectionMethod( $frame['class'], $frame['function'] );
235
+ $file = $ref->getFileName();
236
+ } elseif ( isset( $frame['function'] ) && function_exists( $frame['function'] ) ) {
237
+ $ref = new ReflectionFunction( $frame['function'] );
238
+ $file = $ref->getFileName();
239
+ } elseif ( isset( $frame['file'] ) ) {
240
+ $file = $frame['file'];
241
+ } else {
242
  return null;
243
  }
244
+
245
+ return QM_Util::get_file_component( $file );
246
+
247
+ } catch ( ReflectionException $e ) {
248
+ return null;
249
+ }
250
  }
251
 
252
+ /**
253
+ * @return mixed[]
254
+ */
255
  public function get_trace() {
256
  return $this->trace;
257
  }
258
 
259
+ /**
260
+ * @deprecated Use the `::get_filtered_trace()` method instead.
261
+ *
262
+ * @return mixed[]
263
+ */
264
  public function get_display_trace() {
265
  return $this->get_filtered_trace();
266
  }
267
 
268
+ /**
269
+ * @return mixed[]
270
+ */
271
  public function get_filtered_trace() {
272
 
273
  if ( ! isset( $this->filtered_trace ) ) {
276
  $trace = array_values( array_filter( $trace ) );
277
 
278
  if ( empty( $trace ) && ! empty( $this->trace ) ) {
279
+ $lowest = $this->trace[0];
280
+ $file = QM_Util::standard_dir( $lowest['file'], '' );
281
  $lowest['calling_file'] = $lowest['file'];
282
  $lowest['calling_line'] = $lowest['line'];
283
+ $lowest['function'] = $file;
284
+ $lowest['display'] = $file;
285
+ $lowest['id'] = $file;
286
  unset( $lowest['class'], $lowest['args'], $lowest['type'] );
287
  $trace[0] = $lowest;
288
  }
295
 
296
  }
297
 
298
+ /**
299
+ * @param array<int, string> $stack
300
+ * @return array<int, string>
301
+ */
302
+ public static function get_filtered_stack( array $stack ) {
303
+ $trace = new self( array(), array() );
304
+ $return = array();
305
+
306
+ foreach ( $stack as $i => $item ) {
307
+ $frame = array(
308
+ 'function' => $item,
309
+ );
310
+
311
+ if ( false !== strpos( $item, '->' ) ) {
312
+ list( $class, $function ) = explode( '->', $item );
313
+ $frame = array(
314
+ 'class' => $class,
315
+ 'type' => '->',
316
+ 'function' => $function,
317
+ );
318
+ }
319
+
320
+ if ( false !== strpos( $item, '::' ) ) {
321
+ list( $class, $function ) = explode( '::', $item );
322
+ $frame = array(
323
+ 'class' => $class,
324
+ 'type' => '::',
325
+ 'function' => $function,
326
+ );
327
+ }
328
+
329
+ $frame['args'] = array();
330
+
331
+ if ( $trace->filter_trace( $frame ) ) {
332
+ $return[] = $item;
333
+ }
334
+ }
335
+
336
+ return $return;
337
+ }
338
+
339
+ /**
340
+ * @deprecated Use the `ignore_class`, `ignore_method`, `ignore_func`, and `ignore_hook` arguments instead.
341
+ *
342
+ * @param int $num
343
+ * @return self
344
+ */
345
  public function ignore( $num ) {
346
  for ( $i = 0; $i < $num; $i++ ) {
347
  unset( $this->trace[ $i ] );
350
  return $this;
351
  }
352
 
353
+ /**
354
+ * @param mixed[] $frame
355
+ * @return mixed[]|null
356
+ */
 
 
 
 
 
 
357
  public function filter_trace( array $frame ) {
358
 
359
  if ( ! self::$filtered && function_exists( 'did_action' ) && did_action( 'plugins_loaded' ) ) {
366
  * @param bool[] $ignore_class Array of class names to ignore. The array keys are class names to ignore,
367
  * the array values are whether to ignore the class or not (usually true).
368
  */
369
+ self::$ignore_class = apply_filters( 'qm/trace/ignore_class', self::$ignore_class );
370
 
371
  /**
372
  * Filters which class methods to ignore when constructing user-facing call stacks.
386
  * @param bool[] $ignore_func Array of function names to ignore. The array keys are function names to ignore,
387
  * the array values are whether to ignore the function or not (usually true).
388
  */
389
+ self::$ignore_func = apply_filters( 'qm/trace/ignore_func', self::$ignore_func );
390
+
391
+ /**
392
+ * Filters which action and filter names to ignore when constructing user-facing call stacks.
393
+ *
394
+ * @since x.x.x
395
+ *
396
+ * @param bool[] $ignore_hook Array of hook names to ignore. The array keys are hook names to ignore,
397
+ * the array values are whether to ignore the hook or not (usually true).
398
+ */
399
+ self::$ignore_hook = apply_filters( 'qm/trace/ignore_hook', self::$ignore_hook );
400
 
401
  /**
402
  * Filters the number of argument values to show for the given function name when constructing user-facing
408
  * array keys are function names, the array values are either integers or
409
  * "dir" to specifically treat the function argument as a directory path.
410
  */
411
+ self::$show_args = apply_filters( 'qm/trace/show_args', self::$show_args );
412
 
413
  self::$filtered = true;
414
 
415
  }
416
 
417
  $return = $frame;
418
+ $ignore_class = array_filter( array_merge( self::$ignore_class, $this->args['ignore_class'] ) );
419
+ $ignore_method = array_filter( array_merge( self::$ignore_method, $this->args['ignore_method'] ) );
420
+ $ignore_func = array_filter( array_merge( self::$ignore_func, $this->args['ignore_func'] ) );
421
+ $ignore_hook = array_filter( array_merge( self::$ignore_hook, $this->args['ignore_hook'] ) );
422
+ $show_args = array_merge( self::$show_args, $this->args['show_args'] );
423
+
424
+ $hook_functions = array(
425
+ 'apply_filters' => true,
426
+ 'do_action' => true,
427
+ 'apply_filters_ref_array' => true,
428
+ 'do_action_ref_array' => true,
429
+ 'apply_filters_deprecated' => true,
430
+ 'do_action_deprecated' => true,
431
+ );
432
 
433
  if ( isset( $frame['class'] ) ) {
434
+ if ( isset( $ignore_class[ $frame['class'] ] ) ) {
435
  $return = null;
436
+ } elseif ( isset( $ignore_method[ $frame['class'] ][ $frame['function'] ] ) ) {
437
  $return = null;
438
  } elseif ( 0 === strpos( $frame['class'], 'QM' ) ) {
439
  $return = null;
440
  } else {
441
+ $return['id'] = $frame['class'] . $frame['type'] . $frame['function'] . '()';
442
  $return['display'] = QM_Util::shorten_fqn( $frame['class'] . $frame['type'] . $frame['function'] ) . '()';
443
  }
444
  } else {
445
+ if ( isset( $ignore_func[ $frame['function'] ] ) ) {
446
  $return = null;
447
+ } elseif ( isset( $show_args[ $frame['function'] ] ) ) {
448
+ $show = $show_args[ $frame['function'] ];
449
 
450
  if ( 'dir' === $show ) {
451
  if ( isset( $frame['args'][0] ) ) {
452
  $arg = QM_Util::standard_dir( $frame['args'][0], '' );
453
+ $return['id'] = $frame['function'] . '()';
454
  $return['display'] = QM_Util::shorten_fqn( $frame['function'] ) . "('{$arg}')";
455
  }
456
  } else {
457
+ if ( isset( $hook_functions[ $frame['function'] ] ) && isset( $frame['args'][0] ) && is_string( $frame['args'][0] ) && isset( $ignore_hook[ $frame['args'][0] ] ) ) {
458
+ $return = null;
459
+ } else {
460
+ $args = array();
461
+ for ( $i = 0; $i < $show; $i++ ) {
462
+ if ( isset( $frame['args'][ $i ] ) ) {
463
+ if ( is_string( $frame['args'][ $i ] ) ) {
464
+ $args[] = '\'' . $frame['args'][ $i ] . '\'';
465
+ } else {
466
+ $args[] = QM_Util::display_variable( $frame['args'][ $i ] );
467
+ }
468
  }
469
  }
470
+ $return['id'] = $frame['function'] . '()';
471
+ $return['display'] = QM_Util::shorten_fqn( $frame['function'] ) . '(' . implode( ',', $args ) . ')';
472
  }
 
 
473
  }
474
  } else {
475
+ $return['id'] = $frame['function'] . '()';
476
  $return['display'] = QM_Util::shorten_fqn( $frame['function'] ) . '()';
477
  }
478
  }
classes/CLI.php CHANGED
@@ -7,6 +7,9 @@
7
 
8
  class QM_CLI extends QM_Plugin {
9
 
 
 
 
10
  protected function __construct( $file ) {
11
 
12
  # Register command
@@ -19,6 +22,8 @@ class QM_CLI extends QM_Plugin {
19
 
20
  /**
21
  * Enable QM by creating the symlink for db.php
 
 
22
  */
23
  public function enable() {
24
  $drop_in = WP_CONTENT_DIR . '/db.php';
@@ -28,7 +33,7 @@ class QM_CLI extends QM_Plugin {
28
  WP_CLI::success( "Query Monitor's wp-content/db.php is already in place" );
29
  exit( 0 );
30
  } else {
31
- WP_CLI::error( 'Unknown wp-content/db.php already is already in place' );
32
  }
33
  }
34
 
@@ -49,6 +54,10 @@ class QM_CLI extends QM_Plugin {
49
  }
50
  }
51
 
 
 
 
 
52
  public static function init( $file = null ) {
53
 
54
  static $instance = null;
7
 
8
  class QM_CLI extends QM_Plugin {
9
 
10
+ /**
11
+ * @param string $file
12
+ */
13
  protected function __construct( $file ) {
14
 
15
  # Register command
22
 
23
  /**
24
  * Enable QM by creating the symlink for db.php
25
+ *
26
+ * @return void
27
  */
28
  public function enable() {
29
  $drop_in = WP_CONTENT_DIR . '/db.php';
33
  WP_CLI::success( "Query Monitor's wp-content/db.php is already in place" );
34
  exit( 0 );
35
  } else {
36
+ WP_CLI::error( 'Unknown wp-content/db.php is already in place' );
37
  }
38
  }
39
 
54
  }
55
  }
56
 
57
+ /**
58
+ * @param string $file
59
+ * @return self
60
+ */
61
  public static function init( $file = null ) {
62
 
63
  static $instance = null;
classes/Collector.php CHANGED
@@ -8,24 +8,67 @@
8
  if ( ! class_exists( 'QM_Collector' ) ) {
9
  abstract class QM_Collector {
10
 
 
 
 
11
  protected $timer;
 
 
 
 
12
  protected $data = array(
13
- 'types' => array(),
14
  'component_times' => array(),
15
  );
 
 
 
 
16
  protected static $hide_qm = null;
17
 
18
- public $concerned_actions = array();
19
- public $concerned_filters = array();
 
 
 
 
 
 
 
 
 
 
 
20
  public $concerned_constants = array();
21
- public $tracked_hooks = array();
 
 
 
 
 
 
 
 
 
22
 
23
  public function __construct() {}
24
 
 
 
 
 
 
 
 
 
25
  final public function id() {
26
  return "qm-{$this->id}";
27
  }
28
 
 
 
 
 
29
  protected function log_type( $type ) {
30
 
31
  if ( isset( $this->data['types'][ $type ] ) ) {
@@ -36,6 +79,11 @@ abstract class QM_Collector {
36
 
37
  }
38
 
 
 
 
 
 
39
  protected function maybe_log_dupe( $sql, $i ) {
40
 
41
  $sql = str_replace( array( "\r\n", "\r", "\n" ), ' ', $sql );
@@ -48,13 +96,19 @@ abstract class QM_Collector {
48
 
49
  }
50
 
 
 
 
 
 
 
51
  protected function log_component( $component, $ltime, $type ) {
52
 
53
  if ( ! isset( $this->data['component_times'][ $component->name ] ) ) {
54
  $this->data['component_times'][ $component->name ] = array(
55
  'component' => $component->name,
56
- 'ltime' => 0,
57
- 'types' => array(),
58
  );
59
  }
60
 
@@ -68,11 +122,18 @@ abstract class QM_Collector {
68
 
69
  }
70
 
 
 
 
71
  public static function timer_stop_float() {
72
  global $timestart;
73
  return microtime( true ) - $timestart;
74
  }
75
 
 
 
 
 
76
  public static function format_bool_constant( $constant ) {
77
  // @TODO this should be in QM_Util
78
 
@@ -88,19 +149,36 @@ abstract class QM_Collector {
88
  }
89
  }
90
 
 
 
 
91
  final public function get_data() {
92
  return $this->data;
93
  }
94
 
 
 
 
 
 
 
 
 
 
 
 
95
  final public function set_id( $id ) {
96
  $this->id = $id;
97
  }
98
 
 
 
 
99
  final public function process_concerns() {
100
  global $wp_filter;
101
 
102
  $tracked = array();
103
- $id = $this->id;
104
 
105
  /**
106
  * Filters the concerned actions for the given panel.
@@ -152,14 +230,14 @@ abstract class QM_Collector {
152
 
153
  foreach ( $concerned_actions as $action ) {
154
  if ( has_action( $action ) ) {
155
- $this->concerned_actions[ $action ] = QM_Hook::process( $action, $wp_filter, true, true );
156
  }
157
  $tracked[] = $action;
158
  }
159
 
160
  foreach ( $concerned_filters as $filter ) {
161
  if ( has_filter( $filter ) ) {
162
- $this->concerned_filters[ $filter ] = QM_Hook::process( $filter, $wp_filter, true, true );
163
  }
164
  $tracked[] = $filter;
165
  }
@@ -181,7 +259,7 @@ abstract class QM_Collector {
181
  $option
182
  );
183
  if ( has_filter( $filter ) ) {
184
- $this->concerned_filters[ $filter ] = QM_Hook::process( $filter, $wp_filter, true, true );
185
  }
186
  $tracked[] = $filter;
187
  }
@@ -201,10 +279,18 @@ abstract class QM_Collector {
201
  $this->tracked_hooks = $tracked;
202
  }
203
 
 
 
 
 
204
  public function filter_concerns( $concerns ) {
205
  return ! empty( $concerns['actions'] );
206
  }
207
 
 
 
 
 
208
  public static function format_user( WP_User $user_object ) {
209
  $user = get_object_vars( $user_object->data );
210
  unset(
@@ -216,10 +302,16 @@ abstract class QM_Collector {
216
  return $user;
217
  }
218
 
 
 
 
219
  public static function enabled() {
220
  return true;
221
  }
222
 
 
 
 
223
  public static function hide_qm() {
224
  if ( ! defined( 'QM_HIDE_SELF' ) ) {
225
  return false;
@@ -232,41 +324,76 @@ abstract class QM_Collector {
232
  return self::$hide_qm;
233
  }
234
 
 
 
 
 
235
  public function filter_remove_qm( array $item ) {
236
- $component = $item['trace']->get_component();
237
- return ( 'query-monitor' !== $component->context );
238
  }
239
 
 
 
 
 
240
  public function filter_dupe_items( $items ) {
241
  return ( count( $items ) > 1 );
242
  }
243
 
 
 
 
244
  public function process() {}
245
 
 
 
 
246
  public function post_process() {}
247
 
 
 
 
248
  public function tear_down() {}
249
 
 
 
 
250
  public function get_timer() {
251
  return $this->timer;
252
  }
253
 
 
 
 
 
254
  public function set_timer( QM_Timer $timer ) {
255
  $this->timer = $timer;
256
  }
257
 
 
 
 
258
  public function get_concerned_actions() {
259
  return array();
260
  }
261
 
 
 
 
262
  public function get_concerned_filters() {
263
  return array();
264
  }
265
 
 
 
 
266
  public function get_concerned_options() {
267
  return array();
268
  }
269
 
 
 
 
270
  public function get_concerned_constants() {
271
  return array();
272
  }
8
  if ( ! class_exists( 'QM_Collector' ) ) {
9
  abstract class QM_Collector {
10
 
11
+ /**
12
+ * @var QM_Timer|null
13
+ */
14
  protected $timer;
15
+
16
+ /**
17
+ * @var array<string, mixed>
18
+ */
19
  protected $data = array(
20
+ 'types' => array(),
21
  'component_times' => array(),
22
  );
23
+
24
+ /**
25
+ * @var bool|null
26
+ */
27
  protected static $hide_qm = null;
28
 
29
+ /**
30
+ * @var array<string, array<string, mixed>>
31
+ */
32
+ public $concerned_actions = array();
33
+
34
+ /**
35
+ * @var array<string, array<string, mixed>>
36
+ */
37
+ public $concerned_filters = array();
38
+
39
+ /**
40
+ * @var array<string, array<string, mixed>>
41
+ */
42
  public $concerned_constants = array();
43
+
44
+ /**
45
+ * @var array<int, string>
46
+ */
47
+ public $tracked_hooks = array();
48
+
49
+ /**
50
+ * @var string
51
+ */
52
+ public $id = '';
53
 
54
  public function __construct() {}
55
 
56
+ /**
57
+ * @return void
58
+ */
59
+ public function set_up() {}
60
+
61
+ /**
62
+ * @return string
63
+ */
64
  final public function id() {
65
  return "qm-{$this->id}";
66
  }
67
 
68
+ /**
69
+ * @param string $type
70
+ * @return void
71
+ */
72
  protected function log_type( $type ) {
73
 
74
  if ( isset( $this->data['types'][ $type ] ) ) {
79
 
80
  }
81
 
82
+ /**
83
+ * @param string $sql
84
+ * @param int $i
85
+ * @return void
86
+ */
87
  protected function maybe_log_dupe( $sql, $i ) {
88
 
89
  $sql = str_replace( array( "\r\n", "\r", "\n" ), ' ', $sql );
96
 
97
  }
98
 
99
+ /**
100
+ * @param stdClass $component
101
+ * @param float $ltime
102
+ * @param string $type
103
+ * @return void
104
+ */
105
  protected function log_component( $component, $ltime, $type ) {
106
 
107
  if ( ! isset( $this->data['component_times'][ $component->name ] ) ) {
108
  $this->data['component_times'][ $component->name ] = array(
109
  'component' => $component->name,
110
+ 'ltime' => 0,
111
+ 'types' => array(),
112
  );
113
  }
114
 
122
 
123
  }
124
 
125
+ /**
126
+ * @return float
127
+ */
128
  public static function timer_stop_float() {
129
  global $timestart;
130
  return microtime( true ) - $timestart;
131
  }
132
 
133
+ /**
134
+ * @param string $constant
135
+ * @return string
136
+ */
137
  public static function format_bool_constant( $constant ) {
138
  // @TODO this should be in QM_Util
139
 
149
  }
150
  }
151
 
152
+ /**
153
+ * @return array<string, mixed>
154
+ */
155
  final public function get_data() {
156
  return $this->data;
157
  }
158
 
159
+ /**
160
+ * @return void
161
+ */
162
+ final public function discard_data() {
163
+ $this->data = array();
164
+ }
165
+
166
+ /**
167
+ * @param string $id
168
+ * @return void
169
+ */
170
  final public function set_id( $id ) {
171
  $this->id = $id;
172
  }
173
 
174
+ /**
175
+ * @return void
176
+ */
177
  final public function process_concerns() {
178
  global $wp_filter;
179
 
180
  $tracked = array();
181
+ $id = $this->id;
182
 
183
  /**
184
  * Filters the concerned actions for the given panel.
230
 
231
  foreach ( $concerned_actions as $action ) {
232
  if ( has_action( $action ) ) {
233
+ $this->concerned_actions[ $action ] = QM_Hook::process( $action, $wp_filter, true, false );
234
  }
235
  $tracked[] = $action;
236
  }
237
 
238
  foreach ( $concerned_filters as $filter ) {
239
  if ( has_filter( $filter ) ) {
240
+ $this->concerned_filters[ $filter ] = QM_Hook::process( $filter, $wp_filter, true, false );
241
  }
242
  $tracked[] = $filter;
243
  }
259
  $option
260
  );
261
  if ( has_filter( $filter ) ) {
262
+ $this->concerned_filters[ $filter ] = QM_Hook::process( $filter, $wp_filter, true, false );
263
  }
264
  $tracked[] = $filter;
265
  }
279
  $this->tracked_hooks = $tracked;
280
  }
281
 
282
+ /**
283
+ * @param array<string, mixed> $concerns
284
+ * @return bool
285
+ */
286
  public function filter_concerns( $concerns ) {
287
  return ! empty( $concerns['actions'] );
288
  }
289
 
290
+ /**
291
+ * @param WP_User $user_object
292
+ * @return array<string, mixed>
293
+ */
294
  public static function format_user( WP_User $user_object ) {
295
  $user = get_object_vars( $user_object->data );
296
  unset(
302
  return $user;
303
  }
304
 
305
+ /**
306
+ * @return bool
307
+ */
308
  public static function enabled() {
309
  return true;
310
  }
311
 
312
+ /**
313
+ * @return bool
314
+ */
315
  public static function hide_qm() {
316
  if ( ! defined( 'QM_HIDE_SELF' ) ) {
317
  return false;
324
  return self::$hide_qm;
325
  }
326
 
327
+ /**
328
+ * @param array<string, mixed> $item
329
+ * @return bool
330
+ */
331
  public function filter_remove_qm( array $item ) {
332
+ return ( 'query-monitor' !== $item['component']->context );
 
333
  }
334
 
335
+ /**
336
+ * @param mixed[] $items
337
+ * @return bool
338
+ */
339
  public function filter_dupe_items( $items ) {
340
  return ( count( $items ) > 1 );
341
  }
342
 
343
+ /**
344
+ * @return void
345
+ */
346
  public function process() {}
347
 
348
+ /**
349
+ * @return void
350
+ */
351
  public function post_process() {}
352
 
353
+ /**
354
+ * @return void
355
+ */
356
  public function tear_down() {}
357
 
358
+ /**
359
+ * @return QM_Timer|null
360
+ */
361
  public function get_timer() {
362
  return $this->timer;
363
  }
364
 
365
+ /**
366
+ * @param QM_Timer $timer
367
+ * @return void
368
+ */
369
  public function set_timer( QM_Timer $timer ) {
370
  $this->timer = $timer;
371
  }
372
 
373
+ /**
374
+ * @return array<int, string>
375
+ */
376
  public function get_concerned_actions() {
377
  return array();
378
  }
379
 
380
+ /**
381
+ * @return array<int, string>
382
+ */
383
  public function get_concerned_filters() {
384
  return array();
385
  }
386
 
387
+ /**
388
+ * @return array<int, string>
389
+ */
390
  public function get_concerned_options() {
391
  return array();
392
  }
393
 
394
+ /**
395
+ * @return array<int, string>
396
+ */
397
  public function get_concerned_constants() {
398
  return array();
399
  }
classes/Collectors.php CHANGED
@@ -6,18 +6,37 @@
6
  */
7
 
8
  if ( ! class_exists( 'QM_Collectors' ) ) {
 
 
 
9
  class QM_Collectors implements IteratorAggregate {
10
 
11
- private $items = array();
 
 
 
 
 
 
 
12
  private $processed = false;
13
 
 
 
 
14
  public function getIterator() {
15
  return new ArrayIterator( $this->items );
16
  }
17
 
 
 
 
 
18
  public static function add( QM_Collector $collector ) {
19
  $collectors = self::init();
20
 
 
 
21
  $collectors->items[ $collector->id ] = $collector;
22
  }
23
 
@@ -35,6 +54,9 @@ class QM_Collectors implements IteratorAggregate {
35
  return null;
36
  }
37
 
 
 
 
38
  public static function init() {
39
  static $instance;
40
 
@@ -46,6 +68,9 @@ class QM_Collectors implements IteratorAggregate {
46
 
47
  }
48
 
 
 
 
49
  public function process() {
50
  if ( $this->processed ) {
51
  return;
@@ -70,5 +95,19 @@ class QM_Collectors implements IteratorAggregate {
70
  $this->processed = true;
71
  }
72
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  }
74
  }
6
  */
7
 
8
  if ( ! class_exists( 'QM_Collectors' ) ) {
9
+ /**
10
+ * @implements \IteratorAggregate<string, QM_Collector>
11
+ */
12
  class QM_Collectors implements IteratorAggregate {
13
 
14
+ /**
15
+ * @var array<string, QM_Collector>
16
+ */
17
+ private $items = array();
18
+
19
+ /**
20
+ * @var boolean
21
+ */
22
  private $processed = false;
23
 
24
+ /**
25
+ * @return ArrayIterator<string, QM_Collector>
26
+ */
27
  public function getIterator() {
28
  return new ArrayIterator( $this->items );
29
  }
30
 
31
+ /**
32
+ * @param QM_Collector $collector
33
+ * @return void
34
+ */
35
  public static function add( QM_Collector $collector ) {
36
  $collectors = self::init();
37
 
38
+ $collector->set_up();
39
+
40
  $collectors->items[ $collector->id ] = $collector;
41
  }
42
 
54
  return null;
55
  }
56
 
57
+ /**
58
+ * @return self
59
+ */
60
  public static function init() {
61
  static $instance;
62
 
68
 
69
  }
70
 
71
+ /**
72
+ * @return void
73
+ */
74
  public function process() {
75
  if ( $this->processed ) {
76
  return;
95
  $this->processed = true;
96
  }
97
 
98
+ /**
99
+ * @return void
100
+ */
101
+ public static function cease() {
102
+ $collectors = self::init();
103
+
104
+ $collectors->processed = true;
105
+
106
+ /** @var QM_Collector $collector */
107
+ foreach ( $collectors as $collector ) {
108
+ $collector->tear_down();
109
+ $collector->discard_data();
110
+ }
111
+ }
112
  }
113
  }
classes/Dispatcher.php CHANGED
@@ -11,7 +11,7 @@ abstract class QM_Dispatcher {
11
  /**
12
  * Outputter instances.
13
  *
14
- * @var QM_Output[] Array of outputters.
15
  */
16
  protected $outputters = array();
17
 
@@ -22,6 +22,16 @@ abstract class QM_Dispatcher {
22
  */
23
  protected $qm;
24
 
 
 
 
 
 
 
 
 
 
 
25
  public function __construct( QM_Plugin $qm ) {
26
  $this->qm = $qm;
27
 
@@ -36,8 +46,14 @@ abstract class QM_Dispatcher {
36
 
37
  }
38
 
 
 
 
39
  abstract public function is_active();
40
 
 
 
 
41
  final public function should_dispatch() {
42
 
43
  $e = error_get_last();
@@ -72,6 +88,15 @@ abstract class QM_Dispatcher {
72
 
73
  }
74
 
 
 
 
 
 
 
 
 
 
75
  /**
76
  * Processes and fetches the outputters for this dispatcher.
77
  *
@@ -89,16 +114,20 @@ abstract class QM_Dispatcher {
89
  *
90
  * @since 2.8.0
91
  *
92
- * @param QM_Output[] $outputters Array of outputters.
93
- * @param QM_Collectors $collectors List of collectors.
94
  */
95
  $this->outputters = apply_filters( "qm/outputter/{$outputter_id}", array(), $collectors );
96
 
97
  return $this->outputters;
98
  }
99
 
 
 
 
100
  public function init() {
101
  if ( ! self::user_can_view() ) {
 
102
  return;
103
  }
104
 
@@ -109,14 +138,21 @@ abstract class QM_Dispatcher {
109
  add_action( 'send_headers', 'nocache_headers' );
110
  }
111
 
 
 
 
112
  protected function before_output() {
113
- // nothing
114
  }
115
 
 
 
 
116
  protected function after_output() {
117
- // nothing
118
  }
119
 
 
 
 
120
  public static function user_can_view() {
121
 
122
  if ( ! did_action( 'plugins_loaded' ) ) {
@@ -131,6 +167,9 @@ abstract class QM_Dispatcher {
131
 
132
  }
133
 
 
 
 
134
  public static function user_verified() {
135
  if ( isset( $_COOKIE[QM_COOKIE] ) ) { // phpcs:ignore
136
  return self::verify_cookie( wp_unslash( $_COOKIE[QM_COOKIE] ) ); // phpcs:ignore
@@ -138,6 +177,9 @@ abstract class QM_Dispatcher {
138
  return false;
139
  }
140
 
 
 
 
141
  public static function editor_cookie() {
142
  if ( defined( 'QM_EDITOR_COOKIE' ) && isset( $_COOKIE[QM_EDITOR_COOKIE] ) ) { // phpcs:ignore
143
  return $_COOKIE[QM_EDITOR_COOKIE]; // phpcs:ignore
@@ -145,6 +187,10 @@ abstract class QM_Dispatcher {
145
  return '';
146
  }
147
 
 
 
 
 
148
  public static function verify_cookie( $value ) {
149
  $old_user_id = wp_validate_auth_cookie( $value, 'logged_in' );
150
  if ( $old_user_id ) {
@@ -153,5 +199,41 @@ abstract class QM_Dispatcher {
153
  return false;
154
  }
155
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  }
157
  }
11
  /**
12
  * Outputter instances.
13
  *
14
+ * @var array<string, QM_Output> Array of outputters.
15
  */
16
  protected $outputters = array();
17
 
22
  */
23
  protected $qm;
24
 
25
+ /**
26
+ * @var string
27
+ */
28
+ public $id = '';
29
+
30
+ /**
31
+ * @var bool
32
+ */
33
+ protected $ceased = false;
34
+
35
  public function __construct( QM_Plugin $qm ) {
36
  $this->qm = $qm;
37
 
46
 
47
  }
48
 
49
+ /**
50
+ * @return bool
51
+ */
52
  abstract public function is_active();
53
 
54
+ /**
55
+ * @return bool
56
+ */
57
  final public function should_dispatch() {
58
 
59
  $e = error_get_last();
88
 
89
  }
90
 
91
+ /**
92
+ * @return void
93
+ */
94
+ public function cease() {
95
+ $this->ceased = true;
96
+
97
+ add_filter( "qm/dispatch/{$this->id}", '__return_false' );
98
+ }
99
+
100
  /**
101
  * Processes and fetches the outputters for this dispatcher.
102
  *
114
  *
115
  * @since 2.8.0
116
  *
117
+ * @param array<string, QM_Output> $outputters Array of outputters.
118
+ * @param QM_Collectors $collectors List of collectors.
119
  */
120
  $this->outputters = apply_filters( "qm/outputter/{$outputter_id}", array(), $collectors );
121
 
122
  return $this->outputters;
123
  }
124
 
125
+ /**
126
+ * @return void
127
+ */
128
  public function init() {
129
  if ( ! self::user_can_view() ) {
130
+ do_action( 'qm/cease' );
131
  return;
132
  }
133
 
138
  add_action( 'send_headers', 'nocache_headers' );
139
  }
140
 
141
+ /**
142
+ * @return void
143
+ */
144
  protected function before_output() {
 
145
  }
146
 
147
+ /**
148
+ * @return void
149
+ */
150
  protected function after_output() {
 
151
  }
152
 
153
+ /**
154
+ * @return bool
155
+ */
156
  public static function user_can_view() {
157
 
158
  if ( ! did_action( 'plugins_loaded' ) ) {
167
 
168
  }
169
 
170
+ /**
171
+ * @return bool
172
+ */
173
  public static function user_verified() {
174
  if ( isset( $_COOKIE[QM_COOKIE] ) ) { // phpcs:ignore
175
  return self::verify_cookie( wp_unslash( $_COOKIE[QM_COOKIE] ) ); // phpcs:ignore
177
  return false;
178
  }
179
 
180
+ /**
181
+ * @return string
182
+ */
183
  public static function editor_cookie() {
184
  if ( defined( 'QM_EDITOR_COOKIE' ) && isset( $_COOKIE[QM_EDITOR_COOKIE] ) ) { // phpcs:ignore
185
  return $_COOKIE[QM_EDITOR_COOKIE]; // phpcs:ignore
187
  return '';
188
  }
189
 
190
+ /**
191
+ * @param string $value
192
+ * @return bool
193
+ */
194
  public static function verify_cookie( $value ) {
195
  $old_user_id = wp_validate_auth_cookie( $value, 'logged_in' );
196
  if ( $old_user_id ) {
199
  return false;
200
  }
201
 
202
+ /**
203
+ * Attempts to switch to the given locale.
204
+ *
205
+ * This is a wrapper around `switch_to_locale()` which is safe to call at any point, even
206
+ * before the `$wp_locale_switcher` global is initialised or if the function does not exist.
207
+ *
208
+ * @param string $locale The locale.
209
+ * @return bool True on success, false on failure.
210
+ */
211
+ public static function switch_to_locale( $locale ) {
212
+ global $wp_locale_switcher;
213
+
214
+ if ( function_exists( 'switch_to_locale' ) && ( $wp_locale_switcher instanceof WP_Locale_Switcher ) ) {
215
+ return switch_to_locale( $locale );
216
+ }
217
+
218
+ return false;
219
+ }
220
+
221
+ /**
222
+ * Attempts to restore the previous locale.
223
+ *
224
+ * This is a wrapper around `restore_previous_locale()` which is safe to call at any point, even
225
+ * before the `$wp_locale_switcher` global is initialised or if the function does not exist.
226
+ *
227
+ * @return string|false Locale on success, false on error.
228
+ */
229
+ public static function restore_previous_locale() {
230
+ global $wp_locale_switcher;
231
+
232
+ if ( function_exists( 'restore_previous_locale' ) && ( $wp_locale_switcher instanceof WP_Locale_Switcher ) ) {
233
+ return restore_previous_locale();
234
+ }
235
+
236
+ return false;
237
+ }
238
  }
239
  }
classes/Dispatchers.php CHANGED
@@ -5,19 +5,36 @@
5
  * @package query-monitor
6
  */
7
 
 
 
 
8
  class QM_Dispatchers implements IteratorAggregate {
9
 
 
 
 
10
  private $items = array();
11
 
 
 
 
12
  public function getIterator() {
13
  return new ArrayIterator( $this->items );
14
  }
15
 
 
 
 
 
16
  public static function add( QM_Dispatcher $dispatcher ) {
17
- $dispatchers = self::init();
18
  $dispatchers->items[ $dispatcher->id ] = $dispatcher;
19
  }
20
 
 
 
 
 
21
  public static function get( $id ) {
22
  $dispatchers = self::init();
23
  if ( isset( $dispatchers->items[ $id ] ) ) {
@@ -26,6 +43,21 @@ class QM_Dispatchers implements IteratorAggregate {
26
  return false;
27
  }
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  public static function init() {
30
  static $instance;
31
 
5
  * @package query-monitor
6
  */
7
 
8
+ /**
9
+ * @implements \IteratorAggregate<string, QM_Dispatcher>
10
+ */
11
  class QM_Dispatchers implements IteratorAggregate {
12
 
13
+ /**
14
+ * @var array<string, QM_Dispatcher>
15
+ */
16
  private $items = array();
17
 
18
+ /**
19
+ * @return ArrayIterator<string, QM_Dispatcher>
20
+ */
21
  public function getIterator() {
22
  return new ArrayIterator( $this->items );
23
  }
24
 
25
+ /**
26
+ * @param QM_Dispatcher $dispatcher
27
+ * @return void
28
+ */
29
  public static function add( QM_Dispatcher $dispatcher ) {
30
+ $dispatchers = self::init();
31
  $dispatchers->items[ $dispatcher->id ] = $dispatcher;
32
  }
33
 
34
+ /**
35
+ * @param string $id
36
+ * @return QM_Dispatcher|false
37
+ */
38
  public static function get( $id ) {
39
  $dispatchers = self::init();
40
  if ( isset( $dispatchers->items[ $id ] ) ) {
43
  return false;
44
  }
45
 
46
+ /**
47
+ * @return void
48
+ */
49
+ public static function cease() {
50
+ $dispatchers = self::init();
51
+
52
+ /** @var QM_Dispatcher $dispatcher */
53
+ foreach ( $dispatchers as $dispatcher ) {
54
+ $dispatcher->cease();
55
+ }
56
+ }
57
+
58
+ /**
59
+ * @return self
60
+ */
61
  public static function init() {
62
  static $instance;
63
 
classes/Hook.php CHANGED
@@ -7,9 +7,16 @@
7
 
8
  class QM_Hook {
9
 
 
 
 
 
 
 
 
10
  public static function process( $name, array $wp_filter, $hide_qm = false, $hide_core = false ) {
11
 
12
- $actions = array();
13
  $components = array();
14
 
15
  if ( isset( $wp_filter[ $name ] ) ) {
@@ -49,9 +56,9 @@ class QM_Hook {
49
  $parts = array_values( array_filter( preg_split( '#[_/.-]#', $name ) ) );
50
 
51
  return array(
52
- 'name' => $name,
53
- 'actions' => $actions,
54
- 'parts' => $parts,
55
  'components' => $components,
56
  );
57
 
7
 
8
  class QM_Hook {
9
 
10
+ /**
11
+ * @param string $name
12
+ * @param array<string, mixed> $wp_filter
13
+ * @param bool $hide_qm
14
+ * @param bool $hide_core
15
+ * @return mixed[]
16
+ */
17
  public static function process( $name, array $wp_filter, $hide_qm = false, $hide_core = false ) {
18
 
19
+ $actions = array();
20
  $components = array();
21
 
22
  if ( isset( $wp_filter[ $name ] ) ) {
56
  $parts = array_values( array_filter( preg_split( '#[_/.-]#', $name ) ) );
57
 
58
  return array(
59
+ 'name' => $name,
60
+ 'actions' => $actions,
61
+ 'parts' => $parts,
62
  'components' => $components,
63
  );
64
 
classes/Output.php CHANGED
@@ -18,7 +18,7 @@ abstract class QM_Output {
18
  /**
19
  * Timer instance.
20
  *
21
- * @var QM_Timer Timer.
22
  */
23
  protected $timer;
24
 
@@ -26,20 +26,36 @@ abstract class QM_Output {
26
  $this->collector = $collector;
27
  }
28
 
 
 
 
29
  abstract public function get_output();
30
 
 
 
 
31
  public function output() {
32
  // nothing
33
  }
34
 
 
 
 
35
  public function get_collector() {
36
  return $this->collector;
37
  }
38
 
 
 
 
39
  final public function get_timer() {
40
  return $this->timer;
41
  }
42
 
 
 
 
 
43
  final public function set_timer( QM_Timer $timer ) {
44
  $this->timer = $timer;
45
  }
18
  /**
19
  * Timer instance.
20
  *
21
+ * @var QM_Timer|null Timer.
22
  */
23
  protected $timer;
24
 
26
  $this->collector = $collector;
27
  }
28
 
29
+ /**
30
+ * @return mixed
31
+ */
32
  abstract public function get_output();
33
 
34
+ /**
35
+ * @return void
36
+ */
37
  public function output() {
38
  // nothing
39
  }
40
 
41
+ /**
42
+ * @return QM_Collector
43
+ */
44
  public function get_collector() {
45
  return $this->collector;
46
  }
47
 
48
+ /**
49
+ * @return QM_Timer|null
50
+ */
51
  final public function get_timer() {
52
  return $this->timer;
53
  }
54
 
55
+ /**
56
+ * @param QM_Timer $timer
57
+ * @return void
58
+ */
59
  final public function set_timer( QM_Timer $timer ) {
60
  $this->timer = $timer;
61
  }
classes/Plugin.php CHANGED
@@ -8,11 +8,25 @@
8
  if ( ! class_exists( 'QM_Plugin' ) ) {
9
  abstract class QM_Plugin {
10
 
 
 
 
11
  private $plugin = array();
 
 
 
 
12
  public static $minimum_php_version = '5.3.6';
13
 
 
 
 
 
 
14
  /**
15
  * Class constructor
 
 
16
  */
17
  protected function __construct( $file ) {
18
  $this->file = $file;
@@ -45,7 +59,7 @@ abstract class QM_Plugin {
45
  * @return string Version
46
  */
47
  final public function plugin_ver( $file ) {
48
- return filemtime( $this->plugin_path( $file ) );
49
  }
50
 
51
  /**
@@ -59,6 +73,10 @@ abstract class QM_Plugin {
59
 
60
  /**
61
  * Populates and returns the current plugin info.
 
 
 
 
62
  */
63
  private function _plugin( $item, $file = '' ) {
64
  if ( ! array_key_exists( $item, $this->plugin ) ) {
@@ -77,6 +95,9 @@ abstract class QM_Plugin {
77
  return $this->plugin[ $item ] . ltrim( $file, '/' );
78
  }
79
 
 
 
 
80
  public static function php_version_met() {
81
  static $met = null;
82
 
@@ -87,6 +108,9 @@ abstract class QM_Plugin {
87
  return $met;
88
  }
89
 
 
 
 
90
  public static function php_version_nope() {
91
  printf(
92
  '<div id="qm-php-nope" class="notice notice-error is-dismissible"><p>%s</p></div>',
8
  if ( ! class_exists( 'QM_Plugin' ) ) {
9
  abstract class QM_Plugin {
10
 
11
+ /**
12
+ * @var array<string, string>
13
+ */
14
  private $plugin = array();
15
+
16
+ /**
17
+ * @var string
18
+ */
19
  public static $minimum_php_version = '5.3.6';
20
 
21
+ /**
22
+ * @var string
23
+ */
24
+ public $file = '';
25
+
26
  /**
27
  * Class constructor
28
+ *
29
+ * @param string $file
30
  */
31
  protected function __construct( $file ) {
32
  $this->file = $file;
59
  * @return string Version
60
  */
61
  final public function plugin_ver( $file ) {
62
+ return (string) filemtime( $this->plugin_path( $file ) );
63
  }
64
 
65
  /**
73
 
74
  /**
75
  * Populates and returns the current plugin info.
76
+ *
77
+ * @param string $item
78
+ * @param string $file
79
+ * @return string
80
  */
81
  private function _plugin( $item, $file = '' ) {
82
  if ( ! array_key_exists( $item, $this->plugin ) ) {
95
  return $this->plugin[ $item ] . ltrim( $file, '/' );
96
  }
97
 
98
+ /**
99
+ * @return bool
100
+ */
101
  public static function php_version_met() {
102
  static $met = null;
103
 
108
  return $met;
109
  }
110
 
111
+ /**
112
+ * @return void
113
+ */
114
  public static function php_version_nope() {
115
  printf(
116
  '<div id="qm-php-nope" class="notice notice-error is-dismissible"><p>%s</p></div>',
classes/QM.php CHANGED
@@ -7,6 +7,11 @@
7
 
8
  class QM {
9
 
 
 
 
 
 
10
  public static function emergency( $message, array $context = array() ) {
11
  /**
12
  * Fires when an `emergency` level message is logged.
@@ -19,6 +24,11 @@ class QM {
19
  do_action( 'qm/emergency', $message, $context );
20
  }
21
 
 
 
 
 
 
22
  public static function alert( $message, array $context = array() ) {
23
  /**
24
  * Fires when an `alert` level message is logged.
@@ -31,6 +41,11 @@ class QM {
31
  do_action( 'qm/alert', $message, $context );
32
  }
33
 
 
 
 
 
 
34
  public static function critical( $message, array $context = array() ) {
35
  /**
36
  * Fires when a `critical` level message is logged.
@@ -43,6 +58,11 @@ class QM {
43
  do_action( 'qm/critical', $message, $context );
44
  }
45
 
 
 
 
 
 
46
  public static function error( $message, array $context = array() ) {
47
  /**
48
  * Fires when an `error` level message is logged.
@@ -55,6 +75,11 @@ class QM {
55
  do_action( 'qm/error', $message, $context );
56
  }
57
 
 
 
 
 
 
58
  public static function warning( $message, array $context = array() ) {
59
  /**
60
  * Fires when a `warning` level message is logged.
@@ -67,6 +92,11 @@ class QM {
67
  do_action( 'qm/warning', $message, $context );
68
  }
69
 
 
 
 
 
 
70
  public static function notice( $message, array $context = array() ) {
71
  /**
72
  * Fires when a `notice` level message is logged.
@@ -79,6 +109,11 @@ class QM {
79
  do_action( 'qm/notice', $message, $context );
80
  }
81
 
 
 
 
 
 
82
  public static function info( $message, array $context = array() ) {
83
  /**
84
  * Fires when an `info` level message is logged.
@@ -91,6 +126,11 @@ class QM {
91
  do_action( 'qm/info', $message, $context );
92
  }
93
 
 
 
 
 
 
94
  public static function debug( $message, array $context = array() ) {
95
  /**
96
  * Fires when a `debug` level message is logged.
@@ -103,7 +143,15 @@ class QM {
103
  do_action( 'qm/debug', $message, $context );
104
  }
105
 
 
 
 
 
 
 
 
106
  public static function log( $level, $message, array $context = array() ) {
 
107
  $logger = QM_Collectors::get( 'logger' );
108
  $logger->log( $level, $message, $context );
109
  }
7
 
8
  class QM {
9
 
10
+ /**
11
+ * @param string $message
12
+ * @param array<string, mixed> $context
13
+ * @return void
14
+ */
15
  public static function emergency( $message, array $context = array() ) {
16
  /**
17
  * Fires when an `emergency` level message is logged.
24
  do_action( 'qm/emergency', $message, $context );
25
  }
26
 
27
+ /**
28
+ * @param string $message
29
+ * @param array<string, mixed> $context
30
+ * @return void
31
+ */
32
  public static function alert( $message, array $context = array() ) {
33
  /**
34
  * Fires when an `alert` level message is logged.
41
  do_action( 'qm/alert', $message, $context );
42
  }
43
 
44
+ /**
45
+ * @param string $message
46
+ * @param array<string, mixed> $context
47
+ * @return void
48
+ */
49
  public static function critical( $message, array $context = array() ) {
50
  /**
51
  * Fires when a `critical` level message is logged.
58
  do_action( 'qm/critical', $message, $context );
59
  }
60
 
61
+ /**
62
+ * @param string $message
63
+ * @param array<string, mixed> $context
64
+ * @return void
65
+ */
66
  public static function error( $message, array $context = array() ) {
67
  /**
68
  * Fires when an `error` level message is logged.
75
  do_action( 'qm/error', $message, $context );
76
  }
77
 
78
+ /**
79
+ * @param string $message
80
+ * @param array<string, mixed> $context
81
+ * @return void
82
+ */
83
  public static function warning( $message, array $context = array() ) {
84
  /**
85
  * Fires when a `warning` level message is logged.
92
  do_action( 'qm/warning', $message, $context );
93
  }
94
 
95
+ /**
96
+ * @param string $message
97
+ * @param array<string, mixed> $context
98
+ * @return void
99
+ */
100
  public static function notice( $message, array $context = array() ) {
101
  /**
102
  * Fires when a `notice` level message is logged.
109
  do_action( 'qm/notice', $message, $context );
110
  }
111
 
112
+ /**
113
+ * @param string $message
114
+ * @param array<string, mixed> $context
115
+ * @return void
116
+ */
117
  public static function info( $message, array $context = array() ) {
118
  /**
119
  * Fires when an `info` level message is logged.
126
  do_action( 'qm/info', $message, $context );
127
  }
128
 
129
+ /**
130
+ * @param string $message
131
+ * @param array<string, mixed> $context
132
+ * @return void
133
+ */
134
  public static function debug( $message, array $context = array() ) {
135
  /**
136
  * Fires when a `debug` level message is logged.
143
  do_action( 'qm/debug', $message, $context );
144
  }
145
 
146
+ /**
147
+ * @param string $level
148
+ * @param string $message
149
+ * @param array<string, mixed> $context
150
+ * @phpstan-param QM_Collector_Logger::* $level
151
+ * @return void
152
+ */
153
  public static function log( $level, $message, array $context = array() ) {
154
+ /** @var QM_Collector_Logger */
155
  $logger = QM_Collectors::get( 'logger' );
156
  $logger->log( $level, $message, $context );
157
  }
classes/QueryMonitor.php CHANGED
@@ -7,29 +7,30 @@
7
 
8
  class QueryMonitor extends QM_Plugin {
9
 
10
- protected function __construct( $file ) {
 
 
 
11
 
12
  # Actions
13
  add_action( 'plugins_loaded', array( $this, 'action_plugins_loaded' ) );
14
- add_action( 'init', array( $this, 'action_init' ) );
15
- add_action( 'members_register_caps', array( $this, 'action_register_members_caps' ) );
16
  add_action( 'members_register_cap_groups', array( $this, 'action_register_members_groups' ) );
 
17
 
18
  # Filters
19
- add_filter( 'user_has_cap', array( $this, 'filter_user_has_cap' ), 10, 4 );
20
- add_filter( 'ure_built_in_wp_caps', array( $this, 'filter_ure_caps' ) );
21
  add_filter( 'ure_capabilities_groups_tree', array( $this, 'filter_ure_groups' ) );
22
  add_filter( 'network_admin_plugin_action_links_query-monitor/query-monitor.php', array( $this, 'filter_plugin_action_links' ) );
23
- add_filter( 'plugin_action_links_query-monitor/query-monitor.php', array( $this, 'filter_plugin_action_links' ) );
24
  add_filter( 'plugin_row_meta', array( $this, 'filter_plugin_row_meta' ), 10, 4 );
25
 
26
- # Parent setup:
27
- parent::__construct( $file );
28
-
29
  # Load and register built-in collectors:
30
  $collectors = array();
31
  foreach ( glob( $this->plugin_path( 'collectors/*.php' ) ) as $file ) {
32
- $key = basename( $file, '.php' );
33
  $collectors[ $key ] = $file;
34
  }
35
 
@@ -41,15 +42,19 @@ class QueryMonitor extends QM_Plugin {
41
  * @param string[] $collectors Array of file paths to be loaded.
42
  */
43
  foreach ( apply_filters( 'qm/built-in-collectors', $collectors ) as $file ) {
44
- include $file;
45
  }
46
 
47
  }
48
 
 
 
 
 
49
  public function filter_plugin_action_links( array $actions ) {
50
  return array_merge( array(
51
  'settings' => '<a href="#qm-settings">' . esc_html__( 'Settings', 'query-monitor' ) . '</a>',
52
- 'add-ons' => '<a href="https://github.com/johnbillion/query-monitor/wiki/Query-Monitor-Add-on-Plugins">' . esc_html__( 'Add-ons', 'query-monitor' ) . '</a>',
53
  ), $actions );
54
  }
55
 
@@ -85,7 +90,7 @@ class QueryMonitor extends QM_Plugin {
85
  * @param bool[] $user_caps Array of key/value pairs where keys represent a capability name and boolean values
86
  * represent whether the user has that capability.
87
  * @param string[] $required_caps Required primitive capabilities for the requested capability.
88
- * @param array $args {
89
  * Arguments that accompany the requested capability check.
90
  *
91
  * @type string $0 Requested capability.
@@ -111,6 +116,9 @@ class QueryMonitor extends QM_Plugin {
111
  return $user_caps;
112
  }
113
 
 
 
 
114
  public function action_plugins_loaded() {
115
  // Hide QM itself from output by default:
116
  if ( ! defined( 'QM_HIDE_SELF' ) ) {
@@ -131,7 +139,7 @@ class QueryMonitor extends QM_Plugin {
131
 
132
  # Load dispatchers:
133
  foreach ( glob( $this->plugin_path( 'dispatchers/*.php' ) ) as $file ) {
134
- include $file;
135
  }
136
 
137
  /**
@@ -148,10 +156,16 @@ class QueryMonitor extends QM_Plugin {
148
 
149
  }
150
 
 
 
 
151
  public function action_init() {
152
  load_plugin_textdomain( 'query-monitor', false, dirname( $this->plugin_base() ) . '/languages' );
153
  }
154
 
 
 
 
155
  public static function symlink_warning() {
156
  $db = WP_CONTENT_DIR . '/db.php';
157
  trigger_error( sprintf(
@@ -165,14 +179,16 @@ class QueryMonitor extends QM_Plugin {
165
  * Registers the Query Monitor user capability group for the Members plugin.
166
  *
167
  * @link https://wordpress.org/plugins/members/
 
 
168
  */
169
  public function action_register_members_groups() {
170
  members_register_cap_group( 'query_monitor', array(
171
- 'label' => __( 'Query Monitor', 'query-monitor' ),
172
- 'caps' => array(
173
  'view_query_monitor',
174
  ),
175
- 'icon' => 'dashicons-admin-tools',
176
  'priority' => 30,
177
  ) );
178
  }
@@ -181,6 +197,8 @@ class QueryMonitor extends QM_Plugin {
181
  * Registers the View Query Monitor user capability for the Members plugin.
182
  *
183
  * @link https://wordpress.org/plugins/members/
 
 
184
  */
185
  public function action_register_members_caps() {
186
  members_register_cap( 'view_query_monitor', array(
@@ -194,14 +212,14 @@ class QueryMonitor extends QM_Plugin {
194
  *
195
  * @link https://wordpress.org/plugins/user-role-editor/
196
  *
197
- * @param array[] $groups Array of existing groups.
198
- * @return array[] Updated array of groups.
199
  */
200
  public function filter_ure_groups( array $groups ) {
201
  $groups['query_monitor'] = array(
202
  'caption' => esc_html__( 'Query Monitor', 'query-monitor' ),
203
- 'parent' => 'custom',
204
- 'level' => 2,
205
  );
206
 
207
  return $groups;
@@ -212,8 +230,8 @@ class QueryMonitor extends QM_Plugin {
212
  *
213
  * @link https://wordpress.org/plugins/user-role-editor/
214
  *
215
- * @param array[] $caps Array of existing capabilities.
216
- * @return array[] Updated array of capabilities.
217
  */
218
  public function filter_ure_caps( array $caps ) {
219
  $caps['view_query_monitor'] = array(
@@ -224,6 +242,22 @@ class QueryMonitor extends QM_Plugin {
224
  return $caps;
225
  }
226
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  public static function init( $file = null ) {
228
 
229
  static $instance = null;
7
 
8
  class QueryMonitor extends QM_Plugin {
9
 
10
+ /**
11
+ * @return void
12
+ */
13
+ public function set_up() {
14
 
15
  # Actions
16
  add_action( 'plugins_loaded', array( $this, 'action_plugins_loaded' ) );
17
+ add_action( 'init', array( $this, 'action_init' ) );
18
+ add_action( 'members_register_caps', array( $this, 'action_register_members_caps' ) );
19
  add_action( 'members_register_cap_groups', array( $this, 'action_register_members_groups' ) );
20
+ add_action( 'qm/cease', array( $this, 'action_cease' ) );
21
 
22
  # Filters
23
+ add_filter( 'user_has_cap', array( $this, 'filter_user_has_cap' ), 10, 4 );
24
+ add_filter( 'ure_built_in_wp_caps', array( $this, 'filter_ure_caps' ) );
25
  add_filter( 'ure_capabilities_groups_tree', array( $this, 'filter_ure_groups' ) );
26
  add_filter( 'network_admin_plugin_action_links_query-monitor/query-monitor.php', array( $this, 'filter_plugin_action_links' ) );
27
+ add_filter( 'plugin_action_links_query-monitor/query-monitor.php', array( $this, 'filter_plugin_action_links' ) );
28
  add_filter( 'plugin_row_meta', array( $this, 'filter_plugin_row_meta' ), 10, 4 );
29
 
 
 
 
30
  # Load and register built-in collectors:
31
  $collectors = array();
32
  foreach ( glob( $this->plugin_path( 'collectors/*.php' ) ) as $file ) {
33
+ $key = basename( $file, '.php' );
34
  $collectors[ $key ] = $file;
35
  }
36
 
42
  * @param string[] $collectors Array of file paths to be loaded.
43
  */
44
  foreach ( apply_filters( 'qm/built-in-collectors', $collectors ) as $file ) {
45
+ include_once $file;
46
  }
47
 
48
  }
49
 
50
+ /**
51
+ * @param array<string, string> $actions
52
+ * @return array<string, string>
53
+ */
54
  public function filter_plugin_action_links( array $actions ) {
55
  return array_merge( array(
56
  'settings' => '<a href="#qm-settings">' . esc_html__( 'Settings', 'query-monitor' ) . '</a>',
57
+ 'add-ons' => '<a href="https://github.com/johnbillion/query-monitor/wiki/Query-Monitor-Add-on-Plugins">' . esc_html__( 'Add-ons', 'query-monitor' ) . '</a>',
58
  ), $actions );
59
  }
60
 
90
  * @param bool[] $user_caps Array of key/value pairs where keys represent a capability name and boolean values
91
  * represent whether the user has that capability.
92
  * @param string[] $required_caps Required primitive capabilities for the requested capability.
93
+ * @param mixed[] $args {
94
  * Arguments that accompany the requested capability check.
95
  *
96
  * @type string $0 Requested capability.
116
  return $user_caps;
117
  }
118
 
119
+ /**
120
+ * @return void
121
+ */
122
  public function action_plugins_loaded() {
123
  // Hide QM itself from output by default:
124
  if ( ! defined( 'QM_HIDE_SELF' ) ) {
139
 
140
  # Load dispatchers:
141
  foreach ( glob( $this->plugin_path( 'dispatchers/*.php' ) ) as $file ) {
142
+ include_once $file;
143
  }
144
 
145
  /**
156
 
157
  }
158
 
159
+ /**
160
+ * @return void
161
+ */
162
  public function action_init() {
163
  load_plugin_textdomain( 'query-monitor', false, dirname( $this->plugin_base() ) . '/languages' );
164
  }
165
 
166
+ /**
167
+ * @return void
168
+ */
169
  public static function symlink_warning() {
170
  $db = WP_CONTENT_DIR . '/db.php';
171
  trigger_error( sprintf(
179
  * Registers the Query Monitor user capability group for the Members plugin.
180
  *
181
  * @link https://wordpress.org/plugins/members/
182
+ *
183
+ * @return void
184
  */
185
  public function action_register_members_groups() {
186
  members_register_cap_group( 'query_monitor', array(
187
+ 'label' => __( 'Query Monitor', 'query-monitor' ),
188
+ 'caps' => array(
189
  'view_query_monitor',
190
  ),
191
+ 'icon' => 'dashicons-admin-tools',
192
  'priority' => 30,
193
  ) );
194
  }
197
  * Registers the View Query Monitor user capability for the Members plugin.
198
  *
199
  * @link https://wordpress.org/plugins/members/
200
+ *
201
+ * @return void
202
  */
203
  public function action_register_members_caps() {
204
  members_register_cap( 'view_query_monitor', array(
212
  *
213
  * @link https://wordpress.org/plugins/user-role-editor/
214
  *
215
+ * @param array<string, array<string, mixed>> $groups Array of existing groups.
216
+ * @return array<string, array<string, mixed>> Updated array of groups.
217
  */
218
  public function filter_ure_groups( array $groups ) {
219
  $groups['query_monitor'] = array(
220
  'caption' => esc_html__( 'Query Monitor', 'query-monitor' ),
221
+ 'parent' => 'custom',
222
+ 'level' => 2,
223
  );
224
 
225
  return $groups;
230
  *
231
  * @link https://wordpress.org/plugins/user-role-editor/
232
  *
233
+ * @param array<string, array<string, mixed>> $caps Array of existing capabilities.
234
+ * @return array<string, array<string, mixed>> Updated array of capabilities.
235
  */
236
  public function filter_ure_caps( array $caps ) {
237
  $caps['view_query_monitor'] = array(
242
  return $caps;
243
  }
244
 
245
+ /**
246
+ * @return void
247
+ */
248
+ public function action_cease() {
249
+ // iterate collectors, call tear_down
250
+ // discard all collected data
251
+ QM_Collectors::cease();
252
+
253
+ // remove dispatchers or prevent them from doing anything
254
+ QM_Dispatchers::cease();
255
+ }
256
+
257
+ /**
258
+ * @param string $file
259
+ * @return self
260
+ */
261
  public static function init( $file = null ) {
262
 
263
  static $instance = null;
classes/Timer.php CHANGED
@@ -7,44 +7,90 @@
7
 
8
  class QM_Timer {
9
 
 
 
 
 
 
 
 
 
10
  protected $start = null;
11
- protected $end = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  protected $trace = null;
13
- protected $laps = array();
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  public function start( array $data = null ) {
16
  $this->trace = new QM_Backtrace();
17
  $this->start = array(
18
- 'time' => microtime( true ),
19
  'memory' => memory_get_usage(),
20
- 'data' => $data,
21
  );
22
  return $this;
23
  }
24
 
 
 
 
 
25
  public function stop( array $data = null ) {
26
 
27
  $this->end = array(
28
- 'time' => microtime( true ),
29
  'memory' => memory_get_usage(),
30
- 'data' => $data,
31
  );
32
 
33
  return $this;
34
 
35
  }
36
 
 
 
 
 
 
37
  public function lap( array $data = null, $name = null ) {
38
 
39
  $lap = array(
40
- 'time' => microtime( true ),
41
  'memory' => memory_get_usage(),
42
- 'data' => $data,
43
  );
44
 
45
  if ( ! isset( $name ) ) {
46
- /* translators: %d: Timing lap number */
47
- $i = sprintf( __( 'Lap %d', 'query-monitor' ), count( $this->laps ) + 1 );
 
 
 
48
  } else {
49
  $i = $name;
50
  }
@@ -55,6 +101,9 @@ class QM_Timer {
55
 
56
  }
57
 
 
 
 
58
  public function get_laps() {
59
 
60
  $laps = array();
@@ -62,11 +111,11 @@ class QM_Timer {
62
 
63
  foreach ( $this->laps as $lap_id => $lap ) {
64
 
65
- $lap['time_used'] = $lap['time'] - $prev['time'];
66
  $lap['memory_used'] = $lap['memory'] - $prev['memory'];
67
 
68
  $laps[ $lap_id ] = $lap;
69
- $prev = $lap;
70
 
71
  }
72
 
@@ -74,34 +123,59 @@ class QM_Timer {
74
 
75
  }
76
 
 
 
 
77
  public function get_time() {
78
  return $this->end['time'] - $this->start['time'];
79
  }
80
 
 
 
 
81
  public function get_memory() {
82
  return $this->end['memory'] - $this->start['memory'];
83
  }
84
 
 
 
 
85
  public function get_start_time() {
86
  return $this->start['time'];
87
  }
88
 
 
 
 
89
  public function get_start_memory() {
90
  return $this->start['memory'];
91
  }
92
 
 
 
 
93
  public function get_end_time() {
94
  return $this->end['time'];
95
  }
96
 
 
 
 
97
  public function get_end_memory() {
98
  return $this->end['memory'];
99
  }
100
 
 
 
 
101
  public function get_trace() {
102
  return $this->trace;
103
  }
104
 
 
 
 
 
105
  public function end( array $data = null ) {
106
  return $this->stop( $data );
107
  }
7
 
8
  class QM_Timer {
9
 
10
+ /**
11
+ * @var array<string, mixed>|null
12
+ * @phpstan-var array{
13
+ * time: float,
14
+ * memory: int,
15
+ * data: mixed[],
16
+ * }|null
17
+ */
18
  protected $start = null;
19
+
20
+ /**
21
+ * @var array<string, mixed>|null
22
+ * @phpstan-var array{
23
+ * time: float,
24
+ * memory: int,
25
+ * data: mixed[],
26
+ * }|null
27
+ */
28
+ protected $end = null;
29
+
30
+ /**
31
+ * @var QM_Backtrace|null
32
+ */
33
  protected $trace = null;
 
34
 
35
+ /**
36
+ * @var array<string, array<string, mixed>>
37
+ * @phpstan-var array<string, array{
38
+ * time: float,
39
+ * memory: int,
40
+ * data: mixed[],
41
+ * }>
42
+ */
43
+ protected $laps = array();
44
+
45
+ /**
46
+ * @param mixed[] $data
47
+ * @return self
48
+ */
49
  public function start( array $data = null ) {
50
  $this->trace = new QM_Backtrace();
51
  $this->start = array(
52
+ 'time' => microtime( true ),
53
  'memory' => memory_get_usage(),
54
+ 'data' => $data,
55
  );
56
  return $this;
57
  }
58
 
59
+ /**
60
+ * @param mixed[] $data
61
+ * @return self
62
+ */
63
  public function stop( array $data = null ) {
64
 
65
  $this->end = array(
66
+ 'time' => microtime( true ),
67
  'memory' => memory_get_usage(),
68
+ 'data' => $data,
69
  );
70
 
71
  return $this;
72
 
73
  }
74
 
75
+ /**
76
+ * @param mixed[] $data
77
+ * @param string $name
78
+ * @return self
79
+ */
80
  public function lap( array $data = null, $name = null ) {
81
 
82
  $lap = array(
83
+ 'time' => microtime( true ),
84
  'memory' => memory_get_usage(),
85
+ 'data' => $data,
86
  );
87
 
88
  if ( ! isset( $name ) ) {
89
+ $i = sprintf(
90
+ /* translators: %s: Timing lap number */
91
+ __( 'Lap %s', 'query-monitor' ),
92
+ number_format_i18n( count( $this->laps ) + 1 )
93
+ );
94
  } else {
95
  $i = $name;
96
  }
101
 
102
  }
103
 
104
+ /**
105
+ * @return mixed[]
106
+ */
107
  public function get_laps() {
108
 
109
  $laps = array();
111
 
112
  foreach ( $this->laps as $lap_id => $lap ) {
113
 
114
+ $lap['time_used'] = $lap['time'] - $prev['time'];
115
  $lap['memory_used'] = $lap['memory'] - $prev['memory'];
116
 
117
  $laps[ $lap_id ] = $lap;
118
+ $prev = $lap;
119
 
120
  }
121
 
123
 
124
  }
125
 
126
+ /**
127
+ * @return float
128
+ */
129
  public function get_time() {
130
  return $this->end['time'] - $this->start['time'];
131
  }
132
 
133
+ /**
134
+ * @return int
135
+ */
136
  public function get_memory() {
137
  return $this->end['memory'] - $this->start['memory'];
138
  }
139
 
140
+ /**
141
+ * @return float
142
+ */
143
  public function get_start_time() {
144
  return $this->start['time'];
145
  }
146
 
147
+ /**
148
+ * @return int
149
+ */
150
  public function get_start_memory() {
151
  return $this->start['memory'];
152
  }
153
 
154
+ /**
155
+ * @return float
156
+ */
157
  public function get_end_time() {
158
  return $this->end['time'];
159
  }
160
 
161
+ /**
162
+ * @return int
163
+ */
164
  public function get_end_memory() {
165
  return $this->end['memory'];
166
  }
167
 
168
+ /**
169
+ * @return QM_Backtrace
170
+ */
171
  public function get_trace() {
172
  return $this->trace;
173
  }
174
 
175
+ /**
176
+ * @param mixed[] $data
177
+ * @return self
178
+ */
179
  public function end( array $data = null ) {
180
  return $this->stop( $data );
181
  }
classes/Util.php CHANGED
@@ -8,14 +8,37 @@
8
  if ( ! class_exists( 'QM_Util' ) ) {
9
  class QM_Util {
10
 
 
 
 
11
  protected static $file_components = array();
12
- protected static $file_dirs = array();
13
- protected static $abspath = null;
14
- protected static $contentpath = null;
15
- protected static $sort_field = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
  private function __construct() {}
18
 
 
 
 
 
19
  public static function convert_hr_to_bytes( $size ) {
20
 
21
  # Annoyingly, wp_convert_hr_to_bytes() is defined in a file that's only
@@ -37,13 +60,18 @@ class QM_Util {
37
 
38
  }
39
 
 
 
 
 
 
40
  public static function standard_dir( $dir, $path_replace = null ) {
41
 
42
  $dir = self::normalize_path( $dir );
43
 
44
  if ( is_string( $path_replace ) ) {
45
  if ( ! self::$abspath ) {
46
- self::$abspath = self::normalize_path( ABSPATH );
47
  self::$contentpath = self::normalize_path( dirname( WP_CONTENT_DIR ) . '/' );
48
  }
49
  $dir = str_replace( array(
@@ -56,6 +84,10 @@ class QM_Util {
56
 
57
  }
58
 
 
 
 
 
59
  public static function normalize_path( $path ) {
60
  if ( function_exists( 'wp_normalize_path' ) ) {
61
  $path = wp_normalize_path( $path );
@@ -67,6 +99,9 @@ class QM_Util {
67
  return $path;
68
  }
69
 
 
 
 
70
  public static function get_file_dirs() {
71
  if ( empty( self::$file_dirs ) ) {
72
 
@@ -76,7 +111,10 @@ class QM_Util {
76
  * Note that this filter is applied before QM adds its built-in list of components. This is
77
  * so custom registered components take precedence during component detection.
78
  *
79
- * See the corresponding `qm/component_name/{$type}` filter for specifying the component name.
 
 
 
80
  *
81
  * @since 3.6.0
82
  *
@@ -84,10 +122,10 @@ class QM_Util {
84
  */
85
  self::$file_dirs = apply_filters( 'qm/component_dirs', self::$file_dirs );
86
 
87
- self::$file_dirs['plugin'] = WP_PLUGIN_DIR;
88
- self::$file_dirs['mu-vendor'] = WPMU_PLUGIN_DIR . '/vendor';
89
- self::$file_dirs['go-plugin'] = WPMU_PLUGIN_DIR . '/shared-plugins';
90
- self::$file_dirs['mu-plugin'] = WPMU_PLUGIN_DIR;
91
  self::$file_dirs['vip-plugin'] = get_theme_root() . '/vip/plugins';
92
 
93
  if ( defined( 'WPCOM_VIP_CLIENT_MU_PLUGIN_DIR' ) ) {
@@ -98,12 +136,12 @@ class QM_Util {
98
  self::$file_dirs['altis-vendor'] = \Altis\ROOT_DIR . '/vendor';
99
  }
100
 
101
- self::$file_dirs['theme'] = null;
102
  self::$file_dirs['stylesheet'] = get_stylesheet_directory();
103
- self::$file_dirs['template'] = get_template_directory();
104
- self::$file_dirs['other'] = WP_CONTENT_DIR;
105
- self::$file_dirs['core'] = ABSPATH;
106
- self::$file_dirs['unknown'] = null;
107
 
108
  foreach ( self::$file_dirs as $type => $dir ) {
109
  self::$file_dirs[ $type ] = self::standard_dir( $dir );
@@ -113,11 +151,18 @@ class QM_Util {
113
  return self::$file_dirs;
114
  }
115
 
 
 
 
 
 
 
116
  public static function get_file_component( $file ) {
117
 
118
  # @TODO turn this into a class (eg QM_File_Component)
119
 
120
  $file = self::standard_dir( $file );
 
121
 
122
  if ( isset( self::$file_components[ $file ] ) ) {
123
  return self::$file_components[ $file ];
@@ -195,14 +240,14 @@ class QM_Util {
195
  case 'other':
196
  // Anything else that's within the content directory should appear as
197
  // `wp-content/{dir}` or `wp-content/{file}`
198
- $name = self::standard_dir( $file );
199
- $name = str_replace( dirname( self::$file_dirs['other'] ), '', $name );
200
- $parts = explode( '/', trim( $name, '/' ) );
201
- $name = $parts[0] . '/' . $parts[1];
202
  $context = $file;
203
  break;
204
  case 'core':
205
- $name = __( 'Core', 'query-monitor' );
206
  break;
207
  case 'unknown':
208
  default:
@@ -213,7 +258,10 @@ class QM_Util {
213
  *
214
  * The dynamic portion of the hook name, `$type`, refers to the component identifier.
215
  *
216
- * See the corresponding `qm/component_dirs` filter for specifying the component directories.
 
 
 
217
  *
218
  * @since 3.6.0
219
  *
@@ -221,6 +269,25 @@ class QM_Util {
221
  * @param string $file The full file path for the file within the component.
222
  */
223
  $name = apply_filters( "qm/component_name/{$type}", $name, $file );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  break;
225
  }
226
 
@@ -229,6 +296,10 @@ class QM_Util {
229
  return self::$file_components[ $file ];
230
  }
231
 
 
 
 
 
232
  public static function populate_callback( array $callback ) {
233
 
234
  if ( is_string( $callback['function'] ) && ( false !== strpos( $callback['function'], '::' ) ) ) {
@@ -246,10 +317,10 @@ class QM_Util {
246
 
247
  if ( is_array( $callback['function'] ) ) {
248
  if ( is_object( $callback['function'][0] ) ) {
249
- $class = get_class( $callback['function'][0] );
250
  $access = '->';
251
  } else {
252
- $class = $callback['function'][0];
253
  $access = '::';
254
  }
255
 
@@ -257,7 +328,7 @@ class QM_Util {
257
  $ref = new ReflectionMethod( $class, $callback['function'][1] );
258
  } elseif ( is_object( $callback['function'] ) ) {
259
  if ( is_a( $callback['function'], 'Closure' ) ) {
260
- $ref = new ReflectionFunction( $callback['function'] );
261
  $file = self::standard_dir( $ref->getFileName(), '' );
262
  if ( 0 === strpos( $file, '/' ) ) {
263
  $file = basename( $ref->getFileName() );
@@ -300,8 +371,8 @@ class QM_Util {
300
  $callback['component'] = self::get_file_component( $callback['file'] );
301
  } else {
302
  $callback['component'] = (object) array(
303
- 'type' => 'php',
304
- 'name' => 'PHP',
305
  'context' => '',
306
  );
307
  }
@@ -315,6 +386,9 @@ class QM_Util {
315
 
316
  }
317
 
 
 
 
318
  public static function is_ajax() {
319
  if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
320
  return true;
@@ -322,6 +396,9 @@ class QM_Util {
322
  return false;
323
  }
324
 
 
 
 
325
  public static function is_async() {
326
  if ( self::is_ajax() ) {
327
  return true;
@@ -332,6 +409,9 @@ class QM_Util {
332
  return false;
333
  }
334
 
 
 
 
335
  public static function get_admins() {
336
  if ( is_multisite() ) {
337
  return false;
@@ -340,6 +420,9 @@ class QM_Util {
340
  }
341
  }
342
 
 
 
 
343
  public static function is_multi_network() {
344
  global $wpdb;
345
 
@@ -361,6 +444,15 @@ class QM_Util {
361
  return ( $num_sites > 1 );
362
  }
363
 
 
 
 
 
 
 
 
 
 
364
  public static function get_client_version( $client ) {
365
 
366
  $client = intval( $client );
@@ -375,6 +467,10 @@ class QM_Util {
375
 
376
  }
377
 
 
 
 
 
378
  public static function get_query_type( $sql ) {
379
  // Trim leading whitespace and brackets
380
  $sql = ltrim( $sql, ' \t\n\r\0\x0B(' );
@@ -390,6 +486,10 @@ class QM_Util {
390
  return $type;
391
  }
392
 
 
 
 
 
393
  public static function display_variable( $value ) {
394
  if ( is_string( $value ) ) {
395
  return $value;
@@ -404,44 +504,42 @@ class QM_Util {
404
 
405
  case ( $value instanceof WP_Post ):
406
  case ( $value instanceof WP_User ):
407
- return sprintf( '%s (ID: %s)', $class, $value->ID );
408
  break;
409
 
410
  case ( $value instanceof WP_Term ):
411
- return sprintf( '%s (term_id: %s)', $class, $value->term_id );
412
  break;
413
 
414
  case ( $value instanceof WP_Comment ):
415
- return sprintf( '%s (comment_ID: %s)', $class, $value->comment_ID );
416
  break;
417
 
418
  case ( $value instanceof WP_Error ):
419
- return sprintf( '%s (%s)', $class, $value->get_error_code() );
420
  break;
421
 
422
  case ( $value instanceof WP_Role ):
423
  case ( $value instanceof WP_Post_Type ):
424
  case ( $value instanceof WP_Taxonomy ):
425
- return sprintf( '%s (%s)', $class, $value->name );
426
  break;
427
 
428
  case ( $value instanceof WP_Network ):
429
- return sprintf( '%s (id: %s)', $class, $value->id );
430
  break;
431
 
432
  case ( $value instanceof WP_Site ):
433
- return sprintf( '%s (blog_id: %s)', $class, $value->blog_id );
434
  break;
435
 
436
  case ( $value instanceof WP_Theme ):
437
- return sprintf( '%s (%s)', $class, $value->get_stylesheet() );
438
- break;
439
-
440
- default:
441
- return $class;
442
  break;
443
 
444
  }
 
 
445
  } else {
446
  return gettype( $value );
447
  }
@@ -491,20 +589,40 @@ class QM_Util {
491
  return $json;
492
  }
493
 
 
 
 
 
494
  public static function is_stringy( $data ) {
495
  return ( is_string( $data ) || ( is_object( $data ) && method_exists( $data, '__toString' ) ) );
496
  }
497
 
 
 
 
 
 
498
  public static function sort( array &$array, $field ) {
499
  self::$sort_field = $field;
500
  usort( $array, array( __CLASS__, '_sort' ) );
501
  }
502
 
 
 
 
 
 
503
  public static function rsort( array &$array, $field ) {
504
  self::$sort_field = $field;
505
  usort( $array, array( __CLASS__, '_rsort' ) );
506
  }
507
 
 
 
 
 
 
 
508
  private static function _rsort( $a, $b ) {
509
  $field = self::$sort_field;
510
 
@@ -515,6 +633,12 @@ class QM_Util {
515
  }
516
  }
517
 
 
 
 
 
 
 
518
  private static function _sort( $a, $b ) {
519
  $field = self::$sort_field;
520
 
8
  if ( ! class_exists( 'QM_Util' ) ) {
9
  class QM_Util {
10
 
11
+ /**
12
+ * @var array<string, stdClass>
13
+ */
14
  protected static $file_components = array();
15
+
16
+ /**
17
+ * @var array<string, string|null>
18
+ */
19
+ protected static $file_dirs = array();
20
+
21
+ /**
22
+ * @var string|null
23
+ */
24
+ protected static $abspath = null;
25
+
26
+ /**
27
+ * @var string|null
28
+ */
29
+ protected static $contentpath = null;
30
+
31
+ /**
32
+ * @var string|null
33
+ */
34
+ protected static $sort_field = null;
35
 
36
  private function __construct() {}
37
 
38
+ /**
39
+ * @param string $size
40
+ * @return float
41
+ */
42
  public static function convert_hr_to_bytes( $size ) {
43
 
44
  # Annoyingly, wp_convert_hr_to_bytes() is defined in a file that's only
60
 
61
  }
62
 
63
+ /**
64
+ * @param string $dir
65
+ * @param string $path_replace
66
+ * @return string
67
+ */
68
  public static function standard_dir( $dir, $path_replace = null ) {
69
 
70
  $dir = self::normalize_path( $dir );
71
 
72
  if ( is_string( $path_replace ) ) {
73
  if ( ! self::$abspath ) {
74
+ self::$abspath = self::normalize_path( ABSPATH );
75
  self::$contentpath = self::normalize_path( dirname( WP_CONTENT_DIR ) . '/' );
76
  }
77
  $dir = str_replace( array(
84
 
85
  }
86
 
87
+ /**
88
+ * @param string $path
89
+ * @return string
90
+ */
91
  public static function normalize_path( $path ) {
92
  if ( function_exists( 'wp_normalize_path' ) ) {
93
  $path = wp_normalize_path( $path );
99
  return $path;
100
  }
101
 
102
+ /**
103
+ * @return array<string, string|null>
104
+ */
105
  public static function get_file_dirs() {
106
  if ( empty( self::$file_dirs ) ) {
107
 
111
  * Note that this filter is applied before QM adds its built-in list of components. This is
112
  * so custom registered components take precedence during component detection.
113
  *
114
+ * See also the corresponding filters:
115
+ *
116
+ * - `qm/component_context/{$type}`
117
+ * - `qm/component_name/{$type}`
118
  *
119
  * @since 3.6.0
120
  *
122
  */
123
  self::$file_dirs = apply_filters( 'qm/component_dirs', self::$file_dirs );
124
 
125
+ self::$file_dirs['plugin'] = WP_PLUGIN_DIR;
126
+ self::$file_dirs['mu-vendor'] = WPMU_PLUGIN_DIR . '/vendor';
127
+ self::$file_dirs['go-plugin'] = WPMU_PLUGIN_DIR . '/shared-plugins';
128
+ self::$file_dirs['mu-plugin'] = WPMU_PLUGIN_DIR;
129
  self::$file_dirs['vip-plugin'] = get_theme_root() . '/vip/plugins';
130
 
131
  if ( defined( 'WPCOM_VIP_CLIENT_MU_PLUGIN_DIR' ) ) {
136
  self::$file_dirs['altis-vendor'] = \Altis\ROOT_DIR . '/vendor';
137
  }
138
 
139
+ self::$file_dirs['theme'] = null;
140
  self::$file_dirs['stylesheet'] = get_stylesheet_directory();
141
+ self::$file_dirs['template'] = get_template_directory();
142
+ self::$file_dirs['other'] = WP_CONTENT_DIR;
143
+ self::$file_dirs['core'] = ABSPATH;
144
+ self::$file_dirs['unknown'] = null;
145
 
146
  foreach ( self::$file_dirs as $type => $dir ) {
147
  self::$file_dirs[ $type ] = self::standard_dir( $dir );
151
  return self::$file_dirs;
152
  }
153
 
154
+ /**
155
+ * Attempts to determine the component responsible for a given file name.
156
+ *
157
+ * @param string $file An absolute file path.
158
+ * @return stdClass A stdClass object (ouch) representing the component.
159
+ */
160
  public static function get_file_component( $file ) {
161
 
162
  # @TODO turn this into a class (eg QM_File_Component)
163
 
164
  $file = self::standard_dir( $file );
165
+ $type = '';
166
 
167
  if ( isset( self::$file_components[ $file ] ) ) {
168
  return self::$file_components[ $file ];
240
  case 'other':
241
  // Anything else that's within the content directory should appear as
242
  // `wp-content/{dir}` or `wp-content/{file}`
243
+ $name = self::standard_dir( $file );
244
+ $name = str_replace( dirname( self::$file_dirs['other'] ), '', $name );
245
+ $parts = explode( '/', trim( $name, '/' ) );
246
+ $name = $parts[0] . '/' . $parts[1];
247
  $context = $file;
248
  break;
249
  case 'core':
250
+ $name = __( 'WordPress Core', 'query-monitor' );
251
  break;
252
  case 'unknown':
253
  default:
258
  *
259
  * The dynamic portion of the hook name, `$type`, refers to the component identifier.
260
  *
261
+ * See also the corresponding filters:
262
+ *
263
+ * - `qm/component_dirs`
264
+ * - `qm/component_context/{$type}`
265
  *
266
  * @since 3.6.0
267
  *
269
  * @param string $file The full file path for the file within the component.
270
  */
271
  $name = apply_filters( "qm/component_name/{$type}", $name, $file );
272
+
273
+ /**
274
+ * Filters the context for a custom or unknown component. The context is usually a
275
+ * representation of its type more specific to the individual component.
276
+ *
277
+ * The dynamic portion of the hook name, `$type`, refers to the component identifier.
278
+ *
279
+ * See also the corresponding filters:
280
+ *
281
+ * - `qm/component_dirs`
282
+ * - `qm/component_name/{$type}`
283
+ *
284
+ * @since 3.8.0
285
+ *
286
+ * @param string $context The context for the component.
287
+ * @param string $file The full file path for the file within the component.
288
+ * @param string $name The component name.
289
+ */
290
+ $context = apply_filters( "qm/component_context/{$type}", $context, $file, $name );
291
  break;
292
  }
293
 
296
  return self::$file_components[ $file ];
297
  }
298
 
299
+ /**
300
+ * @param array<string, mixed> $callback
301
+ * @return array<string, mixed>
302
+ */
303
  public static function populate_callback( array $callback ) {
304
 
305
  if ( is_string( $callback['function'] ) && ( false !== strpos( $callback['function'], '::' ) ) ) {
317
 
318
  if ( is_array( $callback['function'] ) ) {
319
  if ( is_object( $callback['function'][0] ) ) {
320
+ $class = get_class( $callback['function'][0] );
321
  $access = '->';
322
  } else {
323
+ $class = $callback['function'][0];
324
  $access = '::';
325
  }
326
 
328
  $ref = new ReflectionMethod( $class, $callback['function'][1] );
329
  } elseif ( is_object( $callback['function'] ) ) {
330
  if ( is_a( $callback['function'], 'Closure' ) ) {
331
+ $ref = new ReflectionFunction( $callback['function'] );
332
  $file = self::standard_dir( $ref->getFileName(), '' );
333
  if ( 0 === strpos( $file, '/' ) ) {
334
  $file = basename( $ref->getFileName() );
371
  $callback['component'] = self::get_file_component( $callback['file'] );
372
  } else {
373
  $callback['component'] = (object) array(
374
+ 'type' => 'php',
375
+ 'name' => 'PHP',
376
  'context' => '',
377
  );
378
  }
386
 
387
  }
388
 
389
+ /**
390
+ * @return bool
391
+ */
392
  public static function is_ajax() {
393
  if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
394
  return true;
396
  return false;
397
  }
398
 
399
+ /**
400
+ * @return bool
401
+ */
402
  public static function is_async() {
403
  if ( self::is_ajax() ) {
404
  return true;
409
  return false;
410
  }
411
 
412
+ /**
413
+ * @return WP_Role|false
414
+ */
415
  public static function get_admins() {
416
  if ( is_multisite() ) {
417
  return false;
420
  }
421
  }
422
 
423
+ /**
424
+ * @return bool
425
+ */
426
  public static function is_multi_network() {
427
  global $wpdb;
428
 
444
  return ( $num_sites > 1 );
445
  }
446
 
447
+ /**
448
+ * @param int|string $client
449
+ * @return array<string, int>
450
+ * @phpstan-return array{
451
+ * major: int,
452
+ * minor: int,
453
+ * patch: int,
454
+ * }
455
+ */
456
  public static function get_client_version( $client ) {
457
 
458
  $client = intval( $client );
467
 
468
  }
469
 
470
+ /**
471
+ * @param string $sql
472
+ * @return string
473
+ */
474
  public static function get_query_type( $sql ) {
475
  // Trim leading whitespace and brackets
476
  $sql = ltrim( $sql, ' \t\n\r\0\x0B(' );
486
  return $type;
487
  }
488
 
489
+ /**
490
+ * @param mixed $value
491
+ * @return string|float|int
492
+ */
493
  public static function display_variable( $value ) {
494
  if ( is_string( $value ) ) {
495
  return $value;
504
 
505
  case ( $value instanceof WP_Post ):
506
  case ( $value instanceof WP_User ):
507
+ $class = sprintf( '%s (ID: %s)', $class, $value->ID );
508
  break;
509
 
510
  case ( $value instanceof WP_Term ):
511
+ $class = sprintf( '%s (term_id: %s)', $class, $value->term_id );
512
  break;
513
 
514
  case ( $value instanceof WP_Comment ):
515
+ $class = sprintf( '%s (comment_ID: %s)', $class, $value->comment_ID );
516
  break;
517
 
518
  case ( $value instanceof WP_Error ):
519
+ $class = sprintf( '%s (%s)', $class, $value->get_error_code() );
520
  break;
521
 
522
  case ( $value instanceof WP_Role ):
523
  case ( $value instanceof WP_Post_Type ):
524
  case ( $value instanceof WP_Taxonomy ):
525
+ $class = sprintf( '%s (%s)', $class, $value->name );
526
  break;
527
 
528
  case ( $value instanceof WP_Network ):
529
+ $class = sprintf( '%s (id: %s)', $class, $value->id );
530
  break;
531
 
532
  case ( $value instanceof WP_Site ):
533
+ $class = sprintf( '%s (blog_id: %s)', $class, $value->blog_id );
534
  break;
535
 
536
  case ( $value instanceof WP_Theme ):
537
+ $class = sprintf( '%s (%s)', $class, $value->get_stylesheet() );
 
 
 
 
538
  break;
539
 
540
  }
541
+
542
+ return $class;
543
  } else {
544
  return gettype( $value );
545
  }
589
  return $json;
590
  }
591
 
592
+ /**
593
+ * @param mixed $data
594
+ * @return bool
595
+ */
596
  public static function is_stringy( $data ) {
597
  return ( is_string( $data ) || ( is_object( $data ) && method_exists( $data, '__toString' ) ) );
598
  }
599
 
600
+ /**
601
+ * @param mixed[] $array
602
+ * @param string $field
603
+ * @return void
604
+ */
605
  public static function sort( array &$array, $field ) {
606
  self::$sort_field = $field;
607
  usort( $array, array( __CLASS__, '_sort' ) );
608
  }
609
 
610
+ /**
611
+ * @param mixed[] $array
612
+ * @param string $field
613
+ * @return void
614
+ */
615
  public static function rsort( array &$array, $field ) {
616
  self::$sort_field = $field;
617
  usort( $array, array( __CLASS__, '_rsort' ) );
618
  }
619
 
620
+ /**
621
+ * @param array<string, mixed> $a
622
+ * @param array<string, mixed> $b
623
+ * @return int
624
+ * @phpstan-return -1|0|1
625
+ */
626
  private static function _rsort( $a, $b ) {
627
  $field = self::$sort_field;
628
 
633
  }
634
  }
635
 
636
+ /**
637
+ * @param array<string, mixed> $a
638
+ * @param array<string, mixed> $b
639
+ * @return int
640
+ * @phpstan-return -1|0|1
641
+ */
642
  private static function _sort( $a, $b ) {
643
  $field = self::$sort_field;
644
 
classes/debug_bar.php CHANGED
@@ -6,6 +6,9 @@
6
  */
7
 
8
  class Debug_Bar {
 
 
 
9
  public $panels = array();
10
 
11
  public function __construct() {
@@ -15,6 +18,9 @@ class Debug_Bar {
15
  $this->init_panels();
16
  }
17
 
 
 
 
18
  public function enqueue() {
19
  // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
20
  wp_register_style( 'debug-bar', false, array(
@@ -33,6 +39,9 @@ class Debug_Bar {
33
  do_action( 'debug_bar_enqueue_scripts' );
34
  }
35
 
 
 
 
36
  public function init_panels() {
37
  require_once 'debug_bar_panel.php';
38
 
@@ -46,6 +55,9 @@ class Debug_Bar {
46
  $this->panels = apply_filters( 'debug_bar_panels', array() );
47
  }
48
 
 
 
 
49
  public function ensure_ajaxurl() {
50
  $dispatcher = QM_Dispatchers::get( 'html' );
51
 
@@ -58,6 +70,9 @@ class Debug_Bar {
58
  }
59
  }
60
 
 
 
 
61
  public function Debug_Bar() {
62
  self::__construct();
63
  }
6
  */
7
 
8
  class Debug_Bar {
9
+ /**
10
+ * @var Debug_Bar_Panel[]
11
+ */
12
  public $panels = array();
13
 
14
  public function __construct() {
18
  $this->init_panels();
19
  }
20
 
21
+ /**
22
+ * @return void
23
+ */
24
  public function enqueue() {
25
  // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
26
  wp_register_style( 'debug-bar', false, array(
39
  do_action( 'debug_bar_enqueue_scripts' );
40
  }
41
 
42
+ /**
43
+ * @return void
44
+ */
45
  public function init_panels() {
46
  require_once 'debug_bar_panel.php';
47
 
55
  $this->panels = apply_filters( 'debug_bar_panels', array() );
56
  }
57
 
58
+ /**
59
+ * @return void
60
+ */
61
  public function ensure_ajaxurl() {
62
  $dispatcher = QM_Dispatchers::get( 'html' );
63
 
70
  }
71
  }
72
 
73
+ /**
74
+ * @return void
75
+ */
76
  public function Debug_Bar() {
77
  self::__construct();
78
  }
classes/debug_bar_panel.php CHANGED
@@ -7,9 +7,19 @@
7
 
8
  abstract class Debug_Bar_Panel {
9
 
10
- public $_title = '';
 
 
 
 
 
 
 
11
  public $_visible = true;
12
 
 
 
 
13
  public function __construct( $title = '' ) {
14
  $this->title( $title );
15
 
@@ -18,30 +28,47 @@ abstract class Debug_Bar_Panel {
18
  return;
19
  }
20
 
21
- # @TODO convert to QM classes
22
  add_filter( 'debug_bar_classes', array( $this, 'debug_bar_classes' ) );
23
  }
24
 
25
  /**
26
  * Initializes the panel.
 
 
27
  */
28
  public function init() {}
29
 
 
 
 
30
  public function prerender() {}
31
 
32
  /**
33
  * Renders the panel.
 
 
34
  */
35
  public function render() {}
36
 
 
 
 
37
  public function is_visible() {
38
  return $this->_visible;
39
  }
40
 
 
 
 
 
41
  public function set_visible( $visible ) {
42
  $this->_visible = $visible;
43
  }
44
 
 
 
 
 
45
  public function title( $title = null ) {
46
  if ( ! isset( $title ) ) {
47
  return $this->_title;
@@ -49,10 +76,18 @@ abstract class Debug_Bar_Panel {
49
  $this->_title = $title;
50
  }
51
 
 
 
 
 
52
  public function debug_bar_classes( $classes ) {
53
  return $classes;
54
  }
55
 
 
 
 
 
56
  public function Debug_Bar_Panel( $title = '' ) {
57
  self::__construct( $title );
58
  }
7
 
8
  abstract class Debug_Bar_Panel {
9
 
10
+ /**
11
+ * @var string
12
+ */
13
+ public $_title = '';
14
+
15
+ /**
16
+ * @var bool
17
+ */
18
  public $_visible = true;
19
 
20
+ /**
21
+ * @param string $title
22
+ */
23
  public function __construct( $title = '' ) {
24
  $this->title( $title );
25
 
28
  return;
29
  }
30
 
 
31
  add_filter( 'debug_bar_classes', array( $this, 'debug_bar_classes' ) );
32
  }
33
 
34
  /**
35
  * Initializes the panel.
36
+ *
37
+ * @return false|void
38
  */
39
  public function init() {}
40
 
41
+ /**
42
+ * @return void
43
+ */
44
  public function prerender() {}
45
 
46
  /**
47
  * Renders the panel.
48
+ *
49
+ * @return void
50
  */
51
  public function render() {}
52
 
53
+ /**
54
+ * @return bool
55
+ */
56
  public function is_visible() {
57
  return $this->_visible;
58
  }
59
 
60
+ /**
61
+ * @param bool $visible
62
+ * @return void
63
+ */
64
  public function set_visible( $visible ) {
65
  $this->_visible = $visible;
66
  }
67
 
68
+ /**
69
+ * @param string|null $title
70
+ * @return string|void
71
+ */
72
  public function title( $title = null ) {
73
  if ( ! isset( $title ) ) {
74
  return $this->_title;
76
  $this->_title = $title;
77
  }
78
 
79
+ /**
80
+ * @param array<int, string> $classes
81
+ * @return array<int, string>
82
+ */
83
  public function debug_bar_classes( $classes ) {
84
  return $classes;
85
  }
86
 
87
+ /**
88
+ * @param string $title
89
+ * @return void
90
+ */
91
  public function Debug_Bar_Panel( $title = '' ) {
92
  self::__construct( $title );
93
  }
collectors/admin.php CHANGED
@@ -5,12 +5,17 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Collector_Admin extends QM_Collector {
11
 
12
  public $id = 'response';
13
 
 
 
 
14
  public function get_concerned_actions() {
15
  $actions = array(
16
  'current_screen',
@@ -27,6 +32,9 @@ class QM_Collector_Admin extends QM_Collector {
27
  return $actions;
28
  }
29
 
 
 
 
30
  public function get_concerned_filters() {
31
  $filters = array();
32
 
@@ -38,6 +46,9 @@ class QM_Collector_Admin extends QM_Collector {
38
  return $filters;
39
  }
40
 
 
 
 
41
  public function process() {
42
 
43
  global $pagenow, $wp_list_table;
@@ -50,24 +61,24 @@ class QM_Collector_Admin extends QM_Collector {
50
  $this->data['base'] = $pagenow;
51
  }
52
 
53
- $this->data['pagenow'] = $pagenow;
54
- $this->data['typenow'] = isset( $GLOBALS['typenow'] ) ? $GLOBALS['typenow'] : '';
55
- $this->data['taxnow'] = isset( $GLOBALS['taxnow'] ) ? $GLOBALS['taxnow'] : '';
56
- $this->data['hook_suffix'] = isset( $GLOBALS['hook_suffix'] ) ? $GLOBALS['hook_suffix'] : '';
57
  $this->data['current_screen'] = ( $current_screen ) ? get_object_vars( $current_screen ) : null;
58
 
59
  $screens = array(
60
- 'edit' => true,
61
- 'edit-comments' => true,
62
- 'edit-tags' => true,
63
- 'link-manager' => true,
64
- 'plugins' => true,
65
  'plugins-network' => true,
66
- 'sites-network' => true,
67
- 'themes-network' => true,
68
- 'upload' => true,
69
- 'users' => true,
70
- 'users-network' => true,
71
  );
72
 
73
  if ( ! empty( $this->data['current_screen'] ) && isset( $screens[ $this->data['current_screen']['base'] ] ) ) {
@@ -101,9 +112,9 @@ class QM_Collector_Admin extends QM_Collector {
101
  $list_table['sortables'] = $this->data['current_screen']['id'];
102
 
103
  $this->data['list_table'] = array(
104
- 'columns_filter' => "manage_{$list_table['columns']}_columns",
105
  'sortables_filter' => "manage_{$list_table['sortables']}_sortable_columns",
106
- 'column_action' => "manage_{$list_table['column']}_custom_column",
107
  );
108
 
109
  if ( ! empty( $wp_list_table ) ) {
@@ -115,6 +126,11 @@ class QM_Collector_Admin extends QM_Collector {
115
 
116
  }
117
 
 
 
 
 
 
118
  function register_qm_collector_admin( array $collectors, QueryMonitor $qm ) {
119
  $collectors['response'] = new QM_Collector_Admin();
120
  return $collectors;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Collector_Admin extends QM_Collector {
13
 
14
  public $id = 'response';
15
 
16
+ /**
17
+ * @return array<int, string>
18
+ */
19
  public function get_concerned_actions() {
20
  $actions = array(
21
  'current_screen',
32
  return $actions;
33
  }
34
 
35
+ /**
36
+ * @return array<int, string>
37
+ */
38
  public function get_concerned_filters() {
39
  $filters = array();
40
 
46
  return $filters;
47
  }
48
 
49
+ /**
50
+ * @return void
51
+ */
52
  public function process() {
53
 
54
  global $pagenow, $wp_list_table;
61
  $this->data['base'] = $pagenow;
62
  }
63
 
64
+ $this->data['pagenow'] = $pagenow;
65
+ $this->data['typenow'] = isset( $GLOBALS['typenow'] ) ? $GLOBALS['typenow'] : '';
66
+ $this->data['taxnow'] = isset( $GLOBALS['taxnow'] ) ? $GLOBALS['taxnow'] : '';
67
+ $this->data['hook_suffix'] = isset( $GLOBALS['hook_suffix'] ) ? $GLOBALS['hook_suffix'] : '';
68
  $this->data['current_screen'] = ( $current_screen ) ? get_object_vars( $current_screen ) : null;
69
 
70
  $screens = array(
71
+ 'edit' => true,
72
+ 'edit-comments' => true,
73
+ 'edit-tags' => true,
74
+ 'link-manager' => true,
75
+ 'plugins' => true,
76
  'plugins-network' => true,
77
+ 'sites-network' => true,
78
+ 'themes-network' => true,
79
+ 'upload' => true,
80
+ 'users' => true,
81
+ 'users-network' => true,
82
  );
83
 
84
  if ( ! empty( $this->data['current_screen'] ) && isset( $screens[ $this->data['current_screen']['base'] ] ) ) {
112
  $list_table['sortables'] = $this->data['current_screen']['id'];
113
 
114
  $this->data['list_table'] = array(
115
+ 'columns_filter' => "manage_{$list_table['columns']}_columns",
116
  'sortables_filter' => "manage_{$list_table['sortables']}_sortable_columns",
117
+ 'column_action' => "manage_{$list_table['column']}_custom_column",
118
  );
119
 
120
  if ( ! empty( $wp_list_table ) ) {
126
 
127
  }
128
 
129
+ /**
130
+ * @param array<string, QM_Collector> $collectors
131
+ * @param QueryMonitor $qm
132
+ * @return array<string, QM_Collector>
133
+ */
134
  function register_qm_collector_admin( array $collectors, QueryMonitor $qm ) {
135
  $collectors['response'] = new QM_Collector_Admin();
136
  return $collectors;
collectors/assets.php CHANGED
@@ -5,28 +5,56 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  abstract class QM_Collector_Assets extends QM_Collector {
11
 
12
- public function __construct() {
13
- parent::__construct();
 
 
 
14
  add_action( 'admin_print_footer_scripts', array( $this, 'action_print_footer_scripts' ) );
15
- add_action( 'wp_print_footer_scripts', array( $this, 'action_print_footer_scripts' ) );
16
- add_action( 'admin_head', array( $this, 'action_head' ), 9999 );
17
- add_action( 'wp_head', array( $this, 'action_head' ), 9999 );
18
- add_action( 'login_head', array( $this, 'action_head' ), 9999 );
19
- add_action( 'embed_head', array( $this, 'action_head' ), 9999 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  }
21
 
 
 
 
22
  abstract public function get_dependency_type();
23
 
 
 
 
24
  public function action_head() {
25
  $type = $this->get_dependency_type();
26
 
27
  $this->data['header'] = $GLOBALS[ "wp_{$type}" ]->done;
28
  }
29
 
 
 
 
30
  public function action_print_footer_scripts() {
31
  if ( empty( $this->data['header'] ) ) {
32
  return;
@@ -38,16 +66,20 @@ abstract class QM_Collector_Assets extends QM_Collector {
38
 
39
  }
40
 
 
 
 
41
  public function process() {
42
  if ( empty( $this->data['header'] ) && empty( $this->data['footer'] ) ) {
43
  return;
44
  }
45
 
46
- $this->data['is_ssl'] = is_ssl();
47
- $this->data['host'] = wp_unslash( $_SERVER['HTTP_HOST'] );
48
  $this->data['default_version'] = get_bloginfo( 'version' );
 
49
 
50
- $home_url = home_url();
51
  $positions = array(
52
  'missing',
53
  'broken',
@@ -57,10 +89,10 @@ abstract class QM_Collector_Assets extends QM_Collector {
57
 
58
  $this->data['counts'] = array(
59
  'missing' => 0,
60
- 'broken' => 0,
61
- 'header' => 0,
62
- 'footer' => 0,
63
- 'total' => 0,
64
  );
65
 
66
  $type = $this->get_dependency_type();
@@ -70,8 +102,8 @@ abstract class QM_Collector_Assets extends QM_Collector {
70
  $this->data[ $position ] = array();
71
  }
72
  }
73
- $raw = $GLOBALS[ "wp_{$type}" ];
74
- $broken = array_values( array_diff( $raw->queue, $raw->done ) );
75
  $missing = array_values( array_diff( $raw->queue, array_keys( $raw->registered ) ) );
76
 
77
  // A broken asset is one which has been deregistered without also being dequeued
@@ -104,7 +136,7 @@ abstract class QM_Collector_Assets extends QM_Collector {
104
  }
105
 
106
  $all_dependencies = array();
107
- $all_dependents = array();
108
 
109
  $missing_dependencies = array();
110
 
@@ -121,10 +153,10 @@ abstract class QM_Collector_Assets extends QM_Collector {
121
  }
122
 
123
  $all_dependencies = array_merge( $all_dependencies, $dependency->deps );
124
- $dependents = $this->get_dependents( $dependency, $raw );
125
- $all_dependents = array_merge( $all_dependents, $dependents );
126
 
127
- list( $host, $source, $local ) = $this->get_dependency_data( $dependency );
128
 
129
  if ( empty( $dependency->ver ) ) {
130
  $ver = '';
@@ -137,7 +169,7 @@ abstract class QM_Collector_Assets extends QM_Collector {
137
  if ( is_wp_error( $source ) ) {
138
  $display = $source->get_error_message();
139
  } else {
140
- $display = ltrim( str_replace( $home_url, '', remove_query_arg( 'ver', $source ) ), '/' );
141
  }
142
 
143
  $dependencies = $dependency->deps;
@@ -150,13 +182,14 @@ abstract class QM_Collector_Assets extends QM_Collector {
150
  }
151
 
152
  $this->data['assets'][ $position ][ $handle ] = array(
153
- 'host' => $host,
154
- 'source' => $source,
155
- 'local' => $local,
156
- 'ver' => $ver,
157
- 'warning' => $warning,
158
- 'display' => $display,
159
- 'dependents' => $dependents,
 
160
  'dependencies' => $dependencies,
161
  );
162
 
@@ -178,12 +211,17 @@ abstract class QM_Collector_Assets extends QM_Collector {
178
  $this->data['missing_dependencies'] = $missing_dependencies;
179
  }
180
 
 
 
 
 
 
181
  protected static function get_broken_dependencies( _WP_Dependency $item, WP_Dependencies $dependencies ) {
182
  $broken = array();
183
 
184
  foreach ( $item->deps as $handle ) {
185
  $dep = $dependencies->query( $handle );
186
- if ( $dep ) {
187
  $broken = array_merge( $broken, self::get_broken_dependencies( $dep, $dependencies ) );
188
  } else {
189
  $broken[] = $item->handle;
@@ -193,13 +231,18 @@ abstract class QM_Collector_Assets extends QM_Collector {
193
  return $broken;
194
  }
195
 
 
 
 
 
 
196
  public function get_dependents( _WP_Dependency $dependency, WP_Dependencies $dependencies ) {
197
  $dependents = array();
198
- $handles = array_unique( array_merge( $dependencies->queue, $dependencies->done ) );
199
 
200
  foreach ( $handles as $handle ) {
201
  $item = $dependencies->query( $handle );
202
- if ( $item ) {
203
  if ( in_array( $dependency->handle, $item->deps, true ) ) {
204
  $dependents[] = $handle;
205
  }
@@ -211,10 +254,19 @@ abstract class QM_Collector_Assets extends QM_Collector {
211
  return $dependents;
212
  }
213
 
 
 
 
 
 
 
 
 
 
214
  public function get_dependency_data( _WP_Dependency $dependency ) {
215
- $data = $this->get_data();
216
  $loader = rtrim( $this->get_dependency_type(), 's' );
217
- $src = $dependency->src;
218
 
219
  if ( null === $dependency->ver ) {
220
  $ver = '';
@@ -229,12 +281,15 @@ abstract class QM_Collector_Assets extends QM_Collector {
229
  /** This filter is documented in wp-includes/class.wp-scripts.php */
230
  $source = apply_filters( "{$loader}_loader_src", $src, $dependency->handle );
231
 
232
- $host = (string) parse_url( $source, PHP_URL_HOST );
233
- $scheme = (string) parse_url( $source, PHP_URL_SCHEME );
 
234
  $http_host = $data['host'];
 
235
 
236
  if ( empty( $host ) && ! empty( $http_host ) ) {
237
  $host = $http_host;
 
238
  }
239
 
240
  if ( $scheme && $data['is_ssl'] && ( 'https' !== $scheme ) && ( 'localhost' !== $host ) ) {
@@ -250,12 +305,12 @@ abstract class QM_Collector_Assets extends QM_Collector {
250
  }
251
  } elseif ( empty( $source ) ) {
252
  $source = '';
253
- $host = '';
254
  }
255
 
256
  $local = ( $http_host === $host );
257
 
258
- return array( $host, $source, $local );
259
  }
260
 
261
  }
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  abstract class QM_Collector_Assets extends QM_Collector {
13
 
14
+ /**
15
+ * @return void
16
+ */
17
+ public function set_up() {
18
+ parent::set_up();
19
  add_action( 'admin_print_footer_scripts', array( $this, 'action_print_footer_scripts' ) );
20
+ add_action( 'wp_print_footer_scripts', array( $this, 'action_print_footer_scripts' ) );
21
+ add_action( 'admin_head', array( $this, 'action_head' ), 9999 );
22
+ add_action( 'wp_head', array( $this, 'action_head' ), 9999 );
23
+ add_action( 'login_head', array( $this, 'action_head' ), 9999 );
24
+ add_action( 'embed_head', array( $this, 'action_head' ), 9999 );
25
+ }
26
+
27
+ /**
28
+ * @return void
29
+ */
30
+ public function tear_down() {
31
+ remove_action( 'admin_print_footer_scripts', array( $this, 'action_print_footer_scripts' ) );
32
+ remove_action( 'wp_print_footer_scripts', array( $this, 'action_print_footer_scripts' ) );
33
+ remove_action( 'admin_head', array( $this, 'action_head' ), 9999 );
34
+ remove_action( 'wp_head', array( $this, 'action_head' ), 9999 );
35
+ remove_action( 'login_head', array( $this, 'action_head' ), 9999 );
36
+ remove_action( 'embed_head', array( $this, 'action_head' ), 9999 );
37
+
38
+ parent::tear_down();
39
  }
40
 
41
+ /**
42
+ * @return string
43
+ */
44
  abstract public function get_dependency_type();
45
 
46
+ /**
47
+ * @return void
48
+ */
49
  public function action_head() {
50
  $type = $this->get_dependency_type();
51
 
52
  $this->data['header'] = $GLOBALS[ "wp_{$type}" ]->done;
53
  }
54
 
55
+ /**
56
+ * @return void
57
+ */
58
  public function action_print_footer_scripts() {
59
  if ( empty( $this->data['header'] ) ) {
60
  return;
66
 
67
  }
68
 
69
+ /**
70
+ * @return void
71
+ */
72
  public function process() {
73
  if ( empty( $this->data['header'] ) && empty( $this->data['footer'] ) ) {
74
  return;
75
  }
76
 
77
+ $this->data['is_ssl'] = is_ssl();
78
+ $this->data['host'] = wp_unslash( $_SERVER['HTTP_HOST'] );
79
  $this->data['default_version'] = get_bloginfo( 'version' );
80
+ $this->data['port'] = (string) parse_url( $this->data['host'], PHP_URL_PORT );
81
 
82
+ $home_url = home_url();
83
  $positions = array(
84
  'missing',
85
  'broken',
89
 
90
  $this->data['counts'] = array(
91
  'missing' => 0,
92
+ 'broken' => 0,
93
+ 'header' => 0,
94
+ 'footer' => 0,
95
+ 'total' => 0,
96
  );
97
 
98
  $type = $this->get_dependency_type();
102
  $this->data[ $position ] = array();
103
  }
104
  }
105
+ $raw = $GLOBALS[ "wp_{$type}" ];
106
+ $broken = array_values( array_diff( $raw->queue, $raw->done ) );
107
  $missing = array_values( array_diff( $raw->queue, array_keys( $raw->registered ) ) );
108
 
109
  // A broken asset is one which has been deregistered without also being dequeued
136
  }
137
 
138
  $all_dependencies = array();
139
+ $all_dependents = array();
140
 
141
  $missing_dependencies = array();
142
 
153
  }
154
 
155
  $all_dependencies = array_merge( $all_dependencies, $dependency->deps );
156
+ $dependents = $this->get_dependents( $dependency, $raw );
157
+ $all_dependents = array_merge( $all_dependents, $dependents );
158
 
159
+ list( $host, $source, $local, $port ) = $this->get_dependency_data( $dependency );
160
 
161
  if ( empty( $dependency->ver ) ) {
162
  $ver = '';
169
  if ( is_wp_error( $source ) ) {
170
  $display = $source->get_error_message();
171
  } else {
172
+ $display = ltrim( str_replace( "{$home_url}/", '/', remove_query_arg( 'ver', $source ) ), '/' );
173
  }
174
 
175
  $dependencies = $dependency->deps;
182
  }
183
 
184
  $this->data['assets'][ $position ][ $handle ] = array(
185
+ 'host' => $host,
186
+ 'port' => $port,
187
+ 'source' => $source,
188
+ 'local' => $local,
189
+ 'ver' => $ver,
190
+ 'warning' => $warning,
191
+ 'display' => $display,
192
+ 'dependents' => $dependents,
193
  'dependencies' => $dependencies,
194
  );
195
 
211
  $this->data['missing_dependencies'] = $missing_dependencies;
212
  }
213
 
214
+ /**
215
+ * @param _WP_Dependency $item
216
+ * @param WP_Dependencies $dependencies
217
+ * @return array<int, string>
218
+ */
219
  protected static function get_broken_dependencies( _WP_Dependency $item, WP_Dependencies $dependencies ) {
220
  $broken = array();
221
 
222
  foreach ( $item->deps as $handle ) {
223
  $dep = $dependencies->query( $handle );
224
+ if ( $dep instanceof _WP_Dependency ) {
225
  $broken = array_merge( $broken, self::get_broken_dependencies( $dep, $dependencies ) );
226
  } else {
227
  $broken[] = $item->handle;
231
  return $broken;
232
  }
233
 
234
+ /**
235
+ * @param _WP_Dependency $dependency
236
+ * @param WP_Dependencies $dependencies
237
+ * @return array<int, string>
238
+ */
239
  public function get_dependents( _WP_Dependency $dependency, WP_Dependencies $dependencies ) {
240
  $dependents = array();
241
+ $handles = array_unique( array_merge( $dependencies->queue, $dependencies->done ) );
242
 
243
  foreach ( $handles as $handle ) {
244
  $item = $dependencies->query( $handle );
245
+ if ( $item instanceof _WP_Dependency ) {
246
  if ( in_array( $dependency->handle, $item->deps, true ) ) {
247
  $dependents[] = $handle;
248
  }
254
  return $dependents;
255
  }
256
 
257
+ /**
258
+ * @param _WP_Dependency $dependency
259
+ * @return array{
260
+ * 0: string,
261
+ * 1: string|WP_Error,
262
+ * 2: bool,
263
+ * 3: string,
264
+ * }
265
+ */
266
  public function get_dependency_data( _WP_Dependency $dependency ) {
267
+ $data = $this->get_data();
268
  $loader = rtrim( $this->get_dependency_type(), 's' );
269
+ $src = $dependency->src;
270
 
271
  if ( null === $dependency->ver ) {
272
  $ver = '';
281
  /** This filter is documented in wp-includes/class.wp-scripts.php */
282
  $source = apply_filters( "{$loader}_loader_src", $src, $dependency->handle );
283
 
284
+ $host = (string) parse_url( $source, PHP_URL_HOST );
285
+ $scheme = (string) parse_url( $source, PHP_URL_SCHEME );
286
+ $port = (string) parse_url( $source, PHP_URL_PORT );
287
  $http_host = $data['host'];
288
+ $http_port = $data['port'];
289
 
290
  if ( empty( $host ) && ! empty( $http_host ) ) {
291
  $host = $http_host;
292
+ $port = $http_port;
293
  }
294
 
295
  if ( $scheme && $data['is_ssl'] && ( 'https' !== $scheme ) && ( 'localhost' !== $host ) ) {
305
  }
306
  } elseif ( empty( $source ) ) {
307
  $source = '';
308
+ $host = '';
309
  }
310
 
311
  $local = ( $http_host === $host );
312
 
313
+ return array( $host, $source, $local, $port );
314
  }
315
 
316
  }
collectors/assets_scripts.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Collector_Assets_Scripts extends QM_Collector_Assets {
11
 
@@ -15,6 +17,9 @@ class QM_Collector_Assets_Scripts extends QM_Collector_Assets {
15
  return 'scripts';
16
  }
17
 
 
 
 
18
  public function get_concerned_actions() {
19
  if ( is_admin() ) {
20
  return array(
@@ -31,6 +36,9 @@ class QM_Collector_Assets_Scripts extends QM_Collector_Assets {
31
  }
32
  }
33
 
 
 
 
34
  public function get_concerned_filters() {
35
  return array(
36
  'print_scripts_array',
@@ -40,6 +48,11 @@ class QM_Collector_Assets_Scripts extends QM_Collector_Assets {
40
  }
41
  }
42
 
 
 
 
 
 
43
  function register_qm_collector_assets_scripts( array $collectors, QueryMonitor $qm ) {
44
  $collectors['assets_scripts'] = new QM_Collector_Assets_Scripts();
45
  return $collectors;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Collector_Assets_Scripts extends QM_Collector_Assets {
13
 
17
  return 'scripts';
18
  }
19
 
20
+ /**
21
+ * @return array<int, string>
22
+ */
23
  public function get_concerned_actions() {
24
  if ( is_admin() ) {
25
  return array(
36
  }
37
  }
38
 
39
+ /**
40
+ * @return array<int, string>
41
+ */
42
  public function get_concerned_filters() {
43
  return array(
44
  'print_scripts_array',
48
  }
49
  }
50
 
51
+ /**
52
+ * @param array<string, QM_Collector> $collectors
53
+ * @param QueryMonitor $qm
54
+ * @return array<string, QM_Collector>
55
+ */
56
  function register_qm_collector_assets_scripts( array $collectors, QueryMonitor $qm ) {
57
  $collectors['assets_scripts'] = new QM_Collector_Assets_Scripts();
58
  return $collectors;
collectors/assets_styles.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Collector_Assets_Styles extends QM_Collector_Assets {
11
 
@@ -15,6 +17,9 @@ class QM_Collector_Assets_Styles extends QM_Collector_Assets {
15
  return 'styles';
16
  }
17
 
 
 
 
18
  public function get_concerned_filters() {
19
  return array(
20
  'print_styles_array',
@@ -24,6 +29,11 @@ class QM_Collector_Assets_Styles extends QM_Collector_Assets {
24
  }
25
  }
26
 
 
 
 
 
 
27
  function register_qm_collector_assets_styles( array $collectors, QueryMonitor $qm ) {
28
  $collectors['assets_styles'] = new QM_Collector_Assets_Styles();
29
  return $collectors;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Collector_Assets_Styles extends QM_Collector_Assets {
13
 
17
  return 'styles';
18
  }
19
 
20
+ /**
21
+ * @return array<int, string>
22
+ */
23
  public function get_concerned_filters() {
24
  return array(
25
  'print_styles_array',
29
  }
30
  }
31
 
32
+ /**
33
+ * @param array<string, QM_Collector> $collectors
34
+ * @param QueryMonitor $qm
35
+ * @return array<string, QM_Collector>
36
+ */
37
  function register_qm_collector_assets_styles( array $collectors, QueryMonitor $qm ) {
38
  $collectors['assets_styles'] = new QM_Collector_Assets_Styles();
39
  return $collectors;
collectors/block_editor.php CHANGED
@@ -5,36 +5,78 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Collector_Block_Editor extends QM_Collector {
11
 
12
  public $id = 'block_editor';
13
 
 
 
 
14
  protected $block_context = array();
 
 
 
 
15
  protected $block_timing = array();
16
- protected $block_timer = null;
17
 
18
- public function __construct() {
19
- parent::__construct();
 
 
 
 
 
 
 
 
20
 
21
- add_filter( 'pre_render_block', array( $this, 'filter_pre_render_block' ), 9999, 2 );
22
  add_filter( 'render_block_context', array( $this, 'filter_render_block_context' ), -9999, 2 );
23
  add_filter( 'render_block_data', array( $this, 'filter_render_block_data' ), -9999 );
24
- add_filter( 'render_block', array( $this, 'filter_render_block' ), 9999, 2 );
25
  }
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  public function get_concerned_filters() {
28
  return array(
29
  'allowed_block_types',
 
 
 
 
30
  'block_parser_class',
31
  'pre_render_block',
 
32
  'render_block_context',
33
  'render_block_data',
34
  'render_block',
 
35
  );
36
  }
37
 
 
 
 
 
 
38
  public function filter_pre_render_block( $pre_render, array $block ) {
39
  if ( null !== $pre_render ) {
40
  $this->block_timing[] = false;
@@ -43,12 +85,21 @@ class QM_Collector_Block_Editor extends QM_Collector {
43
  return $pre_render;
44
  }
45
 
 
 
 
 
 
46
  public function filter_render_block_context( array $context, array $block ) {
47
  $this->block_context[] = $context;
48
 
49
  return $context;
50
  }
51
 
 
 
 
 
52
  public function filter_render_block_data( array $block ) {
53
  $this->block_timer = new QM_Timer();
54
  $this->block_timer->start();
@@ -56,6 +107,11 @@ class QM_Collector_Block_Editor extends QM_Collector {
56
  return $block;
57
  }
58
 
 
 
 
 
 
59
  public function filter_render_block( $block_content, array $block ) {
60
  if ( isset( $this->block_timer ) ) {
61
  $this->block_timing[] = $this->block_timer->stop();
@@ -80,21 +136,25 @@ class QM_Collector_Block_Editor extends QM_Collector {
80
  return;
81
  }
82
 
83
- $this->data['post_has_blocks'] = self::wp_has_blocks( $content );
84
- $this->data['post_blocks'] = self::wp_parse_blocks( $content );
85
  $this->data['all_dynamic_blocks'] = self::wp_get_dynamic_block_names();
86
- $this->data['total_blocks'] = 0;
87
- $this->data['has_block_context'] = false;
88
- $this->data['has_block_timing'] = false;
89
 
90
  if ( $this->data['post_has_blocks'] ) {
91
  $this->data['post_blocks'] = array_values( array_filter( array_map( array( $this, 'process_block' ), $this->data['post_blocks'] ) ) );
92
  }
93
  }
94
 
 
 
 
 
95
  protected function process_block( array $block ) {
96
  $context = array_shift( $this->block_context );
97
- $timing = array_shift( $this->block_timing );
98
 
99
  // Remove empty blocks caused by two consecutive line breaks in content
100
  if ( ! $block['blockName'] && ! trim( $block['innerHTML'] ) ) {
@@ -104,11 +164,11 @@ class QM_Collector_Block_Editor extends QM_Collector {
104
  $this->data['total_blocks']++;
105
 
106
  $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
107
- $dynamic = false;
108
- $callback = null;
109
 
110
  if ( $block_type && $block_type->is_dynamic() ) {
111
- $dynamic = true;
112
  $callback = QM_Util::populate_callback( array(
113
  'function' => $block_type->render_callback,
114
  ) );
@@ -116,10 +176,10 @@ class QM_Collector_Block_Editor extends QM_Collector {
116
 
117
  $timing = array_shift( $this->block_timing );
118
 
119
- $block['dynamic'] = $dynamic;
120
- $block['callback'] = $callback;
121
  $block['innerHTML'] = trim( $block['innerHTML'] );
122
- $block['size'] = strlen( $block['innerHTML'] );
123
 
124
  if ( $context ) {
125
  $block['context'] = $context;
@@ -138,10 +198,17 @@ class QM_Collector_Block_Editor extends QM_Collector {
138
  return $block;
139
  }
140
 
 
 
 
141
  protected static function wp_block_editor_enabled() {
142
  return ( function_exists( 'parse_blocks' ) || function_exists( 'gutenberg_parse_blocks' ) );
143
  }
144
 
 
 
 
 
145
  protected static function wp_has_blocks( $content ) {
146
  if ( function_exists( 'has_blocks' ) ) {
147
  return has_blocks( $content );
@@ -152,6 +219,10 @@ class QM_Collector_Block_Editor extends QM_Collector {
152
  return false;
153
  }
154
 
 
 
 
 
155
  protected static function wp_parse_blocks( $content ) {
156
  if ( function_exists( 'parse_blocks' ) ) {
157
  return parse_blocks( $content );
@@ -162,6 +233,9 @@ class QM_Collector_Block_Editor extends QM_Collector {
162
  return null;
163
  }
164
 
 
 
 
165
  protected static function wp_get_dynamic_block_names() {
166
  if ( function_exists( 'get_dynamic_block_names' ) ) {
167
  return get_dynamic_block_names();
@@ -172,6 +246,11 @@ class QM_Collector_Block_Editor extends QM_Collector {
172
 
173
  }
174
 
 
 
 
 
 
175
  function register_qm_collector_block_editor( array $collectors, QueryMonitor $qm ) {
176
  $collectors['block_editor'] = new QM_Collector_Block_Editor();
177
  return $collectors;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Collector_Block_Editor extends QM_Collector {
13
 
14
  public $id = 'block_editor';
15
 
16
+ /**
17
+ * @var array<int, mixed[]>
18
+ */
19
  protected $block_context = array();
20
+
21
+ /**
22
+ * @var array<int, QM_Timer|false>
23
+ */
24
  protected $block_timing = array();
 
25
 
26
+ /**
27
+ * @var QM_Timer|null
28
+ */
29
+ protected $block_timer = null;
30
+
31
+ /**
32
+ * @return void
33
+ */
34
+ public function set_up() {
35
+ parent::set_up();
36
 
37
+ add_filter( 'pre_render_block', array( $this, 'filter_pre_render_block' ), 9999, 2 );
38
  add_filter( 'render_block_context', array( $this, 'filter_render_block_context' ), -9999, 2 );
39
  add_filter( 'render_block_data', array( $this, 'filter_render_block_data' ), -9999 );
40
+ add_filter( 'render_block', array( $this, 'filter_render_block' ), 9999, 2 );
41
  }
42
 
43
+ /**
44
+ * @return void
45
+ */
46
+ public function tear_down() {
47
+ remove_filter( 'pre_render_block', array( $this, 'filter_pre_render_block' ), 9999 );
48
+ remove_filter( 'render_block_context', array( $this, 'filter_render_block_context' ), -9999 );
49
+ remove_filter( 'render_block_data', array( $this, 'filter_render_block_data' ), -9999 );
50
+ remove_filter( 'render_block', array( $this, 'filter_render_block' ), 9999 );
51
+
52
+ parent::tear_down();
53
+ }
54
+
55
+ /**
56
+ * @return array<int, string>
57
+ */
58
  public function get_concerned_filters() {
59
  return array(
60
  'allowed_block_types',
61
+ 'allowed_block_types_all',
62
+ 'block_editor_settings_all',
63
+ 'block_type_metadata',
64
+ 'block_type_metadata_settings',
65
  'block_parser_class',
66
  'pre_render_block',
67
+ 'register_block_type_args',
68
  'render_block_context',
69
  'render_block_data',
70
  'render_block',
71
+ 'use_widgets_block_editor',
72
  );
73
  }
74
 
75
+ /**
76
+ * @param string|null $pre_render
77
+ * @param mixed[] $block
78
+ * @return string|null
79
+ */
80
  public function filter_pre_render_block( $pre_render, array $block ) {
81
  if ( null !== $pre_render ) {
82
  $this->block_timing[] = false;
85
  return $pre_render;
86
  }
87
 
88
+ /**
89
+ * @param mixed[] $context
90
+ * @param mixed[] $block
91
+ * @return mixed[]
92
+ */
93
  public function filter_render_block_context( array $context, array $block ) {
94
  $this->block_context[] = $context;
95
 
96
  return $context;
97
  }
98
 
99
+ /**
100
+ * @param mixed[] $block
101
+ * @return mixed[]
102
+ */
103
  public function filter_render_block_data( array $block ) {
104
  $this->block_timer = new QM_Timer();
105
  $this->block_timer->start();
107
  return $block;
108
  }
109
 
110
+ /**
111
+ * @param string $block_content
112
+ * @param mixed[] $block
113
+ * @return string
114
+ */
115
  public function filter_render_block( $block_content, array $block ) {
116
  if ( isset( $this->block_timer ) ) {
117
  $this->block_timing[] = $this->block_timer->stop();
136
  return;
137
  }
138
 
139
+ $this->data['post_has_blocks'] = self::wp_has_blocks( $content );
140
+ $this->data['post_blocks'] = self::wp_parse_blocks( $content );
141
  $this->data['all_dynamic_blocks'] = self::wp_get_dynamic_block_names();
142
+ $this->data['total_blocks'] = 0;
143
+ $this->data['has_block_context'] = false;
144
+ $this->data['has_block_timing'] = false;
145
 
146
  if ( $this->data['post_has_blocks'] ) {
147
  $this->data['post_blocks'] = array_values( array_filter( array_map( array( $this, 'process_block' ), $this->data['post_blocks'] ) ) );
148
  }
149
  }
150
 
151
+ /**
152
+ * @param mixed[] $block
153
+ * @return mixed[]|null
154
+ */
155
  protected function process_block( array $block ) {
156
  $context = array_shift( $this->block_context );
157
+ $timing = array_shift( $this->block_timing );
158
 
159
  // Remove empty blocks caused by two consecutive line breaks in content
160
  if ( ! $block['blockName'] && ! trim( $block['innerHTML'] ) ) {
164
  $this->data['total_blocks']++;
165
 
166
  $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
167
+ $dynamic = false;
168
+ $callback = null;
169
 
170
  if ( $block_type && $block_type->is_dynamic() ) {
171
+ $dynamic = true;
172
  $callback = QM_Util::populate_callback( array(
173
  'function' => $block_type->render_callback,
174
  ) );
176
 
177
  $timing = array_shift( $this->block_timing );
178
 
179
+ $block['dynamic'] = $dynamic;
180
+ $block['callback'] = $callback;
181
  $block['innerHTML'] = trim( $block['innerHTML'] );
182
+ $block['size'] = strlen( $block['innerHTML'] );
183
 
184
  if ( $context ) {
185
  $block['context'] = $context;
198
  return $block;
199
  }
200
 
201
+ /**
202
+ * @return bool
203
+ */
204
  protected static function wp_block_editor_enabled() {
205
  return ( function_exists( 'parse_blocks' ) || function_exists( 'gutenberg_parse_blocks' ) );
206
  }
207
 
208
+ /**
209
+ * @param string $content
210
+ * @return bool
211
+ */
212
  protected static function wp_has_blocks( $content ) {
213
  if ( function_exists( 'has_blocks' ) ) {
214
  return has_blocks( $content );
219
  return false;
220
  }
221
 
222
+ /**
223
+ * @param string $content
224
+ * @return mixed[]|null
225
+ */
226
  protected static function wp_parse_blocks( $content ) {
227
  if ( function_exists( 'parse_blocks' ) ) {
228
  return parse_blocks( $content );
233
  return null;
234
  }
235
 
236
+ /**
237
+ * @return string[]|null
238
+ */
239
  protected static function wp_get_dynamic_block_names() {
240
  if ( function_exists( 'get_dynamic_block_names' ) ) {
241
  return get_dynamic_block_names();
246
 
247
  }
248
 
249
+ /**
250
+ * @param array<string, QM_Collector> $collectors
251
+ * @param QueryMonitor $qm
252
+ * @return array<string, QM_Collector>
253
+ */
254
  function register_qm_collector_block_editor( array $collectors, QueryMonitor $qm ) {
255
  $collectors['block_editor'] = new QM_Collector_Block_Editor();
256
  return $collectors;
collectors/cache.php CHANGED
@@ -5,16 +5,21 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Collector_Cache extends QM_Collector {
11
 
12
  public $id = 'cache';
13
 
 
 
 
14
  public function process() {
15
  global $wp_object_cache;
16
 
17
- $this->data['has_object_cache'] = (bool) wp_using_ext_object_cache();
18
  $this->data['cache_hit_percentage'] = 0;
19
 
20
  if ( is_object( $wp_object_cache ) ) {
@@ -79,13 +84,13 @@ class QM_Collector_Cache extends QM_Collector {
79
 
80
  if ( function_exists( 'extension_loaded' ) ) {
81
  $this->data['object_cache_extensions'] = array_map( 'extension_loaded', array(
82
- 'APCu' => 'APCu',
83
- 'Memcache' => 'Memcache',
84
- 'Memcached' => 'Memcached',
85
- 'Redis' => 'Redis',
86
  ) );
87
  $this->data['opcode_cache_extensions'] = array_map( 'extension_loaded', array(
88
- 'APC' => 'APC',
89
  'Zend OPcache' => 'Zend OPcache',
90
  ) );
91
  } else {
@@ -98,6 +103,11 @@ class QM_Collector_Cache extends QM_Collector {
98
 
99
  }
100
 
 
 
 
 
 
101
  function register_qm_collector_cache( array $collectors, QueryMonitor $qm ) {
102
  $collectors['cache'] = new QM_Collector_Cache();
103
  return $collectors;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Collector_Cache extends QM_Collector {
13
 
14
  public $id = 'cache';
15
 
16
+ /**
17
+ * @return void
18
+ */
19
  public function process() {
20
  global $wp_object_cache;
21
 
22
+ $this->data['has_object_cache'] = (bool) wp_using_ext_object_cache();
23
  $this->data['cache_hit_percentage'] = 0;
24
 
25
  if ( is_object( $wp_object_cache ) ) {
84
 
85
  if ( function_exists( 'extension_loaded' ) ) {
86
  $this->data['object_cache_extensions'] = array_map( 'extension_loaded', array(
87
+ 'APCu' => 'APCu',
88
+ 'Memcache' => 'Memcache',
89
+ 'Memcached' => 'Memcached',
90
+ 'Redis' => 'Redis',
91
  ) );
92
  $this->data['opcode_cache_extensions'] = array_map( 'extension_loaded', array(
93
+ 'APC' => 'APC',
94
  'Zend OPcache' => 'Zend OPcache',
95
  ) );
96
  } else {
103
 
104
  }
105
 
106
+ /**
107
+ * @param array<string, QM_Collector> $collectors
108
+ * @param QueryMonitor $qm
109
+ * @return array<string, QM_Collector>
110
+ */
111
  function register_qm_collector_cache( array $collectors, QueryMonitor $qm ) {
112
  $collectors['cache'] = new QM_Collector_Cache();
113
  return $collectors;
collectors/caps.php CHANGED
@@ -5,14 +5,19 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Collector_Caps extends QM_Collector {
11
 
12
  public $id = 'caps';
13
 
14
- public function __construct() {
15
- parent::__construct();
 
 
 
16
 
17
  if ( ! self::enabled() ) {
18
  return;
@@ -22,16 +27,35 @@ class QM_Collector_Caps extends QM_Collector {
22
  add_filter( 'map_meta_cap', array( $this, 'filter_map_meta_cap' ), 9999, 4 );
23
  }
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  public static function enabled() {
26
  return ( defined( 'QM_ENABLE_CAPS_PANEL' ) && QM_ENABLE_CAPS_PANEL );
27
  }
28
 
 
 
 
29
  public function get_concerned_actions() {
30
  return array(
31
  'wp_roles_init',
32
  );
33
  }
34
 
 
 
 
35
  public function get_concerned_filters() {
36
  return array(
37
  'map_meta_cap',
@@ -40,6 +64,9 @@ class QM_Collector_Caps extends QM_Collector {
40
  );
41
  }
42
 
 
 
 
43
  public function get_concerned_options() {
44
  $blog_prefix = $GLOBALS['wpdb']->get_blog_prefix();
45
 
@@ -48,6 +75,9 @@ class QM_Collector_Caps extends QM_Collector {
48
  );
49
  }
50
 
 
 
 
51
  public function get_concerned_constants() {
52
  return array(
53
  'ALLOW_UNFILTERED_UPLOADS',
@@ -56,12 +86,6 @@ class QM_Collector_Caps extends QM_Collector {
56
  );
57
  }
58
 
59
- public function tear_down() {
60
- remove_filter( 'user_has_cap', array( $this, 'filter_user_has_cap' ), 9999 );
61
- remove_filter( 'map_meta_cap', array( $this, 'filter_map_meta_cap' ), 9999 );
62
- parent::tear_down();
63
- }
64
-
65
  /**
66
  * Logs user capability checks.
67
  *
@@ -69,7 +93,7 @@ class QM_Collector_Caps extends QM_Collector {
69
  *
70
  * @param bool[] $user_caps Concerned user's capabilities.
71
  * @param string[] $caps Required primitive capabilities for the requested capability.
72
- * @param array $args {
73
  * Arguments that accompany the requested capability check.
74
  *
75
  * @type string $0 Requested capability.
@@ -79,7 +103,21 @@ class QM_Collector_Caps extends QM_Collector {
79
  * @return bool[] Concerned user's capabilities.
80
  */
81
  public function filter_user_has_cap( array $user_caps, array $caps, array $args ) {
82
- $trace = new QM_Backtrace();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  $result = true;
84
 
85
  foreach ( $caps as $cap ) {
@@ -90,8 +128,9 @@ class QM_Collector_Caps extends QM_Collector {
90
  }
91
 
92
  $this->data['caps'][] = array(
93
- 'args' => $args,
94
- 'trace' => $trace,
 
95
  'result' => $result,
96
  );
97
 
@@ -106,7 +145,7 @@ class QM_Collector_Caps extends QM_Collector {
106
  * @param string[] $required_caps Required primitive capabilities for the requested capability.
107
  * @param string $cap Capability or meta capability being checked.
108
  * @param int $user_id Concerned user ID.
109
- * @param array $args {
110
  * Arguments that accompany the requested capability check.
111
  *
112
  * @type mixed ...$0 Optional second and further parameters.
@@ -122,28 +161,46 @@ class QM_Collector_Caps extends QM_Collector {
122
  return $required_caps;
123
  }
124
 
125
- $trace = new QM_Backtrace();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  $result = ( ! in_array( 'do_not_allow', $required_caps, true ) );
127
 
128
  array_unshift( $args, $user_id );
129
  array_unshift( $args, $cap );
130
 
131
  $this->data['caps'][] = array(
132
- 'args' => $args,
133
- 'trace' => $trace,
 
134
  'result' => $result,
135
  );
136
 
137
  return $required_caps;
138
  }
139
 
 
 
 
140
  public function process() {
141
  if ( empty( $this->data['caps'] ) ) {
142
  return;
143
  }
144
 
145
- $all_parts = array();
146
- $all_users = array();
147
  $components = array();
148
 
149
  $this->data['caps'] = array_values( array_filter( $this->data['caps'], array( $this, 'filter_remove_noise' ) ) );
@@ -159,53 +216,31 @@ class QM_Collector_Caps extends QM_Collector {
159
  $name = '';
160
  }
161
 
162
- $trace = $cap['trace']->get_trace();
163
- $filtered_trace = $cap['trace']->get_display_trace();
164
-
165
- $last = end( $filtered_trace );
166
- if ( isset( $last['function'] ) && 'map_meta_cap' === $last['function'] ) {
167
- array_shift( $filtered_trace ); // remove the map_meta_cap() call
168
- }
169
 
170
- array_shift( $filtered_trace ); // remove the WP_User->has_cap() call
171
- array_shift( $filtered_trace ); // remove the *_user_can() call
172
-
173
- if ( ! count( $filtered_trace ) ) {
174
- $responsible_name = QM_Util::standard_dir( $trace[1]['file'], '' ) . ':' . $trace[1]['line'];
175
-
176
- $responsible_item = $trace[1];
177
- $responsible_item['display'] = $responsible_name;
178
- $responsible_item['calling_file'] = $trace[1]['file'];
179
- $responsible_item['calling_line'] = $trace[1]['line'];
180
- array_unshift( $filtered_trace, $responsible_item );
181
- }
182
-
183
- $component = $cap['trace']->get_component();
184
-
185
- $this->data['caps'][ $i ]['filtered_trace'] = $filtered_trace;
186
- $this->data['caps'][ $i ]['component'] = $component;
187
-
188
- $parts = array_values( array_filter( preg_split( '#[_/-]#', $name ) ) );
189
  $this->data['caps'][ $i ]['parts'] = $parts;
190
- $this->data['caps'][ $i ]['name'] = $name;
191
- $this->data['caps'][ $i ]['user'] = $cap['args'][1];
192
- $this->data['caps'][ $i ]['args'] = array_slice( $cap['args'], 2 );
193
- $all_parts = array_merge( $all_parts, $parts );
194
- $all_users[] = $cap['args'][1];
195
- $components[ $component->name ] = $component->name;
196
-
197
- unset( $this->data['caps'][ $i ]['trace'] );
198
  }
199
 
200
- $this->data['parts'] = array_values( array_unique( array_filter( $all_parts ) ) );
201
- $this->data['users'] = array_values( array_unique( array_filter( $all_users ) ) );
202
  $this->data['components'] = $components;
203
  }
204
 
 
 
 
 
205
  public function filter_remove_noise( array $cap ) {
206
- $trace = $cap['trace']->get_trace();
207
 
208
- $exclude_files = array(
209
  ABSPATH . 'wp-admin/menu.php',
210
  ABSPATH . 'wp-admin/includes/menu.php',
211
  );
@@ -228,6 +263,11 @@ class QM_Collector_Caps extends QM_Collector {
228
 
229
  }
230
 
 
 
 
 
 
231
  function register_qm_collector_caps( array $collectors, QueryMonitor $qm ) {
232
  $collectors['caps'] = new QM_Collector_Caps();
233
  return $collectors;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Collector_Caps extends QM_Collector {
13
 
14
  public $id = 'caps';
15
 
16
+ /**
17
+ * @return void
18
+ */
19
+ public function set_up() {
20
+ parent::set_up();
21
 
22
  if ( ! self::enabled() ) {
23
  return;
27
  add_filter( 'map_meta_cap', array( $this, 'filter_map_meta_cap' ), 9999, 4 );
28
  }
29
 
30
+ /**
31
+ * @return void
32
+ */
33
+ public function tear_down() {
34
+ remove_filter( 'user_has_cap', array( $this, 'filter_user_has_cap' ), 9999 );
35
+ remove_filter( 'map_meta_cap', array( $this, 'filter_map_meta_cap' ), 9999 );
36
+
37
+ parent::tear_down();
38
+ }
39
+
40
+ /**
41
+ * @return bool
42
+ */
43
  public static function enabled() {
44
  return ( defined( 'QM_ENABLE_CAPS_PANEL' ) && QM_ENABLE_CAPS_PANEL );
45
  }
46
 
47
+ /**
48
+ * @return array<int, string>
49
+ */
50
  public function get_concerned_actions() {
51
  return array(
52
  'wp_roles_init',
53
  );
54
  }
55
 
56
+ /**
57
+ * @return array<int, string>
58
+ */
59
  public function get_concerned_filters() {
60
  return array(
61
  'map_meta_cap',
64
  );
65
  }
66
 
67
+ /**
68
+ * @return array<int, string>
69
+ */
70
  public function get_concerned_options() {
71
  $blog_prefix = $GLOBALS['wpdb']->get_blog_prefix();
72
 
75
  );
76
  }
77
 
78
+ /**
79
+ * @return array<int, string>
80
+ */
81
  public function get_concerned_constants() {
82
  return array(
83
  'ALLOW_UNFILTERED_UPLOADS',
86
  );
87
  }
88
 
 
 
 
 
 
 
89
  /**
90
  * Logs user capability checks.
91
  *
93
  *
94
  * @param bool[] $user_caps Concerned user's capabilities.
95
  * @param string[] $caps Required primitive capabilities for the requested capability.
96
+ * @param mixed[] $args {
97
  * Arguments that accompany the requested capability check.
98
  *
99
  * @type string $0 Requested capability.
103
  * @return bool[] Concerned user's capabilities.
104
  */
105
  public function filter_user_has_cap( array $user_caps, array $caps, array $args ) {
106
+ $trace = new QM_Backtrace( array(
107
+ 'ignore_hook' => array(
108
+ current_filter() => true,
109
+ ),
110
+ 'ignore_func' => array(
111
+ 'current_user_can' => true,
112
+ 'map_meta_cap' => true,
113
+ 'user_can' => true,
114
+ ),
115
+ 'ignore_method' => array(
116
+ 'WP_User' => array(
117
+ 'has_cap' => true,
118
+ ),
119
+ ),
120
+ ) );
121
  $result = true;
122
 
123
  foreach ( $caps as $cap ) {
128
  }
129
 
130
  $this->data['caps'][] = array(
131
+ 'args' => $args,
132
+ 'filtered_trace' => $trace->get_filtered_trace(),
133
+ 'component' => $trace->get_component(),
134
  'result' => $result,
135
  );
136
 
145
  * @param string[] $required_caps Required primitive capabilities for the requested capability.
146
  * @param string $cap Capability or meta capability being checked.
147
  * @param int $user_id Concerned user ID.
148
+ * @param mixed[] $args {
149
  * Arguments that accompany the requested capability check.
150
  *
151
  * @type mixed ...$0 Optional second and further parameters.
161
  return $required_caps;
162
  }
163
 
164
+ $trace = new QM_Backtrace( array(
165
+ 'ignore_hook' => array(
166
+ current_filter() => true,
167
+ ),
168
+ 'ignore_func' => array(
169
+ 'current_user_can' => true,
170
+ 'map_meta_cap' => true,
171
+ 'user_can' => true,
172
+ ),
173
+ 'ignore_method' => array(
174
+ 'WP_User' => array(
175
+ 'has_cap' => true,
176
+ ),
177
+ ),
178
+ ) );
179
  $result = ( ! in_array( 'do_not_allow', $required_caps, true ) );
180
 
181
  array_unshift( $args, $user_id );
182
  array_unshift( $args, $cap );
183
 
184
  $this->data['caps'][] = array(
185
+ 'args' => $args,
186
+ 'filtered_trace' => $trace->get_filtered_trace(),
187
+ 'component' => $trace->get_component(),
188
  'result' => $result,
189
  );
190
 
191
  return $required_caps;
192
  }
193
 
194
+ /**
195
+ * @return void
196
+ */
197
  public function process() {
198
  if ( empty( $this->data['caps'] ) ) {
199
  return;
200
  }
201
 
202
+ $all_parts = array();
203
+ $all_users = array();
204
  $components = array();
205
 
206
  $this->data['caps'] = array_values( array_filter( $this->data['caps'], array( $this, 'filter_remove_noise' ) ) );
216
  $name = '';
217
  }
218
 
219
+ $component = $cap['component'];
 
 
 
 
 
 
220
 
221
+ $parts = array_values( array_filter( preg_split( '#[_/-]#', $name ) ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  $this->data['caps'][ $i ]['parts'] = $parts;
223
+ $this->data['caps'][ $i ]['name'] = $name;
224
+ $this->data['caps'][ $i ]['user'] = $cap['args'][1];
225
+ $this->data['caps'][ $i ]['args'] = array_slice( $cap['args'], 2 );
226
+ $all_parts = array_merge( $all_parts, $parts );
227
+ $all_users[] = $cap['args'][1];
228
+ $components[ $component->name ] = $component->name;
 
 
229
  }
230
 
231
+ $this->data['parts'] = array_values( array_unique( array_filter( $all_parts ) ) );
232
+ $this->data['users'] = array_values( array_unique( array_filter( $all_users ) ) );
233
  $this->data['components'] = $components;
234
  }
235
 
236
+ /**
237
+ * @param array<string, mixed> $cap
238
+ * @return bool
239
+ */
240
  public function filter_remove_noise( array $cap ) {
241
+ $trace = $cap['filtered_trace'];
242
 
243
+ $exclude_files = array(
244
  ABSPATH . 'wp-admin/menu.php',
245
  ABSPATH . 'wp-admin/includes/menu.php',
246
  );
263
 
264
  }
265
 
266
+ /**
267
+ * @param array<string, QM_Collector> $collectors
268
+ * @param QueryMonitor $qm
269
+ * @return array<string, QM_Collector>
270
+ */
271
  function register_qm_collector_caps( array $collectors, QueryMonitor $qm ) {
272
  $collectors['caps'] = new QM_Collector_Caps();
273
  return $collectors;
collectors/conditionals.php CHANGED
@@ -5,12 +5,17 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Collector_Conditionals extends QM_Collector {
11
 
12
  public $id = 'conditionals';
13
 
 
 
 
14
  public function process() {
15
 
16
  /**
@@ -71,9 +76,9 @@ class QM_Collector_Conditionals extends QM_Collector {
71
  */
72
  $conds = apply_filters( 'query_monitor_conditionals', $conds );
73
 
74
- $true = array();
75
  $false = array();
76
- $na = array();
77
 
78
  foreach ( $conds as $cond ) {
79
  if ( function_exists( $cond ) ) {
@@ -101,6 +106,11 @@ class QM_Collector_Conditionals extends QM_Collector {
101
 
102
  }
103
 
 
 
 
 
 
104
  function register_qm_collector_conditionals( array $collectors, QueryMonitor $qm ) {
105
  $collectors['conditionals'] = new QM_Collector_Conditionals();
106
  return $collectors;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Collector_Conditionals extends QM_Collector {
13
 
14
  public $id = 'conditionals';
15
 
16
+ /**
17
+ * @return void
18
+ */
19
  public function process() {
20
 
21
  /**
76
  */
77
  $conds = apply_filters( 'query_monitor_conditionals', $conds );
78
 
79
+ $true = array();
80
  $false = array();
81
+ $na = array();
82
 
83
  foreach ( $conds as $cond ) {
84
  if ( function_exists( $cond ) ) {
106
 
107
  }
108
 
109
+ /**
110
+ * @param array<string, QM_Collector> $collectors
111
+ * @param QueryMonitor $qm
112
+ * @return array<string, QM_Collector>
113
+ */
114
  function register_qm_collector_conditionals( array $collectors, QueryMonitor $qm ) {
115
  $collectors['conditionals'] = new QM_Collector_Conditionals();
116
  return $collectors;
collectors/db_callers.php CHANGED
@@ -5,12 +5,17 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Collector_DB_Callers extends QM_Collector {
11
 
12
  public $id = 'db_callers';
13
 
 
 
 
14
  public function process() {
15
  $dbq = QM_Collectors::get( 'db_queries' );
16
 
@@ -28,6 +33,11 @@ class QM_Collector_DB_Callers extends QM_Collector {
28
 
29
  }
30
 
 
 
 
 
 
31
  function register_qm_collector_db_callers( array $collectors, QueryMonitor $qm ) {
32
  $collectors['db_callers'] = new QM_Collector_DB_Callers();
33
  return $collectors;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Collector_DB_Callers extends QM_Collector {
13
 
14
  public $id = 'db_callers';
15
 
16
+ /**
17
+ * @return void
18
+ */
19
  public function process() {
20
  $dbq = QM_Collectors::get( 'db_queries' );
21
 
33
 
34
  }
35
 
36
+ /**
37
+ * @param array<string, QM_Collector> $collectors
38
+ * @param QueryMonitor $qm
39
+ * @return array<string, QM_Collector>
40
+ */
41
  function register_qm_collector_db_callers( array $collectors, QueryMonitor $qm ) {
42
  $collectors['db_callers'] = new QM_Collector_DB_Callers();
43
  return $collectors;
collectors/db_components.php CHANGED
@@ -5,12 +5,17 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Collector_DB_Components extends QM_Collector {
11
 
12
  public $id = 'db_components';
13
 
 
 
 
14
  public function process() {
15
  $dbq = QM_Collectors::get( 'db_queries' );
16
 
@@ -28,6 +33,11 @@ class QM_Collector_DB_Components extends QM_Collector {
28
 
29
  }
30
 
 
 
 
 
 
31
  function register_qm_collector_db_components( array $collectors, QueryMonitor $qm ) {
32
  $collectors['db_components'] = new QM_Collector_DB_Components();
33
  return $collectors;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Collector_DB_Components extends QM_Collector {
13
 
14
  public $id = 'db_components';
15
 
16
+ /**
17
+ * @return void
18
+ */
19
  public function process() {
20
  $dbq = QM_Collectors::get( 'db_queries' );
21
 
33
 
34
  }
35
 
36
+ /**
37
+ * @param array<string, QM_Collector> $collectors
38
+ * @param QueryMonitor $qm
39
+ * @return array<string, QM_Collector>
40
+ */
41
  function register_qm_collector_db_components( array $collectors, QueryMonitor $qm ) {
42
  $collectors['db_components'] = new QM_Collector_DB_Components();
43
  return $collectors;
collectors/db_dupes.php CHANGED
@@ -5,12 +5,17 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Collector_DB_Dupes extends QM_Collector {
11
 
12
  public $id = 'db_dupes';
13
 
 
 
 
14
  public function process() {
15
  $dbq = QM_Collectors::get( 'db_queries' );
16
 
@@ -27,9 +32,8 @@ class QM_Collector_DB_Dupes extends QM_Collector {
27
  // Ignore dupes from `WP_Query->set_found_posts()`
28
  unset( $this->data['dupes']['SELECT FOUND_ROWS()'] );
29
 
30
- $stacks = array();
31
- $tops = array();
32
- $callers = array();
33
  $components = array();
34
 
35
  // Loop over all SQL queries that have dupes
@@ -39,8 +43,8 @@ class QM_Collector_DB_Dupes extends QM_Collector {
39
  foreach ( $query_ids as $query_id ) {
40
 
41
  if ( isset( $dbq->data['dbs']['$wpdb']->rows[ $query_id ]['trace'] ) ) {
42
- $trace = $dbq->data['dbs']['$wpdb']->rows[ $query_id ]['trace'];
43
- $stack = wp_list_pluck( $trace->get_filtered_trace(), 'id' );
44
  $component = $trace->get_component();
45
 
46
  // Populate the component counts for this query
@@ -50,7 +54,7 @@ class QM_Collector_DB_Dupes extends QM_Collector {
50
  $components[ $sql ][ $component->name ] = 1;
51
  }
52
  } else {
53
- $stack = array_reverse( explode( ', ', $dbq->data['dbs']['$wpdb']->rows[ $query_id ]['stack'] ) );
54
  }
55
 
56
  // Populate the caller counts for this query
@@ -84,14 +88,19 @@ class QM_Collector_DB_Dupes extends QM_Collector {
84
  }
85
 
86
  if ( ! empty( $sources ) ) {
87
- $this->data['dupe_sources'] = $sources;
88
- $this->data['dupe_callers'] = $callers;
89
  $this->data['dupe_components'] = $components;
90
  }
91
 
92
  }
93
  }
94
 
 
 
 
 
 
95
  function register_qm_collector_db_dupes( array $collectors, QueryMonitor $qm ) {
96
  $collectors['db_dupes'] = new QM_Collector_DB_Dupes();
97
  return $collectors;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Collector_DB_Dupes extends QM_Collector {
13
 
14
  public $id = 'db_dupes';
15
 
16
+ /**
17
+ * @return void
18
+ */
19
  public function process() {
20
  $dbq = QM_Collectors::get( 'db_queries' );
21
 
32
  // Ignore dupes from `WP_Query->set_found_posts()`
33
  unset( $this->data['dupes']['SELECT FOUND_ROWS()'] );
34
 
35
+ $stacks = array();
36
+ $callers = array();
 
37
  $components = array();
38
 
39
  // Loop over all SQL queries that have dupes
43
  foreach ( $query_ids as $query_id ) {
44
 
45
  if ( isset( $dbq->data['dbs']['$wpdb']->rows[ $query_id ]['trace'] ) ) {
46
+ $trace = $dbq->data['dbs']['$wpdb']->rows[ $query_id ]['trace'];
47
+ $stack = wp_list_pluck( $trace->get_filtered_trace(), 'id' );
48
  $component = $trace->get_component();
49
 
50
  // Populate the component counts for this query
54
  $components[ $sql ][ $component->name ] = 1;
55
  }
56
  } else {
57
+ $stack = $dbq->data['dbs']['$wpdb']->rows[ $query_id ]['stack'];
58
  }
59
 
60
  // Populate the caller counts for this query
88
  }
89
 
90
  if ( ! empty( $sources ) ) {
91
+ $this->data['dupe_sources'] = $sources;
92
+ $this->data['dupe_callers'] = $callers;
93
  $this->data['dupe_components'] = $components;
94
  }
95
 
96
  }
97
  }
98
 
99
+ /**
100
+ * @param array<string, QM_Collector> $collectors
101
+ * @param QueryMonitor $qm
102
+ * @return array<string, QM_Collector>
103
+ */
104
  function register_qm_collector_db_dupes( array $collectors, QueryMonitor $qm ) {
105
  $collectors['db_dupes'] = new QM_Collector_DB_Dupes();
106
  return $collectors;
collectors/db_queries.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  if ( ! defined( 'SAVEQUERIES' ) ) {
11
  define( 'SAVEQUERIES', true );
@@ -20,9 +22,19 @@ if ( SAVEQUERIES && property_exists( $GLOBALS['wpdb'], 'save_queries' ) ) {
20
 
21
  class QM_Collector_DB_Queries extends QM_Collector {
22
 
23
- public $id = 'db_queries';
 
 
 
 
 
 
 
24
  public $db_objects = array();
25
 
 
 
 
26
  public function get_errors() {
27
  if ( ! empty( $this->data['errors'] ) ) {
28
  return $this->data['errors'];
@@ -30,6 +42,9 @@ class QM_Collector_DB_Queries extends QM_Collector {
30
  return false;
31
  }
32
 
 
 
 
33
  public function get_expensive() {
34
  if ( ! empty( $this->data['expensive'] ) ) {
35
  return $this->data['expensive'];
@@ -37,14 +52,21 @@ class QM_Collector_DB_Queries extends QM_Collector {
37
  return false;
38
  }
39
 
 
 
 
 
40
  public static function is_expensive( array $row ) {
41
  return $row['ltime'] > QM_DB_EXPENSIVE;
42
  }
43
 
 
 
 
44
  public function process() {
45
- $this->data['total_qs'] = 0;
46
  $this->data['total_time'] = 0;
47
- $this->data['errors'] = array();
48
 
49
  /**
50
  * Filters the `wpdb` instances that are exposed to QM.
@@ -69,13 +91,19 @@ class QM_Collector_DB_Queries extends QM_Collector {
69
 
70
  }
71
 
 
 
 
 
 
 
72
  protected function log_caller( $caller, $ltime, $type ) {
73
 
74
  if ( ! isset( $this->data['times'][ $caller ] ) ) {
75
  $this->data['times'][ $caller ] = array(
76
  'caller' => $caller,
77
- 'ltime' => 0,
78
- 'types' => array(),
79
  );
80
  }
81
 
@@ -89,6 +117,11 @@ class QM_Collector_DB_Queries extends QM_Collector {
89
 
90
  }
91
 
 
 
 
 
 
92
  public function process_db_object( $id, wpdb $db ) {
93
  global $EZSQL_ERROR, $wp_the_query;
94
 
@@ -98,31 +131,44 @@ class QM_Collector_DB_Queries extends QM_Collector {
98
  return;
99
  }
100
 
101
- $rows = array();
102
- $types = array();
103
  $total_time = 0;
104
  $has_result = false;
105
- $has_trace = false;
106
- $i = 0;
107
- $request = trim( $wp_the_query->request );
108
 
109
  if ( method_exists( $db, 'remove_placeholder_escape' ) ) {
110
  $request = $db->remove_placeholder_escape( $request );
111
  }
112
 
113
- foreach ( (array) $db->queries as $query ) {
 
 
 
114
 
115
- # @TODO: decide what I want to do with this:
116
- if ( false !== strpos( $query[2], 'wp_admin_bar' ) and !isset( $_REQUEST['qm_display_admin_bar'] ) ) { // phpcs:ignore
117
- continue;
 
 
 
 
 
 
 
 
 
 
 
118
  }
119
 
120
- $sql = $query[0];
121
- $ltime = $query[1];
122
- $stack = $query[2];
123
- $has_start = isset( $query[3] );
124
- $has_trace = isset( $query['trace'] );
125
- $has_result = isset( $query['result'] );
126
 
127
  if ( $has_result ) {
128
  $result = $query['result'];
@@ -134,27 +180,25 @@ class QM_Collector_DB_Queries extends QM_Collector {
134
 
135
  if ( $has_trace ) {
136
 
137
- $trace = $query['trace'];
138
- $component = $query['trace']->get_component();
139
- $caller = $query['trace']->get_caller();
140
  $caller_name = $caller['display'];
141
- $caller = $caller['display'];
142
 
143
  } else {
144
 
145
- $trace = null;
146
  $component = null;
147
- $callers = explode( ',', $stack );
148
- $caller = trim( end( $callers ) );
149
-
150
- if ( false !== strpos( $caller, '(' ) ) {
151
- $caller_name = substr( $caller, 0, strpos( $caller, '(' ) ) . '()';
152
- } else {
153
- $caller_name = $caller;
154
- }
155
  }
156
 
157
- $sql = trim( $sql );
158
  $type = QM_Util::get_query_type( $sql );
159
 
160
  $this->log_type( $type );
@@ -183,7 +227,7 @@ class QM_Collector_DB_Queries extends QM_Collector {
183
  $row = compact( 'caller', 'caller_name', 'sql', 'ltime', 'result', 'type', 'component', 'trace', 'is_main_query' );
184
 
185
  if ( ! isset( $trace ) ) {
186
- $row['stack'] = $stack;
187
  }
188
 
189
  if ( is_wp_error( $result ) ) {
@@ -203,15 +247,15 @@ class QM_Collector_DB_Queries extends QM_Collector {
203
  // Fallback for displaying database errors when wp-content/db.php isn't in place
204
  foreach ( $EZSQL_ERROR as $error ) {
205
  $row = array(
206
- 'caller' => null,
207
  'caller_name' => null,
208
- 'stack' => '',
209
- 'sql' => $error['query'],
210
- 'ltime' => 0,
211
- 'result' => new WP_Error( 'qmdb', $error['error_str'] ),
212
- 'type' => '',
213
- 'component' => false,
214
- 'trace' => null,
215
  'is_main_query' => false,
216
  );
217
  $this->data['errors'][] = $row;
@@ -235,6 +279,11 @@ class QM_Collector_DB_Queries extends QM_Collector {
235
 
236
  }
237
 
 
 
 
 
 
238
  function register_qm_collector_db_queries( array $collectors, QueryMonitor $qm ) {
239
  $collectors['db_queries'] = new QM_Collector_DB_Queries();
240
  return $collectors;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  if ( ! defined( 'SAVEQUERIES' ) ) {
13
  define( 'SAVEQUERIES', true );
22
 
23
  class QM_Collector_DB_Queries extends QM_Collector {
24
 
25
+ /**
26
+ * @var string
27
+ */
28
+ public $id = 'db_queries';
29
+
30
+ /**
31
+ * @var array<string, wpdb>
32
+ */
33
  public $db_objects = array();
34
 
35
+ /**
36
+ * @return mixed[]|false
37
+ */
38
  public function get_errors() {
39
  if ( ! empty( $this->data['errors'] ) ) {
40
  return $this->data['errors'];
42
  return false;
43
  }
44
 
45
+ /**
46
+ * @return mixed[]|false
47
+ */
48
  public function get_expensive() {
49
  if ( ! empty( $this->data['expensive'] ) ) {
50
  return $this->data['expensive'];
52
  return false;
53
  }
54
 
55
+ /**
56
+ * @param array<string, mixed> $row
57
+ * @return bool
58
+ */
59
  public static function is_expensive( array $row ) {
60
  return $row['ltime'] > QM_DB_EXPENSIVE;
61
  }
62
 
63
+ /**
64
+ * @return void
65
+ */
66
  public function process() {
67
+ $this->data['total_qs'] = 0;
68
  $this->data['total_time'] = 0;
69
+ $this->data['errors'] = array();
70
 
71
  /**
72
  * Filters the `wpdb` instances that are exposed to QM.
91
 
92
  }
93
 
94
+ /**
95
+ * @param string $caller
96
+ * @param float $ltime
97
+ * @param string $type
98
+ * @return void
99
+ */
100
  protected function log_caller( $caller, $ltime, $type ) {
101
 
102
  if ( ! isset( $this->data['times'][ $caller ] ) ) {
103
  $this->data['times'][ $caller ] = array(
104
  'caller' => $caller,
105
+ 'ltime' => 0,
106
+ 'types' => array(),
107
  );
108
  }
109
 
117
 
118
  }
119
 
120
+ /**
121
+ * @param string $id
122
+ * @param wpdb $db
123
+ * @return void
124
+ */
125
  public function process_db_object( $id, wpdb $db ) {
126
  global $EZSQL_ERROR, $wp_the_query;
127
 
131
  return;
132
  }
133
 
134
+ $rows = array();
135
+ $types = array();
136
  $total_time = 0;
137
  $has_result = false;
138
+ $has_trace = false;
139
+ $i = 0;
140
+ $request = trim( $wp_the_query->request );
141
 
142
  if ( method_exists( $db, 'remove_placeholder_escape' ) ) {
143
  $request = $db->remove_placeholder_escape( $request );
144
  }
145
 
146
+ foreach ( $db->queries as $query ) {
147
+ $has_trace = false;
148
+ $has_result = false;
149
+ $callers = array();
150
 
151
+ if ( isset( $query['query'], $query['elapsed'], $query['debug'] ) ) {
152
+ // WordPress.com VIP.
153
+ $sql = $query['query'];
154
+ $ltime = $query['elapsed'];
155
+ $stack = $query['debug'];
156
+ } else {
157
+ // Standard WP.
158
+ $sql = $query[0];
159
+ $ltime = $query[1];
160
+ $stack = $query[2];
161
+
162
+ // Query Monitor db.php drop-in.
163
+ $has_trace = isset( $query['trace'] );
164
+ $has_result = isset( $query['result'] );
165
  }
166
 
167
+ // @TODO: decide what I want to do with this:
168
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
169
+ if ( false !== strpos( $stack, 'wp_admin_bar' ) && ! isset( $_REQUEST['qm_display_admin_bar'] ) ) {
170
+ continue;
171
+ }
 
172
 
173
  if ( $has_result ) {
174
  $result = $query['result'];
180
 
181
  if ( $has_trace ) {
182
 
183
+ $trace = $query['trace'];
184
+ $component = $query['trace']->get_component();
185
+ $caller = $query['trace']->get_caller();
186
  $caller_name = $caller['display'];
187
+ $caller = $caller['display'];
188
 
189
  } else {
190
 
191
+ $trace = null;
192
  $component = null;
193
+ $callers = array_reverse( explode( ',', $stack ) );
194
+ $callers = array_map( 'trim', $callers );
195
+ $callers = QM_Backtrace::get_filtered_stack( $callers );
196
+ $caller = reset( $callers );
197
+ $caller_name = $caller;
198
+
 
 
199
  }
200
 
201
+ $sql = trim( $sql );
202
  $type = QM_Util::get_query_type( $sql );
203
 
204
  $this->log_type( $type );
227
  $row = compact( 'caller', 'caller_name', 'sql', 'ltime', 'result', 'type', 'component', 'trace', 'is_main_query' );
228
 
229
  if ( ! isset( $trace ) ) {
230
+ $row['stack'] = $callers;
231
  }
232
 
233
  if ( is_wp_error( $result ) ) {
247
  // Fallback for displaying database errors when wp-content/db.php isn't in place
248
  foreach ( $EZSQL_ERROR as $error ) {
249
  $row = array(
250
+ 'caller' => null,
251
  'caller_name' => null,
252
+ 'stack' => '',
253
+ 'sql' => $error['query'],
254
+ 'ltime' => 0,
255
+ 'result' => new WP_Error( 'qmdb', $error['error_str'] ),
256
+ 'type' => '',
257
+ 'component' => false,
258
+ 'trace' => null,
259
  'is_main_query' => false,
260
  );
261
  $this->data['errors'][] = $row;
279
 
280
  }
281
 
282
+ /**
283
+ * @param array<string, QM_Collector> $collectors
284
+ * @param QueryMonitor $qm
285
+ * @return array<string, QM_Collector>
286
+ */
287
  function register_qm_collector_db_queries( array $collectors, QueryMonitor $qm ) {
288
  $collectors['db_queries'] = new QM_Collector_DB_Queries();
289
  return $collectors;
collectors/debug_bar.php CHANGED
@@ -5,35 +5,63 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  final class QM_Collector_Debug_Bar extends QM_Collector {
11
 
12
- public $id = 'debug_bar';
 
 
 
 
 
 
 
13
  private $panel = null;
14
 
 
 
 
 
15
  public function set_panel( Debug_Bar_Panel $panel ) {
16
  $this->panel = $panel;
17
  }
18
 
 
 
 
19
  public function get_panel() {
20
  return $this->panel;
21
  }
22
 
 
 
 
23
  public function process() {
24
  $this->get_panel()->prerender();
25
  }
26
 
 
 
 
27
  public function is_visible() {
28
  return $this->get_panel()->is_visible();
29
  }
30
 
 
 
 
31
  public function render() {
32
- return $this->get_panel()->render();
33
  }
34
 
35
  }
36
 
 
 
 
37
  function register_qm_collectors_debug_bar() {
38
 
39
  global $debug_bar;
@@ -43,7 +71,7 @@ function register_qm_collectors_debug_bar() {
43
  }
44
 
45
  $collectors = QM_Collectors::init();
46
- $qm = QueryMonitor::init();
47
 
48
  require_once $qm->plugin_path( 'classes/debug_bar.php' );
49
 
@@ -71,6 +99,9 @@ function register_qm_collectors_debug_bar() {
71
 
72
  }
73
 
 
 
 
74
  function qm_debug_bar_being_activated() {
75
  // phpcs:disable
76
 
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  final class QM_Collector_Debug_Bar extends QM_Collector {
13
 
14
+ /**
15
+ * @var string
16
+ */
17
+ public $id = 'debug_bar';
18
+
19
+ /**
20
+ * @var Debug_Bar_Panel|null
21
+ */
22
  private $panel = null;
23
 
24
+ /**
25
+ * @param Debug_Bar_Panel $panel
26
+ * @return void
27
+ */
28
  public function set_panel( Debug_Bar_Panel $panel ) {
29
  $this->panel = $panel;
30
  }
31
 
32
+ /**
33
+ * @return Debug_Bar_Panel|null
34
+ */
35
  public function get_panel() {
36
  return $this->panel;
37
  }
38
 
39
+ /**
40
+ * @return void
41
+ */
42
  public function process() {
43
  $this->get_panel()->prerender();
44
  }
45
 
46
+ /**
47
+ * @return bool
48
+ */
49
  public function is_visible() {
50
  return $this->get_panel()->is_visible();
51
  }
52
 
53
+ /**
54
+ * @return void
55
+ */
56
  public function render() {
57
+ $this->get_panel()->render();
58
  }
59
 
60
  }
61
 
62
+ /**
63
+ * @return void
64
+ */
65
  function register_qm_collectors_debug_bar() {
66
 
67
  global $debug_bar;
71
  }
72
 
73
  $collectors = QM_Collectors::init();
74
+ $qm = QueryMonitor::init();
75
 
76
  require_once $qm->plugin_path( 'classes/debug_bar.php' );
77
 
99
 
100
  }
101
 
102
+ /**
103
+ * @return bool
104
+ */
105
  function qm_debug_bar_being_activated() {
106
  // phpcs:disable
107
 
collectors/environment.php CHANGED
@@ -5,11 +5,20 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Collector_Environment extends QM_Collector {
11
 
12
- public $id = 'environment';
 
 
 
 
 
 
 
13
  protected $php_vars = array(
14
  'max_execution_time',
15
  'memory_limit',
@@ -19,11 +28,13 @@ class QM_Collector_Environment extends QM_Collector {
19
  'log_errors',
20
  );
21
 
22
- public function __construct() {
23
-
 
 
24
  global $wpdb;
25
 
26
- parent::__construct();
27
 
28
  # If QM_DB is in place then we'll use the values which were
29
  # caught early before any plugins had a chance to alter them
@@ -39,24 +50,28 @@ class QM_Collector_Environment extends QM_Collector {
39
 
40
  }
41
 
 
 
 
 
42
  protected static function get_error_levels( $error_reporting ) {
43
  $levels = array(
44
- 'E_ERROR' => false,
45
- 'E_WARNING' => false,
46
- 'E_PARSE' => false,
47
- 'E_NOTICE' => false,
48
- 'E_CORE_ERROR' => false,
49
- 'E_CORE_WARNING' => false,
50
- 'E_COMPILE_ERROR' => false,
51
- 'E_COMPILE_WARNING' => false,
52
- 'E_USER_ERROR' => false,
53
- 'E_USER_WARNING' => false,
54
- 'E_USER_NOTICE' => false,
55
- 'E_STRICT' => false,
56
  'E_RECOVERABLE_ERROR' => false,
57
- 'E_DEPRECATED' => false,
58
- 'E_USER_DEPRECATED' => false,
59
- 'E_ALL' => false,
60
  );
61
 
62
  foreach ( $levels as $level => $reported ) {
@@ -71,20 +86,24 @@ class QM_Collector_Environment extends QM_Collector {
71
  return $levels;
72
  }
73
 
 
 
 
74
  public function process() {
75
 
76
  global $wp_version;
77
 
78
  $mysql_vars = array(
79
- 'key_buffer_size' => true, # Key cache size limit
80
- 'max_allowed_packet' => false, # Individual query size limit
81
- 'max_connections' => false, # Max number of client connections
82
- 'query_cache_limit' => true, # Individual query cache size limit
83
- 'query_cache_size' => true, # Total cache size limit
84
- 'query_cache_type' => 'ON', # Query cache on or off
85
  'innodb_buffer_pool_size' => false, # The amount of memory allocated to the InnoDB buffer pool
86
  );
87
 
 
88
  $dbq = QM_Collectors::get( 'db_queries' );
89
 
90
  if ( $dbq ) {
@@ -97,8 +116,6 @@ class QM_Collector_Environment extends QM_Collector {
97
  if ( version_compare( $server, '5.7.20', '>=' ) ) {
98
  unset( $mysql_vars['query_cache_limit'], $mysql_vars['query_cache_size'], $mysql_vars['query_cache_type'] );
99
  }
100
- } else {
101
- $server = null;
102
  }
103
 
104
  $variables = $db->get_results( "
@@ -106,12 +123,15 @@ class QM_Collector_Environment extends QM_Collector {
106
  WHERE Variable_name IN ( '" . implode( "', '", array_keys( $mysql_vars ) ) . "' )
107
  " );
108
 
109
- if ( is_resource( $db->dbh ) ) {
 
 
 
110
  # Old mysql extension
111
  $extension = 'mysql';
112
- } elseif ( is_object( $db->dbh ) ) {
113
  # mysqli or PDO
114
- $extension = get_class( $db->dbh );
115
  } else {
116
  # Who knows?
117
  $extension = null;
@@ -119,7 +139,7 @@ class QM_Collector_Environment extends QM_Collector {
119
 
120
  if ( isset( $db->use_mysqli ) && $db->use_mysqli ) {
121
  $client = mysqli_get_client_version();
122
- $info = mysqli_get_server_info( $db->dbh );
123
  } else {
124
  // Please do not report this code as a PHP 7 incompatibility. Observe the surrounding logic.
125
  // phpcs:ignore
@@ -130,7 +150,7 @@ class QM_Collector_Environment extends QM_Collector {
130
  }
131
  // Please do not report this code as a PHP 7 incompatibility. Observe the surrounding logic.
132
  // phpcs:ignore
133
- $info = mysql_get_server_info( $db->dbh );
134
  }
135
 
136
  if ( $client ) {
@@ -140,18 +160,20 @@ class QM_Collector_Environment extends QM_Collector {
140
  $client_version = null;
141
  }
142
 
 
 
143
  $info = array(
144
- 'server-version' => $server,
145
- 'extension' => $extension,
146
  'client-version' => $client_version,
147
- 'user' => $db->dbuser,
148
- 'host' => $db->dbhost,
149
- 'database' => $db->dbname,
150
  );
151
 
152
  $this->data['db'][ $id ] = array(
153
- 'info' => $info,
154
- 'vars' => $mysql_vars,
155
  'variables' => $variables,
156
  );
157
 
@@ -159,9 +181,11 @@ class QM_Collector_Environment extends QM_Collector {
159
  }
160
 
161
  $this->data['php']['version'] = phpversion();
162
- $this->data['php']['sapi'] = php_sapi_name();
163
- $this->data['php']['user'] = self::get_current_user();
164
- $this->data['php']['old'] = version_compare( $this->data['php']['version'], 7.2, '<' );
 
 
165
 
166
  foreach ( $this->php_vars as $setting ) {
167
  $this->data['php']['variables'][ $setting ]['after'] = ini_get( $setting );
@@ -174,7 +198,7 @@ class QM_Collector_Environment extends QM_Collector {
174
  $sort_flags = SORT_STRING;
175
  }
176
 
177
- if ( is_callable( 'get_loaded_extensions' ) ) {
178
  $extensions = get_loaded_extensions();
179
  sort( $extensions, $sort_flags );
180
  $this->data['php']['extensions'] = array_combine( $extensions, array_map( array( $this, 'get_extension_version' ), $extensions ) );
@@ -183,18 +207,18 @@ class QM_Collector_Environment extends QM_Collector {
183
  }
184
 
185
  $this->data['php']['error_reporting'] = error_reporting();
186
- $this->data['php']['error_levels'] = self::get_error_levels( $this->data['php']['error_reporting'] );
187
-
188
- $this->data['wp']['version'] = $wp_version;
189
- $constants = array(
190
- 'WP_DEBUG' => self::format_bool_constant( 'WP_DEBUG' ),
191
- 'WP_DEBUG_DISPLAY' => self::format_bool_constant( 'WP_DEBUG_DISPLAY' ),
192
- 'WP_DEBUG_LOG' => self::format_bool_constant( 'WP_DEBUG_LOG' ),
193
- 'SCRIPT_DEBUG' => self::format_bool_constant( 'SCRIPT_DEBUG' ),
194
- 'WP_CACHE' => self::format_bool_constant( 'WP_CACHE' ),
195
  'CONCATENATE_SCRIPTS' => self::format_bool_constant( 'CONCATENATE_SCRIPTS' ),
196
- 'COMPRESS_SCRIPTS' => self::format_bool_constant( 'COMPRESS_SCRIPTS' ),
197
- 'COMPRESS_CSS' => self::format_bool_constant( 'COMPRESS_CSS' ),
198
  'WP_ENVIRONMENT_TYPE' => self::format_bool_constant( 'WP_ENVIRONMENT_TYPE' ),
199
  );
200
 
@@ -228,20 +252,25 @@ class QM_Collector_Environment extends QM_Collector {
228
  }
229
 
230
  $this->data['server'] = array(
231
- 'name' => $server[0],
232
  'version' => $server_version,
233
  'address' => $address,
234
- 'host' => null,
235
- 'OS' => null,
236
  );
237
 
238
  if ( function_exists( 'php_uname' ) ) {
239
  $this->data['server']['host'] = php_uname( 'n' );
240
- $this->data['server']['OS'] = php_uname( 's' ) . ' ' . php_uname( 'r' );
 
241
  }
242
 
243
  }
244
 
 
 
 
 
245
  public function get_extension_version( $extension ) {
246
  // Nothing is simple in PHP. The exif and mysqlnd extensions (and probably others) add a bunch of
247
  // crap to their version number, so we need to pluck out the first numeric value in the string.
@@ -263,6 +292,31 @@ class QM_Collector_Environment extends QM_Collector {
263
  return $version;
264
  }
265
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  protected static function get_current_user() {
267
 
268
  $php_u = null;
@@ -304,6 +358,11 @@ class QM_Collector_Environment extends QM_Collector {
304
 
305
  }
306
 
 
 
 
 
 
307
  function register_qm_collector_environment( array $collectors, QueryMonitor $qm ) {
308
  $collectors['environment'] = new QM_Collector_Environment();
309
  return $collectors;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Collector_Environment extends QM_Collector {
13
 
14
+ /**
15
+ * @var string
16
+ */
17
+ public $id = 'environment';
18
+
19
+ /**
20
+ * @var array<int, string>
21
+ */
22
  protected $php_vars = array(
23
  'max_execution_time',
24
  'memory_limit',
28
  'log_errors',
29
  );
30
 
31
+ /**
32
+ * @return void
33
+ */
34
+ public function set_up() {
35
  global $wpdb;
36
 
37
+ parent::set_up();
38
 
39
  # If QM_DB is in place then we'll use the values which were
40
  # caught early before any plugins had a chance to alter them
50
 
51
  }
52
 
53
+ /**
54
+ * @param int $error_reporting
55
+ * @return array<string, bool>
56
+ */
57
  protected static function get_error_levels( $error_reporting ) {
58
  $levels = array(
59
+ 'E_ERROR' => false,
60
+ 'E_WARNING' => false,
61
+ 'E_PARSE' => false,
62
+ 'E_NOTICE' => false,
63
+ 'E_CORE_ERROR' => false,
64
+ 'E_CORE_WARNING' => false,
65
+ 'E_COMPILE_ERROR' => false,
66
+ 'E_COMPILE_WARNING' => false,
67
+ 'E_USER_ERROR' => false,
68
+ 'E_USER_WARNING' => false,
69
+ 'E_USER_NOTICE' => false,
70
+ 'E_STRICT' => false,
71
  'E_RECOVERABLE_ERROR' => false,
72
+ 'E_DEPRECATED' => false,
73
+ 'E_USER_DEPRECATED' => false,
74
+ 'E_ALL' => false,
75
  );
76
 
77
  foreach ( $levels as $level => $reported ) {
86
  return $levels;
87
  }
88
 
89
+ /**
90
+ * @return void
91
+ */
92
  public function process() {
93
 
94
  global $wp_version;
95
 
96
  $mysql_vars = array(
97
+ 'key_buffer_size' => true, # Key cache size limit
98
+ 'max_allowed_packet' => false, # Individual query size limit
99
+ 'max_connections' => false, # Max number of client connections
100
+ 'query_cache_limit' => true, # Individual query cache size limit
101
+ 'query_cache_size' => true, # Total cache size limit
102
+ 'query_cache_type' => 'ON', # Query cache on or off
103
  'innodb_buffer_pool_size' => false, # The amount of memory allocated to the InnoDB buffer pool
104
  );
105
 
106
+ /** @var QM_Collector_DB_Queries|null */
107
  $dbq = QM_Collectors::get( 'db_queries' );
108
 
109
  if ( $dbq ) {
116
  if ( version_compare( $server, '5.7.20', '>=' ) ) {
117
  unset( $mysql_vars['query_cache_limit'], $mysql_vars['query_cache_size'], $mysql_vars['query_cache_type'] );
118
  }
 
 
119
  }
120
 
121
  $variables = $db->get_results( "
123
  WHERE Variable_name IN ( '" . implode( "', '", array_keys( $mysql_vars ) ) . "' )
124
  " );
125
 
126
+ /** @var mysqli|resource|false|null $dbh */
127
+ $dbh = $db->dbh;
128
+
129
+ if ( is_resource( $dbh ) ) {
130
  # Old mysql extension
131
  $extension = 'mysql';
132
+ } elseif ( is_object( $dbh ) ) {
133
  # mysqli or PDO
134
+ $extension = get_class( $dbh );
135
  } else {
136
  # Who knows?
137
  $extension = null;
139
 
140
  if ( isset( $db->use_mysqli ) && $db->use_mysqli ) {
141
  $client = mysqli_get_client_version();
142
+ $info = mysqli_get_server_info( $dbh );
143
  } else {
144
  // Please do not report this code as a PHP 7 incompatibility. Observe the surrounding logic.
145
  // phpcs:ignore
150
  }
151
  // Please do not report this code as a PHP 7 incompatibility. Observe the surrounding logic.
152
  // phpcs:ignore
153
+ $info = mysql_get_server_info( $dbh );
154
  }
155
 
156
  if ( $client ) {
160
  $client_version = null;
161
  }
162
 
163
+ $server_version = self::get_server_version( $db );
164
+
165
  $info = array(
166
+ 'server-version' => $server_version,
167
+ 'extension' => $extension,
168
  'client-version' => $client_version,
169
+ 'user' => $db->dbuser,
170
+ 'host' => $db->dbhost,
171
+ 'database' => $db->dbname,
172
  );
173
 
174
  $this->data['db'][ $id ] = array(
175
+ 'info' => $info,
176
+ 'vars' => $mysql_vars,
177
  'variables' => $variables,
178
  );
179
 
181
  }
182
 
183
  $this->data['php']['version'] = phpversion();
184
+ $this->data['php']['sapi'] = php_sapi_name();
185
+ $this->data['php']['user'] = self::get_current_user();
186
+
187
+ // https://www.php.net/supported-versions.php
188
+ $this->data['php']['old'] = version_compare( $this->data['php']['version'], '7.4', '<' );
189
 
190
  foreach ( $this->php_vars as $setting ) {
191
  $this->data['php']['variables'][ $setting ]['after'] = ini_get( $setting );
198
  $sort_flags = SORT_STRING;
199
  }
200
 
201
+ if ( function_exists( 'get_loaded_extensions' ) ) {
202
  $extensions = get_loaded_extensions();
203
  sort( $extensions, $sort_flags );
204
  $this->data['php']['extensions'] = array_combine( $extensions, array_map( array( $this, 'get_extension_version' ), $extensions ) );
207
  }
208
 
209
  $this->data['php']['error_reporting'] = error_reporting();
210
+ $this->data['php']['error_levels'] = self::get_error_levels( $this->data['php']['error_reporting'] );
211
+
212
+ $this->data['wp']['version'] = $wp_version;
213
+ $constants = array(
214
+ 'WP_DEBUG' => self::format_bool_constant( 'WP_DEBUG' ),
215
+ 'WP_DEBUG_DISPLAY' => self::format_bool_constant( 'WP_DEBUG_DISPLAY' ),
216
+ 'WP_DEBUG_LOG' => self::format_bool_constant( 'WP_DEBUG_LOG' ),
217
+ 'SCRIPT_DEBUG' => self::format_bool_constant( 'SCRIPT_DEBUG' ),
218
+ 'WP_CACHE' => self::format_bool_constant( 'WP_CACHE' ),
219
  'CONCATENATE_SCRIPTS' => self::format_bool_constant( 'CONCATENATE_SCRIPTS' ),
220
+ 'COMPRESS_SCRIPTS' => self::format_bool_constant( 'COMPRESS_SCRIPTS' ),
221
+ 'COMPRESS_CSS' => self::format_bool_constant( 'COMPRESS_CSS' ),
222
  'WP_ENVIRONMENT_TYPE' => self::format_bool_constant( 'WP_ENVIRONMENT_TYPE' ),
223
  );
224
 
252
  }
253
 
254
  $this->data['server'] = array(
255
+ 'name' => $server[0],
256
  'version' => $server_version,
257
  'address' => $address,
258
+ 'host' => null,
259
+ 'OS' => null,
260
  );
261
 
262
  if ( function_exists( 'php_uname' ) ) {
263
  $this->data['server']['host'] = php_uname( 'n' );
264
+ $this->data['server']['OS'] = php_uname( 's' ) . ' ' . php_uname( 'r' );
265
+ $this->data['server']['arch'] = php_uname( 'm' );
266
  }
267
 
268
  }
269
 
270
+ /**
271
+ * @param string $extension
272
+ * @return string
273
+ */
274
  public function get_extension_version( $extension ) {
275
  // Nothing is simple in PHP. The exif and mysqlnd extensions (and probably others) add a bunch of
276
  // crap to their version number, so we need to pluck out the first numeric value in the string.
292
  return $version;
293
  }
294
 
295
+ /**
296
+ * @param wpdb $db
297
+ * @return string
298
+ */
299
+ protected static function get_server_version( wpdb $db ) {
300
+ $version = null;
301
+
302
+ if ( method_exists( $db, 'db_server_info' ) ) {
303
+ $version = $db->db_server_info();
304
+ }
305
+
306
+ if ( ! $version ) {
307
+ $version = $db->get_var( 'SELECT VERSION()' );
308
+ }
309
+
310
+ if ( ! $version ) {
311
+ $version = __( 'Unknown', 'query-monitor' );
312
+ }
313
+
314
+ return $version;
315
+ }
316
+
317
+ /**
318
+ * @return string
319
+ */
320
  protected static function get_current_user() {
321
 
322
  $php_u = null;
358
 
359
  }
360
 
361
+ /**
362
+ * @param array<string, QM_Collector> $collectors
363
+ * @param QueryMonitor $qm
364
+ * @return array<string, QM_Collector>
365
+ */
366
  function register_qm_collector_environment( array $collectors, QueryMonitor $qm ) {
367
  $collectors['environment'] = new QM_Collector_Environment();
368
  return $collectors;
collectors/hooks.php CHANGED
@@ -5,22 +5,34 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Collector_Hooks extends QM_Collector {
11
 
 
 
 
12
  public $id = 'hooks';
 
 
 
 
13
  protected static $hide_core;
14
 
 
 
 
15
  public function process() {
16
 
17
  global $wp_actions, $wp_filter;
18
 
19
- self::$hide_qm = self::hide_qm();
20
  self::$hide_core = ( defined( 'QM_HIDE_CORE_ACTIONS' ) && QM_HIDE_CORE_ACTIONS );
21
 
22
- $hooks = array();
23
- $all_parts = array();
24
  $components = array();
25
 
26
  if ( has_filter( 'all' ) ) {
@@ -39,16 +51,16 @@ class QM_Collector_Hooks extends QM_Collector {
39
 
40
  foreach ( $hook_names as $name ) {
41
 
42
- $hook = QM_Hook::process( $name, $wp_filter, self::$hide_qm, self::$hide_core );
43
  $hooks[] = $hook;
44
 
45
- $all_parts = array_merge( $all_parts, $hook['parts'] );
46
  $components = array_merge( $components, $hook['components'] );
47
 
48
  }
49
 
50
- $this->data['hooks'] = $hooks;
51
- $this->data['parts'] = array_unique( array_filter( $all_parts ) );
52
  $this->data['components'] = array_unique( array_filter( $components ) );
53
 
54
  usort( $this->data['parts'], 'strcasecmp' );
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Collector_Hooks extends QM_Collector {
13
 
14
+ /**
15
+ * @var string
16
+ */
17
  public $id = 'hooks';
18
+
19
+ /**
20
+ * @var bool
21
+ */
22
  protected static $hide_core;
23
 
24
+ /**
25
+ * @return void
26
+ */
27
  public function process() {
28
 
29
  global $wp_actions, $wp_filter;
30
 
31
+ self::$hide_qm = self::hide_qm();
32
  self::$hide_core = ( defined( 'QM_HIDE_CORE_ACTIONS' ) && QM_HIDE_CORE_ACTIONS );
33
 
34
+ $hooks = array();
35
+ $all_parts = array();
36
  $components = array();
37
 
38
  if ( has_filter( 'all' ) ) {
51
 
52
  foreach ( $hook_names as $name ) {
53
 
54
+ $hook = QM_Hook::process( $name, $wp_filter, self::$hide_qm, self::$hide_core );
55
  $hooks[] = $hook;
56
 
57
+ $all_parts = array_merge( $all_parts, $hook['parts'] );
58
  $components = array_merge( $components, $hook['components'] );
59
 
60
  }
61
 
62
+ $this->data['hooks'] = $hooks;
63
+ $this->data['parts'] = array_unique( array_filter( $all_parts ) );
64
  $this->data['components'] = array_unique( array_filter( $components ) );
65
 
66
  usort( $this->data['parts'], 'strcasecmp' );
collectors/http.php CHANGED
@@ -5,31 +5,66 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Collector_HTTP extends QM_Collector {
11
 
12
- public $id = 'http';
 
 
 
 
 
 
 
13
  private $transport = null;
14
- private $info = null;
15
 
16
- public function __construct() {
 
 
 
 
 
 
 
 
17
 
18
- parent::__construct();
19
 
20
  add_filter( 'http_request_args', array( $this, 'filter_http_request_args' ), 9999, 2 );
21
- add_filter( 'pre_http_request', array( $this, 'filter_pre_http_request' ), 9999, 3 );
22
- add_action( 'http_api_debug', array( $this, 'action_http_api_debug' ), 9999, 5 );
23
 
24
- add_action( 'requests-curl.before_request', array( $this, 'action_curl_before_request' ), 9999 );
25
- add_action( 'requests-curl.after_request', array( $this, 'action_curl_after_request' ), 9999, 2 );
26
  add_action( 'requests-fsockopen.before_request', array( $this, 'action_fsockopen_before_request' ), 9999 );
27
- add_action( 'requests-fsockopen.after_request', array( $this, 'action_fsockopen_after_request' ), 9999, 2 );
28
 
29
  }
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  public function get_concerned_actions() {
32
- $actions = array(
33
  'http_api_curl',
34
  'requests-multiple.request.complete',
35
  'requests-request.progress',
@@ -61,6 +96,9 @@ class QM_Collector_HTTP extends QM_Collector {
61
  return $actions;
62
  }
63
 
 
 
 
64
  public function get_concerned_filters() {
65
  return array(
66
  'block_local_requests',
@@ -74,6 +112,9 @@ class QM_Collector_HTTP extends QM_Collector {
74
  );
75
  }
76
 
 
 
 
77
  public function get_concerned_constants() {
78
  return array(
79
  'WP_PROXY_HOST',
@@ -91,28 +132,52 @@ class QM_Collector_HTTP extends QM_Collector {
91
  *
92
  * Used to log the request, and to add the logging key to the arguments array.
93
  *
94
- * @param array $args HTTP request arguments.
95
- * @param string $url The request URL.
96
- * @return array HTTP request arguments.
97
  */
98
  public function filter_http_request_args( array $args, $url ) {
99
- $trace = new QM_Backtrace();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  if ( isset( $args['_qm_key'] ) ) {
101
  // Something has triggered another HTTP request from within the `pre_http_request` filter
102
  // (eg. WordPress Beta Tester does this). This allows for one level of nested queries.
103
  $args['_qm_original_key'] = $args['_qm_key'];
104
- $start = $this->data['http'][ $args['_qm_key'] ]['start'];
105
  } else {
106
  $start = microtime( true );
107
  }
108
- $key = microtime( true ) . $url;
109
  $this->data['http'][ $key ] = array(
110
- 'url' => $url,
111
- 'args' => $args,
112
  'start' => $start,
113
- 'trace' => $trace,
 
114
  );
115
- $args['_qm_key'] = $key;
116
  return $args;
117
  }
118
 
@@ -123,10 +188,10 @@ class QM_Collector_HTTP extends QM_Collector {
123
  * $response should be one of boolean false, an array, or a `WP_Error`, but be aware that plugins
124
  * which short-circuit the request using this filter may (incorrectly) return data of another type.
125
  *
126
- * @param bool|array|WP_Error $response The preemptive HTTP response. Default false.
127
- * @param array $args HTTP request arguments.
128
- * @param string $url The request URL.
129
- * @return bool|array|WP_Error The preemptive HTTP response.
130
  */
131
  public function filter_pre_http_request( $response, array $args, $url ) {
132
 
@@ -144,11 +209,12 @@ class QM_Collector_HTTP extends QM_Collector {
144
  /**
145
  * Debugging action for the HTTP API.
146
  *
147
- * @param mixed $response A parameter which varies depending on $action.
148
- * @param string $action The debug action. Currently one of 'response' or 'transports_list'.
149
- * @param string $class The HTTP transport class name.
150
- * @param array $args HTTP request arguments.
151
- * @param string $url The request URL.
 
152
  */
153
  public function action_http_api_debug( $response, $action, $class, $args, $url ) {
154
 
@@ -173,18 +239,34 @@ class QM_Collector_HTTP extends QM_Collector {
173
 
174
  }
175
 
 
 
 
176
  public function action_curl_before_request() {
177
  $this->transport = 'curl';
178
  }
179
 
 
 
 
 
 
180
  public function action_curl_after_request( $headers, array $info = null ) {
181
  $this->info = $info;
182
  }
183
 
 
 
 
184
  public function action_fsockopen_before_request() {
185
  $this->transport = 'fsockopen';
186
  }
187
 
 
 
 
 
 
188
  public function action_fsockopen_after_request( $headers, array $info = null ) {
189
  $this->info = $info;
190
  }
@@ -192,16 +274,17 @@ class QM_Collector_HTTP extends QM_Collector {
192
  /**
193
  * Log an HTTP response.
194
  *
195
- * @param array|WP_Error $response The HTTP response.
196
- * @param array $args HTTP request arguments.
197
- * @param string $url The request URL.
 
198
  */
199
  public function log_http_response( $response, array $args, $url ) {
200
- $this->data['http'][ $args['_qm_key'] ]['end'] = microtime( true );
201
  $this->data['http'][ $args['_qm_key'] ]['response'] = $response;
202
- $this->data['http'][ $args['_qm_key'] ]['args'] = $args;
203
  if ( isset( $args['_qm_original_key'] ) ) {
204
- $this->data['http'][ $args['_qm_original_key'] ]['end'] = $this->data['http'][ $args['_qm_original_key'] ]['start'];
205
  $this->data['http'][ $args['_qm_original_key'] ]['response'] = new WP_Error( 'http_request_not_executed', sprintf(
206
  /* translators: %s: Hook name */
207
  __( 'Request not executed due to a filter on %s', 'query-monitor' ),
@@ -209,12 +292,15 @@ class QM_Collector_HTTP extends QM_Collector {
209
  ) );
210
  }
211
 
212
- $this->data['http'][ $args['_qm_key'] ]['info'] = $this->info;
213
  $this->data['http'][ $args['_qm_key'] ]['transport'] = $this->transport;
214
- $this->info = null;
215
  $this->transport = null;
216
  }
217
 
 
 
 
218
  public function process() {
219
  $this->data['ltime'] = 0;
220
 
@@ -241,7 +327,7 @@ class QM_Collector_HTTP extends QM_Collector {
241
  if ( ! isset( $http['response'] ) ) {
242
  // Timed out
243
  $http['response'] = new WP_Error( 'http_request_timed_out', __( 'Request timed out', 'query-monitor' ) );
244
- $http['end'] = floatval( $http['start'] + $http['args']['timeout'] );
245
  }
246
 
247
  if ( is_wp_error( $http['response'] ) ) {
@@ -260,18 +346,17 @@ class QM_Collector_HTTP extends QM_Collector {
260
 
261
  $http['ltime'] = ( $http['end'] - $http['start'] );
262
 
263
- if ( isset( $http['info'] ) ) {
264
- if ( ! empty( $http['info']['url'] ) ) {
265
- if ( rtrim( $http['url'], '/' ) !== rtrim( $http['info']['url'], '/' ) ) {
266
- $http['redirected_to'] = $http['info']['url'];
267
- }
 
268
  }
269
  }
270
 
271
  $this->data['ltime'] += $http['ltime'];
272
 
273
- $http['component'] = $http['trace']->get_component();
274
-
275
  $host = (string) parse_url( $http['url'], PHP_URL_HOST );
276
 
277
  $http['local'] = ( $host === $home_host );
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Collector_HTTP extends QM_Collector {
13
 
14
+ /**
15
+ * @var string
16
+ */
17
+ public $id = 'http';
18
+
19
+ /**
20
+ * @var string|null
21
+ */
22
  private $transport = null;
 
23
 
24
+ /**
25
+ * @var mixed|null
26
+ */
27
+ private $info = null;
28
+
29
+ /**
30
+ * @return void
31
+ */
32
+ public function set_up() {
33
 
34
+ parent::set_up();
35
 
36
  add_filter( 'http_request_args', array( $this, 'filter_http_request_args' ), 9999, 2 );
37
+ add_filter( 'pre_http_request', array( $this, 'filter_pre_http_request' ), 9999, 3 );
38
+ add_action( 'http_api_debug', array( $this, 'action_http_api_debug' ), 9999, 5 );
39
 
40
+ add_action( 'requests-curl.before_request', array( $this, 'action_curl_before_request' ), 9999 );
41
+ add_action( 'requests-curl.after_request', array( $this, 'action_curl_after_request' ), 9999, 2 );
42
  add_action( 'requests-fsockopen.before_request', array( $this, 'action_fsockopen_before_request' ), 9999 );
43
+ add_action( 'requests-fsockopen.after_request', array( $this, 'action_fsockopen_after_request' ), 9999, 2 );
44
 
45
  }
46
 
47
+ /**
48
+ * @return void
49
+ */
50
+ public function tear_down() {
51
+ remove_filter( 'http_request_args', array( $this, 'filter_http_request_args' ), 9999 );
52
+ remove_filter( 'pre_http_request', array( $this, 'filter_pre_http_request' ), 9999 );
53
+ remove_action( 'http_api_debug', array( $this, 'action_http_api_debug' ), 9999 );
54
+
55
+ remove_action( 'requests-curl.before_request', array( $this, 'action_curl_before_request' ), 9999 );
56
+ remove_action( 'requests-curl.after_request', array( $this, 'action_curl_after_request' ), 9999 );
57
+ remove_action( 'requests-fsockopen.before_request', array( $this, 'action_fsockopen_before_request' ), 9999 );
58
+ remove_action( 'requests-fsockopen.after_request', array( $this, 'action_fsockopen_after_request' ), 9999 );
59
+
60
+ parent::tear_down();
61
+ }
62
+
63
+ /**
64
+ * @return array<int, string>
65
+ */
66
  public function get_concerned_actions() {
67
+ $actions = array(
68
  'http_api_curl',
69
  'requests-multiple.request.complete',
70
  'requests-request.progress',
96
  return $actions;
97
  }
98
 
99
+ /**
100
+ * @return array<int, string>
101
+ */
102
  public function get_concerned_filters() {
103
  return array(
104
  'block_local_requests',
112
  );
113
  }
114
 
115
+ /**
116
+ * @return array<int, string>
117
+ */
118
  public function get_concerned_constants() {
119
  return array(
120
  'WP_PROXY_HOST',
132
  *
133
  * Used to log the request, and to add the logging key to the arguments array.
134
  *
135
+ * @param array<string, mixed> $args HTTP request arguments.
136
+ * @param string $url The request URL.
137
+ * @return array<string, mixed> HTTP request arguments.
138
  */
139
  public function filter_http_request_args( array $args, $url ) {
140
+ $trace = new QM_Backtrace( array(
141
+ 'ignore_hook' => array(
142
+ current_filter() => true,
143
+ ),
144
+ 'ignore_class' => array(
145
+ 'WP_Http' => true,
146
+ ),
147
+ 'ignore_func' => array(
148
+ 'wp_safe_remote_request' => true,
149
+ 'wp_safe_remote_get' => true,
150
+ 'wp_safe_remote_post' => true,
151
+ 'wp_safe_remote_head' => true,
152
+ 'wp_remote_request' => true,
153
+ 'wp_remote_get' => true,
154
+ 'wp_remote_post' => true,
155
+ 'wp_remote_head' => true,
156
+ 'wp_remote_fopen' => true,
157
+ 'download_url' => true,
158
+ 'vip_safe_wp_remote_get' => true,
159
+ 'vip_safe_wp_remote_request' => true,
160
+ 'wpcom_vip_file_get_contents' => true,
161
+ ),
162
+ ) );
163
+
164
  if ( isset( $args['_qm_key'] ) ) {
165
  // Something has triggered another HTTP request from within the `pre_http_request` filter
166
  // (eg. WordPress Beta Tester does this). This allows for one level of nested queries.
167
  $args['_qm_original_key'] = $args['_qm_key'];
168
+ $start = $this->data['http'][ $args['_qm_key'] ]['start'];
169
  } else {
170
  $start = microtime( true );
171
  }
172
+ $key = microtime( true ) . $url;
173
  $this->data['http'][ $key ] = array(
174
+ 'url' => $url,
175
+ 'args' => $args,
176
  'start' => $start,
177
+ 'filtered_trace' => $trace->get_filtered_trace(),
178
+ 'component' => $trace->get_component(),
179
  );
180
+ $args['_qm_key'] = $key;
181
  return $args;
182
  }
183
 
188
  * $response should be one of boolean false, an array, or a `WP_Error`, but be aware that plugins
189
  * which short-circuit the request using this filter may (incorrectly) return data of another type.
190
  *
191
+ * @param bool|mixed[]|WP_Error $response The preemptive HTTP response. Default false.
192
+ * @param array<string, mixed> $args HTTP request arguments.
193
+ * @param string $url The request URL.
194
+ * @return bool|mixed[]|WP_Error The preemptive HTTP response.
195
  */
196
  public function filter_pre_http_request( $response, array $args, $url ) {
197
 
209
  /**
210
  * Debugging action for the HTTP API.
211
  *
212
+ * @param mixed $response A parameter which varies depending on $action.
213
+ * @param string $action The debug action. Currently one of 'response' or 'transports_list'.
214
+ * @param string $class The HTTP transport class name.
215
+ * @param array<string, mixed> $args HTTP request arguments.
216
+ * @param string $url The request URL.
217
+ * @return void
218
  */
219
  public function action_http_api_debug( $response, $action, $class, $args, $url ) {
220
 
239
 
240
  }
241
 
242
+ /**
243
+ * @return void
244
+ */
245
  public function action_curl_before_request() {
246
  $this->transport = 'curl';
247
  }
248
 
249
+ /**
250
+ * @param mixed $headers
251
+ * @param mixed[] $info
252
+ * @return void
253
+ */
254
  public function action_curl_after_request( $headers, array $info = null ) {
255
  $this->info = $info;
256
  }
257
 
258
+ /**
259
+ * @return void
260
+ */
261
  public function action_fsockopen_before_request() {
262
  $this->transport = 'fsockopen';
263
  }
264
 
265
+ /**
266
+ * @param mixed $headers
267
+ * @param mixed[] $info
268
+ * @return void
269
+ */
270
  public function action_fsockopen_after_request( $headers, array $info = null ) {
271
  $this->info = $info;
272
  }
274
  /**
275
  * Log an HTTP response.
276
  *
277
+ * @param mixed[]|WP_Error $response The HTTP response.
278
+ * @param array<string, mixed> $args HTTP request arguments.
279
+ * @param string $url The request URL.
280
+ * @return void
281
  */
282
  public function log_http_response( $response, array $args, $url ) {
283
+ $this->data['http'][ $args['_qm_key'] ]['end'] = microtime( true );
284
  $this->data['http'][ $args['_qm_key'] ]['response'] = $response;
285
+ $this->data['http'][ $args['_qm_key'] ]['args'] = $args;
286
  if ( isset( $args['_qm_original_key'] ) ) {
287
+ $this->data['http'][ $args['_qm_original_key'] ]['end'] = $this->data['http'][ $args['_qm_original_key'] ]['start'];
288
  $this->data['http'][ $args['_qm_original_key'] ]['response'] = new WP_Error( 'http_request_not_executed', sprintf(
289
  /* translators: %s: Hook name */
290
  __( 'Request not executed due to a filter on %s', 'query-monitor' ),
292
  ) );
293
  }
294
 
295
+ $this->data['http'][ $args['_qm_key'] ]['info'] = $this->info;
296
  $this->data['http'][ $args['_qm_key'] ]['transport'] = $this->transport;
297
+ $this->info = null;
298
  $this->transport = null;
299
  }
300
 
301
+ /**
302
+ * @return void
303
+ */
304
  public function process() {
305
  $this->data['ltime'] = 0;
306
 
327
  if ( ! isset( $http['response'] ) ) {
328
  // Timed out
329
  $http['response'] = new WP_Error( 'http_request_timed_out', __( 'Request timed out', 'query-monitor' ) );
330
+ $http['end'] = floatval( $http['start'] + $http['args']['timeout'] );
331
  }
332
 
333
  if ( is_wp_error( $http['response'] ) ) {
346
 
347
  $http['ltime'] = ( $http['end'] - $http['start'] );
348
 
349
+ if ( isset( $http['info'] ) && ! empty( $http['info']['url'] ) ) {
350
+ // Ignore query variables when detecting a redirect.
351
+ $from = untrailingslashit( preg_replace( '#\?[^$]+$#', '', $http['url'] ) );
352
+ $to = untrailingslashit( preg_replace( '#\?[^$]+$#', '', $http['info']['url'] ) );
353
+ if ( $from !== $to ) {
354
+ $http['redirected_to'] = $http['info']['url'];
355
  }
356
  }
357
 
358
  $this->data['ltime'] += $http['ltime'];
359
 
 
 
360
  $host = (string) parse_url( $http['url'], PHP_URL_HOST );
361
 
362
  $http['local'] = ( $host === $home_host );
collectors/languages.php CHANGED
@@ -5,21 +5,39 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Collector_Languages extends QM_Collector {
11
 
12
  public $id = 'languages';
13
 
14
- public function __construct() {
 
 
 
15
 
16
- parent::__construct();
17
 
18
  add_filter( 'override_load_textdomain', array( $this, 'log_file_load' ), 9999, 3 );
19
  add_filter( 'load_script_translation_file', array( $this, 'log_script_file_load' ), 9999, 3 );
20
 
21
  }
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  public function get_concerned_actions() {
24
  return array(
25
  'load_textdomain',
@@ -27,6 +45,9 @@ class QM_Collector_Languages extends QM_Collector {
27
  );
28
  }
29
 
 
 
 
30
  public function get_concerned_filters() {
31
  return array(
32
  'determine_locale',
@@ -48,20 +69,33 @@ class QM_Collector_Languages extends QM_Collector {
48
  );
49
  }
50
 
 
 
 
51
  public function get_concerned_options() {
52
  return array(
53
  'WPLANG',
54
  );
55
  }
56
 
 
 
 
57
  public function get_concerned_constants() {
58
  return array(
59
  'WPLANG',
60
  );
61
  }
62
 
 
 
 
63
  public function process() {
64
- $this->data['locale'] = get_locale();
 
 
 
 
65
  $this->data['user_locale'] = function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale();
66
  ksort( $this->data['languages'] );
67
 
@@ -81,55 +115,33 @@ class QM_Collector_Languages extends QM_Collector {
81
  * @return bool
82
  */
83
  public function log_file_load( $override, $domain, $mofile ) {
84
-
85
  if ( 'query-monitor' === $domain && self::hide_qm() ) {
86
  return $override;
87
  }
88
 
89
- $trace = new QM_Backtrace();
90
- $filtered = $trace->get_filtered_trace();
91
- $caller = array();
92
-
93
- foreach ( $filtered as $i => $item ) {
94
-
95
- if ( in_array( $item['function'], array(
96
- 'load_muplugin_textdomain',
97
- 'load_plugin_textdomain',
98
- 'load_theme_textdomain',
99
- 'load_child_theme_textdomain',
100
- 'load_default_textdomain',
101
- ), true ) ) {
102
- $caller = $item;
103
- $display = $i + 1;
104
- if ( isset( $filtered[ $display ] ) ) {
105
- $caller['display'] = $filtered[ $display ]['display'];
106
- }
107
- break;
108
- }
109
- }
110
-
111
- if ( empty( $caller ) ) {
112
- if ( isset( $filtered[1] ) ) {
113
- $caller = $filtered[1];
114
- } else {
115
- $caller = $filtered[0];
116
- }
117
- }
118
-
119
- if ( ! isset( $caller['file'] ) && isset( $filtered[0]['file'] ) && isset( $filtered[0]['line'] ) ) {
120
- $caller['file'] = $filtered[0]['file'];
121
- $caller['line'] = $filtered[0]['line'];
122
- }
123
 
124
  $found = file_exists( $mofile ) ? filesize( $mofile ) : false;
125
 
126
  $this->data['languages'][ $domain ][] = array(
127
- 'caller' => $caller,
128
  'domain' => $domain,
129
- 'file' => $mofile,
130
- 'found' => $found,
131
  'handle' => null,
132
- 'type' => 'gettext',
133
  );
134
 
135
  return $override;
@@ -146,19 +158,21 @@ class QM_Collector_Languages extends QM_Collector {
146
  * @return string|false Path to the translation file to load. False if there isn't one.
147
  */
148
  public function log_script_file_load( $file, $handle, $domain ) {
149
- $trace = new QM_Backtrace();
150
- $filtered = $trace->get_filtered_trace();
151
- $caller = $filtered[0];
 
 
152
 
153
  $found = ( $file && file_exists( $file ) ) ? filesize( $file ) : false;
154
 
155
  $this->data['languages'][ $domain ][] = array(
156
- 'caller' => $caller,
157
  'domain' => $domain,
158
- 'file' => $file,
159
- 'found' => $found,
160
  'handle' => $handle,
161
- 'type' => 'jed',
162
  );
163
 
164
  return $file;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Collector_Languages extends QM_Collector {
13
 
14
  public $id = 'languages';
15
 
16
+ /**
17
+ * @return void
18
+ */
19
+ public function set_up() {
20
 
21
+ parent::set_up();
22
 
23
  add_filter( 'override_load_textdomain', array( $this, 'log_file_load' ), 9999, 3 );
24
  add_filter( 'load_script_translation_file', array( $this, 'log_script_file_load' ), 9999, 3 );
25
 
26
  }
27
 
28
+ /**
29
+ * @return void
30
+ */
31
+ public function tear_down() {
32
+ remove_filter( 'override_load_textdomain', array( $this, 'log_file_load' ), 9999 );
33
+ remove_filter( 'load_script_translation_file', array( $this, 'log_script_file_load' ), 9999 );
34
+
35
+ parent::tear_down();
36
+ }
37
+
38
+ /**
39
+ * @return array<int, string>
40
+ */
41
  public function get_concerned_actions() {
42
  return array(
43
  'load_textdomain',
45
  );
46
  }
47
 
48
+ /**
49
+ * @return array<int, string>
50
+ */
51
  public function get_concerned_filters() {
52
  return array(
53
  'determine_locale',
69
  );
70
  }
71
 
72
+ /**
73
+ * @return array<int, string>
74
+ */
75
  public function get_concerned_options() {
76
  return array(
77
  'WPLANG',
78
  );
79
  }
80
 
81
+ /**
82
+ * @return array<int, string>
83
+ */
84
  public function get_concerned_constants() {
85
  return array(
86
  'WPLANG',
87
  );
88
  }
89
 
90
+ /**
91
+ * @return void
92
+ */
93
  public function process() {
94
+ if ( empty( $this->data['languages'] ) ) {
95
+ return;
96
+ }
97
+
98
+ $this->data['locale'] = get_locale();
99
  $this->data['user_locale'] = function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale();
100
  ksort( $this->data['languages'] );
101
 
115
  * @return bool
116
  */
117
  public function log_file_load( $override, $domain, $mofile ) {
 
118
  if ( 'query-monitor' === $domain && self::hide_qm() ) {
119
  return $override;
120
  }
121
 
122
+ $trace = new QM_Backtrace( array(
123
+ 'ignore_hook' => array(
124
+ current_filter() => true,
125
+ ),
126
+ 'ignore_func' => array(
127
+ 'load_textdomain' => ( 'default' !== $domain ),
128
+ 'load_muplugin_textdomain' => true,
129
+ 'load_plugin_textdomain' => true,
130
+ 'load_theme_textdomain' => true,
131
+ 'load_child_theme_textdomain' => true,
132
+ 'load_default_textdomain' => true,
133
+ ),
134
+ ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
  $found = file_exists( $mofile ) ? filesize( $mofile ) : false;
137
 
138
  $this->data['languages'][ $domain ][] = array(
139
+ 'caller' => $trace->get_caller(),
140
  'domain' => $domain,
141
+ 'file' => $mofile,
142
+ 'found' => $found,
143
  'handle' => null,
144
+ 'type' => 'gettext',
145
  );
146
 
147
  return $override;
158
  * @return string|false Path to the translation file to load. False if there isn't one.
159
  */
160
  public function log_script_file_load( $file, $handle, $domain ) {
161
+ $trace = new QM_Backtrace( array(
162
+ 'ignore_hook' => array(
163
+ current_filter() => true,
164
+ ),
165
+ ) );
166
 
167
  $found = ( $file && file_exists( $file ) ) ? filesize( $file ) : false;
168
 
169
  $this->data['languages'][ $domain ][] = array(
170
+ 'caller' => $trace->get_caller(),
171
  'domain' => $domain,
172
+ 'file' => $file,
173
+ 'found' => $found,
174
  'handle' => $handle,
175
+ 'type' => 'jed',
176
  );
177
 
178
  return $file;
collectors/logger.php CHANGED
@@ -5,23 +5,31 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Collector_Logger extends QM_Collector {
11
 
12
  public $id = 'logger';
13
 
14
  const EMERGENCY = 'emergency';
15
- const ALERT = 'alert';
16
- const CRITICAL = 'critical';
17
- const ERROR = 'error';
18
- const WARNING = 'warning';
19
- const NOTICE = 'notice';
20
- const INFO = 'info';
21
- const DEBUG = 'debug';
22
-
23
- public function __construct() {
24
- parent::__construct();
 
 
 
 
 
 
25
  foreach ( $this->get_levels() as $level ) {
26
  add_action( "qm/{$level}", array( $this, $level ), 10, 2 );
27
  }
@@ -29,38 +37,98 @@ class QM_Collector_Logger extends QM_Collector {
29
  add_action( 'qm/log', array( $this, 'log' ), 10, 3 );
30
  }
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  public function emergency( $message, array $context = array() ) {
33
  $this->store( self::EMERGENCY, $message, $context );
34
  }
35
 
 
 
 
 
 
36
  public function alert( $message, array $context = array() ) {
37
  $this->store( self::ALERT, $message, $context );
38
  }
39
 
 
 
 
 
 
40
  public function critical( $message, array $context = array() ) {
41
  $this->store( self::CRITICAL, $message, $context );
42
  }
43
 
 
 
 
 
 
44
  public function error( $message, array $context = array() ) {
45
  $this->store( self::ERROR, $message, $context );
46
  }
47
 
 
 
 
 
 
48
  public function warning( $message, array $context = array() ) {
49
  $this->store( self::WARNING, $message, $context );
50
  }
51
 
 
 
 
 
 
52
  public function notice( $message, array $context = array() ) {
53
  $this->store( self::NOTICE, $message, $context );
54
  }
55
 
 
 
 
 
 
56
  public function info( $message, array $context = array() ) {
57
  $this->store( self::INFO, $message, $context );
58
  }
59
 
 
 
 
 
 
60
  public function debug( $message, array $context = array() ) {
61
  $this->store( self::DEBUG, $message, $context );
62
  }
63
 
 
 
 
 
 
 
 
64
  public function log( $level, $message, array $context = array() ) {
65
  if ( ! in_array( $level, $this->get_levels(), true ) ) {
66
  throw new InvalidArgumentException( __( 'Unsupported log level', 'query-monitor' ) );
@@ -69,14 +137,21 @@ class QM_Collector_Logger extends QM_Collector {
69
  $this->store( $level, $message, $context );
70
  }
71
 
 
 
 
 
 
 
 
72
  protected function store( $level, $message, array $context = array() ) {
73
- $type = 'string';
74
  $trace = new QM_Backtrace( array(
75
- 'ignore_frames' => 2,
 
 
76
  ) );
77
 
78
  if ( is_wp_error( $message ) ) {
79
- $type = 'wp_error';
80
  $message = sprintf(
81
  'WP_Error: %s (%s)',
82
  $message->get_error_message(),
@@ -85,7 +160,6 @@ class QM_Collector_Logger extends QM_Collector {
85
  }
86
 
87
  if ( ( $message instanceof Exception ) || ( $message instanceof Throwable ) ) {
88
- $type = 'throwable';
89
  $message = get_class( $message ) . ': ' . $message->getMessage();
90
  }
91
 
@@ -98,21 +172,25 @@ class QM_Collector_Logger extends QM_Collector {
98
  $message = 'true';
99
  }
100
 
101
- $type = 'dump';
102
  $message = print_r( $message, true );
103
  } elseif ( '' === trim( $message ) ) {
104
  $message = '(Empty string)';
105
  }
106
 
 
107
  $this->data['logs'][] = array(
108
  'message' => self::interpolate( $message, $context ),
109
- 'context' => $context,
110
- 'trace' => $trace,
111
- 'level' => $level,
112
- 'type' => $type,
113
  );
114
  }
115
 
 
 
 
 
 
116
  protected static function interpolate( $message, array $context = array() ) {
117
  // build a replacement array with braces around the context keys
118
  $replace = array();
@@ -130,6 +208,9 @@ class QM_Collector_Logger extends QM_Collector {
130
  return strtr( $message, $replace );
131
  }
132
 
 
 
 
133
  public function process() {
134
  if ( empty( $this->data['logs'] ) ) {
135
  return;
@@ -138,13 +219,17 @@ class QM_Collector_Logger extends QM_Collector {
138
  $components = array();
139
 
140
  foreach ( $this->data['logs'] as $row ) {
141
- $component = $row['trace']->get_component();
142
  $components[ $component->name ] = $component->name;
143
  }
144
 
145
  $this->data['components'] = $components;
146
  }
147
 
 
 
 
 
148
  public function get_levels() {
149
  return array(
150
  self::EMERGENCY,
@@ -158,6 +243,10 @@ class QM_Collector_Logger extends QM_Collector {
158
  );
159
  }
160
 
 
 
 
 
161
  public function get_warning_levels() {
162
  return array(
163
  self::EMERGENCY,
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Collector_Logger extends QM_Collector {
13
 
14
  public $id = 'logger';
15
 
16
  const EMERGENCY = 'emergency';
17
+ const ALERT = 'alert';
18
+ const CRITICAL = 'critical';
19
+ const ERROR = 'error';
20
+ const WARNING = 'warning';
21
+ const NOTICE = 'notice';
22
+ const INFO = 'info';
23
+ const DEBUG = 'debug';
24
+
25
+ /**
26
+ * @return void
27
+ */
28
+ public function set_up() {
29
+ parent::set_up();
30
+
31
+ $this->data['counts'] = array_fill_keys( $this->get_levels(), 0 );
32
+
33
  foreach ( $this->get_levels() as $level ) {
34
  add_action( "qm/{$level}", array( $this, $level ), 10, 2 );
35
  }
37
  add_action( 'qm/log', array( $this, 'log' ), 10, 3 );
38
  }
39
 
40
+ /**
41
+ * @return void
42
+ */
43
+ public function tear_down() {
44
+ foreach ( $this->get_levels() as $level ) {
45
+ remove_action( "qm/{$level}", array( $this, $level ), 10 );
46
+ }
47
+
48
+ remove_action( 'qm/log', array( $this, 'log' ), 10 );
49
+
50
+ parent::tear_down();
51
+ }
52
+
53
+ /**
54
+ * @param mixed $message
55
+ * @param array<string, mixed> $context
56
+ * @return void
57
+ */
58
  public function emergency( $message, array $context = array() ) {
59
  $this->store( self::EMERGENCY, $message, $context );
60
  }
61
 
62
+ /**
63
+ * @param mixed $message
64
+ * @param array<string, mixed> $context
65
+ * @return void
66
+ */
67
  public function alert( $message, array $context = array() ) {
68
  $this->store( self::ALERT, $message, $context );
69
  }
70
 
71
+ /**
72
+ * @param mixed $message
73
+ * @param array<string, mixed> $context
74
+ * @return void
75
+ */
76
  public function critical( $message, array $context = array() ) {
77
  $this->store( self::CRITICAL, $message, $context );
78
  }
79
 
80
+ /**
81
+ * @param mixed $message
82
+ * @param array<string, mixed> $context
83
+ * @return void
84
+ */
85
  public function error( $message, array $context = array() ) {
86
  $this->store( self::ERROR, $message, $context );
87
  }
88
 
89
+ /**
90
+ * @param mixed $message
91
+ * @param array<string, mixed> $context
92
+ * @return void
93
+ */
94
  public function warning( $message, array $context = array() ) {
95
  $this->store( self::WARNING, $message, $context );
96
  }
97
 
98
+ /**
99
+ * @param mixed $message
100
+ * @param array<string, mixed> $context
101
+ * @return void
102
+ */
103
  public function notice( $message, array $context = array() ) {
104
  $this->store( self::NOTICE, $message, $context );
105
  }
106
 
107
+ /**
108
+ * @param mixed $message
109
+ * @param array<string, mixed> $context
110
+ * @return void
111
+ */
112
  public function info( $message, array $context = array() ) {
113
  $this->store( self::INFO, $message, $context );
114
  }
115
 
116
+ /**
117
+ * @param mixed $message
118
+ * @param array<string, mixed> $context
119
+ * @return void
120
+ */
121
  public function debug( $message, array $context = array() ) {
122
  $this->store( self::DEBUG, $message, $context );
123
  }
124
 
125
+ /**
126
+ * @param string $level
127
+ * @param mixed $message
128
+ * @param array<string, mixed> $context
129
+ * @phpstan-param self::* $level
130
+ * @return void
131
+ */
132
  public function log( $level, $message, array $context = array() ) {
133
  if ( ! in_array( $level, $this->get_levels(), true ) ) {
134
  throw new InvalidArgumentException( __( 'Unsupported log level', 'query-monitor' ) );
137
  $this->store( $level, $message, $context );
138
  }
139
 
140
+ /**
141
+ * @param string $level
142
+ * @param mixed $message
143
+ * @param array<string, mixed> $context
144
+ * @phpstan-param self::* $level
145
+ * @return void
146
+ */
147
  protected function store( $level, $message, array $context = array() ) {
 
148
  $trace = new QM_Backtrace( array(
149
+ 'ignore_hook' => array(
150
+ current_filter() => true,
151
+ ),
152
  ) );
153
 
154
  if ( is_wp_error( $message ) ) {
 
155
  $message = sprintf(
156
  'WP_Error: %s (%s)',
157
  $message->get_error_message(),
160
  }
161
 
162
  if ( ( $message instanceof Exception ) || ( $message instanceof Throwable ) ) {
 
163
  $message = get_class( $message ) . ': ' . $message->getMessage();
164
  }
165
 
172
  $message = 'true';
173
  }
174
 
 
175
  $message = print_r( $message, true );
176
  } elseif ( '' === trim( $message ) ) {
177
  $message = '(Empty string)';
178
  }
179
 
180
+ $this->data['counts'][ $level ]++;
181
  $this->data['logs'][] = array(
182
  'message' => self::interpolate( $message, $context ),
183
+ 'filtered_trace' => $trace->get_filtered_trace(),
184
+ 'component' => $trace->get_component(),
185
+ 'level' => $level,
 
186
  );
187
  }
188
 
189
+ /**
190
+ * @param string $message
191
+ * @param array<string, mixed> $context
192
+ * @return string
193
+ */
194
  protected static function interpolate( $message, array $context = array() ) {
195
  // build a replacement array with braces around the context keys
196
  $replace = array();
208
  return strtr( $message, $replace );
209
  }
210
 
211
+ /**
212
+ * @return void
213
+ */
214
  public function process() {
215
  if ( empty( $this->data['logs'] ) ) {
216
  return;
219
  $components = array();
220
 
221
  foreach ( $this->data['logs'] as $row ) {
222
+ $component = $row['component'];
223
  $components[ $component->name ] = $component->name;
224
  }
225
 
226
  $this->data['components'] = $components;
227
  }
228
 
229
+ /**
230
+ * @return array<int, string>
231
+ * @phpstan-return array<int, self::*>
232
+ */
233
  public function get_levels() {
234
  return array(
235
  self::EMERGENCY,
243
  );
244
  }
245
 
246
+ /**
247
+ * @return array<int, string>
248
+ * @phpstan-return array<int, self::*>
249
+ */
250
  public function get_warning_levels() {
251
  return array(
252
  self::EMERGENCY,
collectors/overview.php CHANGED
@@ -5,24 +5,37 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Collector_Overview extends QM_Collector {
11
 
12
  public $id = 'overview';
13
 
14
- public function __construct() {
 
 
 
 
 
15
  add_action( 'shutdown', array( $this, 'process_timing' ), 0 );
16
  }
17
 
 
 
 
18
  public function tear_down() {
19
  remove_action( 'shutdown', array( $this, 'process_timing' ), 0 );
 
20
  parent::tear_down();
21
  }
22
 
23
  /**
24
  * Processes the timing and memory related stats as early as possible, so the
25
  * data isn't skewed by collectors that are processed before this one.
 
 
26
  */
27
  public function process_timing() {
28
  $this->data['time_taken'] = self::timer_stop_float();
@@ -36,8 +49,11 @@ class QM_Collector_Overview extends QM_Collector {
36
  }
37
  }
38
 
 
 
 
39
  public function process() {
40
- if ( ! isset( $data['time_taken'] ) ) {
41
  $this->process_timing();
42
  }
43
 
@@ -78,7 +94,7 @@ class QM_Collector_Overview extends QM_Collector {
78
  $this->data['wp_memory_usage'] = 0;
79
  }
80
 
81
- $this->data['display_time_usage_warning'] = ( $this->data['time_usage'] >= 75 );
82
  $this->data['display_memory_usage_warning'] = ( $this->data['memory_usage'] >= 75 );
83
 
84
  $this->data['is_admin'] = is_admin();
@@ -86,6 +102,11 @@ class QM_Collector_Overview extends QM_Collector {
86
 
87
  }
88
 
 
 
 
 
 
89
  function register_qm_collector_overview( array $collectors, QueryMonitor $qm ) {
90
  $collectors['overview'] = new QM_Collector_Overview();
91
  return $collectors;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Collector_Overview extends QM_Collector {
13
 
14
  public $id = 'overview';
15
 
16
+ /**
17
+ * @return void
18
+ */
19
+ public function set_up() {
20
+ parent::set_up();
21
+
22
  add_action( 'shutdown', array( $this, 'process_timing' ), 0 );
23
  }
24
 
25
+ /**
26
+ * @return void
27
+ */
28
  public function tear_down() {
29
  remove_action( 'shutdown', array( $this, 'process_timing' ), 0 );
30
+
31
  parent::tear_down();
32
  }
33
 
34
  /**
35
  * Processes the timing and memory related stats as early as possible, so the
36
  * data isn't skewed by collectors that are processed before this one.
37
+ *
38
+ * @return void
39
  */
40
  public function process_timing() {
41
  $this->data['time_taken'] = self::timer_stop_float();
49
  }
50
  }
51
 
52
+ /**
53
+ * @return void
54
+ */
55
  public function process() {
56
+ if ( ! isset( $this->data['time_taken'] ) ) {
57
  $this->process_timing();
58
  }
59
 
94
  $this->data['wp_memory_usage'] = 0;
95
  }
96
 
97
+ $this->data['display_time_usage_warning'] = ( $this->data['time_usage'] >= 75 );
98
  $this->data['display_memory_usage_warning'] = ( $this->data['memory_usage'] >= 75 );
99
 
100
  $this->data['is_admin'] = is_admin();
102
 
103
  }
104
 
105
+ /**
106
+ * @param array<string, QM_Collector> $collectors
107
+ * @param QueryMonitor $qm
108
+ * @return array<string, QM_Collector>
109
+ */
110
  function register_qm_collector_overview( array $collectors, QueryMonitor $qm ) {
111
  $collectors['overview'] = new QM_Collector_Overview();
112
  return $collectors;
collectors/php_errors.php CHANGED
@@ -5,31 +5,69 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  define( 'QM_ERROR_FATALS', E_ERROR | E_PARSE | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR );
11
 
12
  class QM_Collector_PHP_Errors extends QM_Collector {
13
 
14
- public $id = 'php_errors';
15
- public $types = array();
 
 
 
 
 
 
 
 
 
 
 
16
  private $error_reporting = null;
17
- private $display_errors = null;
18
- private $exception_handler = null;
19
- private static $unexpected_error;
20
 
21
- public function __construct() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  if ( defined( 'QM_DISABLE_ERROR_HANDLER' ) && QM_DISABLE_ERROR_HANDLER ) {
23
  return;
24
  }
25
 
26
- parent::__construct();
27
 
28
  // Capture the last error that occurred before QM loaded:
29
  $prior_error = error_get_last();
30
 
31
  // Non-fatal error handler for all PHP versions:
32
- set_error_handler( array( $this, 'error_handler' ), ( E_ALL ^ QM_ERROR_FATALS ) );
33
 
34
  if ( ! interface_exists( 'Throwable' ) ) {
35
  // Fatal error handler for PHP < 7:
@@ -37,11 +75,11 @@ class QM_Collector_PHP_Errors extends QM_Collector {
37
  }
38
 
39
  // Fatal error handler for PHP >= 7, and uncaught exception handler for all PHP versions:
40
- $this->exception_handler = set_exception_handler( array( $this, 'exception_handler' ) );
41
 
42
  $this->error_reporting = error_reporting();
43
- $this->display_errors = ini_get( 'display_errors' );
44
- ini_set( 'display_errors', 0 );
45
 
46
  if ( $prior_error ) {
47
  $this->error_handler(
@@ -55,6 +93,33 @@ class QM_Collector_PHP_Errors extends QM_Collector {
55
  }
56
  }
57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  /**
59
  * Uncaught exception handler.
60
  *
@@ -62,6 +127,7 @@ class QM_Collector_PHP_Errors extends QM_Collector {
62
  * In PHP < 7 it will receive an Exception object.
63
  *
64
  * @param Throwable|Exception $e The error or exception.
 
65
  */
66
  public function exception_handler( $e ) {
67
  if ( is_a( $e, 'Exception' ) ) {
@@ -76,15 +142,15 @@ class QM_Collector_PHP_Errors extends QM_Collector {
76
  $error,
77
  $e->getMessage()
78
  ),
79
- 'file' => $e->getFile(),
80
- 'line' => $e->getLine(),
81
- 'trace' => $e->getTrace(),
82
  ) );
83
 
84
  // The exception must be re-thrown or passed to the previously registered exception handler so that the error
85
  // is logged appropriately instead of discarded silently.
86
- if ( $this->exception_handler ) {
87
- call_user_func( $this->exception_handler, $e );
88
  } else {
89
  throw $e;
90
  }
@@ -92,18 +158,28 @@ class QM_Collector_PHP_Errors extends QM_Collector {
92
  exit( 1 );
93
  }
94
 
 
 
 
 
 
 
 
 
 
95
  public function error_handler( $errno, $message, $file = null, $line = null, $context = null, $do_trace = true ) {
 
96
 
97
  /**
98
  * Fires before logging the PHP error in Query Monitor.
99
  *
100
  * @since 2.7.0
101
  *
102
- * @param int $errno The error number.
103
- * @param string $message The error message.
104
- * @param string $file The file location.
105
- * @param string $line The line number.
106
- * @param string $context The context being passed.
107
  */
108
  do_action( 'qm/collect/new_php_error', $errno, $message, $file, $line, $context );
109
 
@@ -128,10 +204,10 @@ class QM_Collector_PHP_Errors extends QM_Collector {
128
  $type = 'deprecated';
129
  break;
130
 
131
- default:
132
- return false;
133
- break;
134
 
 
 
135
  }
136
 
137
  if ( ! class_exists( 'QM_Backtrace' ) ) {
@@ -163,24 +239,23 @@ class QM_Collector_PHP_Errors extends QM_Collector {
163
  return false;
164
  }
165
 
166
- $trace = new QM_Backtrace( array(
167
- 'ignore_current_filter' => false,
168
- ) );
169
  $caller = $trace->get_caller();
170
- $key = md5( $message . $file . $line . $caller['id'] );
171
 
172
  if ( isset( $this->data[ $error_group ][ $type ][ $key ] ) ) {
173
  $this->data[ $error_group ][ $type ][ $key ]['calls']++;
174
  } else {
175
  $this->data[ $error_group ][ $type ][ $key ] = array(
176
- 'errno' => $errno,
177
- 'type' => $type,
178
- 'message' => wp_strip_all_tags( $message ),
179
- 'file' => $file,
180
  'filename' => QM_Util::standard_dir( $file, '' ),
181
- 'line' => $line,
182
- 'trace' => ( $do_trace ? $trace : null ),
183
- 'calls' => 1,
 
184
  );
185
  }
186
 
@@ -198,6 +273,8 @@ class QM_Collector_PHP_Errors extends QM_Collector {
198
 
199
  /**
200
  * Displays fatal error output for sites running PHP < 7.
 
 
201
  */
202
  public function shutdown_handler() {
203
 
@@ -216,6 +293,18 @@ class QM_Collector_PHP_Errors extends QM_Collector {
216
  $this->output_fatal( $error, $e );
217
  }
218
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  protected function output_fatal( $error, array $e ) {
220
  $dispatcher = QM_Dispatchers::get( 'html' );
221
 
@@ -256,7 +345,7 @@ class QM_Collector_PHP_Errors extends QM_Collector {
256
  '<div id="qm-fatal" data-qm-message="%1$s" data-qm-file="%2$s" data-qm-line="%3$d">',
257
  esc_attr( $e['message'] ),
258
  esc_attr( QM_Util::standard_dir( $e['file'], '' ) ),
259
- esc_attr( $e['line'] )
260
  );
261
 
262
  echo '<div class="qm-fatal-wrap">';
@@ -297,41 +386,37 @@ class QM_Collector_PHP_Errors extends QM_Collector {
297
  echo '</div>';
298
  }
299
 
300
- public function post_process() {
301
- ini_set( 'display_errors', $this->display_errors );
302
- restore_error_handler();
303
- restore_exception_handler();
304
- }
305
-
306
  /**
307
  * Runs post-processing on the collected errors and updates the
308
  * errors collected in the data->errors property.
309
  *
310
  * Any unreportable errors are placed in the data->filtered_errors
311
  * property.
 
 
312
  */
313
  public function process() {
314
  $this->types = array(
315
- 'errors' => array(
316
- 'warning' => _x( 'Warning', 'PHP error level', 'query-monitor' ),
317
- 'notice' => _x( 'Notice', 'PHP error level', 'query-monitor' ),
318
- 'strict' => _x( 'Strict', 'PHP error level', 'query-monitor' ),
319
  'deprecated' => _x( 'Deprecated', 'PHP error level', 'query-monitor' ),
320
  ),
321
  'suppressed' => array(
322
- 'warning' => _x( 'Warning (Suppressed)', 'Suppressed PHP error level', 'query-monitor' ),
323
- 'notice' => _x( 'Notice (Suppressed)', 'Suppressed PHP error level', 'query-monitor' ),
324
- 'strict' => _x( 'Strict (Suppressed)', 'Suppressed PHP error level', 'query-monitor' ),
325
  'deprecated' => _x( 'Deprecated (Suppressed)', 'Suppressed PHP error level', 'query-monitor' ),
326
  ),
327
- 'silenced' => array(
328
- 'warning' => _x( 'Warning (Silenced)', 'Silenced PHP error level', 'query-monitor' ),
329
- 'notice' => _x( 'Notice (Silenced)', 'Silenced PHP error level', 'query-monitor' ),
330
- 'strict' => _x( 'Strict (Silenced)', 'Silenced PHP error level', 'query-monitor' ),
331
  'deprecated' => _x( 'Deprecated (Silenced)', 'Silenced PHP error level', 'query-monitor' ),
332
  ),
333
  );
334
- $components = array();
335
 
336
  if ( ! empty( $this->data ) && ! empty( $this->data['errors'] ) ) {
337
  /**
@@ -370,7 +455,7 @@ class QM_Collector_PHP_Errors extends QM_Collector {
370
  *
371
  * @since 2.7.0
372
  *
373
- * @param int[] $levels The error levels used for each component.
374
  */
375
  $levels = apply_filters( 'qm/collect/php_error_levels', array() );
376
 
@@ -393,8 +478,8 @@ class QM_Collector_PHP_Errors extends QM_Collector {
393
  foreach ( $error_types as $type => $title ) {
394
  if ( isset( $this->data[ $error_group ][ $type ] ) ) {
395
  foreach ( $this->data[ $error_group ][ $type ] as $error ) {
396
- if ( $error['trace'] ) {
397
- $component = $error['trace']->get_component();
398
  $components[ $component->name ] = $component->name;
399
  }
400
  }
@@ -412,6 +497,7 @@ class QM_Collector_PHP_Errors extends QM_Collector {
412
  *
413
  * @param int[] $components The error levels keyed by component name.
414
  * @param string $component_type The component type, for example 'plugin' or 'theme'.
 
415
  */
416
  public function filter_reportable_errors( array $components, $component_type ) {
417
  $all_errors = $this->data['errors'];
@@ -423,11 +509,11 @@ class QM_Collector_PHP_Errors extends QM_Collector {
423
  continue;
424
  }
425
 
426
- if ( ! $error['trace'] ) {
427
  continue;
428
  }
429
 
430
- if ( ! $this->is_affected_component( $error['trace']->get_component(), $component_type, $component_context ) ) {
431
  continue;
432
  }
433
 
@@ -455,9 +541,6 @@ class QM_Collector_PHP_Errors extends QM_Collector {
455
  * @return bool
456
  */
457
  public function is_affected_component( $component, $component_type, $component_context ) {
458
- if ( empty( $component ) ) {
459
- return false;
460
- }
461
  return ( $component->type === $component_type && $component->context === $component_context );
462
  }
463
 
@@ -465,37 +548,38 @@ class QM_Collector_PHP_Errors extends QM_Collector {
465
  * Checks if the error number specified is viewable based on the
466
  * flags specified.
467
  *
468
- * @param int $error_no The errno from PHP
469
- * @param int $flags The config flags specified by users
470
- * @return int Truthy int value if reportable else 0.
471
- *
472
  * Eg:- If a plugin had the config flags,
473
  *
474
- * E_ALL & ~E_NOTICE
475
  *
476
  * then,
477
  *
478
- * is_reportable_error( E_NOTICE, E_ALL & ~E_NOTICE ) is false
479
- * is_reportable_error( E_WARNING, E_ALL & ~E_NOTICE ) is true
480
  *
481
- * If the $flag is null, all errors are assumed to be
482
  * reportable by default.
 
 
 
 
483
  */
484
  public function is_reportable_error( $error_no, $flags ) {
485
- if ( ! is_null( $flags ) ) {
486
- $result = $error_no & $flags;
487
- } else {
488
- $result = 1;
489
  }
490
 
491
- return (bool) $result;
492
  }
493
 
494
  /**
495
  * For testing purposes only. Sets the errors property manually.
496
  * Needed to test the filter since the data property is protected.
497
  *
498
- * @param array $errors The list of errors
 
499
  */
500
  public function set_php_errors( $errors ) {
501
  $this->data['errors'] = $errors;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  define( 'QM_ERROR_FATALS', E_ERROR | E_PARSE | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR );
13
 
14
  class QM_Collector_PHP_Errors extends QM_Collector {
15
 
16
+ /**
17
+ * @var string
18
+ */
19
+ public $id = 'php_errors';
20
+
21
+ /**
22
+ * @var array<string, array<string, string>>
23
+ */
24
+ public $types = array();
25
+
26
+ /**
27
+ * @var int|null
28
+ */
29
  private $error_reporting = null;
 
 
 
30
 
31
+ /**
32
+ * @var string|false|null
33
+ */
34
+ private $display_errors = null;
35
+
36
+ /**
37
+ * @var callable|null
38
+ */
39
+ private $previous_error_handler = null;
40
+
41
+ /**
42
+ * @var callable|null
43
+ */
44
+ private $previous_exception_handler = null;
45
+
46
+ /**
47
+ * @var string|null
48
+ */
49
+ private static $unexpected_error = null;
50
+
51
+ /**
52
+ * @var bool
53
+ */
54
+ protected $hide_silenced_php_errors = false;
55
+
56
+ /**
57
+ * @return void
58
+ */
59
+ public function set_up() {
60
  if ( defined( 'QM_DISABLE_ERROR_HANDLER' ) && QM_DISABLE_ERROR_HANDLER ) {
61
  return;
62
  }
63
 
64
+ parent::set_up();
65
 
66
  // Capture the last error that occurred before QM loaded:
67
  $prior_error = error_get_last();
68
 
69
  // Non-fatal error handler for all PHP versions:
70
+ $this->previous_error_handler = set_error_handler( array( $this, 'error_handler' ), ( E_ALL ^ QM_ERROR_FATALS ) );
71
 
72
  if ( ! interface_exists( 'Throwable' ) ) {
73
  // Fatal error handler for PHP < 7:
75
  }
76
 
77
  // Fatal error handler for PHP >= 7, and uncaught exception handler for all PHP versions:
78
+ $this->previous_exception_handler = set_exception_handler( array( $this, 'exception_handler' ) );
79
 
80
  $this->error_reporting = error_reporting();
81
+ $this->display_errors = ini_get( 'display_errors' );
82
+ ini_set( 'display_errors', '0' );
83
 
84
  if ( $prior_error ) {
85
  $this->error_handler(
93
  }
94
  }
95
 
96
+ /**
97
+ * @return void
98
+ */
99
+ public function tear_down() {
100
+ if ( defined( 'QM_DISABLE_ERROR_HANDLER' ) && QM_DISABLE_ERROR_HANDLER ) {
101
+ return;
102
+ }
103
+
104
+ if ( null !== $this->previous_error_handler ) {
105
+ restore_error_handler();
106
+ }
107
+
108
+ if ( null !== $this->previous_exception_handler ) {
109
+ restore_exception_handler();
110
+ }
111
+
112
+ if ( null !== $this->error_reporting ) {
113
+ error_reporting( $this->error_reporting );
114
+ }
115
+
116
+ if ( false !== $this->display_errors ) {
117
+ ini_set( 'display_errors', $this->display_errors );
118
+ }
119
+
120
+ parent::tear_down();
121
+ }
122
+
123
  /**
124
  * Uncaught exception handler.
125
  *
127
  * In PHP < 7 it will receive an Exception object.
128
  *
129
  * @param Throwable|Exception $e The error or exception.
130
+ * @return void
131
  */
132
  public function exception_handler( $e ) {
133
  if ( is_a( $e, 'Exception' ) ) {
142
  $error,
143
  $e->getMessage()
144
  ),
145
+ 'file' => $e->getFile(),
146
+ 'line' => $e->getLine(),
147
+ 'trace' => $e->getTrace(),
148
  ) );
149
 
150
  // The exception must be re-thrown or passed to the previously registered exception handler so that the error
151
  // is logged appropriately instead of discarded silently.
152
+ if ( $this->previous_exception_handler ) {
153
+ call_user_func( $this->previous_exception_handler, $e );
154
  } else {
155
  throw $e;
156
  }
158
  exit( 1 );
159
  }
160
 
161
+ /**
162
+ * @param int $errno The error number.
163
+ * @param string $message The error message.
164
+ * @param string $file The file location.
165
+ * @param int $line The line number.
166
+ * @param mixed[] $context The context being passed.
167
+ * @param bool $do_trace Whether a stack trace should be included in the logged error data.
168
+ * @return bool
169
+ */
170
  public function error_handler( $errno, $message, $file = null, $line = null, $context = null, $do_trace = true ) {
171
+ $type = null;
172
 
173
  /**
174
  * Fires before logging the PHP error in Query Monitor.
175
  *
176
  * @since 2.7.0
177
  *
178
+ * @param int $errno The error number.
179
+ * @param string $message The error message.
180
+ * @param string|null $file The file location.
181
+ * @param int|null $line The line number.
182
+ * @param mixed[]|null $context The context being passed.
183
  */
184
  do_action( 'qm/collect/new_php_error', $errno, $message, $file, $line, $context );
185
 
204
  $type = 'deprecated';
205
  break;
206
 
207
+ }
 
 
208
 
209
+ if ( null === $type ) {
210
+ return false;
211
  }
212
 
213
  if ( ! class_exists( 'QM_Backtrace' ) ) {
239
  return false;
240
  }
241
 
242
+ $trace = new QM_Backtrace();
 
 
243
  $caller = $trace->get_caller();
244
+ $key = md5( $message . $file . $line . $caller['id'] );
245
 
246
  if ( isset( $this->data[ $error_group ][ $type ][ $key ] ) ) {
247
  $this->data[ $error_group ][ $type ][ $key ]['calls']++;
248
  } else {
249
  $this->data[ $error_group ][ $type ][ $key ] = array(
250
+ 'errno' => $errno,
251
+ 'type' => $type,
252
+ 'message' => wp_strip_all_tags( $message ),
253
+ 'file' => $file,
254
  'filename' => QM_Util::standard_dir( $file, '' ),
255
+ 'line' => $line,
256
+ 'filtered_trace' => ( $do_trace ? $trace->get_filtered_trace() : null ),
257
+ 'component' => $trace->get_component(),
258
+ 'calls' => 1,
259
  );
260
  }
261
 
273
 
274
  /**
275
  * Displays fatal error output for sites running PHP < 7.
276
+ *
277
+ * @return void
278
  */
279
  public function shutdown_handler() {
280
 
293
  $this->output_fatal( $error, $e );
294
  }
295
 
296
+ /**
297
+ * @param string $error
298
+ * @param mixed[] $e
299
+ * @phpstan-param array{
300
+ * message: string,
301
+ * file: string,
302
+ * line: int,
303
+ * type?: int,
304
+ * trace?: mixed|null,
305
+ * } $e
306
+ * @return void
307
+ */
308
  protected function output_fatal( $error, array $e ) {
309
  $dispatcher = QM_Dispatchers::get( 'html' );
310
 
345
  '<div id="qm-fatal" data-qm-message="%1$s" data-qm-file="%2$s" data-qm-line="%3$d">',
346
  esc_attr( $e['message'] ),
347
  esc_attr( QM_Util::standard_dir( $e['file'], '' ) ),
348
+ intval( $e['line'] )
349
  );
350
 
351
  echo '<div class="qm-fatal-wrap">';
386
  echo '</div>';
387
  }
388
 
 
 
 
 
 
 
389
  /**
390
  * Runs post-processing on the collected errors and updates the
391
  * errors collected in the data->errors property.
392
  *
393
  * Any unreportable errors are placed in the data->filtered_errors
394
  * property.
395
+ *
396
+ * @return void
397
  */
398
  public function process() {
399
  $this->types = array(
400
+ 'errors' => array(
401
+ 'warning' => _x( 'Warning', 'PHP error level', 'query-monitor' ),
402
+ 'notice' => _x( 'Notice', 'PHP error level', 'query-monitor' ),
403
+ 'strict' => _x( 'Strict', 'PHP error level', 'query-monitor' ),
404
  'deprecated' => _x( 'Deprecated', 'PHP error level', 'query-monitor' ),
405
  ),
406
  'suppressed' => array(
407
+ 'warning' => _x( 'Warning (Suppressed)', 'Suppressed PHP error level', 'query-monitor' ),
408
+ 'notice' => _x( 'Notice (Suppressed)', 'Suppressed PHP error level', 'query-monitor' ),
409
+ 'strict' => _x( 'Strict (Suppressed)', 'Suppressed PHP error level', 'query-monitor' ),
410
  'deprecated' => _x( 'Deprecated (Suppressed)', 'Suppressed PHP error level', 'query-monitor' ),
411
  ),
412
+ 'silenced' => array(
413
+ 'warning' => _x( 'Warning (Silenced)', 'Silenced PHP error level', 'query-monitor' ),
414
+ 'notice' => _x( 'Notice (Silenced)', 'Silenced PHP error level', 'query-monitor' ),
415
+ 'strict' => _x( 'Strict (Silenced)', 'Silenced PHP error level', 'query-monitor' ),
416
  'deprecated' => _x( 'Deprecated (Silenced)', 'Silenced PHP error level', 'query-monitor' ),
417
  ),
418
  );
419
+ $components = array();
420
 
421
  if ( ! empty( $this->data ) && ! empty( $this->data['errors'] ) ) {
422
  /**
455
  *
456
  * @since 2.7.0
457
  *
458
+ * @param array<string,array<string,int>> $levels The error levels used for each component.
459
  */
460
  $levels = apply_filters( 'qm/collect/php_error_levels', array() );
461
 
478
  foreach ( $error_types as $type => $title ) {
479
  if ( isset( $this->data[ $error_group ][ $type ] ) ) {
480
  foreach ( $this->data[ $error_group ][ $type ] as $error ) {
481
+ if ( $error['component'] ) {
482
+ $component = $error['component'];
483
  $components[ $component->name ] = $component->name;
484
  }
485
  }
497
  *
498
  * @param int[] $components The error levels keyed by component name.
499
  * @param string $component_type The component type, for example 'plugin' or 'theme'.
500
+ * @return void
501
  */
502
  public function filter_reportable_errors( array $components, $component_type ) {
503
  $all_errors = $this->data['errors'];
509
  continue;
510
  }
511
 
512
+ if ( ! $error['component'] ) {
513
  continue;
514
  }
515
 
516
+ if ( ! $this->is_affected_component( $error['component'], $component_type, $component_context ) ) {
517
  continue;
518
  }
519
 
541
  * @return bool
542
  */
543
  public function is_affected_component( $component, $component_type, $component_context ) {
 
 
 
544
  return ( $component->type === $component_type && $component->context === $component_context );
545
  }
546
 
548
  * Checks if the error number specified is viewable based on the
549
  * flags specified.
550
  *
 
 
 
 
551
  * Eg:- If a plugin had the config flags,
552
  *
553
+ * E_ALL & ~E_NOTICE
554
  *
555
  * then,
556
  *
557
+ * is_reportable_error( E_NOTICE, E_ALL & ~E_NOTICE ) is false
558
+ * is_reportable_error( E_WARNING, E_ALL & ~E_NOTICE ) is true
559
  *
560
+ * If the `$flag` is null, all errors are assumed to be
561
  * reportable by default.
562
+ *
563
+ * @param int $error_no The errno from PHP
564
+ * @param int|null $flags The config flags specified by users
565
+ * @return bool Whether the error is reportable.
566
  */
567
  public function is_reportable_error( $error_no, $flags ) {
568
+ $result = true;
569
+
570
+ if ( null !== $flags ) {
571
+ $result = (bool) ( $error_no & $flags );
572
  }
573
 
574
+ return $result;
575
  }
576
 
577
  /**
578
  * For testing purposes only. Sets the errors property manually.
579
  * Needed to test the filter since the data property is protected.
580
  *
581
+ * @param array<string, mixed> $errors The list of errors
582
+ * @return void
583
  */
584
  public function set_php_errors( $errors ) {
585
  $this->data['errors'] = $errors;
collectors/raw_request.php CHANGED
@@ -1,6 +1,8 @@
1
  <?php
2
 
3
- defined( 'ABSPATH' ) || exit;
 
 
4
 
5
  class QM_Collector_Raw_Request extends QM_Collector {
6
 
@@ -11,8 +13,8 @@ class QM_Collector_Raw_Request extends QM_Collector {
11
  *
12
  * From WP_REST_Server::get_headers()
13
  *
14
- * @param array $server Associative array similar to `$_SERVER`.
15
- * @return array Headers extracted from the input.
16
  */
17
  protected function get_headers( array $server ) {
18
  $headers = array();
@@ -20,8 +22,8 @@ class QM_Collector_Raw_Request extends QM_Collector {
20
  // CONTENT_* headers are not prefixed with HTTP_.
21
  $additional = array(
22
  'CONTENT_LENGTH' => true,
23
- 'CONTENT_MD5' => true,
24
- 'CONTENT_TYPE' => true,
25
  );
26
 
27
  foreach ( $server as $key => $value ) {
@@ -37,15 +39,17 @@ class QM_Collector_Raw_Request extends QM_Collector {
37
 
38
  /**
39
  * Process request and response data.
 
 
40
  */
41
  public function process() {
42
  $request = array(
43
- 'ip' => $_SERVER['REMOTE_ADDR'],
44
- 'method' => strtoupper( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ),
45
- 'scheme' => is_ssl() ? 'https' : 'http',
46
- 'host' => wp_unslash( $_SERVER['HTTP_HOST'] ),
47
- 'path' => isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '/',
48
- 'query' => isset( $_SERVER['QUERY_STRING'] ) ? wp_unslash( $_SERVER['QUERY_STRING'] ) : '',
49
  'headers' => $this->get_headers( wp_unslash( $_SERVER ) ),
50
  );
51
 
@@ -65,15 +69,18 @@ class QM_Collector_Raw_Request extends QM_Collector {
65
  ksort( $headers );
66
 
67
  $response = array(
68
- 'status' => self::http_response_code(),
69
  'headers' => $headers,
70
  );
71
 
72
  $this->data['response'] = $response;
73
  }
74
 
 
 
 
75
  public static function http_response_code() {
76
- if ( is_callable( 'http_response_code' ) ) {
77
  // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.http_response_codeFound
78
  return http_response_code();
79
  }
@@ -82,6 +89,11 @@ class QM_Collector_Raw_Request extends QM_Collector {
82
  }
83
  }
84
 
 
 
 
 
 
85
  function register_qm_collector_raw_request( array $collectors, QueryMonitor $qm ) {
86
  $collectors['raw_request'] = new QM_Collector_Raw_Request();
87
  return $collectors;
1
  <?php
2
 
3
+ if ( ! defined( 'ABSPATH' ) ) {
4
+ exit;
5
+ }
6
 
7
  class QM_Collector_Raw_Request extends QM_Collector {
8
 
13
  *
14
  * From WP_REST_Server::get_headers()
15
  *
16
+ * @param array<string, string> $server Associative array similar to `$_SERVER`.
17
+ * @return array<string, string> Headers extracted from the input.
18
  */
19
  protected function get_headers( array $server ) {
20
  $headers = array();
22
  // CONTENT_* headers are not prefixed with HTTP_.
23
  $additional = array(
24
  'CONTENT_LENGTH' => true,
25
+ 'CONTENT_MD5' => true,
26
+ 'CONTENT_TYPE' => true,
27
  );
28
 
29
  foreach ( $server as $key => $value ) {
39
 
40
  /**
41
  * Process request and response data.
42
+ *
43
+ * @return void
44
  */
45
  public function process() {
46
  $request = array(
47
+ 'ip' => $_SERVER['REMOTE_ADDR'],
48
+ 'method' => strtoupper( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ),
49
+ 'scheme' => is_ssl() ? 'https' : 'http',
50
+ 'host' => wp_unslash( $_SERVER['HTTP_HOST'] ),
51
+ 'path' => isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '/',
52
+ 'query' => isset( $_SERVER['QUERY_STRING'] ) ? wp_unslash( $_SERVER['QUERY_STRING'] ) : '',
53
  'headers' => $this->get_headers( wp_unslash( $_SERVER ) ),
54
  );
55
 
69
  ksort( $headers );
70
 
71
  $response = array(
72
+ 'status' => self::http_response_code(),
73
  'headers' => $headers,
74
  );
75
 
76
  $this->data['response'] = $response;
77
  }
78
 
79
+ /**
80
+ * @return int|bool|null
81
+ */
82
  public static function http_response_code() {
83
+ if ( function_exists( 'http_response_code' ) ) {
84
  // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.http_response_codeFound
85
  return http_response_code();
86
  }
89
  }
90
  }
91
 
92
+ /**
93
+ * @param array<string, QM_Collector> $collectors
94
+ * @param QueryMonitor $qm
95
+ * @return array<string, QM_Collector>
96
+ */
97
  function register_qm_collector_raw_request( array $collectors, QueryMonitor $qm ) {
98
  $collectors['raw_request'] = new QM_Collector_Raw_Request();
99
  return $collectors;
collectors/redirects.php CHANGED
@@ -5,28 +5,54 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Collector_Redirects extends QM_Collector {
11
 
12
  public $id = 'redirects';
13
 
14
- public function __construct() {
15
- parent::__construct();
 
 
 
16
  add_filter( 'wp_redirect', array( $this, 'filter_wp_redirect' ), 9999, 2 );
17
  }
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  public function filter_wp_redirect( $location, $status ) {
20
 
21
  if ( ! $location ) {
22
  return $location;
23
  }
24
 
25
- $trace = new QM_Backtrace();
 
 
 
 
 
 
 
26
 
27
- $this->data['trace'] = $trace;
28
  $this->data['location'] = $location;
29
- $this->data['status'] = $status;
30
 
31
  return $location;
32
 
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Collector_Redirects extends QM_Collector {
13
 
14
  public $id = 'redirects';
15
 
16
+ /**
17
+ * @return void
18
+ */
19
+ public function set_up() {
20
+ parent::set_up();
21
  add_filter( 'wp_redirect', array( $this, 'filter_wp_redirect' ), 9999, 2 );
22
  }
23
 
24
+ /**
25
+ * @return void
26
+ */
27
+ public function tear_down() {
28
+ remove_filter( 'wp_redirect', array( $this, 'filter_wp_redirect' ), 9999 );
29
+
30
+ parent::tear_down();
31
+ }
32
+
33
+ /**
34
+ * @param string $location
35
+ * @param int $status
36
+ * @return string
37
+ */
38
  public function filter_wp_redirect( $location, $status ) {
39
 
40
  if ( ! $location ) {
41
  return $location;
42
  }
43
 
44
+ $trace = new QM_Backtrace( array(
45
+ 'ignore_hook' => array(
46
+ current_filter() => true,
47
+ ),
48
+ 'ignore_func' => array(
49
+ 'wp_redirect' => true,
50
+ ),
51
+ ) );
52
 
53
+ $this->data['trace'] = $trace;
54
  $this->data['location'] = $location;
55
+ $this->data['status'] = $status;
56
 
57
  return $location;
58
 
collectors/request.php CHANGED
@@ -5,12 +5,17 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Collector_Request extends QM_Collector {
11
 
12
  public $id = 'request';
13
 
 
 
 
14
  public function get_concerned_actions() {
15
  return array(
16
  # Rewrites
@@ -27,6 +32,9 @@ class QM_Collector_Request extends QM_Collector {
27
  );
28
  }
29
 
 
 
 
30
  public function get_concerned_filters() {
31
  global $wp_rewrite;
32
 
@@ -97,6 +105,9 @@ class QM_Collector_Request extends QM_Collector {
97
  return $filters;
98
  }
99
 
 
 
 
100
  public function get_concerned_options() {
101
  return array(
102
  'home',
@@ -106,6 +117,9 @@ class QM_Collector_Request extends QM_Collector {
106
  );
107
  }
108
 
 
 
 
109
  public function get_concerned_constants() {
110
  return array(
111
  'WP_HOME',
@@ -113,11 +127,14 @@ class QM_Collector_Request extends QM_Collector {
113
  );
114
  }
115
 
 
 
 
116
  public function process() {
117
 
118
  global $wp, $wp_query, $current_blog, $current_site, $wp_rewrite;
119
 
120
- $qo = get_queried_object();
121
  $user = wp_get_current_user();
122
 
123
  if ( $user->exists() ) {
@@ -133,7 +150,7 @@ class QM_Collector_Request extends QM_Collector {
133
 
134
  $this->data['user'] = array(
135
  'title' => $user_title,
136
- 'data' => ( $user->exists() ? $user : false ),
137
  );
138
 
139
  if ( is_multisite() ) {
@@ -143,7 +160,7 @@ class QM_Collector_Request extends QM_Collector {
143
  __( 'Current Site: #%d', 'query-monitor' ),
144
  $current_blog->blog_id
145
  ),
146
- 'data' => $current_blog,
147
  );
148
  }
149
 
@@ -154,14 +171,14 @@ class QM_Collector_Request extends QM_Collector {
154
  __( 'Current Network: #%d', 'query-monitor' ),
155
  $current_site->id
156
  ),
157
- 'data' => $current_site,
158
  );
159
  }
160
 
161
  if ( is_admin() ) {
162
  if ( isset( $_SERVER['REQUEST_URI'] ) ) {
163
  $home_path = trim( parse_url( home_url(), PHP_URL_PATH ), '/' );
164
- $request = wp_unslash( $_SERVER['REQUEST_URI'] ); // phpcs:ignore
165
 
166
  $this->data['request']['request'] = str_replace( "/{$home_path}/", '', $request );
167
  } else {
@@ -178,8 +195,8 @@ class QM_Collector_Request extends QM_Collector {
178
 
179
  /** This filter is documented in wp-includes/class-wp.php */
180
  $plugin_qvars = array_flip( apply_filters( 'query_vars', array() ) );
181
- $qvars = $wp_query->query_vars;
182
- $query_vars = array();
183
 
184
  foreach ( $qvars as $k => $v ) {
185
  if ( isset( $plugin_qvars[ $k ] ) ) {
@@ -198,7 +215,7 @@ class QM_Collector_Request extends QM_Collector {
198
  # First add plugin vars to $this->data['qvars']:
199
  foreach ( $query_vars as $k => $v ) {
200
  if ( isset( $plugin_qvars[ $k ] ) ) {
201
- $this->data['qvars'][ $k ] = $v;
202
  $this->data['plugin_qvars'][ $k ] = $v;
203
  }
204
  }
@@ -289,6 +306,11 @@ class QM_Collector_Request extends QM_Collector {
289
 
290
  }
291
 
 
 
 
 
 
292
  function register_qm_collector_request( array $collectors, QueryMonitor $qm ) {
293
  $collectors['request'] = new QM_Collector_Request();
294
  return $collectors;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Collector_Request extends QM_Collector {
13
 
14
  public $id = 'request';
15
 
16
+ /**
17
+ * @return array<int, string>
18
+ */
19
  public function get_concerned_actions() {
20
  return array(
21
  # Rewrites
32
  );
33
  }
34
 
35
+ /**
36
+ * @return array<int, string>
37
+ */
38
  public function get_concerned_filters() {
39
  global $wp_rewrite;
40
 
105
  return $filters;
106
  }
107
 
108
+ /**
109
+ * @return array<int, string>
110
+ */
111
  public function get_concerned_options() {
112
  return array(
113
  'home',
117
  );
118
  }
119
 
120
+ /**
121
+ * @return array<int, string>
122
+ */
123
  public function get_concerned_constants() {
124
  return array(
125
  'WP_HOME',
127
  );
128
  }
129
 
130
+ /**
131
+ * @return void
132
+ */
133
  public function process() {
134
 
135
  global $wp, $wp_query, $current_blog, $current_site, $wp_rewrite;
136
 
137
+ $qo = get_queried_object();
138
  $user = wp_get_current_user();
139
 
140
  if ( $user->exists() ) {
150
 
151
  $this->data['user'] = array(
152
  'title' => $user_title,
153
+ 'data' => ( $user->exists() ? $user : false ),
154
  );
155
 
156
  if ( is_multisite() ) {
160
  __( 'Current Site: #%d', 'query-monitor' ),
161
  $current_blog->blog_id
162
  ),
163
+ 'data' => $current_blog,
164
  );
165
  }
166
 
171
  __( 'Current Network: #%d', 'query-monitor' ),
172
  $current_site->id
173
  ),
174
+ 'data' => $current_site,
175
  );
176
  }
177
 
178
  if ( is_admin() ) {
179
  if ( isset( $_SERVER['REQUEST_URI'] ) ) {
180
  $home_path = trim( parse_url( home_url(), PHP_URL_PATH ), '/' );
181
+ $request = wp_unslash( $_SERVER['REQUEST_URI'] ); // phpcs:ignore
182
 
183
  $this->data['request']['request'] = str_replace( "/{$home_path}/", '', $request );
184
  } else {
195
 
196
  /** This filter is documented in wp-includes/class-wp.php */
197
  $plugin_qvars = array_flip( apply_filters( 'query_vars', array() ) );
198
+ $qvars = $wp_query->query_vars;
199
+ $query_vars = array();
200
 
201
  foreach ( $qvars as $k => $v ) {
202
  if ( isset( $plugin_qvars[ $k ] ) ) {
215
  # First add plugin vars to $this->data['qvars']:
216
  foreach ( $query_vars as $k => $v ) {
217
  if ( isset( $plugin_qvars[ $k ] ) ) {
218
+ $this->data['qvars'][ $k ] = $v;
219
  $this->data['plugin_qvars'][ $k ] = $v;
220
  }
221
  }
306
 
307
  }
308
 
309
+ /**
310
+ * @param array<string, QM_Collector> $collectors
311
+ * @param QueryMonitor $qm
312
+ * @return array<string, QM_Collector>
313
+ */
314
  function register_qm_collector_request( array $collectors, QueryMonitor $qm ) {
315
  $collectors['request'] = new QM_Collector_Request();
316
  return $collectors;
collectors/theme.php CHANGED
@@ -5,28 +5,64 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Collector_Theme extends QM_Collector {
11
 
12
- public $id = 'response';
 
 
 
 
 
 
 
13
  protected $got_theme_compat = false;
14
- protected $query_templates = array();
15
 
16
- public function __construct() {
17
- parent::__construct();
18
- add_filter( 'body_class', array( $this, 'filter_body_class' ), 9999 );
19
- add_filter( 'timber/output', array( $this, 'filter_timber_output' ), 9999, 3 );
 
 
 
 
20
  add_action( 'template_redirect', array( $this, 'action_template_redirect' ) );
21
  add_action( 'get_template_part', array( $this, 'action_get_template_part' ), 10, 3 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  }
23
 
 
 
 
24
  public function get_concerned_actions() {
25
  return array(
26
  'template_redirect',
27
  );
28
  }
29
 
 
 
 
30
  public function get_concerned_filters() {
31
  $filters = array(
32
  'stylesheet',
@@ -36,16 +72,17 @@ class QM_Collector_Theme extends QM_Collector {
36
  'template_include',
37
  );
38
 
39
- foreach ( self::get_query_template_names() as $template => $conditional ) {
40
- // @TODO this isn't correct for post type archives
41
- $filter = str_replace( '_', '', $template );
42
- $filters[] = "{$filter}_template_hierarchy";
43
- $filters[] = "{$filter}_template";
44
  }
45
 
46
  return $filters;
47
  }
48
 
 
 
 
49
  public function get_concerned_options() {
50
  return array(
51
  'stylesheet',
@@ -53,41 +90,76 @@ class QM_Collector_Theme extends QM_Collector {
53
  );
54
  }
55
 
 
 
 
56
  public static function get_query_template_names() {
57
  $names = array();
58
 
59
- $names['embed'] = 'is_embed';
60
- $names['404'] = 'is_404';
61
- $names['search'] = 'is_search';
62
- $names['front_page'] = 'is_front_page';
63
- $names['home'] = 'is_home';
64
 
65
  if ( function_exists( 'is_privacy_policy' ) ) {
66
  $names['privacy_policy'] = 'is_privacy_policy';
67
  }
68
 
69
  $names['post_type_archive'] = 'is_post_type_archive';
70
- $names['taxonomy'] = 'is_tax';
71
- $names['attachment'] = 'is_attachment';
72
- $names['single'] = 'is_single';
73
- $names['page'] = 'is_page';
74
- $names['singular'] = 'is_singular';
75
- $names['category'] = 'is_category';
76
- $names['tag'] = 'is_tag';
77
- $names['author'] = 'is_author';
78
- $names['date'] = 'is_date';
79
- $names['archive'] = 'is_archive';
80
- $names['index'] = '__return_true';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
  return $names;
83
  }
84
 
85
- // https://core.trac.wordpress.org/ticket/14310
 
 
86
  public function action_template_redirect() {
87
  add_filter( 'template_include', array( $this, 'filter_template_include' ), PHP_INT_MAX );
88
 
89
  foreach ( self::get_query_template_names() as $template => $conditional ) {
90
-
91
  // If a matching theme-compat file is found, further conditional checks won't occur in template-loader.php
92
  if ( $this->got_theme_compat ) {
93
  break;
@@ -98,17 +170,10 @@ class QM_Collector_Theme extends QM_Collector {
98
  if ( function_exists( $conditional ) && function_exists( $get_template ) && call_user_func( $conditional ) ) {
99
  $filter = str_replace( '_', '', $template );
100
  add_filter( "{$filter}_template_hierarchy", array( $this, 'filter_template_hierarchy' ), PHP_INT_MAX );
101
- $loaded_template = call_user_func( $get_template );
102
- $default_template = locate_template( $this->query_templates );
103
-
104
- if ( $default_template !== $loaded_template ) {
105
- $this->data['template_altered'] = true;
106
- }
107
-
108
  remove_filter( "{$filter}_template_hierarchy", array( $this, 'filter_template_hierarchy' ), PHP_INT_MAX );
109
  }
110
  }
111
-
112
  }
113
 
114
  /**
@@ -117,20 +182,78 @@ class QM_Collector_Theme extends QM_Collector {
117
  * @param string $slug The slug name for the generic template.
118
  * @param string $name The name of the specialized template.
119
  * @param string[] $templates Array of template files to search for, in order.
 
120
  */
121
  public function action_get_template_part( $slug, $name, $templates ) {
122
  $data = compact( 'slug', 'name', 'templates' );
123
 
124
- $data['trace'] = new QM_Backtrace( array(
125
- 'ignore_frames' => 4,
 
 
126
  ) );
127
 
 
 
128
  $this->data['requested_template_parts'][] = $data;
129
  }
130
 
131
- public function filter_template_hierarchy( array $templates ) {
132
- $this->query_templates = $templates;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  if ( ! isset( $this->data['template_hierarchy'] ) ) {
135
  $this->data['template_hierarchy'] = array();
136
  }
@@ -147,16 +270,30 @@ class QM_Collector_Theme extends QM_Collector {
147
  return $templates;
148
  }
149
 
 
 
 
 
150
  public function filter_body_class( array $class ) {
151
  $this->data['body_class'] = $class;
152
  return $class;
153
  }
154
 
 
 
 
 
155
  public function filter_template_include( $template_path ) {
156
  $this->data['template_path'] = $template_path;
157
  return $template_path;
158
  }
159
 
 
 
 
 
 
 
160
  public function filter_timber_output( $output, $data = null, $file = null ) {
161
  if ( $file ) {
162
  $this->data['timber_files'][] = $file;
@@ -165,11 +302,14 @@ class QM_Collector_Theme extends QM_Collector {
165
  return $output;
166
  }
167
 
 
 
 
168
  public function process() {
169
 
170
  $stylesheet_directory = QM_Util::standard_dir( get_stylesheet_directory() );
171
- $template_directory = QM_Util::standard_dir( get_template_directory() );
172
- $theme_directory = QM_Util::standard_dir( get_theme_root() );
173
 
174
  if ( isset( $this->data['template_hierarchy'] ) ) {
175
  $this->data['template_hierarchy'] = array_unique( $this->data['template_hierarchy'] );
@@ -180,16 +320,13 @@ class QM_Collector_Theme extends QM_Collector {
180
  if ( $this->data['has_template_part_action'] ) {
181
  // Since WP 5.2, the `get_template_part` action populates this data nicely:
182
  if ( ! empty( $this->data['requested_template_parts'] ) ) {
183
- $this->data['template_parts'] = array();
184
  $this->data['theme_template_parts'] = array();
185
  $this->data['count_template_parts'] = array();
186
 
187
  foreach ( $this->data['requested_template_parts'] as $part ) {
188
  $file = locate_template( $part['templates'] );
189
 
190
- $part['caller'] = $part['trace']->get_caller();
191
- unset( $part['trace'] );
192
-
193
  if ( ! $file ) {
194
  $this->data['unsuccessful_template_parts'][] = $part;
195
  continue;
@@ -209,11 +346,10 @@ class QM_Collector_Theme extends QM_Collector {
209
  $template_directory,
210
  ), '', $file );
211
 
212
- $slug = trim( str_replace( '.php', '', $filename ), '/' );
213
- $display = trim( $filename, '/' );
214
  $theme_display = trim( str_replace( $theme_directory, '', $file ), '/' );
215
 
216
- $this->data['template_parts'][ $file ] = $display;
217
  $this->data['theme_template_parts'][ $file ] = $theme_display;
218
  }
219
  }
@@ -227,19 +363,19 @@ class QM_Collector_Theme extends QM_Collector {
227
  $template_directory,
228
  ), '', $file );
229
  if ( $filename !== $file ) {
230
- $slug = trim( str_replace( '.php', '', $filename ), '/' );
231
- $display = trim( $filename, '/' );
232
  $theme_display = trim( str_replace( $theme_directory, '', $file ), '/' );
233
- $count = did_action( "get_template_part_{$slug}" );
234
  if ( $count ) {
235
- $this->data['template_parts'][ $file ] = $display;
236
  $this->data['theme_template_parts'][ $file ] = $theme_display;
237
  $this->data['count_template_parts'][ $file ] = $count;
238
  } else {
239
- $slug = trim( preg_replace( '|\-[^\-]+$|', '', $slug ), '/' );
240
  $count = did_action( "get_template_part_{$slug}" );
241
  if ( $count ) {
242
- $this->data['template_parts'][ $file ] = $display;
243
  $this->data['theme_template_parts'][ $file ] = $theme_display;
244
  $this->data['count_template_parts'][ $file ] = $count;
245
  }
@@ -248,20 +384,71 @@ class QM_Collector_Theme extends QM_Collector {
248
  }
249
  }
250
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  if ( ! empty( $this->data['template_path'] ) ) {
252
- $template_path = QM_Util::standard_dir( $this->data['template_path'] );
253
- $template_file = str_replace( array( $stylesheet_directory, $template_directory, ABSPATH ), '', $template_path );
254
- $template_file = ltrim( $template_file, '/' );
255
  $theme_template_file = str_replace( array( $theme_directory, ABSPATH ), '', $template_path );
256
  $theme_template_file = ltrim( $theme_template_file, '/' );
257
 
258
- $this->data['template_path'] = $template_path;
259
- $this->data['template_file'] = $template_file;
260
  $this->data['theme_template_file'] = $theme_template_file;
261
  }
262
 
263
- $this->data['stylesheet'] = get_stylesheet();
264
- $this->data['template'] = get_template();
265
  $this->data['is_child_theme'] = ( $this->data['stylesheet'] !== $this->data['template'] );
266
 
267
  if ( isset( $this->data['body_class'] ) ) {
@@ -272,6 +459,11 @@ class QM_Collector_Theme extends QM_Collector {
272
 
273
  }
274
 
 
 
 
 
 
275
  function register_qm_collector_theme( array $collectors, QueryMonitor $qm ) {
276
  $collectors['response'] = new QM_Collector_Theme();
277
  return $collectors;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Collector_Theme extends QM_Collector {
13
 
14
+ /**
15
+ * @var string
16
+ */
17
+ public $id = 'response';
18
+
19
+ /**
20
+ * @var bool
21
+ */
22
  protected $got_theme_compat = false;
 
23
 
24
+ /**
25
+ * @return void
26
+ */
27
+ public function set_up() {
28
+ parent::set_up();
29
+
30
+ add_filter( 'body_class', array( $this, 'filter_body_class' ), 9999 );
31
+ add_filter( 'timber/output', array( $this, 'filter_timber_output' ), 9999, 3 );
32
  add_action( 'template_redirect', array( $this, 'action_template_redirect' ) );
33
  add_action( 'get_template_part', array( $this, 'action_get_template_part' ), 10, 3 );
34
+ add_action( 'render_block_core_template_part_post', array( $this, 'action_render_block_core_template_part_post' ), 10, 3 );
35
+ add_action( 'render_block_core_template_part_file', array( $this, 'action_render_block_core_template_part_file' ), 10, 3 );
36
+ add_action( 'render_block_core_template_part_none', array( $this, 'action_render_block_core_template_part_none' ), 10, 3 );
37
+ }
38
+
39
+ /**
40
+ * @return void
41
+ */
42
+ public function tear_down() {
43
+ remove_filter( 'body_class', array( $this, 'filter_body_class' ), 9999 );
44
+ remove_filter( 'timber/output', array( $this, 'filter_timber_output' ), 9999 );
45
+ remove_action( 'template_redirect', array( $this, 'action_template_redirect' ) );
46
+ remove_action( 'get_template_part', array( $this, 'action_get_template_part' ), 10 );
47
+ remove_action( 'render_block_core_template_part_post', array( $this, 'action_render_block_core_template_part_post' ), 10 );
48
+ remove_action( 'render_block_core_template_part_file', array( $this, 'action_render_block_core_template_part_file' ), 10 );
49
+ remove_action( 'render_block_core_template_part_none', array( $this, 'action_render_block_core_template_part_none' ), 10 );
50
+
51
+ parent::tear_down();
52
  }
53
 
54
+ /**
55
+ * @return array<int, string>
56
+ */
57
  public function get_concerned_actions() {
58
  return array(
59
  'template_redirect',
60
  );
61
  }
62
 
63
+ /**
64
+ * @return array<int, string>
65
+ */
66
  public function get_concerned_filters() {
67
  $filters = array(
68
  'stylesheet',
72
  'template_include',
73
  );
74
 
75
+ foreach ( self::get_query_filter_names() as $filter ) {
76
+ $filters[] = $filter;
77
+ $filters[] = "{$filter}_hierarchy";
 
 
78
  }
79
 
80
  return $filters;
81
  }
82
 
83
+ /**
84
+ * @return array<int, string>
85
+ */
86
  public function get_concerned_options() {
87
  return array(
88
  'stylesheet',
90
  );
91
  }
92
 
93
+ /**
94
+ * @return string[]
95
+ */
96
  public static function get_query_template_names() {
97
  $names = array();
98
 
99
+ $names['embed'] = 'is_embed';
100
+ $names['404'] = 'is_404';
101
+ $names['search'] = 'is_search';
102
+ $names['front_page'] = 'is_front_page';
103
+ $names['home'] = 'is_home';
104
 
105
  if ( function_exists( 'is_privacy_policy' ) ) {
106
  $names['privacy_policy'] = 'is_privacy_policy';
107
  }
108
 
109
  $names['post_type_archive'] = 'is_post_type_archive';
110
+ $names['taxonomy'] = 'is_tax';
111
+ $names['attachment'] = 'is_attachment';
112
+ $names['single'] = 'is_single';
113
+ $names['page'] = 'is_page';
114
+ $names['singular'] = 'is_singular';
115
+ $names['category'] = 'is_category';
116
+ $names['tag'] = 'is_tag';
117
+ $names['author'] = 'is_author';
118
+ $names['date'] = 'is_date';
119
+ $names['archive'] = 'is_archive';
120
+ $names['index'] = '__return_true';
121
+
122
+ return $names;
123
+ }
124
+
125
+ /**
126
+ * @return string[]
127
+ */
128
+ public static function get_query_filter_names() {
129
+ $names = array();
130
+
131
+ $names['embed'] = 'embed_template';
132
+ $names['404'] = '404_template';
133
+ $names['search'] = 'search_template';
134
+ $names['front_page'] = 'frontpage_template';
135
+ $names['home'] = 'home_template';
136
+
137
+ if ( function_exists( 'is_privacy_policy' ) ) {
138
+ $names['privacy_policy'] = 'privacypolicy_template';
139
+ }
140
+
141
+ $names['taxonomy'] = 'taxonomy_template';
142
+ $names['attachment'] = 'attachment_template';
143
+ $names['single'] = 'single_template';
144
+ $names['page'] = 'page_template';
145
+ $names['singular'] = 'singular_template';
146
+ $names['category'] = 'category_template';
147
+ $names['tag'] = 'tag_template';
148
+ $names['author'] = 'author_template';
149
+ $names['date'] = 'date_template';
150
+ $names['archive'] = 'archive_template';
151
+ $names['index'] = 'index_template';
152
 
153
  return $names;
154
  }
155
 
156
+ /**
157
+ * @return void
158
+ */
159
  public function action_template_redirect() {
160
  add_filter( 'template_include', array( $this, 'filter_template_include' ), PHP_INT_MAX );
161
 
162
  foreach ( self::get_query_template_names() as $template => $conditional ) {
 
163
  // If a matching theme-compat file is found, further conditional checks won't occur in template-loader.php
164
  if ( $this->got_theme_compat ) {
165
  break;
170
  if ( function_exists( $conditional ) && function_exists( $get_template ) && call_user_func( $conditional ) ) {
171
  $filter = str_replace( '_', '', $template );
172
  add_filter( "{$filter}_template_hierarchy", array( $this, 'filter_template_hierarchy' ), PHP_INT_MAX );
173
+ call_user_func( $get_template );
 
 
 
 
 
 
174
  remove_filter( "{$filter}_template_hierarchy", array( $this, 'filter_template_hierarchy' ), PHP_INT_MAX );
175
  }
176
  }
 
177
  }
178
 
179
  /**
182
  * @param string $slug The slug name for the generic template.
183
  * @param string $name The name of the specialized template.
184
  * @param string[] $templates Array of template files to search for, in order.
185
+ * @return void
186
  */
187
  public function action_get_template_part( $slug, $name, $templates ) {
188
  $data = compact( 'slug', 'name', 'templates' );
189
 
190
+ $trace = new QM_Backtrace( array(
191
+ 'ignore_hook' => array(
192
+ current_filter() => true,
193
+ ),
194
  ) );
195
 
196
+ $data['caller'] = $trace->get_caller();
197
+
198
  $this->data['requested_template_parts'][] = $data;
199
  }
200
 
201
+ /**
202
+ * Fires when a post is loaded for a template part block.
203
+ *
204
+ * @param string $template_part_id
205
+ * @param mixed[] $attributes
206
+ * @param WP_Post $post
207
+ * @return void
208
+ */
209
+ public function action_render_block_core_template_part_post( $template_part_id, $attributes, WP_Post $post ) {
210
+ $data = array(
211
+ 'id' => $template_part_id,
212
+ 'attributes' => $attributes,
213
+ 'post' => $post->ID,
214
+ );
215
+ $this->data['requested_template_part_posts'][] = $data;
216
+ }
217
 
218
+ /**
219
+ * Fires when a file is loaded for a template part block.
220
+ *
221
+ * @param string $template_part_id
222
+ * @param mixed[] $attributes
223
+ * @param string $template_part_file_path
224
+ * @return void
225
+ */
226
+ public function action_render_block_core_template_part_file( $template_part_id, $attributes, $template_part_file_path ) {
227
+ $data = array(
228
+ 'id' => $template_part_id,
229
+ 'attributes' => $attributes,
230
+ 'path' => $template_part_file_path,
231
+ );
232
+ $this->data['requested_template_part_files'][] = $data;
233
+ }
234
+
235
+ /**
236
+ * Fires when neither a post nor file is found for a template part block.
237
+ *
238
+ * @param string $template_part_id
239
+ * @param mixed[] $attributes
240
+ * @param string $template_part_file_path
241
+ * @return void
242
+ */
243
+ public function action_render_block_core_template_part_none( $template_part_id, $attributes, $template_part_file_path ) {
244
+ $data = array(
245
+ 'id' => $template_part_id,
246
+ 'attributes' => $attributes,
247
+ 'path' => $template_part_file_path,
248
+ );
249
+ $this->data['requested_template_part_nopes'][] = $data;
250
+ }
251
+
252
+ /**
253
+ * @param array<int, string> $templates
254
+ * @return array<int, string>
255
+ */
256
+ public function filter_template_hierarchy( array $templates ) {
257
  if ( ! isset( $this->data['template_hierarchy'] ) ) {
258
  $this->data['template_hierarchy'] = array();
259
  }
270
  return $templates;
271
  }
272
 
273
+ /**
274
+ * @param array<int, string> $class
275
+ * @return array<int, string>
276
+ */
277
  public function filter_body_class( array $class ) {
278
  $this->data['body_class'] = $class;
279
  return $class;
280
  }
281
 
282
+ /**
283
+ * @param array<int, string> $template_path
284
+ * @return array<int, string>
285
+ */
286
  public function filter_template_include( $template_path ) {
287
  $this->data['template_path'] = $template_path;
288
  return $template_path;
289
  }
290
 
291
+ /**
292
+ * @param mixed[] $output
293
+ * @param mixed $data
294
+ * @param string $file
295
+ * @return mixed[]
296
+ */
297
  public function filter_timber_output( $output, $data = null, $file = null ) {
298
  if ( $file ) {
299
  $this->data['timber_files'][] = $file;
302
  return $output;
303
  }
304
 
305
+ /**
306
+ * @return void
307
+ */
308
  public function process() {
309
 
310
  $stylesheet_directory = QM_Util::standard_dir( get_stylesheet_directory() );
311
+ $template_directory = QM_Util::standard_dir( get_template_directory() );
312
+ $theme_directory = QM_Util::standard_dir( get_theme_root() );
313
 
314
  if ( isset( $this->data['template_hierarchy'] ) ) {
315
  $this->data['template_hierarchy'] = array_unique( $this->data['template_hierarchy'] );
320
  if ( $this->data['has_template_part_action'] ) {
321
  // Since WP 5.2, the `get_template_part` action populates this data nicely:
322
  if ( ! empty( $this->data['requested_template_parts'] ) ) {
323
+ $this->data['template_parts'] = array();
324
  $this->data['theme_template_parts'] = array();
325
  $this->data['count_template_parts'] = array();
326
 
327
  foreach ( $this->data['requested_template_parts'] as $part ) {
328
  $file = locate_template( $part['templates'] );
329
 
 
 
 
330
  if ( ! $file ) {
331
  $this->data['unsuccessful_template_parts'][] = $part;
332
  continue;
346
  $template_directory,
347
  ), '', $file );
348
 
349
+ $display = trim( $filename, '/' );
 
350
  $theme_display = trim( str_replace( $theme_directory, '', $file ), '/' );
351
 
352
+ $this->data['template_parts'][ $file ] = $display;
353
  $this->data['theme_template_parts'][ $file ] = $theme_display;
354
  }
355
  }
363
  $template_directory,
364
  ), '', $file );
365
  if ( $filename !== $file ) {
366
+ $slug = trim( str_replace( '.php', '', $filename ), '/' );
367
+ $display = trim( $filename, '/' );
368
  $theme_display = trim( str_replace( $theme_directory, '', $file ), '/' );
369
+ $count = did_action( "get_template_part_{$slug}" );
370
  if ( $count ) {
371
+ $this->data['template_parts'][ $file ] = $display;
372
  $this->data['theme_template_parts'][ $file ] = $theme_display;
373
  $this->data['count_template_parts'][ $file ] = $count;
374
  } else {
375
+ $slug = trim( preg_replace( '|\-[^\-]+$|', '', $slug ), '/' );
376
  $count = did_action( "get_template_part_{$slug}" );
377
  if ( $count ) {
378
+ $this->data['template_parts'][ $file ] = $display;
379
  $this->data['theme_template_parts'][ $file ] = $theme_display;
380
  $this->data['count_template_parts'][ $file ] = $count;
381
  }
384
  }
385
  }
386
 
387
+ if (
388
+ ! empty( $this->data['requested_template_part_posts'] ) ||
389
+ ! empty( $this->data['requested_template_part_files'] ) ||
390
+ ! empty( $this->data['requested_template_part_nopes'] )
391
+ ) {
392
+ $this->data['template_parts'] = array();
393
+ $this->data['theme_template_parts'] = array();
394
+ $this->data['count_template_parts'] = array();
395
+
396
+ $posts = ! empty( $this->data['requested_template_part_posts'] ) ? $this->data['requested_template_part_posts'] : array();
397
+ $files = ! empty( $this->data['requested_template_part_files'] ) ? $this->data['requested_template_part_files'] : array();
398
+ $nopes = ! empty( $this->data['requested_template_part_nopes'] ) ? $this->data['requested_template_part_nopes'] : array();
399
+
400
+ $this->data['has_template_part_action'] = true;
401
+
402
+ $all = array_merge( $posts, $files, $nopes );
403
+
404
+ foreach ( $all as $part ) {
405
+ $file = isset( $part['path'] ) ? $part['path'] : $part['post'];
406
+
407
+ if ( isset( $this->data['count_template_parts'][ $file ] ) ) {
408
+ $this->data['count_template_parts'][ $file ]++;
409
+ continue;
410
+ }
411
+
412
+ $this->data['count_template_parts'][ $file ] = 1;
413
+
414
+ if ( isset( $part['post'] ) ) {
415
+ $display = sprintf(
416
+ '%1$s (post ID %2$d)',
417
+ $part['id'],
418
+ $part['post']
419
+ );
420
+ $theme_display = $display;
421
+ } else {
422
+ $file = QM_Util::standard_dir( $file );
423
+
424
+ $filename = str_replace( array(
425
+ $stylesheet_directory,
426
+ $template_directory,
427
+ ), '', $file );
428
+
429
+ $display = trim( $filename, '/' );
430
+ $theme_display = trim( str_replace( $theme_directory, '', $file ), '/' );
431
+ }
432
+
433
+ $this->data['template_parts'][ $file ] = $display;
434
+ $this->data['theme_template_parts'][ $file ] = $theme_display;
435
+ }
436
+ }
437
+
438
  if ( ! empty( $this->data['template_path'] ) ) {
439
+ $template_path = QM_Util::standard_dir( $this->data['template_path'] );
440
+ $template_file = str_replace( array( $stylesheet_directory, $template_directory, ABSPATH ), '', $template_path );
441
+ $template_file = ltrim( $template_file, '/' );
442
  $theme_template_file = str_replace( array( $theme_directory, ABSPATH ), '', $template_path );
443
  $theme_template_file = ltrim( $theme_template_file, '/' );
444
 
445
+ $this->data['template_path'] = $template_path;
446
+ $this->data['template_file'] = $template_file;
447
  $this->data['theme_template_file'] = $theme_template_file;
448
  }
449
 
450
+ $this->data['stylesheet'] = get_stylesheet();
451
+ $this->data['template'] = get_template();
452
  $this->data['is_child_theme'] = ( $this->data['stylesheet'] !== $this->data['template'] );
453
 
454
  if ( isset( $this->data['body_class'] ) ) {
459
 
460
  }
461
 
462
+ /**
463
+ * @param array<string, QM_Collector> $collectors
464
+ * @param QueryMonitor $qm
465
+ * @return array<string, QM_Collector>
466
+ */
467
  function register_qm_collector_theme( array $collectors, QueryMonitor $qm ) {
468
  $collectors['response'] = new QM_Collector_Theme();
469
  return $collectors;
collectors/timing.php CHANGED
@@ -5,34 +5,75 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Collector_Timing extends QM_Collector {
11
 
12
- public $id = 'timing';
 
 
 
 
 
 
 
13
  private $track_timer = array();
14
- private $start = array();
15
- private $stop = array();
16
 
17
- public function __construct() {
18
- parent::__construct();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  add_action( 'qm/start', array( $this, 'action_function_time_start' ), 10, 1 );
20
- add_action( 'qm/stop', array( $this, 'action_function_time_stop' ), 10, 1 );
21
- add_action( 'qm/lap', array( $this, 'action_function_time_lap' ), 10, 2 );
 
 
 
 
 
 
 
 
 
 
 
22
  }
23
 
 
 
 
 
24
  public function action_function_time_start( $function ) {
25
  $this->track_timer[ $function ] = new QM_Timer();
26
- $this->start[ $function ] = $this->track_timer[ $function ]->start();
27
  }
28
 
 
 
 
 
29
  public function action_function_time_stop( $function ) {
30
  if ( ! isset( $this->track_timer[ $function ] ) ) {
31
- $trace = new QM_Backtrace();
32
  $this->data['warning'][] = array(
33
  'function' => $function,
34
- 'message' => __( 'Timer not started', 'query-monitor' ),
35
- 'trace' => $trace,
 
36
  );
37
  return;
38
  }
@@ -40,46 +81,61 @@ class QM_Collector_Timing extends QM_Collector {
40
  $this->calculate_time( $function );
41
  }
42
 
 
 
 
 
 
43
  public function action_function_time_lap( $function, $name = null ) {
44
  if ( ! isset( $this->track_timer[ $function ] ) ) {
45
- $trace = new QM_Backtrace();
46
  $this->data['warning'][] = array(
47
  'function' => $function,
48
- 'message' => __( 'Timer not started', 'query-monitor' ),
49
- 'trace' => $trace,
 
50
  );
51
  return;
52
  }
53
  $this->track_timer[ $function ]->lap( array(), $name );
54
  }
55
 
 
 
 
 
56
  public function calculate_time( $function ) {
57
- $trace = $this->track_timer[ $function ]->get_trace();
58
- $function_time = $this->track_timer[ $function ]->get_time();
59
  $function_memory = $this->track_timer[ $function ]->get_memory();
60
- $function_laps = $this->track_timer[ $function ]->get_laps();
61
- $start_time = $this->track_timer[ $function ]->get_start_time();
62
- $end_time = $this->track_timer[ $function ]->get_end_time();
63
 
64
  $this->data['timing'][] = array(
65
- 'function' => $function,
66
- 'function_time' => $function_time,
67
  'function_memory' => $function_memory,
68
- 'laps' => $function_laps,
69
- 'trace' => $trace,
70
- 'start_time' => ( $start_time - $GLOBALS['timestart'] ),
71
- 'end_time' => ( $end_time - $GLOBALS['timestart'] ),
 
72
  );
73
  }
74
 
 
 
 
75
  public function process() {
76
  foreach ( $this->start as $function => $value ) {
77
  if ( ! isset( $this->stop[ $function ] ) ) {
78
- $trace = $this->track_timer[ $function ]->get_trace();
79
  $this->data['warning'][] = array(
80
  'function' => $function,
81
- 'message' => __( 'Timer not stopped', 'query-monitor' ),
82
- 'trace' => $trace,
 
83
  );
84
  }
85
  }
@@ -89,6 +145,12 @@ class QM_Collector_Timing extends QM_Collector {
89
  }
90
  }
91
 
 
 
 
 
 
 
92
  public function sort_by_start_time( array $a, array $b ) {
93
  if ( $a['start_time'] === $b['start_time'] ) {
94
  return 0;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Collector_Timing extends QM_Collector {
13
 
14
+ /**
15
+ * @var string
16
+ */
17
+ public $id = 'timing';
18
+
19
+ /**
20
+ * @var array<string, QM_Timer>
21
+ */
22
  private $track_timer = array();
 
 
23
 
24
+ /**
25
+ * @var array<string, QM_Timer>
26
+ */
27
+ private $start = array();
28
+
29
+ /**
30
+ * @var array<string, QM_Timer>
31
+ */
32
+ private $stop = array();
33
+
34
+ /**
35
+ * @return void
36
+ */
37
+ public function set_up() {
38
+ parent::set_up();
39
+
40
  add_action( 'qm/start', array( $this, 'action_function_time_start' ), 10, 1 );
41
+ add_action( 'qm/stop', array( $this, 'action_function_time_stop' ), 10, 1 );
42
+ add_action( 'qm/lap', array( $this, 'action_function_time_lap' ), 10, 2 );
43
+ }
44
+
45
+ /**
46
+ * @return void
47
+ */
48
+ public function tear_down() {
49
+ remove_action( 'qm/start', array( $this, 'action_function_time_start' ), 10 );
50
+ remove_action( 'qm/stop', array( $this, 'action_function_time_stop' ), 10 );
51
+ remove_action( 'qm/lap', array( $this, 'action_function_time_lap' ), 10 );
52
+
53
+ parent::tear_down();
54
  }
55
 
56
+ /**
57
+ * @param string $function
58
+ * @return void
59
+ */
60
  public function action_function_time_start( $function ) {
61
  $this->track_timer[ $function ] = new QM_Timer();
62
+ $this->start[ $function ] = $this->track_timer[ $function ]->start();
63
  }
64
 
65
+ /**
66
+ * @param string $function
67
+ * @return void
68
+ */
69
  public function action_function_time_stop( $function ) {
70
  if ( ! isset( $this->track_timer[ $function ] ) ) {
71
+ $trace = new QM_Backtrace();
72
  $this->data['warning'][] = array(
73
  'function' => $function,
74
+ 'message' => __( 'Timer not started', 'query-monitor' ),
75
+ 'filtered_trace' => $trace->get_filtered_trace(),
76
+ 'component' => $trace->get_component(),
77
  );
78
  return;
79
  }
81
  $this->calculate_time( $function );
82
  }
83
 
84
+ /**
85
+ * @param string $function
86
+ * @param string $name
87
+ * @return void
88
+ */
89
  public function action_function_time_lap( $function, $name = null ) {
90
  if ( ! isset( $this->track_timer[ $function ] ) ) {
91
+ $trace = new QM_Backtrace();
92
  $this->data['warning'][] = array(
93
  'function' => $function,
94
+ 'message' => __( 'Timer not started', 'query-monitor' ),
95
+ 'filtered_trace' => $trace->get_filtered_trace(),
96
+ 'component' => $trace->get_component(),
97
  );
98
  return;
99
  }
100
  $this->track_timer[ $function ]->lap( array(), $name );
101
  }
102
 
103
+ /**
104
+ * @param string $function
105
+ * @return void
106
+ */
107
  public function calculate_time( $function ) {
108
+ $trace = $this->track_timer[ $function ]->get_trace();
109
+ $function_time = $this->track_timer[ $function ]->get_time();
110
  $function_memory = $this->track_timer[ $function ]->get_memory();
111
+ $function_laps = $this->track_timer[ $function ]->get_laps();
112
+ $start_time = $this->track_timer[ $function ]->get_start_time();
113
+ $end_time = $this->track_timer[ $function ]->get_end_time();
114
 
115
  $this->data['timing'][] = array(
116
+ 'function' => $function,
117
+ 'function_time' => $function_time,
118
  'function_memory' => $function_memory,
119
+ 'laps' => $function_laps,
120
+ 'filtered_trace' => $trace->get_filtered_trace(),
121
+ 'component' => $trace->get_component(),
122
+ 'start_time' => ( $start_time - $GLOBALS['timestart'] ),
123
+ 'end_time' => ( $end_time - $GLOBALS['timestart'] ),
124
  );
125
  }
126
 
127
+ /**
128
+ * @return void
129
+ */
130
  public function process() {
131
  foreach ( $this->start as $function => $value ) {
132
  if ( ! isset( $this->stop[ $function ] ) ) {
133
+ $trace = $this->track_timer[ $function ]->get_trace();
134
  $this->data['warning'][] = array(
135
  'function' => $function,
136
+ 'message' => __( 'Timer not stopped', 'query-monitor' ),
137
+ 'filtered_trace' => $trace->get_filtered_trace(),
138
+ 'component' => $trace->get_component(),
139
  );
140
  }
141
  }
145
  }
146
  }
147
 
148
+ /**
149
+ * @param mixed[] $a
150
+ * @param mixed[] $b
151
+ * @return int
152
+ * @phpstan-return -1|0|1
153
+ */
154
  public function sort_by_start_time( array $a, array $b ) {
155
  if ( $a['start_time'] === $b['start_time'] ) {
156
  return 0;
collectors/transients.php CHANGED
@@ -5,35 +5,70 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Collector_Transients extends QM_Collector {
11
 
12
  public $id = 'transients';
13
 
14
- public function __construct() {
15
- parent::__construct();
 
 
 
 
16
  add_action( 'setted_site_transient', array( $this, 'action_setted_site_transient' ), 10, 3 );
17
- add_action( 'setted_transient', array( $this, 'action_setted_blog_transient' ), 10, 3 );
18
  }
19
 
 
 
 
20
  public function tear_down() {
21
  remove_action( 'setted_site_transient', array( $this, 'action_setted_site_transient' ), 10 );
22
- remove_action( 'setted_transient', array( $this, 'action_setted_blog_transient' ), 10 );
23
  parent::tear_down();
24
  }
25
 
 
 
 
 
 
 
26
  public function action_setted_site_transient( $transient, $value, $expiration ) {
27
  $this->setted_transient( $transient, 'site', $value, $expiration );
28
  }
29
 
 
 
 
 
 
 
30
  public function action_setted_blog_transient( $transient, $value, $expiration ) {
31
  $this->setted_transient( $transient, 'blog', $value, $expiration );
32
  }
33
 
 
 
 
 
 
 
 
 
34
  public function setted_transient( $transient, $type, $value, $expiration ) {
35
  $trace = new QM_Backtrace( array(
36
- 'ignore_frames' => 1, # Ignore the action_setted_(site|blog)_transient method
 
 
 
 
 
 
37
  ) );
38
 
39
  $name = str_replace( array(
@@ -44,37 +79,23 @@ class QM_Collector_Transients extends QM_Collector {
44
  $size = strlen( maybe_serialize( $value ) );
45
 
46
  $this->data['trans'][] = array(
47
- 'name' => $name,
48
- 'trace' => $trace,
49
- 'type' => $type,
50
- 'value' => $value,
 
51
  'expiration' => $expiration,
52
- 'exp_diff' => ( $expiration ? human_time_diff( 0, $expiration ) : '' ),
53
- 'size' => $size,
54
  'size_formatted' => size_format( $size ),
55
  );
56
  }
57
 
 
 
 
58
  public function process() {
59
  $this->data['has_type'] = is_multisite();
60
-
61
- if ( empty( $this->data['trans'] ) ) {
62
- return;
63
- }
64
-
65
- foreach ( $this->data['trans'] as $i => $transient ) {
66
- $filtered_trace = $transient['trace']->get_display_trace();
67
-
68
- array_shift( $filtered_trace ); // remove do_action('setted_(site_)?transient')
69
- array_shift( $filtered_trace ); // remove set_(site_)?transient()
70
-
71
- $component = $transient['trace']->get_component();
72
-
73
- $this->data['trans'][ $i ]['filtered_trace'] = $filtered_trace;
74
- $this->data['trans'][ $i ]['component'] = $component;
75
-
76
- unset( $this->data['trans'][ $i ]['trace'] );
77
- }
78
  }
79
 
80
  }
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Collector_Transients extends QM_Collector {
13
 
14
  public $id = 'transients';
15
 
16
+ /**
17
+ * @return void
18
+ */
19
+ public function set_up() {
20
+ parent::set_up();
21
+
22
  add_action( 'setted_site_transient', array( $this, 'action_setted_site_transient' ), 10, 3 );
23
+ add_action( 'setted_transient', array( $this, 'action_setted_blog_transient' ), 10, 3 );
24
  }
25
 
26
+ /**
27
+ * @return void
28
+ */
29
  public function tear_down() {
30
  remove_action( 'setted_site_transient', array( $this, 'action_setted_site_transient' ), 10 );
31
+ remove_action( 'setted_transient', array( $this, 'action_setted_blog_transient' ), 10 );
32
  parent::tear_down();
33
  }
34
 
35
+ /**
36
+ * @param string $transient
37
+ * @param mixed $value
38
+ * @param int $expiration
39
+ * @return void
40
+ */
41
  public function action_setted_site_transient( $transient, $value, $expiration ) {
42
  $this->setted_transient( $transient, 'site', $value, $expiration );
43
  }
44
 
45
+ /**
46
+ * @param string $transient
47
+ * @param mixed $value
48
+ * @param int $expiration
49
+ * @return void
50
+ */
51
  public function action_setted_blog_transient( $transient, $value, $expiration ) {
52
  $this->setted_transient( $transient, 'blog', $value, $expiration );
53
  }
54
 
55
+ /**
56
+ * @param string $transient
57
+ * @param string $type
58
+ * @param mixed $value
59
+ * @param int $expiration
60
+ * @phpstan-param 'site'|'blog' $value
61
+ * @return void
62
+ */
63
  public function setted_transient( $transient, $type, $value, $expiration ) {
64
  $trace = new QM_Backtrace( array(
65
+ 'ignore_hook' => array(
66
+ current_filter() => true,
67
+ ),
68
+ 'ignore_func' => array(
69
+ 'set_transient' => true,
70
+ 'set_site_transient' => true,
71
+ ),
72
  ) );
73
 
74
  $name = str_replace( array(
79
  $size = strlen( maybe_serialize( $value ) );
80
 
81
  $this->data['trans'][] = array(
82
+ 'name' => $name,
83
+ 'filtered_trace' => $trace->get_filtered_trace(),
84
+ 'component' => $trace->get_component(),
85
+ 'type' => $type,
86
+ 'value' => $value,
87
  'expiration' => $expiration,
88
+ 'exp_diff' => ( $expiration ? human_time_diff( 0, $expiration ) : '' ),
89
+ 'size' => $size,
90
  'size_formatted' => size_format( $size ),
91
  );
92
  }
93
 
94
+ /**
95
+ * @return void
96
+ */
97
  public function process() {
98
  $this->data['has_type'] = is_multisite();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  }
100
 
101
  }
composer.json ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "johnbillion/query-monitor",
3
+ "type": "wordpress-plugin",
4
+ "description": "The Developer Tools panel for WordPress.",
5
+ "homepage": "https://github.com/johnbillion/query-monitor/",
6
+ "license": "GPL-2.0-or-later",
7
+ "authors": [
8
+ {
9
+ "name": "John Blackbourn",
10
+ "homepage": "https://johnblackbourn.com/"
11
+ }
12
+ ],
13
+ "require": {
14
+ "php": ">=5.3.6",
15
+ "composer/installers": "~1.0"
16
+ },
17
+ "require-dev": {
18
+ "dealerdirect/phpcodesniffer-composer-installer": "0.7.0",
19
+ "johnbillion/falsey-assertequals-detector": "^1 || ^3",
20
+ "phpcompatibility/phpcompatibility-wp": "2.1.0",
21
+ "phpstan/phpstan": "^1.0",
22
+ "phpstan/phpstan-phpunit": "^1.0",
23
+ "phpunit/phpunit": "^5 || ^7",
24
+ "roots/wordpress": "*",
25
+ "squizlabs/php_codesniffer": "3.5.8",
26
+ "szepeviktor/phpstan-wordpress": "^1.0",
27
+ "vlucas/phpdotenv": "^3",
28
+ "wp-cli/db-command": "^2",
29
+ "wp-coding-standards/wpcs": "2.3.0",
30
+ "wp-phpunit/wp-phpunit": "*",
31
+ "yoast/phpunit-polyfills": "^1.0"
32
+ },
33
+ "config": {
34
+ "preferred-install": "dist",
35
+ "sort-packages": true,
36
+ "allow-plugins": {
37
+ "composer/installers": true,
38
+ "dealerdirect/phpcodesniffer-composer-installer": true,
39
+ "roots/wordpress-core-installer": true
40
+ }
41
+ },
42
+ "extra": {
43
+ "wordpress-install-dir": "tests/wordpress"
44
+ },
45
+ "scripts": {
46
+ "post-update-cmd": [
47
+ "@php -r \"! file_exists( 'tests/.env' ) && copy( 'tests/.env.dist', 'tests/.env' );\""
48
+ ],
49
+ "test": [
50
+ "@test:cs",
51
+ "@test:phpstan",
52
+ "@test:ut"
53
+ ],
54
+ "test:cs": [
55
+ "phpcs -nps --colors --report-code --report-width=80 --cache=tests/cache/phpcs --basepath='./' ."
56
+ ],
57
+ "test:phpstan": [
58
+ "phpstan analyze"
59
+ ],
60
+ "test:ut": [
61
+ "wp db reset --yes --path=tests/wordpress #",
62
+ "export WP_MULTISITE=0 && phpunit --verbose --colors=always --exclude-group=ms-required",
63
+ "export WP_MULTISITE=1 && phpunit --verbose --colors=always --exclude-group=ms-excluded"
64
+ ]
65
+ },
66
+ "support": {
67
+ "issues": "https://github.com/johnbillion/query-monitor/issues",
68
+ "forum": "https://wordpress.org/support/plugin/query-monitor",
69
+ "source": "https://github.com/johnbillion/query-monitor"
70
+ },
71
+ "funding": [
72
+ {
73
+ "type": "github",
74
+ "url": "https://github.com/sponsors/johnbillion"
75
+ }
76
+ ]
77
+ }
dispatchers/AJAX.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Dispatcher_AJAX extends QM_Dispatcher {
11
 
@@ -20,6 +22,9 @@ class QM_Dispatcher_AJAX extends QM_Dispatcher {
20
  add_action( 'shutdown', array( $this, 'dispatch' ), 0 );
21
  }
22
 
 
 
 
23
  public function init() {
24
 
25
  if ( ! self::user_can_view() ) {
@@ -34,6 +39,9 @@ class QM_Dispatcher_AJAX extends QM_Dispatcher {
34
  parent::init();
35
  }
36
 
 
 
 
37
  public function dispatch() {
38
 
39
  if ( ! $this->should_dispatch() ) {
@@ -50,6 +58,9 @@ class QM_Dispatcher_AJAX extends QM_Dispatcher {
50
 
51
  }
52
 
 
 
 
53
  protected function before_output() {
54
 
55
  require_once $this->qm->plugin_path( 'output/Headers.php' );
@@ -59,6 +70,9 @@ class QM_Dispatcher_AJAX extends QM_Dispatcher {
59
  }
60
  }
61
 
 
 
 
62
  protected function after_output() {
63
 
64
  # flush once, because we're nice
@@ -68,6 +82,9 @@ class QM_Dispatcher_AJAX extends QM_Dispatcher {
68
 
69
  }
70
 
 
 
 
71
  public function is_active() {
72
 
73
  if ( ! QM_Util::is_ajax() ) {
@@ -100,6 +117,11 @@ class QM_Dispatcher_AJAX extends QM_Dispatcher {
100
 
101
  }
102
 
 
 
 
 
 
103
  function register_qm_dispatcher_ajax( array $dispatchers, QM_Plugin $qm ) {
104
  $dispatchers['ajax'] = new QM_Dispatcher_AJAX( $qm );
105
  return $dispatchers;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Dispatcher_AJAX extends QM_Dispatcher {
13
 
22
  add_action( 'shutdown', array( $this, 'dispatch' ), 0 );
23
  }
24
 
25
+ /**
26
+ * @return void
27
+ */
28
  public function init() {
29
 
30
  if ( ! self::user_can_view() ) {
39
  parent::init();
40
  }
41
 
42
+ /**
43
+ * @return void
44
+ */
45
  public function dispatch() {
46
 
47
  if ( ! $this->should_dispatch() ) {
58
 
59
  }
60
 
61
+ /**
62
+ * @return void
63
+ */
64
  protected function before_output() {
65
 
66
  require_once $this->qm->plugin_path( 'output/Headers.php' );
70
  }
71
  }
72
 
73
+ /**
74
+ * @return void
75
+ */
76
  protected function after_output() {
77
 
78
  # flush once, because we're nice
82
 
83
  }
84
 
85
+ /**
86
+ * @return bool
87
+ */
88
  public function is_active() {
89
 
90
  if ( ! QM_Util::is_ajax() ) {
117
 
118
  }
119
 
120
+ /**
121
+ * @param array<string, QM_Dispatcher> $dispatchers
122
+ * @param QM_Plugin $qm
123
+ * @return array<string, QM_Dispatcher>
124
+ */
125
  function register_qm_dispatcher_ajax( array $dispatchers, QM_Plugin $qm ) {
126
  $dispatchers['ajax'] = new QM_Dispatcher_AJAX( $qm );
127
  return $dispatchers;
dispatchers/Html.php CHANGED
@@ -5,42 +5,61 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Dispatcher_Html extends QM_Dispatcher {
11
 
12
  /**
13
  * Outputter instances.
14
  *
15
- * @var QM_Output_Html[] Array of outputters.
16
  */
17
  protected $outputters = array();
18
 
19
- public $id = 'html';
 
 
 
 
 
 
 
20
  public $did_footer = false;
21
 
 
 
 
22
  protected $admin_bar_menu = array();
23
- protected $panel_menu = array();
 
 
 
 
24
 
25
  public function __construct( QM_Plugin $qm ) {
26
 
27
- add_action( 'admin_bar_menu', array( $this, 'action_admin_bar_menu' ), 999 );
28
- add_action( 'wp_ajax_qm_auth_on', array( $this, 'ajax_on' ) );
29
- add_action( 'wp_ajax_qm_auth_off', array( $this, 'ajax_off' ) );
30
- add_action( 'wp_ajax_qm_editor_set', array( $this, 'ajax_editor_set' ) );
31
  add_action( 'wp_ajax_nopriv_qm_auth_off', array( $this, 'ajax_off' ) );
32
 
33
- add_action( 'shutdown', array( $this, 'dispatch' ), 0 );
34
 
35
- add_action( 'wp_footer', array( $this, 'action_footer' ) );
36
- add_action( 'admin_footer', array( $this, 'action_footer' ) );
37
- add_action( 'login_footer', array( $this, 'action_footer' ) );
38
- add_action( 'gp_footer', array( $this, 'action_footer' ) );
39
 
40
  parent::__construct( $qm );
41
 
42
  }
43
 
 
 
 
44
  public function action_footer() {
45
  $this->did_footer = true;
46
  }
@@ -54,6 +73,9 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
54
  return ( is_ssl() && ( 'https' === parse_url( home_url(), PHP_URL_SCHEME ) ) );
55
  }
56
 
 
 
 
57
  public function ajax_on() {
58
 
59
  if ( ! current_user_can( 'view_query_monitor' ) || ! check_ajax_referer( 'qm-auth-on', 'nonce', false ) ) {
@@ -61,8 +83,8 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
61
  }
62
 
63
  $expiration = time() + ( 2 * DAY_IN_SECONDS );
64
- $secure = self::secure_cookie();
65
- $cookie = wp_generate_auth_cookie( get_current_user_id(), $expiration, 'logged_in' );
66
 
67
  setcookie( QM_COOKIE, $cookie, $expiration, COOKIEPATH, COOKIE_DOMAIN, $secure, false );
68
 
@@ -70,6 +92,9 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
70
 
71
  }
72
 
 
 
 
73
  public function ajax_off() {
74
 
75
  if ( ! self::user_verified() || ! check_ajax_referer( 'qm-auth-off', 'nonce', false ) ) {
@@ -84,6 +109,9 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
84
 
85
  }
86
 
 
 
 
87
  public function ajax_editor_set() {
88
 
89
  if ( ! current_user_can( 'view_query_monitor' ) || ! check_ajax_referer( 'qm-editor-set', 'nonce', false ) ) {
@@ -91,8 +119,8 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
91
  }
92
 
93
  $expiration = time() + ( 2 * YEAR_IN_SECONDS );
94
- $secure = self::secure_cookie();
95
- $editor = wp_unslash( $_POST['editor'] );
96
 
97
  setcookie( QM_EDITOR_COOKIE, $editor, $expiration, COOKIEPATH, COOKIE_DOMAIN, $secure, false );
98
 
@@ -100,6 +128,10 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
100
 
101
  }
102
 
 
 
 
 
103
  public function action_admin_bar_menu( WP_Admin_Bar $wp_admin_bar ) {
104
 
105
  if ( ! self::user_can_view() ) {
@@ -109,40 +141,50 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
109
  $title = __( 'Query Monitor', 'query-monitor' );
110
 
111
  $wp_admin_bar->add_node( array(
112
- 'id' => 'query-monitor',
113
  'title' => esc_html( $title ),
114
- 'href' => '#qm-overview',
115
  ) );
116
 
117
  $wp_admin_bar->add_node( array(
118
  'parent' => 'query-monitor',
119
- 'id' => 'query-monitor-placeholder',
120
- 'title' => esc_html( $title ),
121
- 'href' => '#qm-overview',
122
  ) );
123
 
124
  }
125
 
 
 
 
126
  public function init() {
127
 
128
  if ( ! self::user_can_view() ) {
129
  return;
130
  }
131
 
 
 
 
 
132
  if ( ! file_exists( $this->qm->plugin_path( 'assets/query-monitor.css' ) ) ) {
133
  add_action( 'admin_notices', array( $this, 'build_warning' ) );
134
  }
135
 
136
- add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ), -9999 );
137
  add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ), -9999 );
138
  add_action( 'login_enqueue_scripts', array( $this, 'enqueue_assets' ), -9999 );
139
  add_action( 'enqueue_embed_scripts', array( $this, 'enqueue_assets' ), -9999 );
140
 
141
- add_action( 'gp_head', array( $this, 'manually_print_assets' ), 11 );
142
 
143
  parent::init();
144
  }
145
 
 
 
 
146
  public function manually_print_assets() {
147
  wp_print_scripts( array(
148
  'query-monitor',
@@ -152,6 +194,9 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
152
  ) );
153
  }
154
 
 
 
 
155
  public function build_warning() {
156
  printf(
157
  '<div id="qm-built-nope" class="notice notice-error"><p>%s</p></div>',
@@ -167,8 +212,11 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
167
  );
168
  }
169
 
 
 
 
170
  public function enqueue_assets() {
171
- global $wp_locale, $wp_version;
172
 
173
  $deps = array(
174
  'jquery',
@@ -207,10 +255,10 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
207
  'qm_l10n',
208
  array(
209
  'ajax_error' => __( 'PHP Errors in Ajax Response', 'query-monitor' ),
210
- 'ajaxurl' => admin_url( 'admin-ajax.php' ),
211
  'auth_nonce' => array(
212
- 'on' => wp_create_nonce( 'qm-auth-on' ),
213
- 'off' => wp_create_nonce( 'qm-auth-off' ),
214
  'editor-set' => wp_create_nonce( 'qm-editor-set' ),
215
  ),
216
  'fatal_error' => __( 'PHP Fatal Error', 'query-monitor' ),
@@ -227,13 +275,42 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
227
  do_action( 'qm/output/enqueued-assets', $this );
228
  }
229
 
 
 
 
230
  public function dispatch() {
231
-
232
  if ( ! $this->should_dispatch() ) {
233
  return;
234
  }
235
 
236
- $switched_locale = function_exists( 'switch_to_locale' ) && switch_to_locale( get_user_locale() );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
 
238
  $this->before_output();
239
 
@@ -257,11 +334,14 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
257
  $this->after_output();
258
 
259
  if ( $switched_locale ) {
260
- restore_previous_locale();
261
  }
262
 
263
  }
264
 
 
 
 
265
  protected function before_output() {
266
 
267
  require_once $this->qm->plugin_path( 'output/Html.php' );
@@ -270,7 +350,10 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
270
  require_once $file;
271
  }
272
 
273
- $this->outputters = $this->get_outputters( 'html' );
 
 
 
274
 
275
  /**
276
  * Filters the menu items shown in Query Monitor's admin toolbar menu.
@@ -294,9 +377,14 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
294
  $collector = $output->get_collector();
295
 
296
  if ( ( ! empty( $collector->concerned_filters ) || ! empty( $collector->concerned_actions ) ) && isset( $this->panel_menu[ 'qm-' . $output_id ] ) ) {
 
297
  $this->panel_menu[ 'qm-' . $output_id ]['children'][ 'qm-' . $output_id . '-concerned_hooks' ] = array(
298
- 'href' => esc_attr( '#' . $collector->id() . '-concerned_hooks' ),
299
- 'title' => __( 'Hooks in Use', 'query-monitor' ),
 
 
 
 
300
  );
301
  }
302
  }
@@ -315,7 +403,7 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
315
  }
316
 
317
  $json = array(
318
- 'menu' => $this->js_admin_bar_menu(),
319
  'ajax_errors' => array(), # @TODO move this into the php_errors collector
320
  );
321
 
@@ -390,6 +478,11 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
390
 
391
  }
392
 
 
 
 
 
 
393
  protected function do_panel_menu_item( $id, array $menu ) {
394
  printf(
395
  '<li role="presentation"><button role="tab" data-qm-href="%1$s">%2$s</button>',
@@ -408,12 +501,15 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
408
  echo '</li>';
409
  }
410
 
 
 
 
411
  protected function after_output() {
412
 
413
  $state = self::user_verified() ? 'on' : 'off';
414
  $editor = self::editor_cookie();
415
- $text = array(
416
- 'on' => __( 'Clear authentication cookie', 'query-monitor' ),
417
  'off' => __( 'Set authentication cookie', 'query-monitor' ),
418
  );
419
 
@@ -444,12 +540,12 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
444
  echo '<select id="qm-editor-select" name="qm-editor-select" class="qm-filter">';
445
 
446
  $editors = array(
447
- 'Default/Xdebug' => '',
448
- 'Atom' => 'atom',
449
- 'Netbeans' => 'netbeans',
450
- 'PhpStorm' => 'phpstorm',
451
- 'Sublime Text' => 'sublime',
452
- 'TextMate' => 'textmate',
453
  'Visual Studio Code' => 'vscode',
454
  );
455
 
@@ -467,42 +563,46 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
467
 
468
  echo '<div class="qm-boxed">';
469
  $constants = array(
470
- 'QM_DARK_MODE' => array(
471
- 'label' => __( 'Enable dark mode for Query Monitor\'s interface.', 'query-monitor' ),
472
  'default' => false,
473
  ),
474
- 'QM_DB_EXPENSIVE' => array(
475
- 'label' => __( 'If an individual database query takes longer than this time to execute, it\'s considered "slow" and triggers a warning.', 'query-monitor' ),
476
  'default' => 0.05,
477
  ),
478
- 'QM_DISABLED' => array(
479
- 'label' => __( 'Disable Query Monitor entirely.', 'query-monitor' ),
480
  'default' => false,
481
  ),
482
  'QM_DISABLE_ERROR_HANDLER' => array(
483
- 'label' => __( 'Disable the handling of PHP errors.', 'query-monitor' ),
484
  'default' => false,
485
  ),
486
- 'QM_ENABLE_CAPS_PANEL' => array(
487
- 'label' => __( 'Enable the Capability Checks panel.', 'query-monitor' ),
488
  'default' => false,
489
  ),
490
- 'QM_HIDE_CORE_ACTIONS' => array(
491
- 'label' => __( 'Hide WordPress core on the Hooks & Actions panel.', 'query-monitor' ),
492
  'default' => false,
493
  ),
494
- 'QM_HIDE_SELF' => array(
495
- 'label' => __( 'Hide Query Monitor itself from various panels. Set to false if you want to see how Query Monitor hooks into WordPress.', 'query-monitor' ),
496
  'default' => true,
497
  ),
498
- 'QM_NO_JQUERY' => array(
499
- 'label' => __( 'Don\'t specify jQuery as a dependency of Query Monitor. If jQuery isn\'t enqueued then Query Monitor will still operate, but with some reduced functionality.', 'query-monitor' ),
500
  'default' => false,
501
  ),
502
- 'QM_SHOW_ALL_HOOKS' => array(
503
- 'label' => __( 'In the Hooks & Actions panel, show every hook that has an action or filter attached (instead of every action hook that fired during the request).', 'query-monitor' ),
504
  'default' => false,
505
  ),
 
 
 
 
506
  );
507
 
508
  echo '<section>';
@@ -520,10 +620,7 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
520
  foreach ( $constants as $name => $constant ) {
521
  echo '<dt><code>' . esc_html( $name ) . '</code></dt>';
522
  echo '<dd>';
523
- printf(
524
- esc_html( $constant['label'] ),
525
- '<code>' . esc_html( $constant['default'] ) . '</code>'
526
- );
527
 
528
  $default_value = $constant['default'];
529
  if ( is_bool( $default_value ) ) {
@@ -534,18 +631,16 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
534
  printf(
535
  /* translators: %s: Default value for a PHP constant */
536
  esc_html__( 'Default value: %s', 'query-monitor' ),
537
- '<code>' . esc_html( $default_value ) . '</code>'
538
  );
539
  echo '</span>';
540
 
541
- if ( defined( $name ) ) {
542
  $current_value = constant( $name );
543
  if ( is_bool( $current_value ) ) {
544
  $current_value = QM_Collector::format_bool_constant( $name );
545
  }
546
- }
547
 
548
- if ( defined( $name ) && ( constant( $name ) !== $constant['default'] ) ) {
549
  echo '<br><span class="qm-info">';
550
  printf(
551
  /* translators: %s: Current value for a PHP constant */
@@ -622,6 +717,10 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
622
 
623
  }
624
 
 
 
 
 
625
  public static function size( $var ) {
626
  $start_memory = memory_get_usage();
627
 
@@ -634,6 +733,9 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
634
  return memory_get_usage() - $start_memory - ( PHP_INT_SIZE * 8 );
635
  }
636
 
 
 
 
637
  public function js_admin_bar_menu() {
638
 
639
  /**
@@ -656,7 +758,7 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
656
  *
657
  * @param array $output_title List of titles.
658
  */
659
- $title = implode( '&nbsp;&nbsp;&nbsp;', apply_filters( 'qm/output/title', array() ) );
660
 
661
  if ( empty( $title ) ) {
662
  $title = esc_html__( 'Query Monitor', 'query-monitor' );
@@ -664,7 +766,7 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
664
 
665
  $admin_bar_menu = array(
666
  'top' => array(
667
- 'title' => sprintf(
668
  '<span class="ab-icon">QM</span><span class="ab-label">%s</span>',
669
  $title
670
  ),
@@ -681,6 +783,26 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
681
 
682
  }
683
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
684
  public function is_active() {
685
 
686
  if ( ! self::user_can_view() ) {
@@ -691,8 +813,7 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
691
  return false;
692
  }
693
 
694
- // Don't dispatch if this is an async request and not a customizer preview:
695
- if ( QM_Util::is_async() && ( ! function_exists( 'is_customize_preview' ) || ! is_customize_preview() ) ) {
696
  return false;
697
  }
698
 
@@ -707,11 +828,6 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
707
  }
708
  }
709
 
710
- // Don't dispatch during an iframed request, eg the plugin info modal or an upgrader action:
711
- if ( defined( 'IFRAME_REQUEST' ) && IFRAME_REQUEST ) {
712
- return false;
713
- }
714
-
715
  /** Back-compat filter. Please use `qm/dispatch/html` instead */
716
  if ( ! apply_filters( 'qm/process', true, is_admin_bar_showing() ) ) {
717
  return false;
@@ -721,8 +837,21 @@ class QM_Dispatcher_Html extends QM_Dispatcher {
721
 
722
  }
723
 
 
 
 
 
 
 
 
 
724
  }
725
 
 
 
 
 
 
726
  function register_qm_dispatcher_html( array $dispatchers, QM_Plugin $qm ) {
727
  $dispatchers['html'] = new QM_Dispatcher_Html( $qm );
728
  return $dispatchers;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Dispatcher_Html extends QM_Dispatcher {
13
 
14
  /**
15
  * Outputter instances.
16
  *
17
+ * @var array<string, QM_Output_Html> Array of outputters.
18
  */
19
  protected $outputters = array();
20
 
21
+ /**
22
+ * @var string
23
+ */
24
+ public $id = 'html';
25
+
26
+ /**
27
+ * @var bool
28
+ */
29
  public $did_footer = false;
30
 
31
+ /**
32
+ * @var array<string, mixed[]>
33
+ */
34
  protected $admin_bar_menu = array();
35
+
36
+ /**
37
+ * @var array<string, mixed[]>
38
+ */
39
+ protected $panel_menu = array();
40
 
41
  public function __construct( QM_Plugin $qm ) {
42
 
43
+ add_action( 'admin_bar_menu', array( $this, 'action_admin_bar_menu' ), 999 );
44
+ add_action( 'wp_ajax_qm_auth_on', array( $this, 'ajax_on' ) );
45
+ add_action( 'wp_ajax_qm_auth_off', array( $this, 'ajax_off' ) );
46
+ add_action( 'wp_ajax_qm_editor_set', array( $this, 'ajax_editor_set' ) );
47
  add_action( 'wp_ajax_nopriv_qm_auth_off', array( $this, 'ajax_off' ) );
48
 
49
+ add_action( 'shutdown', array( $this, 'dispatch' ), 0 );
50
 
51
+ add_action( 'wp_footer', array( $this, 'action_footer' ) );
52
+ add_action( 'admin_footer', array( $this, 'action_footer' ) );
53
+ add_action( 'login_footer', array( $this, 'action_footer' ) );
54
+ add_action( 'gp_footer', array( $this, 'action_footer' ) );
55
 
56
  parent::__construct( $qm );
57
 
58
  }
59
 
60
+ /**
61
+ * @return void
62
+ */
63
  public function action_footer() {
64
  $this->did_footer = true;
65
  }
73
  return ( is_ssl() && ( 'https' === parse_url( home_url(), PHP_URL_SCHEME ) ) );
74
  }
75
 
76
+ /**
77
+ * @return void
78
+ */
79
  public function ajax_on() {
80
 
81
  if ( ! current_user_can( 'view_query_monitor' ) || ! check_ajax_referer( 'qm-auth-on', 'nonce', false ) ) {
83
  }
84
 
85
  $expiration = time() + ( 2 * DAY_IN_SECONDS );
86
+ $secure = self::secure_cookie();
87
+ $cookie = wp_generate_auth_cookie( get_current_user_id(), $expiration, 'logged_in' );
88
 
89
  setcookie( QM_COOKIE, $cookie, $expiration, COOKIEPATH, COOKIE_DOMAIN, $secure, false );
90
 
92
 
93
  }
94
 
95
+ /**
96
+ * @return void
97
+ */
98
  public function ajax_off() {
99
 
100
  if ( ! self::user_verified() || ! check_ajax_referer( 'qm-auth-off', 'nonce', false ) ) {
109
 
110
  }
111
 
112
+ /**
113
+ * @return void
114
+ */
115
  public function ajax_editor_set() {
116
 
117
  if ( ! current_user_can( 'view_query_monitor' ) || ! check_ajax_referer( 'qm-editor-set', 'nonce', false ) ) {
119
  }
120
 
121
  $expiration = time() + ( 2 * YEAR_IN_SECONDS );
122
+ $secure = self::secure_cookie();
123
+ $editor = wp_unslash( $_POST['editor'] );
124
 
125
  setcookie( QM_EDITOR_COOKIE, $editor, $expiration, COOKIEPATH, COOKIE_DOMAIN, $secure, false );
126
 
128
 
129
  }
130
 
131
+ /**
132
+ * @param WP_Admin_Bar $wp_admin_bar
133
+ * @return void
134
+ */
135
  public function action_admin_bar_menu( WP_Admin_Bar $wp_admin_bar ) {
136
 
137
  if ( ! self::user_can_view() ) {
141
  $title = __( 'Query Monitor', 'query-monitor' );
142
 
143
  $wp_admin_bar->add_node( array(
144
+ 'id' => 'query-monitor',
145
  'title' => esc_html( $title ),
146
+ 'href' => '#qm-overview',
147
  ) );
148
 
149
  $wp_admin_bar->add_node( array(
150
  'parent' => 'query-monitor',
151
+ 'id' => 'query-monitor-placeholder',
152
+ 'title' => esc_html( $title ),
153
+ 'href' => '#qm-overview',
154
  ) );
155
 
156
  }
157
 
158
+ /**
159
+ * @return void
160
+ */
161
  public function init() {
162
 
163
  if ( ! self::user_can_view() ) {
164
  return;
165
  }
166
 
167
+ if ( ! self::request_supported() ) {
168
+ return;
169
+ }
170
+
171
  if ( ! file_exists( $this->qm->plugin_path( 'assets/query-monitor.css' ) ) ) {
172
  add_action( 'admin_notices', array( $this, 'build_warning' ) );
173
  }
174
 
175
+ add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ), -9999 );
176
  add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ), -9999 );
177
  add_action( 'login_enqueue_scripts', array( $this, 'enqueue_assets' ), -9999 );
178
  add_action( 'enqueue_embed_scripts', array( $this, 'enqueue_assets' ), -9999 );
179
 
180
+ add_action( 'gp_head', array( $this, 'manually_print_assets' ), 11 );
181
 
182
  parent::init();
183
  }
184
 
185
+ /**
186
+ * @return void
187
+ */
188
  public function manually_print_assets() {
189
  wp_print_scripts( array(
190
  'query-monitor',
194
  ) );
195
  }
196
 
197
+ /**
198
+ * @return void
199
+ */
200
  public function build_warning() {
201
  printf(
202
  '<div id="qm-built-nope" class="notice notice-error"><p>%s</p></div>',
212
  );
213
  }
214
 
215
+ /**
216
+ * @return void
217
+ */
218
  public function enqueue_assets() {
219
+ global $wp_locale;
220
 
221
  $deps = array(
222
  'jquery',
255
  'qm_l10n',
256
  array(
257
  'ajax_error' => __( 'PHP Errors in Ajax Response', 'query-monitor' ),
258
+ 'ajaxurl' => admin_url( 'admin-ajax.php' ),
259
  'auth_nonce' => array(
260
+ 'on' => wp_create_nonce( 'qm-auth-on' ),
261
+ 'off' => wp_create_nonce( 'qm-auth-off' ),
262
  'editor-set' => wp_create_nonce( 'qm-editor-set' ),
263
  ),
264
  'fatal_error' => __( 'PHP Fatal Error', 'query-monitor' ),
275
  do_action( 'qm/output/enqueued-assets', $this );
276
  }
277
 
278
+ /**
279
+ * @return void
280
+ */
281
  public function dispatch() {
 
282
  if ( ! $this->should_dispatch() ) {
283
  return;
284
  }
285
 
286
+ if ( $this->ceased ) {
287
+ $admin_bar_menu = array(
288
+ 'top' => array(
289
+ 'title' => 'Query Monitor',
290
+ ),
291
+ 'sub' => array(
292
+ 'ceased' => array(
293
+ 'title' => esc_html__( 'Data collection ceased', 'query-monitor' ),
294
+ 'id' => 'query-monitor-ceased',
295
+ 'href' => '#',
296
+ ),
297
+ ),
298
+ );
299
+
300
+ $json = array(
301
+ 'menu' => $admin_bar_menu,
302
+ );
303
+
304
+ echo '<!-- Begin Query Monitor output -->' . "\n\n";
305
+ echo '<script type="text/javascript">' . "\n\n";
306
+ echo 'var qm = ' . json_encode( $json ) . ';' . "\n\n";
307
+ echo '</script>' . "\n\n";
308
+ echo '<div id="query-monitor-ceased"></div>';
309
+ echo '<!-- End Query Monitor output -->' . "\n\n";
310
+ return;
311
+ }
312
+
313
+ $switched_locale = self::switch_to_locale( get_user_locale() );
314
 
315
  $this->before_output();
316
 
334
  $this->after_output();
335
 
336
  if ( $switched_locale ) {
337
+ self::restore_previous_locale();
338
  }
339
 
340
  }
341
 
342
+ /**
343
+ * @return void
344
+ */
345
  protected function before_output() {
346
 
347
  require_once $this->qm->plugin_path( 'output/Html.php' );
350
  require_once $file;
351
  }
352
 
353
+ /** @var QM_Output_Html[] */
354
+ $outputters = $this->get_outputters( 'html' );
355
+
356
+ $this->outputters = $outputters;
357
 
358
  /**
359
  * Filters the menu items shown in Query Monitor's admin toolbar menu.
377
  $collector = $output->get_collector();
378
 
379
  if ( ( ! empty( $collector->concerned_filters ) || ! empty( $collector->concerned_actions ) ) && isset( $this->panel_menu[ 'qm-' . $output_id ] ) ) {
380
+ $count = count( $collector->concerned_filters ) + count( $collector->concerned_actions );
381
  $this->panel_menu[ 'qm-' . $output_id ]['children'][ 'qm-' . $output_id . '-concerned_hooks' ] = array(
382
+ 'href' => esc_attr( '#' . $collector->id() . '-concerned_hooks' ),
383
+ 'title' => sprintf(
384
+ /* translators: %s: Number of hooks */
385
+ __( 'Hooks in Use (%s)', 'query-monitor' ),
386
+ number_format_i18n( $count )
387
+ ),
388
  );
389
  }
390
  }
403
  }
404
 
405
  $json = array(
406
+ 'menu' => $this->js_admin_bar_menu(),
407
  'ajax_errors' => array(), # @TODO move this into the php_errors collector
408
  );
409
 
478
 
479
  }
480
 
481
+ /**
482
+ * @param string $id
483
+ * @param mixed[] $menu
484
+ * @return void
485
+ */
486
  protected function do_panel_menu_item( $id, array $menu ) {
487
  printf(
488
  '<li role="presentation"><button role="tab" data-qm-href="%1$s">%2$s</button>',
501
  echo '</li>';
502
  }
503
 
504
+ /**
505
+ * @return void
506
+ */
507
  protected function after_output() {
508
 
509
  $state = self::user_verified() ? 'on' : 'off';
510
  $editor = self::editor_cookie();
511
+ $text = array(
512
+ 'on' => __( 'Clear authentication cookie', 'query-monitor' ),
513
  'off' => __( 'Set authentication cookie', 'query-monitor' ),
514
  );
515
 
540
  echo '<select id="qm-editor-select" name="qm-editor-select" class="qm-filter">';
541
 
542
  $editors = array(
543
+ 'Default/Xdebug' => '',
544
+ 'Atom' => 'atom',
545
+ 'Netbeans' => 'netbeans',
546
+ 'PhpStorm' => 'phpstorm',
547
+ 'Sublime Text' => 'sublime',
548
+ 'TextMate' => 'textmate',
549
  'Visual Studio Code' => 'vscode',
550
  );
551
 
563
 
564
  echo '<div class="qm-boxed">';
565
  $constants = array(
566
+ 'QM_DARK_MODE' => array(
567
+ 'label' => __( 'Enable dark mode for Query Monitor\'s interface.', 'query-monitor' ),
568
  'default' => false,
569
  ),
570
+ 'QM_DB_EXPENSIVE' => array(
571
+ 'label' => __( 'If an individual database query takes longer than this time to execute, it\'s considered "slow" and triggers a warning.', 'query-monitor' ),
572
  'default' => 0.05,
573
  ),
574
+ 'QM_DISABLED' => array(
575
+ 'label' => __( 'Disable Query Monitor entirely.', 'query-monitor' ),
576
  'default' => false,
577
  ),
578
  'QM_DISABLE_ERROR_HANDLER' => array(
579
+ 'label' => __( 'Disable the handling of PHP errors.', 'query-monitor' ),
580
  'default' => false,
581
  ),
582
+ 'QM_ENABLE_CAPS_PANEL' => array(
583
+ 'label' => __( 'Enable the Capability Checks panel.', 'query-monitor' ),
584
  'default' => false,
585
  ),
586
+ 'QM_HIDE_CORE_ACTIONS' => array(
587
+ 'label' => __( 'Hide WordPress core on the Hooks & Actions panel.', 'query-monitor' ),
588
  'default' => false,
589
  ),
590
+ 'QM_HIDE_SELF' => array(
591
+ 'label' => __( 'Hide Query Monitor itself from various panels. Set to false if you want to see how Query Monitor hooks into WordPress.', 'query-monitor' ),
592
  'default' => true,
593
  ),
594
+ 'QM_NO_JQUERY' => array(
595
+ 'label' => __( 'Don\'t specify jQuery as a dependency of Query Monitor. If jQuery isn\'t enqueued then Query Monitor will still operate, but with some reduced functionality.', 'query-monitor' ),
596
  'default' => false,
597
  ),
598
+ 'QM_SHOW_ALL_HOOKS' => array(
599
+ 'label' => __( 'In the Hooks & Actions panel, show every hook that has an action or filter attached (instead of every action hook that fired during the request).', 'query-monitor' ),
600
  'default' => false,
601
  ),
602
+ 'QM_DB_SYMLINK' => array(
603
+ 'label' => __( 'Allow the wp-content/db.php file symlink to be put into place during activation. Set to false to prevent the symlink creation.', 'query-monitor' ),
604
+ 'default' => true,
605
+ ),
606
  );
607
 
608
  echo '<section>';
620
  foreach ( $constants as $name => $constant ) {
621
  echo '<dt><code>' . esc_html( $name ) . '</code></dt>';
622
  echo '<dd>';
623
+ echo esc_html( $constant['label'] );
 
 
 
624
 
625
  $default_value = $constant['default'];
626
  if ( is_bool( $default_value ) ) {
631
  printf(
632
  /* translators: %s: Default value for a PHP constant */
633
  esc_html__( 'Default value: %s', 'query-monitor' ),
634
+ '<code>' . esc_html( (string) $default_value ) . '</code>'
635
  );
636
  echo '</span>';
637
 
638
+ if ( defined( $name ) && ( constant( $name ) !== $constant['default'] ) ) {
639
  $current_value = constant( $name );
640
  if ( is_bool( $current_value ) ) {
641
  $current_value = QM_Collector::format_bool_constant( $name );
642
  }
 
643
 
 
644
  echo '<br><span class="qm-info">';
645
  printf(
646
  /* translators: %s: Current value for a PHP constant */
717
 
718
  }
719
 
720
+ /**
721
+ * @param mixed $var
722
+ * @return int|Exception
723
+ */
724
  public static function size( $var ) {
725
  $start_memory = memory_get_usage();
726
 
733
  return memory_get_usage() - $start_memory - ( PHP_INT_SIZE * 8 );
734
  }
735
 
736
+ /**
737
+ * @return array<string, mixed>
738
+ */
739
  public function js_admin_bar_menu() {
740
 
741
  /**
758
  *
759
  * @param array $output_title List of titles.
760
  */
761
+ $title = implode( '&nbsp;&nbsp;', apply_filters( 'qm/output/title', array() ) );
762
 
763
  if ( empty( $title ) ) {
764
  $title = esc_html__( 'Query Monitor', 'query-monitor' );
766
 
767
  $admin_bar_menu = array(
768
  'top' => array(
769
+ 'title' => sprintf(
770
  '<span class="ab-icon">QM</span><span class="ab-label">%s</span>',
771
  $title
772
  ),
783
 
784
  }
785
 
786
+ /**
787
+ * @return bool
788
+ */
789
+ public static function request_supported() {
790
+ // Don't dispatch if this is an async request and not a customizer preview:
791
+ if ( QM_Util::is_async() && ( ! function_exists( 'is_customize_preview' ) || ! is_customize_preview() ) ) {
792
+ return false;
793
+ }
794
+
795
+ // Don't dispatch during an iframed request, eg the plugin info modal, an upgrader action, or the Customizer:
796
+ if ( defined( 'IFRAME_REQUEST' ) && IFRAME_REQUEST ) {
797
+ return false;
798
+ }
799
+
800
+ return true;
801
+ }
802
+
803
+ /**
804
+ * @return bool
805
+ */
806
  public function is_active() {
807
 
808
  if ( ! self::user_can_view() ) {
813
  return false;
814
  }
815
 
816
+ if ( ! self::request_supported() ) {
 
817
  return false;
818
  }
819
 
828
  }
829
  }
830
 
 
 
 
 
 
831
  /** Back-compat filter. Please use `qm/dispatch/html` instead */
832
  if ( ! apply_filters( 'qm/process', true, is_admin_bar_showing() ) ) {
833
  return false;
837
 
838
  }
839
 
840
+ /**
841
+ * Cease without deactivating the dispatcher.
842
+ *
843
+ * @return void
844
+ */
845
+ public function cease() {
846
+ $this->ceased = true;
847
+ }
848
  }
849
 
850
+ /**
851
+ * @param array<string, QM_Dispatcher> $dispatchers
852
+ * @param QM_Plugin $qm
853
+ * @return array<string, QM_Dispatcher>
854
+ */
855
  function register_qm_dispatcher_html( array $dispatchers, QM_Plugin $qm ) {
856
  $dispatchers['html'] = new QM_Dispatcher_Html( $qm );
857
  return $dispatchers;
dispatchers/REST.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Dispatcher_REST extends QM_Dispatcher {
11
 
@@ -45,6 +47,9 @@ class QM_Dispatcher_REST extends QM_Dispatcher {
45
 
46
  }
47
 
 
 
 
48
  protected function before_output() {
49
 
50
  require_once $this->qm->plugin_path( 'output/Headers.php' );
@@ -54,6 +59,9 @@ class QM_Dispatcher_REST extends QM_Dispatcher {
54
  }
55
  }
56
 
 
 
 
57
  public function is_active() {
58
 
59
  # If the headers have already been sent then we can't do anything about it
@@ -75,6 +83,11 @@ class QM_Dispatcher_REST extends QM_Dispatcher {
75
 
76
  }
77
 
 
 
 
 
 
78
  function register_qm_dispatcher_rest( array $dispatchers, QM_Plugin $qm ) {
79
  $dispatchers['rest'] = new QM_Dispatcher_REST( $qm );
80
  return $dispatchers;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Dispatcher_REST extends QM_Dispatcher {
13
 
47
 
48
  }
49
 
50
+ /**
51
+ * @return void
52
+ */
53
  protected function before_output() {
54
 
55
  require_once $this->qm->plugin_path( 'output/Headers.php' );
59
  }
60
  }
61
 
62
+ /**
63
+ * @return bool
64
+ */
65
  public function is_active() {
66
 
67
  # If the headers have already been sent then we can't do anything about it
83
 
84
  }
85
 
86
+ /**
87
+ * @param array<string, QM_Dispatcher> $dispatchers
88
+ * @param QM_Plugin $qm
89
+ * @return array<string, QM_Dispatcher>
90
+ */
91
  function register_qm_dispatcher_rest( array $dispatchers, QM_Plugin $qm ) {
92
  $dispatchers['rest'] = new QM_Dispatcher_REST( $qm );
93
  return $dispatchers;
dispatchers/REST_Envelope.php CHANGED
@@ -18,9 +18,9 @@ class QM_Dispatcher_REST_Envelope extends QM_Dispatcher {
18
  /**
19
  * Filters the enveloped form of a REST API response to add QM's data.
20
  *
21
- * @param array $envelope Envelope data.
22
- * @param WP_REST_Response $response Original response data.
23
- * @return array Envelope data.
24
  */
25
  public function filter_rest_envelope_response( array $envelope, WP_REST_Response $response ) {
26
  if ( ! $this->should_dispatch() ) {
@@ -33,7 +33,7 @@ class QM_Dispatcher_REST_Envelope extends QM_Dispatcher {
33
 
34
  /* @var QM_Output_Raw[] */
35
  foreach ( $this->get_outputters( 'raw' ) as $id => $output ) {
36
- $data[ $id ] = $output->output();
37
  }
38
 
39
  $this->after_output();
@@ -43,6 +43,9 @@ class QM_Dispatcher_REST_Envelope extends QM_Dispatcher {
43
  return $envelope;
44
  }
45
 
 
 
 
46
  protected function before_output() {
47
  require_once $this->qm->plugin_path( 'output/Raw.php' );
48
 
@@ -51,6 +54,9 @@ class QM_Dispatcher_REST_Envelope extends QM_Dispatcher {
51
  }
52
  }
53
 
 
 
 
54
  public function is_active() {
55
  if ( ! self::user_can_view() ) {
56
  return false;
@@ -61,6 +67,11 @@ class QM_Dispatcher_REST_Envelope extends QM_Dispatcher {
61
 
62
  }
63
 
 
 
 
 
 
64
  function register_qm_dispatcher_rest_envelope( array $dispatchers, QM_Plugin $qm ) {
65
  $dispatchers['rest_envelope'] = new QM_Dispatcher_REST_Envelope( $qm );
66
  return $dispatchers;
18
  /**
19
  * Filters the enveloped form of a REST API response to add QM's data.
20
  *
21
+ * @param array<string, mixed> $envelope Envelope data.
22
+ * @param WP_REST_Response $response Original response data.
23
+ * @return array<string, mixed> Envelope data.
24
  */
25
  public function filter_rest_envelope_response( array $envelope, WP_REST_Response $response ) {
26
  if ( ! $this->should_dispatch() ) {
33
 
34
  /* @var QM_Output_Raw[] */
35
  foreach ( $this->get_outputters( 'raw' ) as $id => $output ) {
36
+ $data[ $id ] = $output->get_output();
37
  }
38
 
39
  $this->after_output();
43
  return $envelope;
44
  }
45
 
46
+ /**
47
+ * @return void
48
+ */
49
  protected function before_output() {
50
  require_once $this->qm->plugin_path( 'output/Raw.php' );
51
 
54
  }
55
  }
56
 
57
+ /**
58
+ * @return bool
59
+ */
60
  public function is_active() {
61
  if ( ! self::user_can_view() ) {
62
  return false;
67
 
68
  }
69
 
70
+ /**
71
+ * @param array<string, QM_Dispatcher> $dispatchers
72
+ * @param QM_Plugin $qm
73
+ * @return array<string, QM_Dispatcher>
74
+ */
75
  function register_qm_dispatcher_rest_envelope( array $dispatchers, QM_Plugin $qm ) {
76
  $dispatchers['rest_envelope'] = new QM_Dispatcher_REST_Envelope( $qm );
77
  return $dispatchers;
dispatchers/Redirect.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Dispatcher_Redirect extends QM_Dispatcher {
11
 
@@ -23,6 +25,7 @@ class QM_Dispatcher_Redirect extends QM_Dispatcher {
23
  *
24
  * @param string $location The path to redirect to.
25
  * @param int $status Status code to use.
 
26
  */
27
  public function filter_wp_redirect( $location, $status ) {
28
 
@@ -43,6 +46,9 @@ class QM_Dispatcher_Redirect extends QM_Dispatcher {
43
 
44
  }
45
 
 
 
 
46
  protected function before_output() {
47
 
48
  require_once $this->qm->plugin_path( 'output/Headers.php' );
@@ -52,6 +58,9 @@ class QM_Dispatcher_Redirect extends QM_Dispatcher {
52
  }
53
  }
54
 
 
 
 
55
  public function is_active() {
56
 
57
  if ( ! self::user_can_view() ) {
@@ -80,6 +89,11 @@ class QM_Dispatcher_Redirect extends QM_Dispatcher {
80
 
81
  }
82
 
 
 
 
 
 
83
  function register_qm_dispatcher_redirect( array $dispatchers, QM_Plugin $qm ) {
84
  $dispatchers['redirect'] = new QM_Dispatcher_Redirect( $qm );
85
  return $dispatchers;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Dispatcher_Redirect extends QM_Dispatcher {
13
 
25
  *
26
  * @param string $location The path to redirect to.
27
  * @param int $status Status code to use.
28
+ * @return string
29
  */
30
  public function filter_wp_redirect( $location, $status ) {
31
 
46
 
47
  }
48
 
49
+ /**
50
+ * @return void
51
+ */
52
  protected function before_output() {
53
 
54
  require_once $this->qm->plugin_path( 'output/Headers.php' );
58
  }
59
  }
60
 
61
+ /**
62
+ * @return bool
63
+ */
64
  public function is_active() {
65
 
66
  if ( ! self::user_can_view() ) {
89
 
90
  }
91
 
92
+ /**
93
+ * @param array<string, QM_Dispatcher> $dispatchers
94
+ * @param QM_Plugin $qm
95
+ * @return array<string, QM_Dispatcher>
96
+ */
97
  function register_qm_dispatcher_redirect( array $dispatchers, QM_Plugin $qm ) {
98
  $dispatchers['redirect'] = new QM_Dispatcher_Redirect( $qm );
99
  return $dispatchers;
dispatchers/WP_Die.php CHANGED
@@ -5,14 +5,21 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Dispatcher_WP_Die extends QM_Dispatcher {
11
 
12
- public $id = 'wp_die';
13
- public $trace = null;
 
 
14
 
15
- protected $outputters = array();
 
 
 
16
 
17
  public function __construct( QM_Plugin $qm ) {
18
  add_action( 'shutdown', array( $this, 'dispatch' ), 0 );
@@ -22,14 +29,23 @@ class QM_Dispatcher_WP_Die extends QM_Dispatcher {
22
  parent::__construct( $qm );
23
  }
24
 
 
 
 
 
25
  public function filter_wp_die_handler( $handler ) {
26
  $this->trace = new QM_Backtrace( array(
27
- 'ignore_frames' => 1,
 
 
28
  ) );
29
 
30
  return $handler;
31
  }
32
 
 
 
 
33
  public function dispatch() {
34
  if ( ! $this->should_dispatch() ) {
35
  return;
@@ -37,25 +53,15 @@ class QM_Dispatcher_WP_Die extends QM_Dispatcher {
37
 
38
  require_once $this->qm->plugin_path( 'output/Html.php' );
39
 
40
- $switched_locale = function_exists( 'switch_to_locale' ) && switch_to_locale( get_user_locale() );
41
- $stack = array();
42
- $filtered_trace = $this->trace->get_display_trace();
43
-
44
- // Ignore the `apply_filters('wp_die_handler')` stack frame:
45
- array_shift( $filtered_trace );
46
 
47
  foreach ( $filtered_trace as $i => $item ) {
48
  $stack[] = QM_Output_Html::output_filename( $item['display'], $item['file'], $item['line'] );
49
  }
50
 
51
- if ( isset( $filtered_trace[ $i - 1 ] ) ) {
52
- $culprit = $filtered_trace[ $i - 1 ];
53
- } else {
54
- $culprit = $filtered_trace[ $i ];
55
- }
56
-
57
- $component = QM_Backtrace::get_frame_component( $culprit );
58
-
59
  printf(
60
  // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet
61
  '<link rel="stylesheet" href="%s" media="all" />',
@@ -117,7 +123,7 @@ class QM_Dispatcher_WP_Die extends QM_Dispatcher {
117
  echo '<p>';
118
  echo '<span class="dashicons dashicons-info" aria-hidden="true"></span>';
119
 
120
- if ( $component ) {
121
  $name = ( 'plugin' === $component->type ) ? $component->context : $component->name;
122
  printf(
123
  /* translators: %s: Plugin or theme name */
@@ -138,10 +144,13 @@ class QM_Dispatcher_WP_Die extends QM_Dispatcher {
138
  echo '</div>';
139
 
140
  if ( $switched_locale ) {
141
- restore_previous_locale();
142
  }
143
  }
144
 
 
 
 
145
  public function is_active() {
146
  if ( ! $this->trace ) {
147
  return false;
@@ -156,6 +165,11 @@ class QM_Dispatcher_WP_Die extends QM_Dispatcher {
156
 
157
  }
158
 
 
 
 
 
 
159
  function register_qm_dispatcher_wp_die( array $dispatchers, QM_Plugin $qm ) {
160
  $dispatchers['wp_die'] = new QM_Dispatcher_WP_Die( $qm );
161
  return $dispatchers;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Dispatcher_WP_Die extends QM_Dispatcher {
13
 
14
+ /**
15
+ * @var string
16
+ */
17
+ public $id = 'wp_die';
18
 
19
+ /**
20
+ * @var QM_Backtrace|null
21
+ */
22
+ public $trace = null;
23
 
24
  public function __construct( QM_Plugin $qm ) {
25
  add_action( 'shutdown', array( $this, 'dispatch' ), 0 );
29
  parent::__construct( $qm );
30
  }
31
 
32
+ /**
33
+ * @param callable $handler
34
+ * @return callable
35
+ */
36
  public function filter_wp_die_handler( $handler ) {
37
  $this->trace = new QM_Backtrace( array(
38
+ 'ignore_hook' => array(
39
+ current_filter() => true,
40
+ ),
41
  ) );
42
 
43
  return $handler;
44
  }
45
 
46
+ /**
47
+ * @return void
48
+ */
49
  public function dispatch() {
50
  if ( ! $this->should_dispatch() ) {
51
  return;
53
 
54
  require_once $this->qm->plugin_path( 'output/Html.php' );
55
 
56
+ $switched_locale = self::switch_to_locale( get_user_locale() );
57
+ $stack = array();
58
+ $filtered_trace = $this->trace->get_filtered_trace();
59
+ $component = $this->trace->get_component();
 
 
60
 
61
  foreach ( $filtered_trace as $i => $item ) {
62
  $stack[] = QM_Output_Html::output_filename( $item['display'], $item['file'], $item['line'] );
63
  }
64
 
 
 
 
 
 
 
 
 
65
  printf(
66
  // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet
67
  '<link rel="stylesheet" href="%s" media="all" />',
123
  echo '<p>';
124
  echo '<span class="dashicons dashicons-info" aria-hidden="true"></span>';
125
 
126
+ if ( 'unknown' !== $component->type ) {
127
  $name = ( 'plugin' === $component->type ) ? $component->context : $component->name;
128
  printf(
129
  /* translators: %s: Plugin or theme name */
144
  echo '</div>';
145
 
146
  if ( $switched_locale ) {
147
+ self::restore_previous_locale();
148
  }
149
  }
150
 
151
+ /**
152
+ * @return bool
153
+ */
154
  public function is_active() {
155
  if ( ! $this->trace ) {
156
  return false;
165
 
166
  }
167
 
168
+ /**
169
+ * @param array<string, QM_Dispatcher> $dispatchers
170
+ * @param QM_Plugin $qm
171
+ * @return array<string, QM_Dispatcher>
172
+ */
173
  function register_qm_dispatcher_wp_die( array $dispatchers, QM_Plugin $qm ) {
174
  $dispatchers['wp_die'] = new QM_Dispatcher_WP_Die( $qm );
175
  return $dispatchers;
output/Headers.php CHANGED
@@ -6,7 +6,14 @@
6
  */
7
 
8
  abstract class QM_Output_Headers extends QM_Output {
 
 
 
 
9
 
 
 
 
10
  public function output() {
11
 
12
  $id = $this->collector->id;
6
  */
7
 
8
  abstract class QM_Output_Headers extends QM_Output {
9
+ /**
10
+ * @return array<string, mixed>
11
+ */
12
+ abstract public function get_output();
13
 
14
+ /**
15
+ * @return void
16
+ */
17
  public function output() {
18
 
19
  $id = $this->collector->id;
output/Html.php CHANGED
@@ -7,21 +7,32 @@
7
 
8
  abstract class QM_Output_Html extends QM_Output {
9
 
 
 
 
10
  protected static $file_link_format = null;
11
 
12
- protected $current_id = null;
 
 
 
 
 
 
 
13
  protected $current_name = null;
14
 
 
 
 
15
  public function name() {
16
- _deprecated_function(
17
- esc_html( get_class( $this->collector ) . '::name()' ),
18
- '3.5',
19
- esc_html( get_class( $this ) . '::name()' )
20
- );
21
-
22
- return $this->collector->name();
23
  }
24
 
 
 
 
 
25
  public function admin_menu( array $menu ) {
26
 
27
  $menu[ $this->collector->id() ] = $this->menu( array(
@@ -31,6 +42,9 @@ abstract class QM_Output_Html extends QM_Output {
31
 
32
  }
33
 
 
 
 
34
  public function get_output() {
35
  ob_start();
36
  // compat until I convert all the existing outputters to use `get_output()`
@@ -39,6 +53,11 @@ abstract class QM_Output_Html extends QM_Output {
39
  return $out;
40
  }
41
 
 
 
 
 
 
42
  protected function before_tabular_output( $id = null, $name = null ) {
43
  if ( null === $id ) {
44
  $id = $this->collector->id();
@@ -47,7 +66,7 @@ abstract class QM_Output_Html extends QM_Output {
47
  $name = $this->name();
48
  }
49
 
50
- $this->current_id = $id;
51
  $this->current_name = $name;
52
 
53
  printf(
@@ -64,6 +83,9 @@ abstract class QM_Output_Html extends QM_Output {
64
  );
65
  }
66
 
 
 
 
67
  protected function after_tabular_output() {
68
  echo '</table>';
69
  echo '</div>';
@@ -71,6 +93,11 @@ abstract class QM_Output_Html extends QM_Output {
71
  $this->output_concerns();
72
  }
73
 
 
 
 
 
 
74
  protected function before_non_tabular_output( $id = null, $name = null ) {
75
  if ( null === $id ) {
76
  $id = $this->collector->id();
@@ -79,7 +106,7 @@ abstract class QM_Output_Html extends QM_Output {
79
  $name = $this->name();
80
  }
81
 
82
- $this->current_id = $id;
83
  $this->current_name = $name;
84
 
85
  printf(
@@ -96,6 +123,9 @@ abstract class QM_Output_Html extends QM_Output {
96
  );
97
  }
98
 
 
 
 
99
  protected function after_non_tabular_output() {
100
  echo '</div>';
101
  echo '</div>';
@@ -103,6 +133,9 @@ abstract class QM_Output_Html extends QM_Output {
103
  $this->output_concerns();
104
  }
105
 
 
 
 
106
  protected function output_concerns() {
107
  $concerns = array(
108
  'concerned_actions' => array(
@@ -161,6 +194,11 @@ abstract class QM_Output_Html extends QM_Output {
161
  echo '</div>';
162
  }
163
 
 
 
 
 
 
164
  protected function before_debug_bar_output( $id = null, $name = null ) {
165
  if ( null === $id ) {
166
  $id = $this->collector->id();
@@ -181,10 +219,17 @@ abstract class QM_Output_Html extends QM_Output {
181
  );
182
  }
183
 
 
 
 
184
  protected function after_debug_bar_output() {
185
  echo '</div>';
186
  }
187
 
 
 
 
 
188
  protected function build_notice( $notice ) {
189
  $return = '<section>';
190
  $return .= '<div class="qm-notice">';
@@ -197,6 +242,10 @@ abstract class QM_Output_Html extends QM_Output {
197
  return $return;
198
  }
199
 
 
 
 
 
200
  public static function output_inner( $vars ) {
201
 
202
  echo '<table>';
@@ -237,15 +286,20 @@ abstract class QM_Output_Html extends QM_Output {
237
  * @param string[] $values Option values for this control.
238
  * @param string $label Label text for the filter control.
239
  * @param array $args {
240
- * @type string $highlight The name for the `data-` attributes that get highlighted by this control.
241
- * @type array $prepend Associative array of options to prepend to the list of values.
242
- * @type array $append Associative array of options to append to the list of values.
243
  * }
 
 
 
 
 
244
  * @return string Markup for the table filter controls.
245
  */
246
- protected function build_filter( $name, array $values, $label, $args = array() ) {
247
 
248
- if ( empty( $values ) ) {
249
  return esc_html( $label ); // Return label text, without being marked up as a label element.
250
  }
251
 
@@ -257,16 +311,17 @@ abstract class QM_Output_Html extends QM_Output {
257
 
258
  $args = array_merge( array(
259
  'highlight' => '',
260
- 'prepend' => array(),
261
- 'append' => array(),
 
262
  ), $args );
263
 
264
- $core_val = __( 'Core', 'query-monitor' );
265
  $core_key = array_search( $core_val, $values, true );
266
 
267
  if ( 'component' === $name && count( $values ) > 1 && false !== $core_key ) {
268
  $args['append'][ $core_val ] = $core_val;
269
- $args['append']['non-core'] = __( 'Non-Core', 'query-monitor' );
270
  unset( $values[ $core_key ] );
271
  }
272
 
@@ -275,7 +330,7 @@ abstract class QM_Output_Html extends QM_Output {
275
  $out = '<div class="qm-filter-container">';
276
  $out .= '<label for="' . esc_attr( $filter_id ) . '">' . esc_html( $label ) . '</label>';
277
  $out .= '<select id="' . esc_attr( $filter_id ) . '" class="qm-filter" data-filter="' . esc_attr( $name ) . '" data-highlight="' . esc_attr( $args['highlight'] ) . '">';
278
- $out .= '<option value="">' . esc_html_x( 'All', '"All" option for filters', 'query-monitor' ) . '</option>';
279
 
280
  if ( ! empty( $args['prepend'] ) ) {
281
  foreach ( $args['prepend'] as $value => $label ) {
@@ -339,10 +394,14 @@ abstract class QM_Output_Html extends QM_Output {
339
  return $out;
340
  }
341
 
 
 
 
 
342
  protected function menu( array $args ) {
343
 
344
  return array_merge( array(
345
- 'id' => esc_attr( "query-monitor-{$this->collector->id}" ),
346
  'href' => esc_attr( '#' . $this->collector->id() ),
347
  ), $args );
348
 
@@ -361,10 +420,10 @@ abstract class QM_Output_Html extends QM_Output {
361
  $sql = trim( $sql );
362
 
363
  $regex = 'ADD|AFTER|ALTER|AND|BEGIN|COMMIT|CREATE|DELETE|DESCRIBE|DO|DROP|ELSE|END|EXCEPT|EXPLAIN|FROM|GROUP|HAVING|INNER|INSERT|INTERSECT|LEFT|LIMIT|ON|OR|ORDER|OUTER|RENAME|REPLACE|RIGHT|ROLLBACK|SELECT|SET|SHOW|START|THEN|TRUNCATE|UNION|UPDATE|USE|USING|VALUES|WHEN|WHERE|XOR';
364
- $sql = preg_replace( '# (' . $regex . ') #', '<br> $1 ', $sql );
365
 
366
- $keywords = '\b(?:ACTION|ADD|AFTER|ALTER|AND|ASC|AS|AUTO_INCREMENT|BEGIN|BETWEEN|BIGINT|BINARY|BIT|BLOB|BOOLEAN|BOOL|BREAK|BY|CASE|COLLATE|COLUMNS?|COMMIT|CONTINUE|CREATE|DATA(?:BASES?)?|DATE(?:TIME)?|DECIMAL|DECLARE|DEC|DEFAULT|DELAYED|DELETE|DESCRIBE|DESC|DISTINCT|DOUBLE|DO|DROP|DUPLICATE|ELSE|END|ENUM|EXCEPT|EXISTS|EXPLAIN|FIELDS|FLOAT|FOREIGN|FOR|FROM|FULL|FUNCTION|GROUP|HAVING|IF|IGNORE|INDEX|INNER|INSERT|INTEGER|INTERSECT|INTERVAL|INTO|INT|IN|IS|JOIN|KEYS?|LEFT|LIKE|LIMIT|LONG(?:BLOB|TEXT)|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|NOT|NO|NULLIF|ON|ORDER|OR|OUTER|PRIMARY|PROC(?:EDURE)?|REGEXP|RENAME|REPLACE|RIGHT|RLIKE|ROLLBACK|SCHEMA|SELECT|SET|SHOW|SMALLINT|START|TABLES?|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TRUNCATE|UNION|UNIQUE|UNSIGNED|UPDATE|USE|USING|VALUES?|VAR(?:BINARY|CHAR)|WHEN|WHERE|WHILE|XOR)\b';
367
- $sql = preg_replace( '#' . $keywords . '#', '<b>$0</b>', $sql );
368
 
369
  return '<code>' . $sql . '</code>';
370
 
@@ -468,6 +527,9 @@ abstract class QM_Output_Html extends QM_Output {
468
  }
469
  }
470
 
 
 
 
471
  public static function get_file_link_format() {
472
  if ( ! isset( self::$file_link_format ) ) {
473
  $format = ini_get( 'xdebug.file_link_format' );
@@ -485,7 +547,7 @@ abstract class QM_Output_Html extends QM_Output {
485
  * @link https://querymonitor.com/blog/2019/02/clickable-stack-traces-and-function-names-in-query-monitor/
486
  * @since 3.0.0
487
  *
488
- * @param string $format The format of the clickable file link.
489
  */
490
  $format = apply_filters( 'qm/output/file_link_format', $format );
491
  if ( empty( $format ) ) {
@@ -498,6 +560,9 @@ abstract class QM_Output_Html extends QM_Output {
498
  return self::$file_link_format;
499
  }
500
 
 
 
 
501
  public static function get_file_path_map() {
502
  /**
503
  * Filters the file path mapping for clickable file links.
@@ -510,6 +575,9 @@ abstract class QM_Output_Html extends QM_Output {
510
  return apply_filters( 'qm/output/file_path_map', array() );
511
  }
512
 
 
 
 
513
  public static function has_clickable_links() {
514
  return ( false !== self::get_file_link_format() );
515
  }
7
 
8
  abstract class QM_Output_Html extends QM_Output {
9
 
10
+ /**
11
+ * @var string|false|null
12
+ */
13
  protected static $file_link_format = null;
14
 
15
+ /**
16
+ * @var string|null
17
+ */
18
+ protected $current_id = null;
19
+
20
+ /**
21
+ * @var string|null
22
+ */
23
  protected $current_name = null;
24
 
25
+ /**
26
+ * @return string
27
+ */
28
  public function name() {
29
+ return $this->collector->id;
 
 
 
 
 
 
30
  }
31
 
32
+ /**
33
+ * @param array<string, mixed[]> $menu
34
+ * @return array<string, mixed[]>
35
+ */
36
  public function admin_menu( array $menu ) {
37
 
38
  $menu[ $this->collector->id() ] = $this->menu( array(
42
 
43
  }
44
 
45
+ /**
46
+ * @return string
47
+ */
48
  public function get_output() {
49
  ob_start();
50
  // compat until I convert all the existing outputters to use `get_output()`
53
  return $out;
54
  }
55
 
56
+ /**
57
+ * @param string $id
58
+ * @param string $name
59
+ * @return void
60
+ */
61
  protected function before_tabular_output( $id = null, $name = null ) {
62
  if ( null === $id ) {
63
  $id = $this->collector->id();
66
  $name = $this->name();
67
  }
68
 
69
+ $this->current_id = $id;
70
  $this->current_name = $name;
71
 
72
  printf(
83
  );
84
  }
85
 
86
+ /**
87
+ * @return void
88
+ */
89
  protected function after_tabular_output() {
90
  echo '</table>';
91
  echo '</div>';
93
  $this->output_concerns();
94
  }
95
 
96
+ /**
97
+ * @param string $id
98
+ * @param string $name
99
+ * @return void
100
+ */
101
  protected function before_non_tabular_output( $id = null, $name = null ) {
102
  if ( null === $id ) {
103
  $id = $this->collector->id();
106
  $name = $this->name();
107
  }
108
 
109
+ $this->current_id = $id;
110
  $this->current_name = $name;
111
 
112
  printf(
123
  );
124
  }
125
 
126
+ /**
127
+ * @return void
128
+ */
129
  protected function after_non_tabular_output() {
130
  echo '</div>';
131
  echo '</div>';
133
  $this->output_concerns();
134
  }
135
 
136
+ /**
137
+ * @return void
138
+ */
139
  protected function output_concerns() {
140
  $concerns = array(
141
  'concerned_actions' => array(
194
  echo '</div>';
195
  }
196
 
197
+ /**
198
+ * @param string $id
199
+ * @param string $name
200
+ * @return void
201
+ */
202
  protected function before_debug_bar_output( $id = null, $name = null ) {
203
  if ( null === $id ) {
204
  $id = $this->collector->id();
219
  );
220
  }
221
 
222
+ /**
223
+ * @return void
224
+ */
225
  protected function after_debug_bar_output() {
226
  echo '</div>';
227
  }
228
 
229
+ /**
230
+ * @param string $notice
231
+ * @return string
232
+ */
233
  protected function build_notice( $notice ) {
234
  $return = '<section>';
235
  $return .= '<div class="qm-notice">';
242
  return $return;
243
  }
244
 
245
+ /**
246
+ * @param array<string, mixed> $vars
247
+ * @return void
248
+ */
249
  public static function output_inner( $vars ) {
250
 
251
  echo '<table>';
286
  * @param string[] $values Option values for this control.
287
  * @param string $label Label text for the filter control.
288
  * @param array $args {
289
+ * @type string $highlight The name for the `data-` attributes that get highlighted by this control.
290
+ * @type string[] $prepend Associative array of options to prepend to the list of values.
291
+ * @type string[] $append Associative array of options to append to the list of values.
292
  * }
293
+ * @phpstan-param array{
294
+ * highlight?: string,
295
+ * prepend?: array<string, string>,
296
+ * append?: array<string, string>,
297
+ * } $args
298
  * @return string Markup for the table filter controls.
299
  */
300
+ protected function build_filter( $name, $values, $label, $args = array() ) {
301
 
302
+ if ( empty( $values ) || ! is_array( $values ) ) {
303
  return esc_html( $label ); // Return label text, without being marked up as a label element.
304
  }
305
 
311
 
312
  $args = array_merge( array(
313
  'highlight' => '',
314
+ 'prepend' => array(),
315
+ 'append' => array(),
316
+ 'all' => _x( 'All', '"All" option for filters', 'query-monitor' ),
317
  ), $args );
318
 
319
+ $core_val = __( 'WordPress Core', 'query-monitor' );
320
  $core_key = array_search( $core_val, $values, true );
321
 
322
  if ( 'component' === $name && count( $values ) > 1 && false !== $core_key ) {
323
  $args['append'][ $core_val ] = $core_val;
324
+ $args['append']['non-core'] = __( 'Non-WordPress Core', 'query-monitor' );
325
  unset( $values[ $core_key ] );
326
  }
327
 
330
  $out = '<div class="qm-filter-container">';
331
  $out .= '<label for="' . esc_attr( $filter_id ) . '">' . esc_html( $label ) . '</label>';
332
  $out .= '<select id="' . esc_attr( $filter_id ) . '" class="qm-filter" data-filter="' . esc_attr( $name ) . '" data-highlight="' . esc_attr( $args['highlight'] ) . '">';
333
+ $out .= '<option value="">' . esc_html( $args['all'] ) . '</option>';
334
 
335
  if ( ! empty( $args['prepend'] ) ) {
336
  foreach ( $args['prepend'] as $value => $label ) {
394
  return $out;
395
  }
396
 
397
+ /**
398
+ * @param array<string, mixed> $args
399
+ * @return array<string, mixed>
400
+ */
401
  protected function menu( array $args ) {
402
 
403
  return array_merge( array(
404
+ 'id' => esc_attr( "query-monitor-{$this->collector->id}" ),
405
  'href' => esc_attr( '#' . $this->collector->id() ),
406
  ), $args );
407
 
420
  $sql = trim( $sql );
421
 
422
  $regex = 'ADD|AFTER|ALTER|AND|BEGIN|COMMIT|CREATE|DELETE|DESCRIBE|DO|DROP|ELSE|END|EXCEPT|EXPLAIN|FROM|GROUP|HAVING|INNER|INSERT|INTERSECT|LEFT|LIMIT|ON|OR|ORDER|OUTER|RENAME|REPLACE|RIGHT|ROLLBACK|SELECT|SET|SHOW|START|THEN|TRUNCATE|UNION|UPDATE|USE|USING|VALUES|WHEN|WHERE|XOR';
423
+ $sql = preg_replace( '# (' . $regex . ') #', '<br> $1 ', $sql );
424
 
425
+ $keywords = '\b(?:ACTION|ADD|AFTER|ALTER|AND|ASC|AS|AUTO_INCREMENT|BEGIN|BETWEEN|BIGINT|BINARY|BIT|BLOB|BOOLEAN|BOOL|BREAK|BY|CASE|COLLATE|COLUMNS?|COMMIT|CONTINUE|CREATE|DATA(?:BASES?)?|DATE(?:TIME)?|DECIMAL|DECLARE|DEC|DEFAULT|DELAYED|DELETE|DESCRIBE|DESC|DISTINCT|DOUBLE|DO|DROP|DUPLICATE|ELSE|END|ENUM|EXCEPT|EXISTS|EXPLAIN|FIELDS|FLOAT|FORCE|FOREIGN|FORCE|FOR|FROM|FULL|FUNCTION|GROUP|HAVING|IF|IGNORE|INDEX|INNER|INSERT|INTEGER|INTERSECT|INTERVAL|INTO|INT|IN|IS|JOIN|KEYS?|LEFT|LIKE|LIMIT|LONG(?:BLOB|TEXT)|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|NOT|NO|NULLIF|ON|ORDER|OR|OUTER|PRIMARY|PROC(?:EDURE)?|REGEXP|RENAME|REPLACE|RIGHT|RLIKE|ROLLBACK|SCHEMA|SELECT|SET|SHOW|SMALLINT|START|TABLES?|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TRUNCATE|UNION|UNIQUE|UNSIGNED|UPDATE|USE|USING|VALUES?|VAR(?:BINARY|CHAR)|WHEN|WHERE|WHILE|XOR)\b';
426
+ $sql = preg_replace( '#' . $keywords . '#', '<b>$0</b>', $sql );
427
 
428
  return '<code>' . $sql . '</code>';
429
 
527
  }
528
  }
529
 
530
+ /**
531
+ * @return string|false
532
+ */
533
  public static function get_file_link_format() {
534
  if ( ! isset( self::$file_link_format ) ) {
535
  $format = ini_get( 'xdebug.file_link_format' );
547
  * @link https://querymonitor.com/blog/2019/02/clickable-stack-traces-and-function-names-in-query-monitor/
548
  * @since 3.0.0
549
  *
550
+ * @param string|false $format The format of the clickable file link, or false if there is none.
551
  */
552
  $format = apply_filters( 'qm/output/file_link_format', $format );
553
  if ( empty( $format ) ) {
560
  return self::$file_link_format;
561
  }
562
 
563
+ /**
564
+ * @return array<string, string>
565
+ */
566
  public static function get_file_path_map() {
567
  /**
568
  * Filters the file path mapping for clickable file links.
575
  return apply_filters( 'qm/output/file_path_map', array() );
576
  }
577
 
578
+ /**
579
+ * @return bool
580
+ */
581
  public static function has_clickable_links() {
582
  return ( false !== self::get_file_link_format() );
583
  }
output/Raw.php CHANGED
@@ -6,9 +6,8 @@
6
  */
7
 
8
  abstract class QM_Output_Raw extends QM_Output {
9
-
10
- public function output() {
11
- return $this->get_output();
12
- }
13
-
14
  }
6
  */
7
 
8
  abstract class QM_Output_Raw extends QM_Output {
9
+ /**
10
+ * @return array<string, mixed>
11
+ */
12
+ abstract public function get_output();
 
13
  }
output/headers/overview.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Headers_Overview extends QM_Output_Headers {
11
 
@@ -16,9 +18,12 @@ class QM_Output_Headers_Overview extends QM_Output_Headers {
16
  */
17
  protected $collector;
18
 
 
 
 
19
  public function get_output() {
20
 
21
- $data = $this->collector->get_data();
22
  $headers = array();
23
 
24
  $headers['time_taken'] = number_format_i18n( $data['time_taken'], 4 );
@@ -49,6 +54,11 @@ class QM_Output_Headers_Overview extends QM_Output_Headers {
49
 
50
  }
51
 
 
 
 
 
 
52
  function register_qm_output_headers_overview( array $output, QM_Collectors $collectors ) {
53
  $collector = QM_Collectors::get( 'overview' );
54
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Headers_Overview extends QM_Output_Headers {
13
 
18
  */
19
  protected $collector;
20
 
21
+ /**
22
+ * @return array<string, mixed>
23
+ */
24
  public function get_output() {
25
 
26
+ $data = $this->collector->get_data();
27
  $headers = array();
28
 
29
  $headers['time_taken'] = number_format_i18n( $data['time_taken'], 4 );
54
 
55
  }
56
 
57
+ /**
58
+ * @param array<string, QM_Output> $output
59
+ * @param QM_Collectors $collectors
60
+ * @return array<string, QM_Output>
61
+ */
62
  function register_qm_output_headers_overview( array $output, QM_Collectors $collectors ) {
63
  $collector = QM_Collectors::get( 'overview' );
64
  if ( $collector ) {
output/headers/php_errors.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Headers_PHP_Errors extends QM_Output_Headers {
11
 
@@ -16,9 +18,12 @@ class QM_Output_Headers_PHP_Errors extends QM_Output_Headers {
16
  */
17
  protected $collector;
18
 
 
 
 
19
  public function get_output() {
20
 
21
- $data = $this->collector->get_data();
22
  $headers = array();
23
 
24
  if ( empty( $data['errors'] ) ) {
@@ -37,23 +42,23 @@ class QM_Output_Headers_PHP_Errors extends QM_Output_Headers {
37
  # separately in each output.
38
  if ( $error['trace'] ) {
39
  $component = $error['trace']->get_component()->name;
40
- $stack = $error['trace']->get_stack();
41
  } else {
42
  $component = __( 'Unknown', 'query-monitor' );
43
- $stack = array();
44
  }
45
 
46
  $output_error = array(
47
- 'key' => $error_key,
48
- 'type' => $error['type'],
49
- 'message' => $error['message'],
50
- 'file' => QM_Util::standard_dir( $error['file'], '' ),
51
- 'line' => $error['line'],
52
- 'stack' => $stack,
53
  'component' => $component,
54
  );
55
 
56
- $key = sprintf( 'error-%d', $count );
57
  $headers[ $key ] = json_encode( $output_error );
58
 
59
  }
@@ -69,6 +74,11 @@ class QM_Output_Headers_PHP_Errors extends QM_Output_Headers {
69
 
70
  }
71
 
 
 
 
 
 
72
  function register_qm_output_headers_php_errors( array $output, QM_Collectors $collectors ) {
73
  $collector = QM_Collectors::get( 'php_errors' );
74
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Headers_PHP_Errors extends QM_Output_Headers {
13
 
18
  */
19
  protected $collector;
20
 
21
+ /**
22
+ * @return array<string, mixed>
23
+ */
24
  public function get_output() {
25
 
26
+ $data = $this->collector->get_data();
27
  $headers = array();
28
 
29
  if ( empty( $data['errors'] ) ) {
42
  # separately in each output.
43
  if ( $error['trace'] ) {
44
  $component = $error['trace']->get_component()->name;
45
+ $stack = $error['trace']->get_stack();
46
  } else {
47
  $component = __( 'Unknown', 'query-monitor' );
48
+ $stack = array();
49
  }
50
 
51
  $output_error = array(
52
+ 'key' => $error_key,
53
+ 'type' => $error['type'],
54
+ 'message' => $error['message'],
55
+ 'file' => QM_Util::standard_dir( $error['file'], '' ),
56
+ 'line' => $error['line'],
57
+ 'stack' => $stack,
58
  'component' => $component,
59
  );
60
 
61
+ $key = sprintf( 'error-%d', $count );
62
  $headers[ $key ] = json_encode( $output_error );
63
 
64
  }
74
 
75
  }
76
 
77
+ /**
78
+ * @param array<string, QM_Output> $output
79
+ * @param QM_Collectors $collectors
80
+ * @return array<string, QM_Output>
81
+ */
82
  function register_qm_output_headers_php_errors( array $output, QM_Collectors $collectors ) {
83
  $collector = QM_Collectors::get( 'php_errors' );
84
  if ( $collector ) {
output/headers/redirects.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Headers_Redirects extends QM_Output_Headers {
11
 
@@ -16,9 +18,12 @@ class QM_Output_Headers_Redirects extends QM_Output_Headers {
16
  */
17
  protected $collector;
18
 
 
 
 
19
  public function get_output() {
20
 
21
- $data = $this->collector->get_data();
22
  $headers = array();
23
 
24
  if ( empty( $data['trace'] ) ) {
@@ -32,6 +37,11 @@ class QM_Output_Headers_Redirects extends QM_Output_Headers {
32
 
33
  }
34
 
 
 
 
 
 
35
  function register_qm_output_headers_redirects( array $output, QM_Collectors $collectors ) {
36
  $collector = QM_Collectors::get( 'redirects' );
37
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Headers_Redirects extends QM_Output_Headers {
13
 
18
  */
19
  protected $collector;
20
 
21
+ /**
22
+ * @return array<string, mixed>
23
+ */
24
  public function get_output() {
25
 
26
+ $data = $this->collector->get_data();
27
  $headers = array();
28
 
29
  if ( empty( $data['trace'] ) ) {
37
 
38
  }
39
 
40
+ /**
41
+ * @param array<string, QM_Output> $output
42
+ * @param QM_Collectors $collectors
43
+ * @return array<string, QM_Output>
44
+ */
45
  function register_qm_output_headers_redirects( array $output, QM_Collectors $collectors ) {
46
  $collector = QM_Collectors::get( 'redirects' );
47
  if ( $collector ) {
output/html/admin.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_Admin extends QM_Output_Html {
11
 
@@ -21,10 +23,16 @@ class QM_Output_Html_Admin extends QM_Output_Html {
21
  add_filter( 'qm/output/menus', array( $this, 'admin_menu' ), 60 );
22
  }
23
 
 
 
 
24
  public function name() {
25
  return __( 'Admin Screen', 'query-monitor' );
26
  }
27
 
 
 
 
28
  public function output() {
29
 
30
  $data = $this->collector->get_data();
@@ -111,6 +119,11 @@ class QM_Output_Html_Admin extends QM_Output_Html {
111
 
112
  }
113
 
 
 
 
 
 
114
  function register_qm_output_html_admin( array $output, QM_Collectors $collectors ) {
115
  if ( ! is_admin() ) {
116
  return $output;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_Admin extends QM_Output_Html {
13
 
23
  add_filter( 'qm/output/menus', array( $this, 'admin_menu' ), 60 );
24
  }
25
 
26
+ /**
27
+ * @return string
28
+ */
29
  public function name() {
30
  return __( 'Admin Screen', 'query-monitor' );
31
  }
32
 
33
+ /**
34
+ * @return void
35
+ */
36
  public function output() {
37
 
38
  $data = $this->collector->get_data();
119
 
120
  }
121
 
122
+ /**
123
+ * @param array<string, QM_Output> $output
124
+ * @param QM_Collectors $collectors
125
+ * @return array<string, QM_Output>
126
+ */
127
  function register_qm_output_html_admin( array $output, QM_Collectors $collectors ) {
128
  if ( ! is_admin() ) {
129
  return $output;
output/html/assets.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  abstract class QM_Output_Html_Assets extends QM_Output_Html {
11
 
@@ -18,30 +20,40 @@ abstract class QM_Output_Html_Assets extends QM_Output_Html {
18
 
19
  public function __construct( QM_Collector $collector ) {
20
  parent::__construct( $collector );
21
- add_filter( 'qm/output/menus', array( $this, 'admin_menu' ), 70 );
22
  add_filter( 'qm/output/menu_class', array( $this, 'admin_class' ) );
23
  }
24
 
 
 
 
25
  abstract public function get_type_labels();
26
 
 
 
 
27
  public function output() {
28
 
29
  $data = $this->collector->get_data();
 
30
 
31
  if ( empty( $data['assets'] ) ) {
 
 
 
 
32
  return;
33
  }
34
 
35
  $position_labels = array(
36
  // @TODO translator comments or context:
37
  'missing' => __( 'Missing', 'query-monitor' ),
38
- 'broken' => __( 'Missing Dependencies', 'query-monitor' ),
39
- 'header' => __( 'Header', 'query-monitor' ),
40
- 'footer' => __( 'Footer', 'query-monitor' ),
41
  );
42
 
43
- $type_label = $this->get_type_labels();
44
- $this->type = $this->collector->get_dependency_type();
45
 
46
  $hosts = array(
47
  __( 'Other', 'query-monitor' ),
@@ -59,14 +71,14 @@ abstract class QM_Output_Html_Assets extends QM_Output_Html {
59
  'local' => $data['host'],
60
  ),
61
  );
62
- echo $this->build_filter( $this->type . '-host', $hosts, __( 'Host', 'query-monitor' ), $args ); // WPCS: XSS ok.
63
  echo '</th>';
64
  echo '<th scope="col">' . esc_html__( 'Source', 'query-monitor' ) . '</th>';
65
  echo '<th scope="col" class="qm-filterable-column">';
66
- echo $this->build_filter( $this->type . '-dependencies', $data['dependencies'], __( 'Dependencies', 'query-monitor' ) ); // WPCS: XSS ok.
67
  echo '</th>';
68
  echo '<th scope="col" class="qm-filterable-column">';
69
- echo $this->build_filter( $this->type . '-dependents', $data['dependents'], __( 'Dependents', 'query-monitor' ) ); // WPCS: XSS ok.
70
  echo '</th>';
71
  echo '<th scope="col">' . esc_html__( 'Version', 'query-monitor' ) . '</th>';
72
  echo '</tr>';
@@ -100,14 +112,20 @@ abstract class QM_Output_Html_Assets extends QM_Output_Html {
100
  $this->after_tabular_output();
101
  }
102
 
 
 
 
 
 
 
103
  protected function dependency_row( $handle, array $asset, $label ) {
104
  $data = $this->collector->get_data();
105
 
106
- $highlight_deps = array_map( array( $this, '_prefix_type' ), $asset['dependencies'] );
107
  $highlight_dependents = array_map( array( $this, '_prefix_type' ), $asset['dependents'] );
108
 
109
  $dependencies_list = implode( ' ', $asset['dependencies'] );
110
- $dependents_list = implode( ' ', $asset['dependents'] );
111
 
112
  $dependency_output = array();
113
 
@@ -134,7 +152,9 @@ abstract class QM_Output_Html_Assets extends QM_Output_Html {
134
  $class = 'qm-warn';
135
  }
136
 
137
- echo '<tr data-qm-subject="' . esc_attr( $this->type . '-' . $handle ) . '" data-qm-' . esc_attr( $this->type ) . '-host="' . esc_attr( $qm_host ) . '" data-qm-' . esc_attr( $this->type ) . '-dependents="' . esc_attr( $dependents_list ) . '" data-qm-' . esc_attr( $this->type ) . '-dependencies="' . esc_attr( $dependencies_list ) . '" class="' . esc_attr( $class ) . '">';
 
 
138
  echo '<td class="qm-nowrap">';
139
 
140
  if ( $asset['warning'] ) {
@@ -144,7 +164,7 @@ abstract class QM_Output_Html_Assets extends QM_Output_Html {
144
  echo esc_html( $label );
145
  echo '</td>';
146
 
147
- $host = $asset['host'];
148
  $parts = explode( '.', $host );
149
 
150
  foreach ( $parts as $k => $part ) {
@@ -155,6 +175,10 @@ abstract class QM_Output_Html_Assets extends QM_Output_Html {
155
 
156
  $host = implode( '.', $parts );
157
 
 
 
 
 
158
  echo '<td class="qm-nowrap qm-ltr">' . esc_html( $handle ) . '</td>';
159
  echo '<td class="qm-nowrap qm-ltr">' . esc_html( $host ) . '</td>';
160
  echo '<td class="qm-ltr">';
@@ -192,10 +216,18 @@ abstract class QM_Output_Html_Assets extends QM_Output_Html {
192
  echo '</tr>';
193
  }
194
 
 
 
 
 
195
  public function _prefix_type( $val ) {
196
- return $this->type . '-' . $val;
197
  }
198
 
 
 
 
 
199
  public function admin_class( array $class ) {
200
 
201
  $data = $this->collector->get_data();
@@ -208,6 +240,10 @@ abstract class QM_Output_Html_Assets extends QM_Output_Html {
208
 
209
  }
210
 
 
 
 
 
211
  public function admin_menu( array $menu ) {
212
 
213
  $data = $this->collector->get_data();
@@ -224,15 +260,15 @@ abstract class QM_Output_Html_Assets extends QM_Output_Html {
224
 
225
  $args = array(
226
  'title' => esc_html( $label ),
227
- 'id' => esc_attr( "query-monitor-{$this->collector->id}" ),
228
- 'href' => esc_attr( '#' . $this->collector->id() ),
229
  );
230
 
231
  if ( ! empty( $data['broken'] ) || ! empty( $data['missing'] ) ) {
232
  $args['meta']['classname'] = 'qm-error';
233
  }
234
 
235
- $id = $this->collector->id();
236
  $menu[ $id ] = $this->menu( $args );
237
 
238
  return $menu;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  abstract class QM_Output_Html_Assets extends QM_Output_Html {
13
 
20
 
21
  public function __construct( QM_Collector $collector ) {
22
  parent::__construct( $collector );
23
+ add_filter( 'qm/output/menus', array( $this, 'admin_menu' ), 70 );
24
  add_filter( 'qm/output/menu_class', array( $this, 'admin_class' ) );
25
  }
26
 
27
+ /**
28
+ * @return array<string, string>
29
+ */
30
  abstract public function get_type_labels();
31
 
32
+ /**
33
+ * @return void
34
+ */
35
  public function output() {
36
 
37
  $data = $this->collector->get_data();
38
+ $type_label = $this->get_type_labels();
39
 
40
  if ( empty( $data['assets'] ) ) {
41
+ $this->before_non_tabular_output();
42
+ $notice = esc_html( $type_label['none'] );
43
+ echo $this->build_notice( $notice ); // WPCS: XSS ok.
44
+ $this->after_non_tabular_output();
45
  return;
46
  }
47
 
48
  $position_labels = array(
49
  // @TODO translator comments or context:
50
  'missing' => __( 'Missing', 'query-monitor' ),
51
+ 'broken' => __( 'Missing Dependencies', 'query-monitor' ),
52
+ 'header' => __( 'Header', 'query-monitor' ),
53
+ 'footer' => __( 'Footer', 'query-monitor' ),
54
  );
55
 
56
+ $type = $this->collector->get_dependency_type();
 
57
 
58
  $hosts = array(
59
  __( 'Other', 'query-monitor' ),
71
  'local' => $data['host'],
72
  ),
73
  );
74
+ echo $this->build_filter( $type . '-host', $hosts, __( 'Host', 'query-monitor' ), $args ); // WPCS: XSS ok.
75
  echo '</th>';
76
  echo '<th scope="col">' . esc_html__( 'Source', 'query-monitor' ) . '</th>';
77
  echo '<th scope="col" class="qm-filterable-column">';
78
+ echo $this->build_filter( $type . '-dependencies', $data['dependencies'], __( 'Dependencies', 'query-monitor' ) ); // WPCS: XSS ok.
79
  echo '</th>';
80
  echo '<th scope="col" class="qm-filterable-column">';
81
+ echo $this->build_filter( $type . '-dependents', $data['dependents'], __( 'Dependents', 'query-monitor' ) ); // WPCS: XSS ok.
82
  echo '</th>';
83
  echo '<th scope="col">' . esc_html__( 'Version', 'query-monitor' ) . '</th>';
84
  echo '</tr>';
112
  $this->after_tabular_output();
113
  }
114
 
115
+ /**
116
+ * @param string $handle
117
+ * @param array<string, mixed> $asset
118
+ * @param string $label
119
+ * @return void
120
+ */
121
  protected function dependency_row( $handle, array $asset, $label ) {
122
  $data = $this->collector->get_data();
123
 
124
+ $highlight_deps = array_map( array( $this, '_prefix_type' ), $asset['dependencies'] );
125
  $highlight_dependents = array_map( array( $this, '_prefix_type' ), $asset['dependents'] );
126
 
127
  $dependencies_list = implode( ' ', $asset['dependencies'] );
128
+ $dependents_list = implode( ' ', $asset['dependents'] );
129
 
130
  $dependency_output = array();
131
 
152
  $class = 'qm-warn';
153
  }
154
 
155
+ $type = $this->collector->get_dependency_type();
156
+
157
+ echo '<tr data-qm-subject="' . esc_attr( $type . '-' . $handle ) . '" data-qm-' . esc_attr( $type ) . '-host="' . esc_attr( $qm_host ) . '" data-qm-' . esc_attr( $type ) . '-dependents="' . esc_attr( $dependents_list ) . '" data-qm-' . esc_attr( $type ) . '-dependencies="' . esc_attr( $dependencies_list ) . '" class="' . esc_attr( $class ) . '">';
158
  echo '<td class="qm-nowrap">';
159
 
160
  if ( $asset['warning'] ) {
164
  echo esc_html( $label );
165
  echo '</td>';
166
 
167
+ $host = $asset['host'];
168
  $parts = explode( '.', $host );
169
 
170
  foreach ( $parts as $k => $part ) {
175
 
176
  $host = implode( '.', $parts );
177
 
178
+ if ( ! empty( $asset['port'] ) ) {
179
+ $host = "{$host}:{$asset['port']}";
180
+ }
181
+
182
  echo '<td class="qm-nowrap qm-ltr">' . esc_html( $handle ) . '</td>';
183
  echo '<td class="qm-nowrap qm-ltr">' . esc_html( $host ) . '</td>';
184
  echo '<td class="qm-ltr">';
216
  echo '</tr>';
217
  }
218
 
219
+ /**
220
+ * @param string $val
221
+ * @return string
222
+ */
223
  public function _prefix_type( $val ) {
224
+ return $this->collector->get_dependency_type() . '-' . $val;
225
  }
226
 
227
+ /**
228
+ * @param array<int, string> $class
229
+ * @return array<int, string>
230
+ */
231
  public function admin_class( array $class ) {
232
 
233
  $data = $this->collector->get_data();
240
 
241
  }
242
 
243
+ /**
244
+ * @param array<string, mixed[]> $menu
245
+ * @return array<string, mixed[]>
246
+ */
247
  public function admin_menu( array $menu ) {
248
 
249
  $data = $this->collector->get_data();
260
 
261
  $args = array(
262
  'title' => esc_html( $label ),
263
+ 'id' => esc_attr( "query-monitor-{$this->collector->id}" ),
264
+ 'href' => esc_attr( '#' . $this->collector->id() ),
265
  );
266
 
267
  if ( ! empty( $data['broken'] ) || ! empty( $data['missing'] ) ) {
268
  $args['meta']['classname'] = 'qm-error';
269
  }
270
 
271
+ $id = $this->collector->id();
272
  $menu[ $id ] = $this->menu( $args );
273
 
274
  return $menu;
output/html/assets_scripts.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_Assets_Scripts extends QM_Output_Html_Assets {
11
 
@@ -16,22 +18,34 @@ class QM_Output_Html_Assets_Scripts extends QM_Output_Html_Assets {
16
  */
17
  protected $collector;
18
 
 
 
 
19
  public function name() {
20
  return __( 'Scripts', 'query-monitor' );
21
  }
22
 
 
 
 
23
  public function get_type_labels() {
24
  return array(
25
  /* translators: %s: Total number of enqueued scripts */
26
- 'total' => _x( 'Total: %s', 'Enqueued scripts', 'query-monitor' ),
27
  'plural' => __( 'Scripts', 'query-monitor' ),
28
  /* translators: %s: Total number of enqueued scripts */
29
- 'count' => _x( 'Scripts (%s)', 'Enqueued scripts', 'query-monitor' ),
 
30
  );
31
  }
32
 
33
  }
34
 
 
 
 
 
 
35
  function register_qm_output_html_assets_scripts( array $output, QM_Collectors $collectors ) {
36
  $collector = QM_Collectors::get( 'assets_scripts' );
37
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_Assets_Scripts extends QM_Output_Html_Assets {
13
 
18
  */
19
  protected $collector;
20
 
21
+ /**
22
+ * @return string
23
+ */
24
  public function name() {
25
  return __( 'Scripts', 'query-monitor' );
26
  }
27
 
28
+ /**
29
+ * @return array<string, string>
30
+ */
31
  public function get_type_labels() {
32
  return array(
33
  /* translators: %s: Total number of enqueued scripts */
34
+ 'total' => _x( 'Total: %s', 'Enqueued scripts', 'query-monitor' ),
35
  'plural' => __( 'Scripts', 'query-monitor' ),
36
  /* translators: %s: Total number of enqueued scripts */
37
+ 'count' => _x( 'Scripts (%s)', 'Enqueued scripts', 'query-monitor' ),
38
+ 'none' => __( 'No JavaScript files were enqueued.', 'query-monitor' ),
39
  );
40
  }
41
 
42
  }
43
 
44
+ /**
45
+ * @param array<string, QM_Output> $output
46
+ * @param QM_Collectors $collectors
47
+ * @return array<string, QM_Output>
48
+ */
49
  function register_qm_output_html_assets_scripts( array $output, QM_Collectors $collectors ) {
50
  $collector = QM_Collectors::get( 'assets_scripts' );
51
  if ( $collector ) {
output/html/assets_styles.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_Assets_Styles extends QM_Output_Html_Assets {
11
 
@@ -16,22 +18,34 @@ class QM_Output_Html_Assets_Styles extends QM_Output_Html_Assets {
16
  */
17
  protected $collector;
18
 
 
 
 
19
  public function name() {
20
  return __( 'Styles', 'query-monitor' );
21
  }
22
 
 
 
 
23
  public function get_type_labels() {
24
  return array(
25
  /* translators: %s: Total number of enqueued styles */
26
- 'total' => _x( 'Total: %s', 'Enqueued styles', 'query-monitor' ),
27
  'plural' => __( 'Styles', 'query-monitor' ),
28
  /* translators: %s: Total number of enqueued styles */
29
- 'count' => _x( 'Styles (%s)', 'Enqueued styles', 'query-monitor' ),
 
30
  );
31
  }
32
 
33
  }
34
 
 
 
 
 
 
35
  function register_qm_output_html_assets_styles( array $output, QM_Collectors $collectors ) {
36
  $collector = QM_Collectors::get( 'assets_styles' );
37
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_Assets_Styles extends QM_Output_Html_Assets {
13
 
18
  */
19
  protected $collector;
20
 
21
+ /**
22
+ * @return string
23
+ */
24
  public function name() {
25
  return __( 'Styles', 'query-monitor' );
26
  }
27
 
28
+ /**
29
+ * @return array<string, string>
30
+ */
31
  public function get_type_labels() {
32
  return array(
33
  /* translators: %s: Total number of enqueued styles */
34
+ 'total' => _x( 'Total: %s', 'Enqueued styles', 'query-monitor' ),
35
  'plural' => __( 'Styles', 'query-monitor' ),
36
  /* translators: %s: Total number of enqueued styles */
37
+ 'count' => _x( 'Styles (%s)', 'Enqueued styles', 'query-monitor' ),
38
+ 'none' => __( 'No CSS files were enqueued.', 'query-monitor' ),
39
  );
40
  }
41
 
42
  }
43
 
44
+ /**
45
+ * @param array<string, QM_Output> $output
46
+ * @param QM_Collectors $collectors
47
+ * @return array<string, QM_Output>
48
+ */
49
  function register_qm_output_html_assets_styles( array $output, QM_Collectors $collectors ) {
50
  $collector = QM_Collectors::get( 'assets_styles' );
51
  if ( $collector ) {
output/html/block_editor.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_Block_Editor extends QM_Output_Html {
11
 
@@ -21,10 +23,16 @@ class QM_Output_Html_Block_Editor extends QM_Output_Html {
21
  add_filter( 'qm/output/menus', array( $this, 'admin_menu' ), 55 );
22
  }
23
 
 
 
 
24
  public function name() {
25
  return __( 'Blocks', 'query-monitor' );
26
  }
27
 
 
 
 
28
  public function output() {
29
  $data = $this->collector->get_data();
30
 
@@ -101,25 +109,31 @@ class QM_Output_Html_Block_Editor extends QM_Output_Html {
101
  $this->after_tabular_output();
102
  }
103
 
 
 
 
 
 
 
104
  protected static function render_block( $i, array $block, array $data ) {
105
- $block_error = false;
106
- $row_class = '';
107
  $referenced_post = null;
108
  $referenced_type = null;
109
- $referenced_pto = null;
110
- $error_message = null;
111
 
112
  if ( 'core/block' === $block['blockName'] && ! empty( $block['attrs']['ref'] ) ) {
113
  $referenced_post = get_post( $block['attrs']['ref'] );
114
 
115
  if ( ! $referenced_post ) {
116
- $block_error = true;
117
  $error_message = esc_html__( 'Referenced block does not exist.', 'query-monitor' );
118
  } else {
119
  $referenced_type = $referenced_post->post_type;
120
- $referenced_pto = get_post_type_object( $referenced_type );
121
  if ( 'wp_block' !== $referenced_type ) {
122
- $block_error = true;
123
  $error_message = sprintf(
124
  /* translators: %1$s: Erroneous post type name, %2$s: WordPress block post type name */
125
  esc_html__( 'Referenced post is of type %1$s instead of %2$s.', 'query-monitor' ),
@@ -131,26 +145,26 @@ class QM_Output_Html_Block_Editor extends QM_Output_Html {
131
  }
132
 
133
  $media_blocks = array(
134
- 'core/audio' => 'id',
135
- 'core/cover' => 'id',
136
  'core/cover-image' => 'id',
137
- 'core/file' => 'id',
138
- 'core/image' => 'id',
139
- 'core/media-text' => 'mediaId', // (╯°□°)╯︵ ┻━┻
140
- 'core/video' => 'id',
141
  );
142
 
143
  if ( isset( $media_blocks[ $block['blockName'] ] ) && is_array( $block['attrs'] ) && ! empty( $block['attrs'][ $media_blocks[ $block['blockName'] ] ] ) ) {
144
  $referenced_post = get_post( $block['attrs'][ $media_blocks[ $block['blockName'] ] ] );
145
 
146
  if ( ! $referenced_post ) {
147
- $block_error = true;
148
  $error_message = esc_html__( 'Referenced media does not exist.', 'query-monitor' );
149
  } else {
150
  $referenced_type = $referenced_post->post_type;
151
- $referenced_pto = get_post_type_object( $referenced_type );
152
  if ( 'attachment' !== $referenced_type ) {
153
- $block_error = true;
154
  $error_message = sprintf(
155
  /* translators: %1$s: Erroneous post type name, %2$s: WordPress attachment post type name */
156
  esc_html__( 'Referenced media is of type %1$s instead of %2$s.', 'query-monitor' ),
@@ -284,6 +298,10 @@ class QM_Output_Html_Block_Editor extends QM_Output_Html {
284
  }
285
  }
286
 
 
 
 
 
287
  public function admin_menu( array $menu ) {
288
  $data = $this->collector->get_data();
289
 
@@ -300,6 +318,11 @@ class QM_Output_Html_Block_Editor extends QM_Output_Html {
300
 
301
  }
302
 
 
 
 
 
 
303
  function register_qm_output_html_block_editor( array $output, QM_Collectors $collectors ) {
304
  $collector = QM_Collectors::get( 'block_editor' );
305
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_Block_Editor extends QM_Output_Html {
13
 
23
  add_filter( 'qm/output/menus', array( $this, 'admin_menu' ), 55 );
24
  }
25
 
26
+ /**
27
+ * @return string
28
+ */
29
  public function name() {
30
  return __( 'Blocks', 'query-monitor' );
31
  }
32
 
33
+ /**
34
+ * @return void
35
+ */
36
  public function output() {
37
  $data = $this->collector->get_data();
38
 
109
  $this->after_tabular_output();
110
  }
111
 
112
+ /**
113
+ * @param int|string $i
114
+ * @param array<string, mixed> $block
115
+ * @param array<string, mixed> $data
116
+ * @return void
117
+ */
118
  protected static function render_block( $i, array $block, array $data ) {
119
+ $block_error = false;
120
+ $row_class = '';
121
  $referenced_post = null;
122
  $referenced_type = null;
123
+ $referenced_pto = null;
124
+ $error_message = null;
125
 
126
  if ( 'core/block' === $block['blockName'] && ! empty( $block['attrs']['ref'] ) ) {
127
  $referenced_post = get_post( $block['attrs']['ref'] );
128
 
129
  if ( ! $referenced_post ) {
130
+ $block_error = true;
131
  $error_message = esc_html__( 'Referenced block does not exist.', 'query-monitor' );
132
  } else {
133
  $referenced_type = $referenced_post->post_type;
134
+ $referenced_pto = get_post_type_object( $referenced_type );
135
  if ( 'wp_block' !== $referenced_type ) {
136
+ $block_error = true;
137
  $error_message = sprintf(
138
  /* translators: %1$s: Erroneous post type name, %2$s: WordPress block post type name */
139
  esc_html__( 'Referenced post is of type %1$s instead of %2$s.', 'query-monitor' ),
145
  }
146
 
147
  $media_blocks = array(
148
+ 'core/audio' => 'id',
149
+ 'core/cover' => 'id',
150
  'core/cover-image' => 'id',
151
+ 'core/file' => 'id',
152
+ 'core/image' => 'id',
153
+ 'core/media-text' => 'mediaId', // (╯°□°)╯︵ ┻━┻
154
+ 'core/video' => 'id',
155
  );
156
 
157
  if ( isset( $media_blocks[ $block['blockName'] ] ) && is_array( $block['attrs'] ) && ! empty( $block['attrs'][ $media_blocks[ $block['blockName'] ] ] ) ) {
158
  $referenced_post = get_post( $block['attrs'][ $media_blocks[ $block['blockName'] ] ] );
159
 
160
  if ( ! $referenced_post ) {
161
+ $block_error = true;
162
  $error_message = esc_html__( 'Referenced media does not exist.', 'query-monitor' );
163
  } else {
164
  $referenced_type = $referenced_post->post_type;
165
+ $referenced_pto = get_post_type_object( $referenced_type );
166
  if ( 'attachment' !== $referenced_type ) {
167
+ $block_error = true;
168
  $error_message = sprintf(
169
  /* translators: %1$s: Erroneous post type name, %2$s: WordPress attachment post type name */
170
  esc_html__( 'Referenced media is of type %1$s instead of %2$s.', 'query-monitor' ),
298
  }
299
  }
300
 
301
+ /**
302
+ * @param array<string, mixed[]> $menu
303
+ * @return array<string, mixed[]>
304
+ */
305
  public function admin_menu( array $menu ) {
306
  $data = $this->collector->get_data();
307
 
318
 
319
  }
320
 
321
+ /**
322
+ * @param array<string, QM_Output> $output
323
+ * @param QM_Collectors $collectors
324
+ * @return array<string, QM_Output>
325
+ */
326
  function register_qm_output_html_block_editor( array $output, QM_Collectors $collectors ) {
327
  $collector = QM_Collectors::get( 'block_editor' );
328
  if ( $collector ) {
output/html/caps.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_Caps extends QM_Output_Html {
11
 
@@ -21,10 +23,16 @@ class QM_Output_Html_Caps extends QM_Output_Html {
21
  add_filter( 'qm/output/menus', array( $this, 'admin_menu' ), 105 );
22
  }
23
 
 
 
 
24
  public function name() {
25
  return __( 'Capability Checks', 'query-monitor' );
26
  }
27
 
 
 
 
28
  public function output() {
29
  $collector = $this->collector;
30
 
@@ -54,12 +62,12 @@ class QM_Output_Html_Caps extends QM_Output_Html {
54
  if ( ! empty( $data['caps'] ) ) {
55
  $this->before_tabular_output();
56
 
57
- $results = array(
58
  'true',
59
  'false',
60
  );
61
- $show_user = ( count( $data['users'] ) > 1 );
62
- $parts = $data['parts'];
63
  $components = $data['components'];
64
 
65
  usort( $parts, 'strcasecmp' );
@@ -94,11 +102,11 @@ class QM_Output_Html_Caps extends QM_Output_Html {
94
  foreach ( $data['caps'] as $row ) {
95
  $component = $row['component'];
96
 
97
- $row_attr = array();
98
- $row_attr['data-qm-name'] = implode( ' ', $row['parts'] );
99
- $row_attr['data-qm-user'] = $row['user'];
100
  $row_attr['data-qm-component'] = $component->name;
101
- $row_attr['data-qm-result'] = ( $row['result'] ) ? 'true' : 'false';
102
 
103
  if ( 'core' !== $component->context ) {
104
  $row_attr['data-qm-component'] .= ' non-core';
@@ -145,8 +153,8 @@ class QM_Output_Html_Caps extends QM_Output_Html {
145
 
146
  $stack = array();
147
 
148
- foreach ( $row['filtered_trace'] as $item ) {
149
- $stack[] = self::output_filename( $item['display'], $item['calling_file'], $item['calling_line'] );
150
  }
151
 
152
  $caller = array_shift( $stack );
@@ -204,6 +212,10 @@ class QM_Output_Html_Caps extends QM_Output_Html {
204
  }
205
  }
206
 
 
 
 
 
207
  public function admin_menu( array $menu ) {
208
  $menu[ $this->collector->id() ] = $this->menu( array(
209
  'title' => $this->name(),
@@ -214,6 +226,11 @@ class QM_Output_Html_Caps extends QM_Output_Html {
214
 
215
  }
216
 
 
 
 
 
 
217
  function register_qm_output_html_caps( array $output, QM_Collectors $collectors ) {
218
  $collector = QM_Collectors::get( 'caps' );
219
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_Caps extends QM_Output_Html {
13
 
23
  add_filter( 'qm/output/menus', array( $this, 'admin_menu' ), 105 );
24
  }
25
 
26
+ /**
27
+ * @return string
28
+ */
29
  public function name() {
30
  return __( 'Capability Checks', 'query-monitor' );
31
  }
32
 
33
+ /**
34
+ * @return void
35
+ */
36
  public function output() {
37
  $collector = $this->collector;
38
 
62
  if ( ! empty( $data['caps'] ) ) {
63
  $this->before_tabular_output();
64
 
65
+ $results = array(
66
  'true',
67
  'false',
68
  );
69
+ $show_user = ( count( $data['users'] ) > 1 );
70
+ $parts = $data['parts'];
71
  $components = $data['components'];
72
 
73
  usort( $parts, 'strcasecmp' );
102
  foreach ( $data['caps'] as $row ) {
103
  $component = $row['component'];
104
 
105
+ $row_attr = array();
106
+ $row_attr['data-qm-name'] = implode( ' ', $row['parts'] );
107
+ $row_attr['data-qm-user'] = $row['user'];
108
  $row_attr['data-qm-component'] = $component->name;
109
+ $row_attr['data-qm-result'] = ( $row['result'] ) ? 'true' : 'false';
110
 
111
  if ( 'core' !== $component->context ) {
112
  $row_attr['data-qm-component'] .= ' non-core';
153
 
154
  $stack = array();
155
 
156
+ foreach ( $row['filtered_trace'] as $frame ) {
157
+ $stack[] = self::output_filename( $frame['display'], $frame['calling_file'], $frame['calling_line'] );
158
  }
159
 
160
  $caller = array_shift( $stack );
212
  }
213
  }
214
 
215
+ /**
216
+ * @param array<string, mixed[]> $menu
217
+ * @return array<string, mixed[]>
218
+ */
219
  public function admin_menu( array $menu ) {
220
  $menu[ $this->collector->id() ] = $this->menu( array(
221
  'title' => $this->name(),
226
 
227
  }
228
 
229
+ /**
230
+ * @param array<string, QM_Output> $output
231
+ * @param QM_Collectors $collectors
232
+ * @return array<string, QM_Output>
233
+ */
234
  function register_qm_output_html_caps( array $output, QM_Collectors $collectors ) {
235
  $collector = QM_Collectors::get( 'caps' );
236
  if ( $collector ) {
output/html/conditionals.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_Conditionals extends QM_Output_Html {
11
 
@@ -22,10 +24,16 @@ class QM_Output_Html_Conditionals extends QM_Output_Html {
22
  add_filter( 'qm/output/panel_menus', array( $this, 'panel_menu' ), 1000 );
23
  }
24
 
 
 
 
25
  public function name() {
26
  return __( 'Conditionals', 'query-monitor' );
27
  }
28
 
 
 
 
29
  public function output() {
30
  $data = $this->collector->get_data();
31
 
@@ -58,16 +66,20 @@ class QM_Output_Html_Conditionals extends QM_Output_Html {
58
  $this->after_non_tabular_output();
59
  }
60
 
 
 
 
 
61
  public function admin_menu( array $menu ) {
62
 
63
  $data = $this->collector->get_data();
64
 
65
  foreach ( $data['conds']['true'] as $cond ) {
66
- $id = $this->collector->id() . '-' . $cond;
67
  $menu[ $id ] = $this->menu( array(
68
  'title' => esc_html( $cond . '()' ),
69
- 'id' => 'query-monitor-conditionals-' . esc_attr( $cond ),
70
- 'meta' => array(
71
  'classname' => 'qm-true qm-ltr',
72
  ),
73
  ) );
@@ -77,6 +89,10 @@ class QM_Output_Html_Conditionals extends QM_Output_Html {
77
 
78
  }
79
 
 
 
 
 
80
  public function panel_menu( array $menu ) {
81
 
82
  $data = $this->collector->get_data();
@@ -88,7 +104,7 @@ class QM_Output_Html_Conditionals extends QM_Output_Html {
88
 
89
  $menu[ $this->collector->id() ] = $this->menu( array(
90
  'title' => esc_html__( 'Conditionals', 'query-monitor' ),
91
- 'id' => 'query-monitor-conditionals',
92
  ) );
93
 
94
  return $menu;
@@ -98,6 +114,11 @@ class QM_Output_Html_Conditionals extends QM_Output_Html {
98
 
99
  }
100
 
 
 
 
 
 
101
  function register_qm_output_html_conditionals( array $output, QM_Collectors $collectors ) {
102
  $collector = QM_Collectors::get( 'conditionals' );
103
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_Conditionals extends QM_Output_Html {
13
 
24
  add_filter( 'qm/output/panel_menus', array( $this, 'panel_menu' ), 1000 );
25
  }
26
 
27
+ /**
28
+ * @return string
29
+ */
30
  public function name() {
31
  return __( 'Conditionals', 'query-monitor' );
32
  }
33
 
34
+ /**
35
+ * @return void
36
+ */
37
  public function output() {
38
  $data = $this->collector->get_data();
39
 
66
  $this->after_non_tabular_output();
67
  }
68
 
69
+ /**
70
+ * @param array<string, mixed[]> $menu
71
+ * @return array<string, mixed[]>
72
+ */
73
  public function admin_menu( array $menu ) {
74
 
75
  $data = $this->collector->get_data();
76
 
77
  foreach ( $data['conds']['true'] as $cond ) {
78
+ $id = $this->collector->id() . '-' . $cond;
79
  $menu[ $id ] = $this->menu( array(
80
  'title' => esc_html( $cond . '()' ),
81
+ 'id' => 'query-monitor-conditionals-' . esc_attr( $cond ),
82
+ 'meta' => array(
83
  'classname' => 'qm-true qm-ltr',
84
  ),
85
  ) );
89
 
90
  }
91
 
92
+ /**
93
+ * @param array<string, mixed[]> $menu
94
+ * @return array<string, mixed[]>
95
+ */
96
  public function panel_menu( array $menu ) {
97
 
98
  $data = $this->collector->get_data();
104
 
105
  $menu[ $this->collector->id() ] = $this->menu( array(
106
  'title' => esc_html__( 'Conditionals', 'query-monitor' ),
107
+ 'id' => 'query-monitor-conditionals',
108
  ) );
109
 
110
  return $menu;
114
 
115
  }
116
 
117
+ /**
118
+ * @param array<string, QM_Output> $output
119
+ * @param QM_Collectors $collectors
120
+ * @return array<string, QM_Output>
121
+ */
122
  function register_qm_output_html_conditionals( array $output, QM_Collectors $collectors ) {
123
  $collector = QM_Collectors::get( 'conditionals' );
124
  if ( $collector ) {
output/html/db_callers.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_DB_Callers extends QM_Output_Html {
11
 
@@ -21,10 +23,16 @@ class QM_Output_Html_DB_Callers extends QM_Output_Html {
21
  add_filter( 'qm/output/panel_menus', array( $this, 'panel_menu' ), 30 );
22
  }
23
 
 
 
 
24
  public function name() {
25
  return __( 'Queries by Caller', 'query-monitor' );
26
  }
27
 
 
 
 
28
  public function output() {
29
 
30
  $data = $this->collector->get_data();
@@ -58,7 +66,7 @@ class QM_Output_Html_DB_Callers extends QM_Output_Html {
58
 
59
  foreach ( $data['times'] as $row ) {
60
  $total_time += $row['ltime'];
61
- $stime = number_format_i18n( $row['ltime'], 4 );
62
 
63
  echo '<tr>';
64
  echo '<td class="qm-ltr"><button class="qm-filter-trigger" data-qm-target="db_queries-wpdb" data-qm-filter="caller" data-qm-value="' . esc_attr( $row['caller'] ) . '"><code>' . esc_html( $row['caller'] ) . '</code></button></td>';
@@ -105,6 +113,10 @@ class QM_Output_Html_DB_Callers extends QM_Output_Html {
105
  }
106
  }
107
 
 
 
 
 
108
  public function panel_menu( array $menu ) {
109
  $dbq = QM_Collectors::get( 'db_queries' );
110
 
@@ -122,6 +134,11 @@ class QM_Output_Html_DB_Callers extends QM_Output_Html {
122
 
123
  }
124
 
 
 
 
 
 
125
  function register_qm_output_html_db_callers( array $output, QM_Collectors $collectors ) {
126
  $collector = QM_Collectors::get( 'db_callers' );
127
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_DB_Callers extends QM_Output_Html {
13
 
23
  add_filter( 'qm/output/panel_menus', array( $this, 'panel_menu' ), 30 );
24
  }
25
 
26
+ /**
27
+ * @return string
28
+ */
29
  public function name() {
30
  return __( 'Queries by Caller', 'query-monitor' );
31
  }
32
 
33
+ /**
34
+ * @return void
35
+ */
36
  public function output() {
37
 
38
  $data = $this->collector->get_data();
66
 
67
  foreach ( $data['times'] as $row ) {
68
  $total_time += $row['ltime'];
69
+ $stime = number_format_i18n( $row['ltime'], 4 );
70
 
71
  echo '<tr>';
72
  echo '<td class="qm-ltr"><button class="qm-filter-trigger" data-qm-target="db_queries-wpdb" data-qm-filter="caller" data-qm-value="' . esc_attr( $row['caller'] ) . '"><code>' . esc_html( $row['caller'] ) . '</code></button></td>';
113
  }
114
  }
115
 
116
+ /**
117
+ * @param array<string, mixed[]> $menu
118
+ * @return array<string, mixed[]>
119
+ */
120
  public function panel_menu( array $menu ) {
121
  $dbq = QM_Collectors::get( 'db_queries' );
122
 
134
 
135
  }
136
 
137
+ /**
138
+ * @param array<string, QM_Output> $output
139
+ * @param QM_Collectors $collectors
140
+ * @return array<string, QM_Output>
141
+ */
142
  function register_qm_output_html_db_callers( array $output, QM_Collectors $collectors ) {
143
  $collector = QM_Collectors::get( 'db_callers' );
144
  if ( $collector ) {
output/html/db_components.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_DB_Components extends QM_Output_Html {
11
 
@@ -21,10 +23,16 @@ class QM_Output_Html_DB_Components extends QM_Output_Html {
21
  add_filter( 'qm/output/panel_menus', array( $this, 'panel_menu' ), 40 );
22
  }
23
 
 
 
 
24
  public function name() {
25
  return __( 'Queries by Component', 'query-monitor' );
26
  }
27
 
 
 
 
28
  public function output() {
29
 
30
  $data = $this->collector->get_data();
@@ -34,7 +42,7 @@ class QM_Output_Html_DB_Components extends QM_Output_Html {
34
  }
35
 
36
  $total_time = 0;
37
- $span = count( $data['types'] ) + 2;
38
 
39
  $this->before_tabular_output();
40
 
@@ -96,6 +104,10 @@ class QM_Output_Html_DB_Components extends QM_Output_Html {
96
  $this->after_tabular_output();
97
  }
98
 
 
 
 
 
99
  public function panel_menu( array $menu ) {
100
  $data = $this->collector->get_data();
101
 
@@ -119,6 +131,11 @@ class QM_Output_Html_DB_Components extends QM_Output_Html {
119
 
120
  }
121
 
 
 
 
 
 
122
  function register_qm_output_html_db_components( array $output, QM_Collectors $collectors ) {
123
  $collector = QM_Collectors::get( 'db_components' );
124
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_DB_Components extends QM_Output_Html {
13
 
23
  add_filter( 'qm/output/panel_menus', array( $this, 'panel_menu' ), 40 );
24
  }
25
 
26
+ /**
27
+ * @return string
28
+ */
29
  public function name() {
30
  return __( 'Queries by Component', 'query-monitor' );
31
  }
32
 
33
+ /**
34
+ * @return void
35
+ */
36
  public function output() {
37
 
38
  $data = $this->collector->get_data();
42
  }
43
 
44
  $total_time = 0;
45
+ $span = count( $data['types'] ) + 2;
46
 
47
  $this->before_tabular_output();
48
 
104
  $this->after_tabular_output();
105
  }
106
 
107
+ /**
108
+ * @param array<string, mixed[]> $menu
109
+ * @return array<string, mixed[]>
110
+ */
111
  public function panel_menu( array $menu ) {
112
  $data = $this->collector->get_data();
113
 
131
 
132
  }
133
 
134
+ /**
135
+ * @param array<string, QM_Output> $output
136
+ * @param QM_Collectors $collectors
137
+ * @return array<string, QM_Output>
138
+ */
139
  function register_qm_output_html_db_components( array $output, QM_Collectors $collectors ) {
140
  $collector = QM_Collectors::get( 'db_components' );
141
  if ( $collector ) {
output/html/db_dupes.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_DB_Dupes extends QM_Output_Html {
11
 
@@ -22,10 +24,16 @@ class QM_Output_Html_DB_Dupes extends QM_Output_Html {
22
  add_filter( 'qm/output/panel_menus', array( $this, 'panel_menu' ), 25 );
23
  }
24
 
 
 
 
25
  public function name() {
26
  return __( 'Duplicate Queries', 'query-monitor' );
27
  }
28
 
 
 
 
29
  public function output() {
30
 
31
  $data = $this->collector->get_data();
@@ -58,7 +66,7 @@ class QM_Output_Html_DB_Dupes extends QM_Output_Html {
58
  foreach ( $data['dupes'] as $sql => $queries ) {
59
 
60
  // This should probably happen in the collector's processor
61
- $type = QM_Util::get_query_type( $sql );
62
  $sql_out = self::format_sql( $sql );
63
 
64
  if ( 'SELECT' !== $type ) {
@@ -118,6 +126,10 @@ class QM_Output_Html_DB_Dupes extends QM_Output_Html {
118
  $this->after_tabular_output();
119
  }
120
 
 
 
 
 
121
  public function admin_menu( array $menu ) {
122
  $dbq = QM_Collectors::get( 'db_dupes' );
123
 
@@ -138,6 +150,10 @@ class QM_Output_Html_DB_Dupes extends QM_Output_Html {
138
 
139
  }
140
 
 
 
 
 
141
  public function panel_menu( array $menu ) {
142
  $id = $this->collector->id();
143
  if ( isset( $menu[ $id ] ) ) {
@@ -152,6 +168,11 @@ class QM_Output_Html_DB_Dupes extends QM_Output_Html {
152
 
153
  }
154
 
 
 
 
 
 
155
  function register_qm_output_html_db_dupes( array $output, QM_Collectors $collectors ) {
156
  $collector = QM_Collectors::get( 'db_dupes' );
157
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_DB_Dupes extends QM_Output_Html {
13
 
24
  add_filter( 'qm/output/panel_menus', array( $this, 'panel_menu' ), 25 );
25
  }
26
 
27
+ /**
28
+ * @return string
29
+ */
30
  public function name() {
31
  return __( 'Duplicate Queries', 'query-monitor' );
32
  }
33
 
34
+ /**
35
+ * @return void
36
+ */
37
  public function output() {
38
 
39
  $data = $this->collector->get_data();
66
  foreach ( $data['dupes'] as $sql => $queries ) {
67
 
68
  // This should probably happen in the collector's processor
69
+ $type = QM_Util::get_query_type( $sql );
70
  $sql_out = self::format_sql( $sql );
71
 
72
  if ( 'SELECT' !== $type ) {
126
  $this->after_tabular_output();
127
  }
128
 
129
+ /**
130
+ * @param array<string, mixed[]> $menu
131
+ * @return array<string, mixed[]>
132
+ */
133
  public function admin_menu( array $menu ) {
134
  $dbq = QM_Collectors::get( 'db_dupes' );
135
 
150
 
151
  }
152
 
153
+ /**
154
+ * @param array<string, mixed[]> $menu
155
+ * @return array<string, mixed[]>
156
+ */
157
  public function panel_menu( array $menu ) {
158
  $id = $this->collector->id();
159
  if ( isset( $menu[ $id ] ) ) {
168
 
169
  }
170
 
171
+ /**
172
+ * @param array<string, QM_Output> $output
173
+ * @param QM_Collectors $collectors
174
+ * @return array<string, QM_Output>
175
+ */
176
  function register_qm_output_html_db_dupes( array $output, QM_Collectors $collectors ) {
177
  $collector = QM_Collectors::get( 'db_dupes' );
178
  if ( $collector ) {
output/html/db_queries.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_DB_Queries extends QM_Output_Html {
11
 
@@ -16,6 +18,9 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
16
  */
17
  protected $collector;
18
 
 
 
 
19
  public $query_row = 0;
20
 
21
  public function __construct( QM_Collector $collector ) {
@@ -26,10 +31,16 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
26
  add_filter( 'qm/output/menu_class', array( $this, 'admin_class' ) );
27
  }
28
 
 
 
 
29
  public function name() {
30
  return __( 'Database Queries', 'query-monitor' );
31
  }
32
 
 
 
 
33
  public function output() {
34
 
35
  $data = $this->collector->get_data();
@@ -53,6 +64,9 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
53
 
54
  }
55
 
 
 
 
56
  protected function output_empty_queries() {
57
  $id = sprintf(
58
  '%s-wpdb',
@@ -75,6 +89,10 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
75
  $this->after_non_tabular_output();
76
  }
77
 
 
 
 
 
78
  protected function output_error_queries( array $errors ) {
79
  $this->before_tabular_output( 'qm-query-errors', __( 'Database Errors', 'query-monitor' ) );
80
 
@@ -98,6 +116,10 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
98
  $this->after_tabular_output();
99
  }
100
 
 
 
 
 
101
  protected function output_expensive_queries( array $expensive ) {
102
  $dp = strlen( substr( strrchr( (string) QM_DB_EXPENSIVE, '.' ), 1 ) );
103
 
@@ -135,6 +157,12 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
135
  $this->after_tabular_output();
136
  }
137
 
 
 
 
 
 
 
138
  protected function output_queries( $name, stdClass $db, array $data ) {
139
  $this->query_row = 0;
140
  $span = 4;
@@ -199,9 +227,9 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
199
  echo '</tr>';
200
  }
201
 
202
- $types = array_keys( $db->types );
203
- $prepend = array();
204
- $callers = wp_list_pluck( $data['times'], 'caller' );
205
 
206
  sort( $types );
207
  usort( $callers, 'strcasecmp' );
@@ -296,6 +324,11 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
296
  }
297
  }
298
 
 
 
 
 
 
299
  protected function output_query_row( array $row, array $cols ) {
300
 
301
  $cols = array_flip( $cols );
@@ -317,14 +350,14 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
317
 
318
  if ( isset( $row['trace'] ) ) {
319
 
320
- $caller = $row['trace']->get_caller();
321
- $caller_name = self::output_filename( $row['caller'], $caller['calling_file'], $caller['calling_line'] );
322
- $stack = array();
323
- $filtered_trace = $row['trace']->get_display_trace();
324
  array_shift( $filtered_trace );
325
 
326
- foreach ( $filtered_trace as $item ) {
327
- $stack[] = self::output_filename( $item['display'], $item['calling_file'], $item['calling_line'] );
328
  }
329
  } else {
330
 
@@ -334,11 +367,10 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
334
  $caller_name = '<code>' . esc_html__( 'Unknown', 'query-monitor' ) . '</code>';
335
  }
336
 
337
- $stack = explode( ', ', $row['stack'] );
338
- $stack = array_reverse( $stack );
339
  array_shift( $stack );
340
- $stack = array_map( function( $item ) {
341
- return '<code>' . esc_html( $item ) . '</code>';
342
  }, $stack );
343
 
344
  }
@@ -446,7 +478,7 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
446
 
447
  if ( isset( $cols['time'] ) ) {
448
  $expensive = $this->collector->is_expensive( $row );
449
- $td_class = ( $expensive ) ? ' qm-warn' : '';
450
 
451
  echo '<td class="qm-num qm-row-time' . esc_attr( $td_class ) . '" data-qm-sort-weight="' . esc_attr( $row['ltime'] ) . '">';
452
 
@@ -462,14 +494,18 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
462
 
463
  }
464
 
 
 
 
 
465
  public function admin_title( array $existing ) {
466
-
467
  $data = $this->collector->get_data();
468
 
469
  if ( isset( $data['dbs'] ) ) {
470
  foreach ( $data['dbs'] as $key => $db ) {
471
- /* translators: %s: Database query time in seconds */
472
- $text = _nx( '%s S', '%s S', $db->total_time, 'Query time', 'query-monitor' );
473
 
474
  // Avoid a potentially blank translation for the plural form.
475
  // @see https://meta.trac.wordpress.org/ticket/5377
@@ -479,12 +515,12 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
479
 
480
  $title[] = sprintf(
481
  esc_html( '%s' . $text ),
482
- ( count( $data['dbs'] ) > 1 ? '&bull;&nbsp;&nbsp;&nbsp;' : '' ),
483
- number_format_i18n( $db->total_time, 4 )
484
  );
485
 
486
- /* translators: %s: Number of database queries */
487
- $text = _nx( '%s Q', '%s Q', $db->total_qs, 'Query count', 'query-monitor' );
488
 
489
  // Avoid a potentially blank translation for the plural form.
490
  // @see https://meta.trac.wordpress.org/ticket/5377
@@ -498,8 +534,8 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
498
  );
499
  }
500
  } elseif ( isset( $data['total_qs'] ) ) {
501
- /* translators: %s: Number of database queries */
502
- $text = _nx( '%s Q', '%s Q', $data['total_qs'], 'Query count', 'query-monitor' );
503
 
504
  // Avoid a potentially blank translation for the plural form.
505
  // @see https://meta.trac.wordpress.org/ticket/5377
@@ -508,7 +544,6 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
508
  }
509
 
510
  $title[] = sprintf(
511
- /* translators: %s: Number of database queries */
512
  esc_html( $text ),
513
  number_format_i18n( $data['total_qs'] )
514
  );
@@ -523,6 +558,10 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
523
  return $title;
524
  }
525
 
 
 
 
 
526
  public function admin_class( array $class ) {
527
 
528
  if ( $this->collector->get_errors() ) {
@@ -535,40 +574,44 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
535
 
536
  }
537
 
 
 
 
 
538
  public function admin_menu( array $menu ) {
539
 
540
- $data = $this->collector->get_data();
541
- $errors = $this->collector->get_errors();
542
  $expensive = $this->collector->get_expensive();
543
 
544
  if ( isset( $data['dbs'] ) && count( $data['dbs'] ) > 1 ) {
545
  foreach ( $data['dbs'] as $name => $db ) {
546
- $name_attr = sanitize_title_with_dashes( $name );
547
- $id = $this->collector->id() . '-' . $name_attr;
548
  $menu[ $id ] = $this->menu( array(
549
- 'id' => esc_attr( sprintf( 'query-monitor-%s-db-%s', $this->collector->id(), $name_attr ) ),
550
  'title' => esc_html( sprintf(
551
  /* translators: %s: Name of database controller */
552
  __( 'Queries: %s', 'query-monitor' ),
553
  $name
554
  ) ),
555
- 'href' => esc_attr( sprintf( '#%s-%s', $this->collector->id(), $name_attr ) ),
556
  ) );
557
  }
558
  } else {
559
- $id = $this->collector->id() . '-$wpdb';
560
  $menu[ $id ] = $this->menu( array(
561
  'title' => esc_html__( 'Queries', 'query-monitor' ),
562
- 'href' => esc_attr( sprintf( '#%s-wpdb', $this->collector->id() ) ),
563
  ) );
564
  }
565
 
566
  if ( $errors ) {
567
- $id = $this->collector->id() . '-errors';
568
- $count = count( $errors );
569
  $menu[ $id ] = $this->menu( array(
570
- 'id' => 'query-monitor-errors',
571
- 'href' => '#qm-query-errors',
572
  'title' => esc_html( sprintf(
573
  /* translators: %s: Number of database errors */
574
  __( 'Database Errors (%s)', 'query-monitor' ),
@@ -578,11 +621,11 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
578
  }
579
 
580
  if ( $expensive ) {
581
- $id = $this->collector->id() . '-expensive';
582
- $count = count( $expensive );
583
  $menu[ $id ] = $this->menu( array(
584
- 'id' => 'query-monitor-expensive',
585
- 'href' => '#qm-query-expensive',
586
  'title' => esc_html( sprintf(
587
  /* translators: %s: Number of slow database queries */
588
  __( 'Slow Queries (%s)', 'query-monitor' ),
@@ -595,6 +638,10 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
595
 
596
  }
597
 
 
 
 
 
598
  public function panel_menu( array $menu ) {
599
  foreach ( array( 'errors', 'expensive' ) as $sub ) {
600
  $id = $this->collector->id() . '-' . $sub;
@@ -611,6 +658,11 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
611
 
612
  }
613
 
 
 
 
 
 
614
  function register_qm_output_html_db_queries( array $output, QM_Collectors $collectors ) {
615
  $collector = QM_Collectors::get( 'db_queries' );
616
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_DB_Queries extends QM_Output_Html {
13
 
18
  */
19
  protected $collector;
20
 
21
+ /**
22
+ * @var int
23
+ */
24
  public $query_row = 0;
25
 
26
  public function __construct( QM_Collector $collector ) {
31
  add_filter( 'qm/output/menu_class', array( $this, 'admin_class' ) );
32
  }
33
 
34
+ /**
35
+ * @return string
36
+ */
37
  public function name() {
38
  return __( 'Database Queries', 'query-monitor' );
39
  }
40
 
41
+ /**
42
+ * @return void
43
+ */
44
  public function output() {
45
 
46
  $data = $this->collector->get_data();
64
 
65
  }
66
 
67
+ /**
68
+ * @return void
69
+ */
70
  protected function output_empty_queries() {
71
  $id = sprintf(
72
  '%s-wpdb',
89
  $this->after_non_tabular_output();
90
  }
91
 
92
+ /**
93
+ * @param array<int, mixed> $errors
94
+ * @return void
95
+ */
96
  protected function output_error_queries( array $errors ) {
97
  $this->before_tabular_output( 'qm-query-errors', __( 'Database Errors', 'query-monitor' ) );
98
 
116
  $this->after_tabular_output();
117
  }
118
 
119
+ /**
120
+ * @param array<int, mixed> $expensive
121
+ * @return void
122
+ */
123
  protected function output_expensive_queries( array $expensive ) {
124
  $dp = strlen( substr( strrchr( (string) QM_DB_EXPENSIVE, '.' ), 1 ) );
125
 
157
  $this->after_tabular_output();
158
  }
159
 
160
+ /**
161
+ * @param string $name
162
+ * @param stdClass $db
163
+ * @param array<string, mixed> $data
164
+ * @return void
165
+ */
166
  protected function output_queries( $name, stdClass $db, array $data ) {
167
  $this->query_row = 0;
168
  $span = 4;
227
  echo '</tr>';
228
  }
229
 
230
+ $types = array_keys( $db->types );
231
+ $prepend = array();
232
+ $callers = wp_list_pluck( $data['times'], 'caller' );
233
 
234
  sort( $types );
235
  usort( $callers, 'strcasecmp' );
324
  }
325
  }
326
 
327
+ /**
328
+ * @param array<string, mixed> $row
329
+ * @param array<int, string> $cols
330
+ * @return void
331
+ */
332
  protected function output_query_row( array $row, array $cols ) {
333
 
334
  $cols = array_flip( $cols );
350
 
351
  if ( isset( $row['trace'] ) ) {
352
 
353
+ $caller = $row['trace']->get_caller();
354
+ $caller_name = self::output_filename( $row['caller'], $caller['calling_file'], $caller['calling_line'] );
355
+ $stack = array();
356
+ $filtered_trace = $row['trace']->get_filtered_trace();
357
  array_shift( $filtered_trace );
358
 
359
+ foreach ( $filtered_trace as $frame ) {
360
+ $stack[] = self::output_filename( $frame['display'], $frame['calling_file'], $frame['calling_line'] );
361
  }
362
  } else {
363
 
367
  $caller_name = '<code>' . esc_html__( 'Unknown', 'query-monitor' ) . '</code>';
368
  }
369
 
370
+ $stack = $row['stack'];
 
371
  array_shift( $stack );
372
+ $stack = array_map( function( $frame ) {
373
+ return '<code>' . esc_html( $frame ) . '</code>';
374
  }, $stack );
375
 
376
  }
478
 
479
  if ( isset( $cols['time'] ) ) {
480
  $expensive = $this->collector->is_expensive( $row );
481
+ $td_class = ( $expensive ) ? ' qm-warn' : '';
482
 
483
  echo '<td class="qm-num qm-row-time' . esc_attr( $td_class ) . '" data-qm-sort-weight="' . esc_attr( $row['ltime'] ) . '">';
484
 
494
 
495
  }
496
 
497
+ /**
498
+ * @param array<int, string> $existing
499
+ * @return array<int, string>
500
+ */
501
  public function admin_title( array $existing ) {
502
+ $title = array();
503
  $data = $this->collector->get_data();
504
 
505
  if ( isset( $data['dbs'] ) ) {
506
  foreach ( $data['dbs'] as $key => $db ) {
507
+ /* translators: %s: Time in seconds. Note the space between value and unit. */
508
+ $text = _n( '%s S', '%s S', $db->total_time, 'query-monitor' );
509
 
510
  // Avoid a potentially blank translation for the plural form.
511
  // @see https://meta.trac.wordpress.org/ticket/5377
515
 
516
  $title[] = sprintf(
517
  esc_html( '%s' . $text ),
518
+ ( count( $data['dbs'] ) > 1 ? '&bull;&nbsp;&nbsp' : '' ),
519
+ number_format_i18n( $db->total_time, 2 )
520
  );
521
 
522
+ /* translators: %s: Number of database queries. Note the space between value and unit. */
523
+ $text = _n( '%s Q', '%s Q', $db->total_qs, 'query-monitor' );
524
 
525
  // Avoid a potentially blank translation for the plural form.
526
  // @see https://meta.trac.wordpress.org/ticket/5377
534
  );
535
  }
536
  } elseif ( isset( $data['total_qs'] ) ) {
537
+ /* translators: %s: Number of database queries. Note the space between value and unit. */
538
+ $text = _n( '%s Q', '%s Q', $data['total_qs'], 'query-monitor' );
539
 
540
  // Avoid a potentially blank translation for the plural form.
541
  // @see https://meta.trac.wordpress.org/ticket/5377
544
  }
545
 
546
  $title[] = sprintf(
 
547
  esc_html( $text ),
548
  number_format_i18n( $data['total_qs'] )
549
  );
558
  return $title;
559
  }
560
 
561
+ /**
562
+ * @param array<int, string> $class
563
+ * @return array<int, string>
564
+ */
565
  public function admin_class( array $class ) {
566
 
567
  if ( $this->collector->get_errors() ) {
574
 
575
  }
576
 
577
+ /**
578
+ * @param array<string, mixed[]> $menu
579
+ * @return array<string, mixed[]>
580
+ */
581
  public function admin_menu( array $menu ) {
582
 
583
+ $data = $this->collector->get_data();
584
+ $errors = $this->collector->get_errors();
585
  $expensive = $this->collector->get_expensive();
586
 
587
  if ( isset( $data['dbs'] ) && count( $data['dbs'] ) > 1 ) {
588
  foreach ( $data['dbs'] as $name => $db ) {
589
+ $name_attr = sanitize_title_with_dashes( $name );
590
+ $id = $this->collector->id() . '-' . $name_attr;
591
  $menu[ $id ] = $this->menu( array(
592
+ 'id' => esc_attr( sprintf( 'query-monitor-%s-db-%s', $this->collector->id(), $name_attr ) ),
593
  'title' => esc_html( sprintf(
594
  /* translators: %s: Name of database controller */
595
  __( 'Queries: %s', 'query-monitor' ),
596
  $name
597
  ) ),
598
+ 'href' => esc_attr( sprintf( '#%s-%s', $this->collector->id(), $name_attr ) ),
599
  ) );
600
  }
601
  } else {
602
+ $id = $this->collector->id() . '-$wpdb';
603
  $menu[ $id ] = $this->menu( array(
604
  'title' => esc_html__( 'Queries', 'query-monitor' ),
605
+ 'href' => esc_attr( sprintf( '#%s-wpdb', $this->collector->id() ) ),
606
  ) );
607
  }
608
 
609
  if ( $errors ) {
610
+ $id = $this->collector->id() . '-errors';
611
+ $count = count( $errors );
612
  $menu[ $id ] = $this->menu( array(
613
+ 'id' => 'query-monitor-errors',
614
+ 'href' => '#qm-query-errors',
615
  'title' => esc_html( sprintf(
616
  /* translators: %s: Number of database errors */
617
  __( 'Database Errors (%s)', 'query-monitor' ),
621
  }
622
 
623
  if ( $expensive ) {
624
+ $id = $this->collector->id() . '-expensive';
625
+ $count = count( $expensive );
626
  $menu[ $id ] = $this->menu( array(
627
+ 'id' => 'query-monitor-expensive',
628
+ 'href' => '#qm-query-expensive',
629
  'title' => esc_html( sprintf(
630
  /* translators: %s: Number of slow database queries */
631
  __( 'Slow Queries (%s)', 'query-monitor' ),
638
 
639
  }
640
 
641
+ /**
642
+ * @param array<string, mixed[]> $menu
643
+ * @return array<string, mixed[]>
644
+ */
645
  public function panel_menu( array $menu ) {
646
  foreach ( array( 'errors', 'expensive' ) as $sub ) {
647
  $id = $this->collector->id() . '-' . $sub;
658
 
659
  }
660
 
661
+ /**
662
+ * @param array<string, QM_Output> $output
663
+ * @param QM_Collectors $collectors
664
+ * @return array<string, QM_Output>
665
+ */
666
  function register_qm_output_html_db_queries( array $output, QM_Collectors $collectors ) {
667
  $collector = QM_Collectors::get( 'db_queries' );
668
  if ( $collector ) {
output/html/debug_bar.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_Debug_Bar extends QM_Output_Html {
11
 
@@ -21,6 +23,9 @@ class QM_Output_Html_Debug_Bar extends QM_Output_Html {
21
  add_filter( 'qm/output/menus', array( $this, 'admin_menu' ), 200 );
22
  }
23
 
 
 
 
24
  public function name() {
25
  $title = $this->collector->get_panel()->title();
26
 
@@ -31,6 +36,9 @@ class QM_Output_Html_Debug_Bar extends QM_Output_Html {
31
  );
32
  }
33
 
 
 
 
34
  public function output() {
35
  $target = sanitize_html_class( get_class( $this->collector->get_panel() ) );
36
 
@@ -71,6 +79,11 @@ class QM_Output_Html_Debug_Bar extends QM_Output_Html {
71
 
72
  }
73
 
 
 
 
 
 
74
  function register_qm_output_html_debug_bar( array $output, QM_Collectors $collectors ) {
75
  global $debug_bar;
76
 
@@ -79,7 +92,8 @@ function register_qm_output_html_debug_bar( array $output, QM_Collectors $collec
79
  }
80
 
81
  foreach ( $debug_bar->panels as $panel ) {
82
- $panel_id = strtolower( sanitize_html_class( get_class( $panel ) ) );
 
83
  $collector = QM_Collectors::get( "debug_bar_{$panel_id}" );
84
 
85
  if ( $collector && $collector->is_visible() ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_Debug_Bar extends QM_Output_Html {
13
 
23
  add_filter( 'qm/output/menus', array( $this, 'admin_menu' ), 200 );
24
  }
25
 
26
+ /**
27
+ * @return string
28
+ */
29
  public function name() {
30
  $title = $this->collector->get_panel()->title();
31
 
36
  );
37
  }
38
 
39
+ /**
40
+ * @return void
41
+ */
42
  public function output() {
43
  $target = sanitize_html_class( get_class( $this->collector->get_panel() ) );
44
 
79
 
80
  }
81
 
82
+ /**
83
+ * @param array<string, QM_Output> $output
84
+ * @param QM_Collectors $collectors
85
+ * @return array<string, QM_Output>
86
+ */
87
  function register_qm_output_html_debug_bar( array $output, QM_Collectors $collectors ) {
88
  global $debug_bar;
89
 
92
  }
93
 
94
  foreach ( $debug_bar->panels as $panel ) {
95
+ $panel_id = strtolower( sanitize_html_class( get_class( $panel ) ) );
96
+ /** @var QM_Collector_Debug_Bar|null */
97
  $collector = QM_Collectors::get( "debug_bar_{$panel_id}" );
98
 
99
  if ( $collector && $collector->is_visible() ) {
output/html/environment.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_Environment extends QM_Output_Html {
11
 
@@ -21,10 +23,16 @@ class QM_Output_Html_Environment extends QM_Output_Html {
21
  add_filter( 'qm/output/menus', array( $this, 'admin_menu' ), 110 );
22
  }
23
 
 
 
 
24
  public function name() {
25
  return __( 'Environment', 'query-monitor' );
26
  }
27
 
 
 
 
28
  public function output() {
29
 
30
  $data = $this->collector->get_data();
@@ -37,8 +45,8 @@ class QM_Output_Html_Environment extends QM_Output_Html {
37
  echo '<table>';
38
  echo '<tbody>';
39
 
40
- $append = '';
41
- $class = '';
42
  $php_warning = $data['php']['old'];
43
 
44
  if ( $php_warning ) {
@@ -47,7 +55,7 @@ class QM_Output_Html_Environment extends QM_Output_Html {
47
  'https://wordpress.org/support/update-php/',
48
  esc_html__( 'Help', 'query-monitor' )
49
  );
50
- $class = 'qm-warn';
51
  }
52
 
53
  echo '<tr class="' . esc_attr( $class ) . '">';
@@ -78,7 +86,7 @@ class QM_Output_Html_Environment extends QM_Output_Html {
78
  echo '</tr>';
79
 
80
  foreach ( $data['php']['variables'] as $key => $val ) {
81
- $class = '';
82
  $warners = array(
83
  'max_execution_time',
84
  'memory_limit',
@@ -182,11 +190,11 @@ class QM_Output_Html_Environment extends QM_Output_Html {
182
 
183
  $info = array(
184
  'server-version' => __( 'Server Version', 'query-monitor' ),
185
- 'extension' => __( 'Extension', 'query-monitor' ),
186
  'client-version' => __( 'Client Version', 'query-monitor' ),
187
- 'user' => __( 'User', 'query-monitor' ),
188
- 'host' => __( 'Host', 'query-monitor' ),
189
- 'database' => __( 'Database', 'query-monitor' ),
190
  );
191
 
192
  foreach ( $info as $field => $label ) {
@@ -282,11 +290,13 @@ class QM_Output_Html_Environment extends QM_Output_Html {
282
  echo '<h3>' . esc_html__( 'Server', 'query-monitor' ) . '</h3>';
283
 
284
  $server = array(
285
- 'name' => __( 'Software', 'query-monitor' ),
286
  'version' => __( 'Version', 'query-monitor' ),
287
- 'address' => __( 'Address', 'query-monitor' ),
288
- 'host' => __( 'Host', 'query-monitor' ),
289
- 'OS' => __( 'OS', 'query-monitor' ),
 
 
290
  );
291
 
292
  echo '<table>';
@@ -312,6 +322,11 @@ class QM_Output_Html_Environment extends QM_Output_Html {
312
 
313
  }
314
 
 
 
 
 
 
315
  function register_qm_output_html_environment( array $output, QM_Collectors $collectors ) {
316
  $collector = QM_Collectors::get( 'environment' );
317
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_Environment extends QM_Output_Html {
13
 
23
  add_filter( 'qm/output/menus', array( $this, 'admin_menu' ), 110 );
24
  }
25
 
26
+ /**
27
+ * @return string
28
+ */
29
  public function name() {
30
  return __( 'Environment', 'query-monitor' );
31
  }
32
 
33
+ /**
34
+ * @return void
35
+ */
36
  public function output() {
37
 
38
  $data = $this->collector->get_data();
45
  echo '<table>';
46
  echo '<tbody>';
47
 
48
+ $append = '';
49
+ $class = '';
50
  $php_warning = $data['php']['old'];
51
 
52
  if ( $php_warning ) {
55
  'https://wordpress.org/support/update-php/',
56
  esc_html__( 'Help', 'query-monitor' )
57
  );
58
+ $class = 'qm-warn';
59
  }
60
 
61
  echo '<tr class="' . esc_attr( $class ) . '">';
86
  echo '</tr>';
87
 
88
  foreach ( $data['php']['variables'] as $key => $val ) {
89
+ $class = '';
90
  $warners = array(
91
  'max_execution_time',
92
  'memory_limit',
190
 
191
  $info = array(
192
  'server-version' => __( 'Server Version', 'query-monitor' ),
193
+ 'extension' => __( 'Extension', 'query-monitor' ),
194
  'client-version' => __( 'Client Version', 'query-monitor' ),
195
+ 'user' => __( 'User', 'query-monitor' ),
196
+ 'host' => __( 'Host', 'query-monitor' ),
197
+ 'database' => __( 'Database', 'query-monitor' ),
198
  );
199
 
200
  foreach ( $info as $field => $label ) {
290
  echo '<h3>' . esc_html__( 'Server', 'query-monitor' ) . '</h3>';
291
 
292
  $server = array(
293
+ 'name' => __( 'Software', 'query-monitor' ),
294
  'version' => __( 'Version', 'query-monitor' ),
295
+ 'address' => __( 'IP Address', 'query-monitor' ),
296
+ 'host' => __( 'Host', 'query-monitor' ),
297
+ /* translators: OS stands for Operating System */
298
+ 'OS' => __( 'OS', 'query-monitor' ),
299
+ 'arch' => __( 'Architecture', 'query-monitor' ),
300
  );
301
 
302
  echo '<table>';
322
 
323
  }
324
 
325
+ /**
326
+ * @param array<string, QM_Output> $output
327
+ * @param QM_Collectors $collectors
328
+ * @return array<string, QM_Output>
329
+ */
330
  function register_qm_output_html_environment( array $output, QM_Collectors $collectors ) {
331
  $collector = QM_Collectors::get( 'environment' );
332
  if ( $collector ) {
output/html/headers.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_Headers extends QM_Output_Html {
11
 
@@ -32,11 +34,17 @@ class QM_Output_Html_Headers extends QM_Output_Html {
32
  return __( 'Request Data', 'query-monitor' );
33
  }
34
 
 
 
 
35
  public function output() {
36
  $this->output_request();
37
  $this->output_response();
38
  }
39
 
 
 
 
40
  public function output_request() {
41
  $data = $this->collector->get_data();
42
 
@@ -47,9 +55,12 @@ class QM_Output_Html_Headers extends QM_Output_Html {
47
  $this->after_tabular_output();
48
  }
49
 
 
 
 
50
  public function output_response() {
51
  $data = $this->collector->get_data();
52
- $id = sprintf( 'qm-%s-response', $this->collector->id );
53
 
54
  $this->before_tabular_output( $id );
55
 
@@ -58,6 +69,11 @@ class QM_Output_Html_Headers extends QM_Output_Html {
58
  $this->after_tabular_output();
59
  }
60
 
 
 
 
 
 
61
  protected function output_header_table( array $headers, $title ) {
62
  echo '<thead>';
63
  echo '<tr>';
@@ -87,19 +103,23 @@ class QM_Output_Html_Headers extends QM_Output_Html {
87
  echo '</tfoot>';
88
  }
89
 
 
 
 
 
90
  public function panel_menu( array $menu ) {
91
  if ( ! isset( $menu['qm-request'] ) ) {
92
  return $menu;
93
  }
94
 
95
  $ids = array(
96
- $this->collector->id() => __( 'Request Headers', 'query-monitor' ),
97
  $this->collector->id() . '-response' => __( 'Response Headers', 'query-monitor' ),
98
  );
99
  foreach ( $ids as $id => $title ) {
100
  $menu['qm-request']['children'][] = array(
101
- 'id' => $id,
102
- 'href' => '#' . $id,
103
  'title' => esc_html( $title ),
104
  );
105
  }
@@ -108,6 +128,11 @@ class QM_Output_Html_Headers extends QM_Output_Html {
108
  }
109
  }
110
 
 
 
 
 
 
111
  function register_qm_output_html_headers( array $output, QM_Collectors $collectors ) {
112
  $collector = QM_Collectors::get( 'raw_request' );
113
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_Headers extends QM_Output_Html {
13
 
34
  return __( 'Request Data', 'query-monitor' );
35
  }
36
 
37
+ /**
38
+ * @return void
39
+ */
40
  public function output() {
41
  $this->output_request();
42
  $this->output_response();
43
  }
44
 
45
+ /**
46
+ * @return void
47
+ */
48
  public function output_request() {
49
  $data = $this->collector->get_data();
50
 
55
  $this->after_tabular_output();
56
  }
57
 
58
+ /**
59
+ * @return void
60
+ */
61
  public function output_response() {
62
  $data = $this->collector->get_data();
63
+ $id = sprintf( 'qm-%s-response', $this->collector->id );
64
 
65
  $this->before_tabular_output( $id );
66
 
69
  $this->after_tabular_output();
70
  }
71
 
72
+ /**
73
+ * @param array<string, string> $headers
74
+ * @param string $title
75
+ * @return void
76
+ */
77
  protected function output_header_table( array $headers, $title ) {
78
  echo '<thead>';
79
  echo '<tr>';
103
  echo '</tfoot>';
104
  }
105
 
106
+ /**
107
+ * @param array<string, mixed[]> $menu
108
+ * @return array<string, mixed[]>
109
+ */
110
  public function panel_menu( array $menu ) {
111
  if ( ! isset( $menu['qm-request'] ) ) {
112
  return $menu;
113
  }
114
 
115
  $ids = array(
116
+ $this->collector->id() => __( 'Request Headers', 'query-monitor' ),
117
  $this->collector->id() . '-response' => __( 'Response Headers', 'query-monitor' ),
118
  );
119
  foreach ( $ids as $id => $title ) {
120
  $menu['qm-request']['children'][] = array(
121
+ 'id' => $id,
122
+ 'href' => '#' . $id,
123
  'title' => esc_html( $title ),
124
  );
125
  }
128
  }
129
  }
130
 
131
+ /**
132
+ * @param array<string, QM_Output> $output
133
+ * @param QM_Collectors $collectors
134
+ * @return array<string, QM_Output>
135
+ */
136
  function register_qm_output_html_headers( array $output, QM_Collectors $collectors ) {
137
  $collector = QM_Collectors::get( 'raw_request' );
138
  if ( $collector ) {
output/html/hooks.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_Hooks extends QM_Output_Html {
11
 
@@ -21,10 +23,16 @@ class QM_Output_Html_Hooks extends QM_Output_Html {
21
  add_filter( 'qm/output/menus', array( $this, 'admin_menu' ), 80 );
22
  }
23
 
 
 
 
24
  public function name() {
25
  return __( 'Hooks & Actions', 'query-monitor' );
26
  }
27
 
 
 
 
28
  public function output() {
29
 
30
  $data = $this->collector->get_data();
@@ -59,12 +67,16 @@ class QM_Output_Html_Hooks extends QM_Output_Html {
59
  $this->after_tabular_output();
60
  }
61
 
 
 
 
 
62
  public static function output_hook_table( array $hooks ) {
63
- $core = __( 'Core', 'query-monitor' );
64
 
65
  foreach ( $hooks as $hook ) {
66
- $row_attr = array();
67
- $row_attr['data-qm-name'] = implode( ' ', $hook['parts'] );
68
  $row_attr['data-qm-component'] = implode( ' ', $hook['components'] );
69
 
70
  if ( ! empty( $row_attr['data-qm-component'] ) && $core !== $row_attr['data-qm-component'] ) {
@@ -89,11 +101,11 @@ class QM_Output_Html_Hooks extends QM_Output_Html {
89
 
90
  foreach ( $hook['actions'] as $action ) {
91
  $component = '';
92
- $subject = '';
93
 
94
  if ( isset( $action['callback']['component'] ) ) {
95
  $component = $action['callback']['component']->name;
96
- $subject = $component;
97
  }
98
 
99
  if ( $core !== $component ) {
@@ -196,6 +208,11 @@ class QM_Output_Html_Hooks extends QM_Output_Html {
196
 
197
  }
198
 
 
 
 
 
 
199
  function register_qm_output_html_hooks( array $output, QM_Collectors $collectors ) {
200
  $collector = QM_Collectors::get( 'hooks' );
201
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_Hooks extends QM_Output_Html {
13
 
23
  add_filter( 'qm/output/menus', array( $this, 'admin_menu' ), 80 );
24
  }
25
 
26
+ /**
27
+ * @return string
28
+ */
29
  public function name() {
30
  return __( 'Hooks & Actions', 'query-monitor' );
31
  }
32
 
33
+ /**
34
+ * @return void
35
+ */
36
  public function output() {
37
 
38
  $data = $this->collector->get_data();
67
  $this->after_tabular_output();
68
  }
69
 
70
+ /**
71
+ * @param array<int, mixed[]> $hooks
72
+ * @return void
73
+ */
74
  public static function output_hook_table( array $hooks ) {
75
+ $core = __( 'WordPress Core', 'query-monitor' );
76
 
77
  foreach ( $hooks as $hook ) {
78
+ $row_attr = array();
79
+ $row_attr['data-qm-name'] = implode( ' ', $hook['parts'] );
80
  $row_attr['data-qm-component'] = implode( ' ', $hook['components'] );
81
 
82
  if ( ! empty( $row_attr['data-qm-component'] ) && $core !== $row_attr['data-qm-component'] ) {
101
 
102
  foreach ( $hook['actions'] as $action ) {
103
  $component = '';
104
+ $subject = '';
105
 
106
  if ( isset( $action['callback']['component'] ) ) {
107
  $component = $action['callback']['component']->name;
108
+ $subject = $component;
109
  }
110
 
111
  if ( $core !== $component ) {
208
 
209
  }
210
 
211
+ /**
212
+ * @param array<string, QM_Output> $output
213
+ * @param QM_Collectors $collectors
214
+ * @return array<string, QM_Output>
215
+ */
216
  function register_qm_output_html_hooks( array $output, QM_Collectors $collectors ) {
217
  $collector = QM_Collectors::get( 'hooks' );
218
  if ( $collector ) {
output/html/http.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_HTTP extends QM_Output_Html {
11
 
@@ -22,16 +24,22 @@ class QM_Output_Html_HTTP extends QM_Output_Html {
22
  add_filter( 'qm/output/menu_class', array( $this, 'admin_class' ) );
23
  }
24
 
 
 
 
25
  public function name() {
26
  return __( 'HTTP API Calls', 'query-monitor' );
27
  }
28
 
 
 
 
29
  public function output() {
30
 
31
  $data = $this->collector->get_data();
32
 
33
  if ( ! empty( $data['http'] ) ) {
34
- $statuses = array_keys( $data['types'] );
35
  $components = wp_list_pluck( $data['component_times'], 'component' );
36
 
37
  usort( $statuses, 'strcasecmp' );
@@ -76,7 +84,7 @@ class QM_Output_Html_HTTP extends QM_Output_Html {
76
  $i++;
77
  $is_error = false;
78
  $row_attr = array();
79
- $css = '';
80
 
81
  if ( is_wp_error( $row['response'] ) ) {
82
  $response = $row['response']->get_error_message();
@@ -86,7 +94,7 @@ class QM_Output_Html_HTTP extends QM_Output_Html {
86
  $response = __( 'Non-blocking', 'query-monitor' );
87
  } else {
88
  $code = wp_remote_retrieve_response_code( $row['response'] );
89
- $msg = wp_remote_retrieve_response_message( $row['response'] );
90
 
91
  if ( intval( $code ) >= 400 ) {
92
  $is_error = true;
@@ -100,7 +108,7 @@ class QM_Output_Html_HTTP extends QM_Output_Html {
100
  $css = 'qm-warn';
101
  }
102
 
103
- $url = self::format_url( $row['url'] );
104
  $info = '';
105
 
106
  $url = preg_replace( '|^http:|', '<span class="qm-warn">http</span>:', $url );
@@ -120,45 +128,16 @@ class QM_Output_Html_HTTP extends QM_Output_Html {
120
 
121
  $component = $row['component'];
122
 
123
- $stack = array();
124
- $filtered_trace = $row['trace']->get_display_trace();
125
-
126
- $filtered_trace = array_filter( $filtered_trace, function( $item ) {
127
- // @TODO This should happen during collection.
128
- if ( isset( $item['class'] ) ) {
129
- return ! in_array( $item['class'], array(
130
- 'WP_Http',
131
- ), true );
132
- }
133
-
134
- if ( isset( $item['function'] ) ) {
135
- return ! in_array( $item['function'], array(
136
- 'wp_safe_remote_request',
137
- 'wp_safe_remote_get',
138
- 'wp_safe_remote_post',
139
- 'wp_safe_remote_head',
140
- 'wp_remote_request',
141
- 'wp_remote_get',
142
- 'wp_remote_post',
143
- 'wp_remote_head',
144
- 'wp_remote_fopen',
145
- 'download_url',
146
- 'vip_safe_wp_remote_get',
147
- 'vip_safe_wp_remote_request',
148
- 'wpcom_vip_file_get_contents',
149
- ), true );
150
- }
151
-
152
- return true;
153
- } );
154
 
155
- foreach ( $filtered_trace as $item ) {
156
- $stack[] = self::output_filename( $item['display'], $item['calling_file'], $item['calling_line'] );
157
  }
158
 
159
  $row_attr['data-qm-component'] = $component->name;
160
- $row_attr['data-qm-type'] = $row['type'];
161
- $row_attr['data-qm-time'] = $row['ltime'];
162
 
163
  if ( 'core' !== $component->context ) {
164
  $row_attr['data-qm-component'] .= ' non-core';
@@ -221,8 +200,8 @@ class QM_Output_Html_HTTP extends QM_Output_Html {
221
 
222
  if ( ! empty( $row['info'] ) ) {
223
  $time_fields = array(
224
- 'namelookup_time' => __( 'DNS Resolution Time', 'query-monitor' ),
225
- 'connect_time' => __( 'Connection Time', 'query-monitor' ),
226
  'starttransfer_time' => __( 'Transfer Start Time (TTFB)', 'query-monitor' ),
227
  );
228
  foreach ( $time_fields as $key => $value ) {
@@ -252,7 +231,7 @@ class QM_Output_Html_HTTP extends QM_Output_Html {
252
 
253
  $other_fields = array(
254
  'content_type' => __( 'Response Content Type', 'query-monitor' ),
255
- 'primary_ip' => __( 'IP Address', 'query-monitor' ),
256
  );
257
  foreach ( $other_fields as $key => $value ) {
258
  if ( ! isset( $row['info'][ $key ] ) ) {
@@ -316,7 +295,7 @@ class QM_Output_Html_HTTP extends QM_Output_Html {
316
  echo '<tfoot>';
317
 
318
  $total_stime = number_format_i18n( $data['ltime'], 4 );
319
- $count = count( $data['http'] );
320
 
321
  echo '<tr>';
322
  printf(
@@ -342,6 +321,10 @@ class QM_Output_Html_HTTP extends QM_Output_Html {
342
  }
343
  }
344
 
 
 
 
 
345
  public function admin_class( array $class ) {
346
 
347
  $data = $this->collector->get_data();
@@ -357,6 +340,10 @@ class QM_Output_Html_HTTP extends QM_Output_Html {
357
 
358
  }
359
 
 
 
 
 
360
  public function admin_menu( array $menu ) {
361
 
362
  $data = $this->collector->get_data();
@@ -390,6 +377,11 @@ class QM_Output_Html_HTTP extends QM_Output_Html {
390
 
391
  }
392
 
 
 
 
 
 
393
  function register_qm_output_html_http( array $output, QM_Collectors $collectors ) {
394
  $collector = QM_Collectors::get( 'http' );
395
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_HTTP extends QM_Output_Html {
13
 
24
  add_filter( 'qm/output/menu_class', array( $this, 'admin_class' ) );
25
  }
26
 
27
+ /**
28
+ * @return string
29
+ */
30
  public function name() {
31
  return __( 'HTTP API Calls', 'query-monitor' );
32
  }
33
 
34
+ /**
35
+ * @return void
36
+ */
37
  public function output() {
38
 
39
  $data = $this->collector->get_data();
40
 
41
  if ( ! empty( $data['http'] ) ) {
42
+ $statuses = array_keys( $data['types'] );
43
  $components = wp_list_pluck( $data['component_times'], 'component' );
44
 
45
  usort( $statuses, 'strcasecmp' );
84
  $i++;
85
  $is_error = false;
86
  $row_attr = array();
87
+ $css = '';
88
 
89
  if ( is_wp_error( $row['response'] ) ) {
90
  $response = $row['response']->get_error_message();
94
  $response = __( 'Non-blocking', 'query-monitor' );
95
  } else {
96
  $code = wp_remote_retrieve_response_code( $row['response'] );
97
+ $msg = wp_remote_retrieve_response_message( $row['response'] );
98
 
99
  if ( intval( $code ) >= 400 ) {
100
  $is_error = true;
108
  $css = 'qm-warn';
109
  }
110
 
111
+ $url = self::format_url( $row['url'] );
112
  $info = '';
113
 
114
  $url = preg_replace( '|^http:|', '<span class="qm-warn">http</span>:', $url );
128
 
129
  $component = $row['component'];
130
 
131
+ $stack = array();
132
+ $filtered_trace = $row['filtered_trace'];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
+ foreach ( $filtered_trace as $frame ) {
135
+ $stack[] = self::output_filename( $frame['display'], $frame['calling_file'], $frame['calling_line'] );
136
  }
137
 
138
  $row_attr['data-qm-component'] = $component->name;
139
+ $row_attr['data-qm-type'] = $row['type'];
140
+ $row_attr['data-qm-time'] = $row['ltime'];
141
 
142
  if ( 'core' !== $component->context ) {
143
  $row_attr['data-qm-component'] .= ' non-core';
200
 
201
  if ( ! empty( $row['info'] ) ) {
202
  $time_fields = array(
203
+ 'namelookup_time' => __( 'DNS Resolution Time', 'query-monitor' ),
204
+ 'connect_time' => __( 'Connection Time', 'query-monitor' ),
205
  'starttransfer_time' => __( 'Transfer Start Time (TTFB)', 'query-monitor' ),
206
  );
207
  foreach ( $time_fields as $key => $value ) {
231
 
232
  $other_fields = array(
233
  'content_type' => __( 'Response Content Type', 'query-monitor' ),
234
+ 'primary_ip' => __( 'IP Address', 'query-monitor' ),
235
  );
236
  foreach ( $other_fields as $key => $value ) {
237
  if ( ! isset( $row['info'][ $key ] ) ) {
295
  echo '<tfoot>';
296
 
297
  $total_stime = number_format_i18n( $data['ltime'], 4 );
298
+ $count = count( $data['http'] );
299
 
300
  echo '<tr>';
301
  printf(
321
  }
322
  }
323
 
324
+ /**
325
+ * @param array<int, string> $class
326
+ * @return array<int, string>
327
+ */
328
  public function admin_class( array $class ) {
329
 
330
  $data = $this->collector->get_data();
340
 
341
  }
342
 
343
+ /**
344
+ * @param array<string, mixed[]> $menu
345
+ * @return array<string, mixed[]>
346
+ */
347
  public function admin_menu( array $menu ) {
348
 
349
  $data = $this->collector->get_data();
377
 
378
  }
379
 
380
+ /**
381
+ * @param array<string, QM_Output> $output
382
+ * @param QM_Collectors $collectors
383
+ * @return array<string, QM_Output>
384
+ */
385
  function register_qm_output_html_http( array $output, QM_Collectors $collectors ) {
386
  $collector = QM_Collectors::get( 'http' );
387
  if ( $collector ) {
output/html/languages.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_Languages extends QM_Output_Html {
11
 
@@ -21,10 +23,16 @@ class QM_Output_Html_Languages extends QM_Output_Html {
21
  add_filter( 'qm/output/menus', array( $this, 'admin_menu' ), 80 );
22
  }
23
 
 
 
 
24
  public function name() {
25
  return __( 'Languages', 'query-monitor' );
26
  }
27
 
 
 
 
28
  public function output() {
29
 
30
  $data = $this->collector->get_data();
@@ -75,7 +83,11 @@ class QM_Output_Html_Languages extends QM_Output_Html {
75
 
76
  echo '<td class="qm-ltr">';
77
  if ( $mofile['file'] ) {
78
- echo esc_html( QM_Util::standard_dir( $mofile['file'], '' ) );
 
 
 
 
79
  } else {
80
  echo '<em>' . esc_html__( 'None', 'query-monitor' ) . '</em>';
81
  }
@@ -100,6 +112,10 @@ class QM_Output_Html_Languages extends QM_Output_Html {
100
  $this->after_tabular_output();
101
  }
102
 
 
 
 
 
103
  public function admin_menu( array $menu ) {
104
 
105
  $data = $this->collector->get_data();
@@ -115,6 +131,11 @@ class QM_Output_Html_Languages extends QM_Output_Html {
115
 
116
  }
117
 
 
 
 
 
 
118
  function register_qm_output_html_languages( array $output, QM_Collectors $collectors ) {
119
  $collector = QM_Collectors::get( 'languages' );
120
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_Languages extends QM_Output_Html {
13
 
23
  add_filter( 'qm/output/menus', array( $this, 'admin_menu' ), 80 );
24
  }
25
 
26
+ /**
27
+ * @return string
28
+ */
29
  public function name() {
30
  return __( 'Languages', 'query-monitor' );
31
  }
32
 
33
+ /**
34
+ * @return void
35
+ */
36
  public function output() {
37
 
38
  $data = $this->collector->get_data();
83
 
84
  echo '<td class="qm-ltr">';
85
  if ( $mofile['file'] ) {
86
+ if ( $mofile['found'] && 'jed' === $mofile['type'] && self::has_clickable_links() ) {
87
+ echo self::output_filename( QM_Util::standard_dir( $mofile['file'], '' ), $mofile['file'], 1, true ); // WPCS: XSS ok.
88
+ } else {
89
+ echo esc_html( QM_Util::standard_dir( $mofile['file'], '' ) );
90
+ }
91
  } else {
92
  echo '<em>' . esc_html__( 'None', 'query-monitor' ) . '</em>';
93
  }
112
  $this->after_tabular_output();
113
  }
114
 
115
+ /**
116
+ * @param array<string, mixed[]> $menu
117
+ * @return array<string, mixed[]>
118
+ */
119
  public function admin_menu( array $menu ) {
120
 
121
  $data = $this->collector->get_data();
131
 
132
  }
133
 
134
+ /**
135
+ * @param array<string, QM_Output> $output
136
+ * @param QM_Collectors $collectors
137
+ * @return array<string, QM_Output>
138
+ */
139
  function register_qm_output_html_languages( array $output, QM_Collectors $collectors ) {
140
  $collector = QM_Collectors::get( 'languages' );
141
  if ( $collector ) {
output/html/logger.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_Logger extends QM_Output_Html {
11
 
@@ -22,10 +24,16 @@ class QM_Output_Html_Logger extends QM_Output_Html {
22
  add_filter( 'qm/output/menu_class', array( $this, 'admin_class' ) );
23
  }
24
 
 
 
 
25
  public function name() {
26
  return __( 'Logger', 'query-monitor' );
27
  }
28
 
 
 
 
29
  public function output() {
30
 
31
  $data = $this->collector->get_data();
@@ -45,14 +53,34 @@ class QM_Output_Html_Logger extends QM_Output_Html {
45
  return;
46
  }
47
 
48
- $levels = array_map( 'ucfirst', $this->collector->get_levels() );
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
  $this->before_tabular_output();
51
 
 
 
 
 
 
 
 
 
52
  echo '<thead>';
53
  echo '<tr>';
54
  echo '<th scope="col" class="qm-filterable-column">';
55
- echo $this->build_filter( 'type', $levels, __( 'Level', 'query-monitor' ) ); // WPCS: XSS ok.
56
  echo '</th>';
57
  echo '<th scope="col" class="qm-col-message">' . esc_html__( 'Message', 'query-monitor' ) . '</th>';
58
  echo '<th scope="col">' . esc_html__( 'Caller', 'query-monitor' ) . '</th>';
@@ -65,11 +93,11 @@ class QM_Output_Html_Logger extends QM_Output_Html {
65
  echo '<tbody>';
66
 
67
  foreach ( $data['logs'] as $row ) {
68
- $component = $row['trace']->get_component();
69
 
70
- $row_attr = array();
71
  $row_attr['data-qm-component'] = $component->name;
72
- $row_attr['data-qm-type'] = ucfirst( $row['level'] );
73
 
74
  $attr = '';
75
 
@@ -103,11 +131,11 @@ class QM_Output_Html_Logger extends QM_Output_Html {
103
  esc_html( $row['message'] )
104
  );
105
 
106
- $stack = array();
107
- $filtered_trace = $row['trace']->get_display_trace();
108
 
109
- foreach ( $filtered_trace as $item ) {
110
- $stack[] = self::output_filename( $item['display'], $item['calling_file'], $item['calling_line'] );
111
  }
112
 
113
  $caller = array_shift( $stack );
@@ -142,6 +170,10 @@ class QM_Output_Html_Logger extends QM_Output_Html {
142
  $this->after_tabular_output();
143
  }
144
 
 
 
 
 
145
  public function admin_class( array $class ) {
146
  $data = $this->collector->get_data();
147
 
@@ -159,9 +191,13 @@ class QM_Output_Html_Logger extends QM_Output_Html {
159
  return $class;
160
  }
161
 
 
 
 
 
162
  public function admin_menu( array $menu ) {
163
- $data = $this->collector->get_data();
164
- $key = 'log';
165
  $count = 0;
166
 
167
  if ( ! empty( $data['logs'] ) ) {
@@ -181,7 +217,7 @@ class QM_Output_Html_Logger extends QM_Output_Html {
181
  }
182
 
183
  $menu[ $this->collector->id() ] = $this->menu( array(
184
- 'id' => "query-monitor-logger-{$key}",
185
  'title' => esc_html( sprintf(
186
  $label,
187
  number_format_i18n( $count )
@@ -193,6 +229,11 @@ class QM_Output_Html_Logger extends QM_Output_Html {
193
 
194
  }
195
 
 
 
 
 
 
196
  function register_qm_output_html_logger( array $output, QM_Collectors $collectors ) {
197
  $collector = QM_Collectors::get( 'logger' );
198
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_Logger extends QM_Output_Html {
13
 
24
  add_filter( 'qm/output/menu_class', array( $this, 'admin_class' ) );
25
  }
26
 
27
+ /**
28
+ * @return string
29
+ */
30
  public function name() {
31
  return __( 'Logger', 'query-monitor' );
32
  }
33
 
34
+ /**
35
+ * @return void
36
+ */
37
  public function output() {
38
 
39
  $data = $this->collector->get_data();
53
  return;
54
  }
55
 
56
+ $levels = array();
57
+
58
+ foreach ( $this->collector->get_levels() as $level ) {
59
+ if ( $data['counts'][ $level ] ) {
60
+ $levels[ $level ] = sprintf(
61
+ '%s (%d)',
62
+ ucfirst( $level ),
63
+ $data['counts'][ $level ]
64
+ );
65
+ } else {
66
+ $levels[ $level ] = ucfirst( $level );
67
+ }
68
+ }
69
 
70
  $this->before_tabular_output();
71
 
72
+ $level_args = array(
73
+ 'all' => sprintf(
74
+ /* translators: %s: Total number of items in a list */
75
+ __( 'All (%d)', 'query-monitor' ),
76
+ count( $data['logs'] )
77
+ ),
78
+ );
79
+
80
  echo '<thead>';
81
  echo '<tr>';
82
  echo '<th scope="col" class="qm-filterable-column">';
83
+ echo $this->build_filter( 'type', $levels, __( 'Level', 'query-monitor' ), $level_args ); // WPCS: XSS ok.
84
  echo '</th>';
85
  echo '<th scope="col" class="qm-col-message">' . esc_html__( 'Message', 'query-monitor' ) . '</th>';
86
  echo '<th scope="col">' . esc_html__( 'Caller', 'query-monitor' ) . '</th>';
93
  echo '<tbody>';
94
 
95
  foreach ( $data['logs'] as $row ) {
96
+ $component = $row['component'];
97
 
98
+ $row_attr = array();
99
  $row_attr['data-qm-component'] = $component->name;
100
+ $row_attr['data-qm-type'] = $row['level'];
101
 
102
  $attr = '';
103
 
131
  esc_html( $row['message'] )
132
  );
133
 
134
+ $stack = array();
135
+ $filtered_trace = $row['filtered_trace'];
136
 
137
+ foreach ( $filtered_trace as $frame ) {
138
+ $stack[] = self::output_filename( $frame['display'], $frame['calling_file'], $frame['calling_line'] );
139
  }
140
 
141
  $caller = array_shift( $stack );
170
  $this->after_tabular_output();
171
  }
172
 
173
+ /**
174
+ * @param array<int, string> $class
175
+ * @return array<int, string>
176
+ */
177
  public function admin_class( array $class ) {
178
  $data = $this->collector->get_data();
179
 
191
  return $class;
192
  }
193
 
194
+ /**
195
+ * @param array<string, mixed[]> $menu
196
+ * @return array<string, mixed[]>
197
+ */
198
  public function admin_menu( array $menu ) {
199
+ $data = $this->collector->get_data();
200
+ $key = 'log';
201
  $count = 0;
202
 
203
  if ( ! empty( $data['logs'] ) ) {
217
  }
218
 
219
  $menu[ $this->collector->id() ] = $this->menu( array(
220
+ 'id' => "query-monitor-logger-{$key}",
221
  'title' => esc_html( sprintf(
222
  $label,
223
  number_format_i18n( $count )
229
 
230
  }
231
 
232
+ /**
233
+ * @param array<string, QM_Output> $output
234
+ * @param QM_Collectors $collectors
235
+ * @return array<string, QM_Output>
236
+ */
237
  function register_qm_output_html_logger( array $output, QM_Collectors $collectors ) {
238
  $collector = QM_Collectors::get( 'logger' );
239
  if ( $collector ) {
output/html/overview.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_Overview extends QM_Output_Html {
11
 
@@ -21,15 +23,21 @@ class QM_Output_Html_Overview extends QM_Output_Html {
21
  add_filter( 'qm/output/title', array( $this, 'admin_title' ), 10 );
22
  }
23
 
 
 
 
24
  public function name() {
25
  return __( 'Overview', 'query-monitor' );
26
  }
27
 
 
 
 
28
  public function output() {
29
  $data = $this->collector->get_data();
30
 
31
- $db_query_num = null;
32
- $db_queries = QM_Collectors::get( 'db_queries' );
33
 
34
  if ( $db_queries ) {
35
  # @TODO: make this less derpy:
@@ -43,7 +51,7 @@ class QM_Output_Html_Overview extends QM_Output_Html {
43
  $cache = QM_Collectors::get( 'cache' );
44
  $http = QM_Collectors::get( 'http' );
45
 
46
- $qm_broken = __( 'A JavaScript problem on the page is preventing Query Monitor from working correctly. jQuery may have been blocked from loading.', 'query-monitor' );
47
  $ajax_errors = __( 'PHP errors were triggered during an Ajax request. See your browser developer console for details.', 'query-monitor' );
48
 
49
  $this->before_non_tabular_output();
@@ -81,7 +89,13 @@ class QM_Output_Html_Overview extends QM_Output_Html {
81
  echo '<section>';
82
  echo '<h3>' . esc_html__( 'Page Generation Time', 'query-monitor' ) . '</h3>';
83
  echo '<p>';
84
- echo esc_html( number_format_i18n( $data['time_taken'], 4 ) );
 
 
 
 
 
 
85
 
86
  if ( $data['time_limit'] > 0 ) {
87
  if ( $data['display_time_usage_warning'] ) {
@@ -117,9 +131,10 @@ class QM_Output_Html_Overview extends QM_Output_Html {
117
  esc_html_e( 'Unknown', 'query-monitor' );
118
  } else {
119
  echo esc_html( sprintf(
120
- /* translators: %s: Memory used in kilobytes */
121
- __( '%s kB', 'query-monitor' ),
122
- number_format_i18n( $data['memory'] / 1024 )
 
123
  ) );
124
 
125
  if ( $data['memory_limit'] > 0 ) {
@@ -129,10 +144,10 @@ class QM_Output_Html_Overview extends QM_Output_Html {
129
  echo '<br><span class="qm-info">';
130
  }
131
  echo esc_html( sprintf(
132
- /* translators: 1: Percentage of memory limit used, 2: Memory limit in kilobytes */
133
- __( '%1$s%% of %2$s kB server limit', 'query-monitor' ),
134
  number_format_i18n( $data['memory_usage'], 1 ),
135
- number_format_i18n( $data['memory_limit'] / 1024 )
136
  ) );
137
  echo '</span>';
138
  } else {
@@ -153,10 +168,10 @@ class QM_Output_Html_Overview extends QM_Output_Html {
153
  echo '<br><span class="qm-info">';
154
  }
155
  echo esc_html( sprintf(
156
- /* translators: 1: Percentage of memory limit used, 2: Memory limit in kilobytes */
157
- __( '%1$s%% of %2$s kB WordPress limit', 'query-monitor' ),
158
  number_format_i18n( $data['wp_memory_usage'], 1 ),
159
- number_format_i18n( $data['wp_memory_limit'] / 1024 )
160
  ) );
161
  echo '</span>';
162
  }
@@ -169,11 +184,15 @@ class QM_Output_Html_Overview extends QM_Output_Html {
169
  echo '<section>';
170
  echo '<h3>' . esc_html__( 'Database Queries', 'query-monitor' ) . '</h3>';
171
 
172
- if ( isset( $db_queries_data ) ) {
173
- echo '<p>';
174
- echo esc_html( number_format_i18n( $db_queries_data['total_time'], 4 ) );
175
- echo '</p>';
176
- }
 
 
 
 
177
 
178
  echo '<p>';
179
 
@@ -205,10 +224,16 @@ class QM_Output_Html_Overview extends QM_Output_Html {
205
  $http_data = $http->get_data();
206
 
207
  if ( ! empty( $http_data['http'] ) ) {
208
- printf(
209
- '<p>%s</p>',
210
- esc_html( number_format_i18n( $http_data['ltime'], 4 ) )
 
 
 
 
211
  );
 
 
212
  printf(
213
  '<button class="qm-filter-trigger" data-qm-target="http" data-qm-filter="type" data-qm-value="">%1$s: %2$s</button>',
214
  esc_html( _x( 'Total', 'HTTP API calls', 'query-monitor' ) ),
@@ -224,10 +249,10 @@ class QM_Output_Html_Overview extends QM_Output_Html {
224
  echo '</section>';
225
  }
226
 
227
- if ( $cache ) {
228
- echo '<section>';
229
- echo '<h3>' . esc_html__( 'Object Cache', 'query-monitor' ) . '</h3>';
230
 
 
231
  $cache_data = $cache->get_data();
232
  if ( isset( $cache_data['stats'] ) && isset( $cache_data['cache_hit_percentage'] ) ) {
233
  $cache_hit_percentage = $cache_data['cache_hit_percentage'];
@@ -243,10 +268,6 @@ class QM_Output_Html_Overview extends QM_Output_Html {
243
  number_format_i18n( $cache_data['stats']['cache_misses'], 0 )
244
  ) );
245
  echo '</p>';
246
- } else {
247
- echo '<p>';
248
- echo esc_html__( 'Object cache statistics are not available', 'query-monitor' );
249
- echo '</p>';
250
  }
251
 
252
  if ( $cache_data['has_object_cache'] ) {
@@ -254,28 +275,59 @@ class QM_Output_Html_Overview extends QM_Output_Html {
254
  printf(
255
  '<a href="%s" class="qm-link">%s</a>',
256
  esc_url( network_admin_url( 'plugins.php?plugin_status=dropins' ) ),
257
- esc_html__( 'External object cache in use', 'query-monitor' )
258
  );
259
  echo '</span></p>';
260
  } else {
261
- echo '<p>';
262
- echo esc_html__( 'External object cache not in use', 'query-monitor' );
263
- echo '</p>';
264
 
265
  $potentials = array_filter( $cache_data['object_cache_extensions'] );
266
 
267
  if ( ! empty( $potentials ) ) {
268
  foreach ( $potentials as $name => $value ) {
 
 
 
 
269
  echo '<p>';
270
- echo esc_html( sprintf(
271
- /* translators: %s: PHP extension name */
272
- __( 'The %s extension for PHP is installed but is not in use by WordPress', 'query-monitor' ),
273
- $name
274
- ) );
 
 
 
 
 
 
 
 
 
 
275
  echo '</p>';
276
  }
 
 
 
 
277
  }
278
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
279
 
280
  if ( $cache_data['has_opcode_cache'] ) {
281
  foreach ( array_filter( $cache_data['opcode_cache_extensions'] ) as $opcache_name => $opcache_state ) {
@@ -287,7 +339,14 @@ class QM_Output_Html_Overview extends QM_Output_Html {
287
  ) );
288
  echo '</p>';
289
  }
290
- }
 
 
 
 
 
 
 
291
 
292
  echo '</section>';
293
  }
@@ -295,6 +354,10 @@ class QM_Output_Html_Overview extends QM_Output_Html {
295
  $this->after_non_tabular_output();
296
  }
297
 
 
 
 
 
298
  public function admin_title( array $existing ) {
299
 
300
  $data = $this->collector->get_data();
@@ -302,17 +365,17 @@ class QM_Output_Html_Overview extends QM_Output_Html {
302
  if ( empty( $data['memory'] ) ) {
303
  $memory = '??';
304
  } else {
305
- $memory = number_format_i18n( ( $data['memory'] / 1024 ), 0 );
306
  }
307
 
308
  $title[] = sprintf(
309
- /* translators: %s: Page load time in seconds with a decimal fraction */
310
- esc_html_x( '%s S', 'Page load time', 'query-monitor' ),
311
  number_format_i18n( $data['time_taken'], 2 )
312
  );
313
  $title[] = sprintf(
314
- /* translators: %s: Memory usage in kilobytes */
315
- esc_html_x( '%s kB', 'Memory usage', 'query-monitor' ),
316
  $memory
317
  );
318
 
@@ -327,6 +390,11 @@ class QM_Output_Html_Overview extends QM_Output_Html {
327
 
328
  }
329
 
 
 
 
 
 
330
  function register_qm_output_html_overview( array $output, QM_Collectors $collectors ) {
331
  $collector = QM_Collectors::get( 'overview' );
332
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_Overview extends QM_Output_Html {
13
 
23
  add_filter( 'qm/output/title', array( $this, 'admin_title' ), 10 );
24
  }
25
 
26
+ /**
27
+ * @return string
28
+ */
29
  public function name() {
30
  return __( 'Overview', 'query-monitor' );
31
  }
32
 
33
+ /**
34
+ * @return void
35
+ */
36
  public function output() {
37
  $data = $this->collector->get_data();
38
 
39
+ $db_query_num = null;
40
+ $db_queries = QM_Collectors::get( 'db_queries' );
41
 
42
  if ( $db_queries ) {
43
  # @TODO: make this less derpy:
51
  $cache = QM_Collectors::get( 'cache' );
52
  $http = QM_Collectors::get( 'http' );
53
 
54
+ $qm_broken = __( 'A JavaScript problem on the page is preventing Query Monitor from working correctly. jQuery may have been blocked from loading.', 'query-monitor' );
55
  $ajax_errors = __( 'PHP errors were triggered during an Ajax request. See your browser developer console for details.', 'query-monitor' );
56
 
57
  $this->before_non_tabular_output();
89
  echo '<section>';
90
  echo '<h3>' . esc_html__( 'Page Generation Time', 'query-monitor' ) . '</h3>';
91
  echo '<p>';
92
+ echo esc_html(
93
+ sprintf(
94
+ /* translators: %s: A time in seconds with a decimal fraction. No space between value and unit. */
95
+ _x( '%ss', 'Time in seconds', 'query-monitor' ),
96
+ number_format_i18n( $data['time_taken'], 4 )
97
+ )
98
+ );
99
 
100
  if ( $data['time_limit'] > 0 ) {
101
  if ( $data['display_time_usage_warning'] ) {
131
  esc_html_e( 'Unknown', 'query-monitor' );
132
  } else {
133
  echo esc_html( sprintf(
134
+ /* translators: 1: Memory used in bytes, 2: Memory used in megabytes */
135
+ __( '%1$s bytes (%2$s MB)', 'query-monitor' ),
136
+ number_format_i18n( $data['memory'] ),
137
+ number_format_i18n( ( $data['memory'] / 1024 / 1024 ), 1 )
138
  ) );
139
 
140
  if ( $data['memory_limit'] > 0 ) {
144
  echo '<br><span class="qm-info">';
145
  }
146
  echo esc_html( sprintf(
147
+ /* translators: 1: Percentage of memory limit used, 2: Memory limit in megabytes */
148
+ __( '%1$s%% of %2$s MB server limit', 'query-monitor' ),
149
  number_format_i18n( $data['memory_usage'], 1 ),
150
+ number_format_i18n( $data['memory_limit'] / 1024 / 1024 )
151
  ) );
152
  echo '</span>';
153
  } else {
168
  echo '<br><span class="qm-info">';
169
  }
170
  echo esc_html( sprintf(
171
+ /* translators: 1: Percentage of memory limit used, 2: Memory limit in megabytes */
172
+ __( '%1$s%% of %2$s MB WordPress limit', 'query-monitor' ),
173
  number_format_i18n( $data['wp_memory_usage'], 1 ),
174
+ number_format_i18n( $data['wp_memory_limit'] / 1024 / 1024 )
175
  ) );
176
  echo '</span>';
177
  }
184
  echo '<section>';
185
  echo '<h3>' . esc_html__( 'Database Queries', 'query-monitor' ) . '</h3>';
186
 
187
+ echo '<p>';
188
+ echo esc_html(
189
+ sprintf(
190
+ /* translators: %s: A time in seconds with a decimal fraction. No space between value and unit. */
191
+ _x( '%ss', 'Time in seconds', 'query-monitor' ),
192
+ number_format_i18n( $db_queries_data['total_time'], 4 )
193
+ )
194
+ );
195
+ echo '</p>';
196
 
197
  echo '<p>';
198
 
224
  $http_data = $http->get_data();
225
 
226
  if ( ! empty( $http_data['http'] ) ) {
227
+ echo '<p>';
228
+ echo esc_html(
229
+ sprintf(
230
+ /* translators: %s: A time in seconds with a decimal fraction. No space between value and unit. */
231
+ _x( '%ss', 'Time in seconds', 'query-monitor' ),
232
+ number_format_i18n( $http_data['ltime'], 4 )
233
+ )
234
  );
235
+ echo '</p>';
236
+
237
  printf(
238
  '<button class="qm-filter-trigger" data-qm-target="http" data-qm-filter="type" data-qm-value="">%1$s: %2$s</button>',
239
  esc_html( _x( 'Total', 'HTTP API calls', 'query-monitor' ) ),
249
  echo '</section>';
250
  }
251
 
252
+ echo '<section>';
253
+ echo '<h3>' . esc_html__( 'Object Cache', 'query-monitor' ) . '</h3>';
 
254
 
255
+ if ( $cache ) {
256
  $cache_data = $cache->get_data();
257
  if ( isset( $cache_data['stats'] ) && isset( $cache_data['cache_hit_percentage'] ) ) {
258
  $cache_hit_percentage = $cache_data['cache_hit_percentage'];
268
  number_format_i18n( $cache_data['stats']['cache_misses'], 0 )
269
  ) );
270
  echo '</p>';
 
 
 
 
271
  }
272
 
273
  if ( $cache_data['has_object_cache'] ) {
275
  printf(
276
  '<a href="%s" class="qm-link">%s</a>',
277
  esc_url( network_admin_url( 'plugins.php?plugin_status=dropins' ) ),
278
+ esc_html__( 'Persistent object cache plugin in use', 'query-monitor' )
279
  );
280
  echo '</span></p>';
281
  } else {
282
+ echo '<p><span class="qm-warn"><span class="dashicons dashicons-warning" aria-hidden="true"></span>';
283
+ echo esc_html__( 'Persistent object cache plugin not in use', 'query-monitor' );
284
+ echo '</span></p>';
285
 
286
  $potentials = array_filter( $cache_data['object_cache_extensions'] );
287
 
288
  if ( ! empty( $potentials ) ) {
289
  foreach ( $potentials as $name => $value ) {
290
+ $url = sprintf(
291
+ 'https://wordpress.org/plugins/search/%s/',
292
+ strtolower( $name )
293
+ );
294
  echo '<p>';
295
+ echo wp_kses(
296
+ sprintf(
297
+ /* translators: 1: PHP extension name, 2: URL to plugin directory */
298
+ __( 'The %1$s object cache extension for PHP is installed but is not in use by WordPress. You should <a href="%2$s" target="_blank" class="qm-external-link">install a %1$s plugin</a>.', 'query-monitor' ),
299
+ esc_html( $name ),
300
+ esc_url( $url )
301
+ ),
302
+ array(
303
+ 'a' => array(
304
+ 'href' => array(),
305
+ 'target' => array(),
306
+ 'class' => array(),
307
+ ),
308
+ )
309
+ );
310
  echo '</p>';
311
  }
312
+ } else {
313
+ echo '<p>';
314
+ echo esc_html__( 'Speak to your web host about enabling an object cache extension such as Redis or Memcached.', 'query-monitor' );
315
+ echo '</p>';
316
  }
317
  }
318
+ } else {
319
+ echo '<p>';
320
+ echo esc_html__( 'Object cache statistics are not available', 'query-monitor' );
321
+ echo '</p>';
322
+ }
323
+
324
+ echo '</section>';
325
+
326
+ if ( $cache ) {
327
+ $cache_data = $cache->get_data();
328
+
329
+ echo '<section>';
330
+ echo '<h3>' . esc_html__( 'Opcode Cache', 'query-monitor' ) . '</h3>';
331
 
332
  if ( $cache_data['has_opcode_cache'] ) {
333
  foreach ( array_filter( $cache_data['opcode_cache_extensions'] ) as $opcache_name => $opcache_state ) {
339
  ) );
340
  echo '</p>';
341
  }
342
+ } else {
343
+ echo '<p><span class="qm-warn"><span class="dashicons dashicons-warning" aria-hidden="true"></span>';
344
+ echo esc_html__( 'Opcode cache not in use', 'query-monitor' );
345
+ echo '</span></p>';
346
+ echo '<p>';
347
+ echo esc_html__( 'Speak to your web host about enabling an opcode cache such as OPcache.', 'query-monitor' );
348
+ echo '</p>';
349
+ }
350
 
351
  echo '</section>';
352
  }
354
  $this->after_non_tabular_output();
355
  }
356
 
357
+ /**
358
+ * @param array<int, string> $existing
359
+ * @return array<int, string>
360
+ */
361
  public function admin_title( array $existing ) {
362
 
363
  $data = $this->collector->get_data();
365
  if ( empty( $data['memory'] ) ) {
366
  $memory = '??';
367
  } else {
368
+ $memory = number_format_i18n( ( $data['memory'] / 1024 / 1024 ), 1 );
369
  }
370
 
371
  $title[] = sprintf(
372
+ /* translators: %s: Time in seconds with a decimal fraction. Note the space between value and unit. */
373
+ esc_html__( '%s S', 'query-monitor' ),
374
  number_format_i18n( $data['time_taken'], 2 )
375
  );
376
  $title[] = sprintf(
377
+ /* translators: %s: Memory usage in megabytes with a decimal fraction. Note the space between value and unit. */
378
+ esc_html__( '%s MB', 'query-monitor' ),
379
  $memory
380
  );
381
 
390
 
391
  }
392
 
393
+ /**
394
+ * @param array<string, QM_Output> $output
395
+ * @param QM_Collectors $collectors
396
+ * @return array<string, QM_Output>
397
+ */
398
  function register_qm_output_html_overview( array $output, QM_Collectors $collectors ) {
399
  $collector = QM_Collectors::get( 'overview' );
400
  if ( $collector ) {
output/html/php_errors.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_PHP_Errors extends QM_Output_Html {
11
 
@@ -23,10 +25,16 @@ class QM_Output_Html_PHP_Errors extends QM_Output_Html {
23
  add_filter( 'qm/output/menu_class', array( $this, 'admin_class' ) );
24
  }
25
 
 
 
 
26
  public function name() {
27
  return __( 'PHP Errors', 'query-monitor' );
28
  }
29
 
 
 
 
30
  public function output() {
31
 
32
  $data = $this->collector->get_data();
@@ -35,7 +43,7 @@ class QM_Output_Html_PHP_Errors extends QM_Output_Html {
35
  return;
36
  }
37
 
38
- $levels = array(
39
  'Warning',
40
  'Notice',
41
  'Strict',
@@ -72,12 +80,12 @@ class QM_Output_Html_PHP_Errors extends QM_Output_Html {
72
 
73
  foreach ( $data[ $error_group ][ $type ] as $error_key => $error ) {
74
 
75
- $row_attr = array();
76
- $row_attr['data-qm-type'] = ucfirst( $type );
77
- $row_attr['data-qm-key'] = $error_key;
78
 
79
- if ( $error['trace'] ) {
80
- $component = $error['trace']->get_component();
81
  $row_attr['data-qm-component'] = $component->name;
82
 
83
  if ( 'core' !== $component->context ) {
@@ -114,10 +122,10 @@ class QM_Output_Html_PHP_Errors extends QM_Output_Html {
114
  echo '<td class="qm-ltr">' . esc_html( $error['message'] ) . '</td>';
115
  echo '<td class="qm-num">' . esc_html( number_format_i18n( $error['calls'] ) ) . '</td>';
116
 
117
- $stack = array();
118
 
119
- if ( $error['trace'] ) {
120
- $filtered_trace = $error['trace']->get_display_trace();
121
 
122
  // debug_backtrace() (used within QM_Backtrace) doesn't like being used within an error handler so
123
  // we need to handle its somewhat unreliable stack trace items.
@@ -167,6 +175,10 @@ class QM_Output_Html_PHP_Errors extends QM_Output_Html {
167
  $this->after_tabular_output();
168
  }
169
 
 
 
 
 
170
  public function admin_class( array $class ) {
171
 
172
  $data = $this->collector->get_data();
@@ -181,41 +193,45 @@ class QM_Output_Html_PHP_Errors extends QM_Output_Html {
181
 
182
  }
183
 
 
 
 
 
184
  public function admin_menu( array $menu ) {
185
 
186
- $data = $this->collector->get_data();
187
  $menu_label = array();
188
 
189
  $types = array(
190
  /* translators: %s: Number of deprecated PHP errors */
191
  'deprecated' => _nx_noop( '%s Deprecated', '%s Deprecated', 'PHP error level', 'query-monitor' ),
192
  /* translators: %s: Number of strict PHP errors */
193
- 'strict' => _nx_noop( '%s Strict', '%s Stricts', 'PHP error level', 'query-monitor' ),
194
  /* translators: %s: Number of PHP notices */
195
- 'notice' => _nx_noop( '%s Notice', '%s Notices', 'PHP error level', 'query-monitor' ),
196
  /* translators: %s: Number of PHP warnings */
197
- 'warning' => _nx_noop( '%s Warning', '%s Warnings', 'PHP error level', 'query-monitor' ),
198
  );
199
 
200
- $key = 'quiet';
201
  $generic = false;
202
 
203
  foreach ( $types as $type => $label ) {
204
 
205
- $count = 0;
206
  $has_errors = false;
207
 
208
  if ( isset( $data['suppressed'][ $type ] ) ) {
209
  $has_errors = true;
210
- $generic = true;
211
  }
212
  if ( isset( $data['silenced'][ $type ] ) ) {
213
  $has_errors = true;
214
- $generic = true;
215
  }
216
  if ( isset( $data['errors'][ $type ] ) ) {
217
  $has_errors = true;
218
- $key = $type;
219
  $count += array_sum( wp_list_pluck( $data['errors'][ $type ], 'calls' ) );
220
  }
221
 
@@ -224,7 +240,7 @@ class QM_Output_Html_PHP_Errors extends QM_Output_Html {
224
  }
225
 
226
  if ( $count ) {
227
- $label = sprintf(
228
  translate_nooped_plural(
229
  $label,
230
  $count,
@@ -256,19 +272,23 @@ class QM_Output_Html_PHP_Errors extends QM_Output_Html {
256
  }
257
 
258
  $menu[ $this->collector->id() ] = $this->menu( array(
259
- 'id' => "query-monitor-{$key}s",
260
  'title' => $title,
261
  ) );
262
  return $menu;
263
 
264
  }
265
 
 
 
 
 
266
  public function panel_menu( array $menu ) {
267
  if ( ! isset( $menu[ $this->collector->id() ] ) ) {
268
  return $menu;
269
  }
270
 
271
- $data = $this->collector->get_data();
272
  $count = 0;
273
  $types = array(
274
  'suppressed',
@@ -295,6 +315,11 @@ class QM_Output_Html_PHP_Errors extends QM_Output_Html {
295
 
296
  }
297
 
 
 
 
 
 
298
  function register_qm_output_html_php_errors( array $output, QM_Collectors $collectors ) {
299
  $collector = QM_Collectors::get( 'php_errors' );
300
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_PHP_Errors extends QM_Output_Html {
13
 
25
  add_filter( 'qm/output/menu_class', array( $this, 'admin_class' ) );
26
  }
27
 
28
+ /**
29
+ * @return string
30
+ */
31
  public function name() {
32
  return __( 'PHP Errors', 'query-monitor' );
33
  }
34
 
35
+ /**
36
+ * @return void
37
+ */
38
  public function output() {
39
 
40
  $data = $this->collector->get_data();
43
  return;
44
  }
45
 
46
+ $levels = array(
47
  'Warning',
48
  'Notice',
49
  'Strict',
80
 
81
  foreach ( $data[ $error_group ][ $type ] as $error_key => $error ) {
82
 
83
+ $row_attr = array();
84
+ $row_attr['data-qm-type'] = ucfirst( $type );
85
+ $row_attr['data-qm-key'] = $error_key;
86
 
87
+ if ( $error['component'] ) {
88
+ $component = $error['component'];
89
  $row_attr['data-qm-component'] = $component->name;
90
 
91
  if ( 'core' !== $component->context ) {
122
  echo '<td class="qm-ltr">' . esc_html( $error['message'] ) . '</td>';
123
  echo '<td class="qm-num">' . esc_html( number_format_i18n( $error['calls'] ) ) . '</td>';
124
 
125
+ $stack = array();
126
 
127
+ if ( $error['filtered_trace'] ) {
128
+ $filtered_trace = $error['filtered_trace'];
129
 
130
  // debug_backtrace() (used within QM_Backtrace) doesn't like being used within an error handler so
131
  // we need to handle its somewhat unreliable stack trace items.
175
  $this->after_tabular_output();
176
  }
177
 
178
+ /**
179
+ * @param array<int, string> $class
180
+ * @return array<int, string>
181
+ */
182
  public function admin_class( array $class ) {
183
 
184
  $data = $this->collector->get_data();
193
 
194
  }
195
 
196
+ /**
197
+ * @param array<string, mixed[]> $menu
198
+ * @return array<string, mixed[]>
199
+ */
200
  public function admin_menu( array $menu ) {
201
 
202
+ $data = $this->collector->get_data();
203
  $menu_label = array();
204
 
205
  $types = array(
206
  /* translators: %s: Number of deprecated PHP errors */
207
  'deprecated' => _nx_noop( '%s Deprecated', '%s Deprecated', 'PHP error level', 'query-monitor' ),
208
  /* translators: %s: Number of strict PHP errors */
209
+ 'strict' => _nx_noop( '%s Strict', '%s Stricts', 'PHP error level', 'query-monitor' ),
210
  /* translators: %s: Number of PHP notices */
211
+ 'notice' => _nx_noop( '%s Notice', '%s Notices', 'PHP error level', 'query-monitor' ),
212
  /* translators: %s: Number of PHP warnings */
213
+ 'warning' => _nx_noop( '%s Warning', '%s Warnings', 'PHP error level', 'query-monitor' ),
214
  );
215
 
216
+ $key = 'quiet';
217
  $generic = false;
218
 
219
  foreach ( $types as $type => $label ) {
220
 
221
+ $count = 0;
222
  $has_errors = false;
223
 
224
  if ( isset( $data['suppressed'][ $type ] ) ) {
225
  $has_errors = true;
226
+ $generic = true;
227
  }
228
  if ( isset( $data['silenced'][ $type ] ) ) {
229
  $has_errors = true;
230
+ $generic = true;
231
  }
232
  if ( isset( $data['errors'][ $type ] ) ) {
233
  $has_errors = true;
234
+ $key = $type;
235
  $count += array_sum( wp_list_pluck( $data['errors'][ $type ], 'calls' ) );
236
  }
237
 
240
  }
241
 
242
  if ( $count ) {
243
+ $label = sprintf(
244
  translate_nooped_plural(
245
  $label,
246
  $count,
272
  }
273
 
274
  $menu[ $this->collector->id() ] = $this->menu( array(
275
+ 'id' => "query-monitor-{$key}s",
276
  'title' => $title,
277
  ) );
278
  return $menu;
279
 
280
  }
281
 
282
+ /**
283
+ * @param array<string, mixed[]> $menu
284
+ * @return array<string, mixed[]>
285
+ */
286
  public function panel_menu( array $menu ) {
287
  if ( ! isset( $menu[ $this->collector->id() ] ) ) {
288
  return $menu;
289
  }
290
 
291
+ $data = $this->collector->get_data();
292
  $count = 0;
293
  $types = array(
294
  'suppressed',
315
 
316
  }
317
 
318
+ /**
319
+ * @param array<string, QM_Output> $output
320
+ * @param QM_Collectors $collectors
321
+ * @return array<string, QM_Output>
322
+ */
323
  function register_qm_output_html_php_errors( array $output, QM_Collectors $collectors ) {
324
  $collector = QM_Collectors::get( 'php_errors' );
325
  if ( $collector ) {
output/html/request.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_Request extends QM_Output_Html {
11
 
@@ -21,10 +23,16 @@ class QM_Output_Html_Request extends QM_Output_Html {
21
  add_filter( 'qm/output/menus', array( $this, 'admin_menu' ), 50 );
22
  }
23
 
 
 
 
24
  public function name() {
25
  return __( 'Request', 'query-monitor' );
26
  }
27
 
 
 
 
28
  public function output() {
29
 
30
  $data = $this->collector->get_data();
@@ -35,10 +43,10 @@ class QM_Output_Html_Request extends QM_Output_Html {
35
  $this->before_non_tabular_output();
36
 
37
  foreach ( array(
38
- 'request' => __( 'Request', 'query-monitor' ),
39
- 'matched_rule' => __( 'Matched Rule', 'query-monitor' ),
40
  'matched_query' => __( 'Matched Query', 'query-monitor' ),
41
- 'query_string' => __( 'Query String', 'query-monitor' ),
42
  ) as $item => $name ) {
43
  if ( is_admin() && ! isset( $data['request'][ $item ] ) ) {
44
  continue;
@@ -179,9 +187,9 @@ class QM_Output_Html_Request extends QM_Output_Html {
179
  echo '<table>';
180
 
181
  foreach ( array(
182
- 'ip' => __( 'Remote IP', 'query-monitor' ),
183
  'method' => __( 'HTTP method', 'query-monitor' ),
184
- 'url' => __( 'Requested URL', 'query-monitor' ),
185
  ) as $item => $name ) {
186
  echo '<tr>';
187
  echo '<th scope="row">' . esc_html( $name ) . '</td>';
@@ -197,9 +205,13 @@ class QM_Output_Html_Request extends QM_Output_Html {
197
  $this->after_non_tabular_output();
198
  }
199
 
 
 
 
 
200
  public function admin_menu( array $menu ) {
201
 
202
- $data = $this->collector->get_data();
203
  $count = isset( $data['plugin_qvars'] ) ? count( $data['plugin_qvars'] ) : 0;
204
 
205
  $title = ( empty( $count ) )
@@ -219,6 +231,11 @@ class QM_Output_Html_Request extends QM_Output_Html {
219
 
220
  }
221
 
 
 
 
 
 
222
  function register_qm_output_html_request( array $output, QM_Collectors $collectors ) {
223
  $collector = QM_Collectors::get( 'request' );
224
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_Request extends QM_Output_Html {
13
 
23
  add_filter( 'qm/output/menus', array( $this, 'admin_menu' ), 50 );
24
  }
25
 
26
+ /**
27
+ * @return string
28
+ */
29
  public function name() {
30
  return __( 'Request', 'query-monitor' );
31
  }
32
 
33
+ /**
34
+ * @return void
35
+ */
36
  public function output() {
37
 
38
  $data = $this->collector->get_data();
43
  $this->before_non_tabular_output();
44
 
45
  foreach ( array(
46
+ 'request' => __( 'Request', 'query-monitor' ),
47
+ 'matched_rule' => __( 'Matched Rule', 'query-monitor' ),
48
  'matched_query' => __( 'Matched Query', 'query-monitor' ),
49
+ 'query_string' => __( 'Query String', 'query-monitor' ),
50
  ) as $item => $name ) {
51
  if ( is_admin() && ! isset( $data['request'][ $item ] ) ) {
52
  continue;
187
  echo '<table>';
188
 
189
  foreach ( array(
190
+ 'ip' => __( 'Remote IP', 'query-monitor' ),
191
  'method' => __( 'HTTP method', 'query-monitor' ),
192
+ 'url' => __( 'Requested URL', 'query-monitor' ),
193
  ) as $item => $name ) {
194
  echo '<tr>';
195
  echo '<th scope="row">' . esc_html( $name ) . '</td>';
205
  $this->after_non_tabular_output();
206
  }
207
 
208
+ /**
209
+ * @param array<string, mixed[]> $menu
210
+ * @return array<string, mixed[]>
211
+ */
212
  public function admin_menu( array $menu ) {
213
 
214
+ $data = $this->collector->get_data();
215
  $count = isset( $data['plugin_qvars'] ) ? count( $data['plugin_qvars'] ) : 0;
216
 
217
  $title = ( empty( $count ) )
231
 
232
  }
233
 
234
+ /**
235
+ * @param array<string, QM_Output> $output
236
+ * @param QM_Collectors $collectors
237
+ * @return array<string, QM_Output>
238
+ */
239
  function register_qm_output_html_request( array $output, QM_Collectors $collectors ) {
240
  $collector = QM_Collectors::get( 'request' );
241
  if ( $collector ) {
output/html/theme.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_Theme extends QM_Output_Html {
11
 
@@ -22,10 +24,16 @@ class QM_Output_Html_Theme extends QM_Output_Html {
22
  add_filter( 'qm/output/panel_menus', array( $this, 'panel_menu' ), 60 );
23
  }
24
 
 
 
 
25
  public function name() {
26
  return __( 'Theme', 'query-monitor' );
27
  }
28
 
 
 
 
29
  public function output() {
30
  $data = $this->collector->get_data();
31
 
@@ -65,13 +73,6 @@ class QM_Output_Html_Theme extends QM_Output_Html {
65
  echo '<p><em>' . esc_html__( 'Unknown', 'query-monitor' ) . '</em></p>';
66
  }
67
 
68
- if ( ! empty( $data['template_altered'] ) ) {
69
- printf(
70
- '<p><button class="qm-filter-trigger qm-filter-info" data-qm-target="response-concerned_hooks">%s</button></p>',
71
- esc_html__( 'Template Hooks in Use', 'query-monitor' )
72
- );
73
- }
74
-
75
  if ( ! empty( $data['template_hierarchy'] ) ) {
76
  echo '<h3>' . esc_html__( 'Template Hierarchy', 'query-monitor' ) . '</h3>';
77
  echo '<ol class="qm-ltr"><li>' . implode( '</li><li>', array_map( 'esc_html', $data['template_hierarchy'] ) ) . '</li></ol>';
@@ -95,7 +96,13 @@ class QM_Output_Html_Theme extends QM_Output_Html {
95
  foreach ( $parts as $filename => $display ) {
96
  echo '<li>';
97
 
98
- if ( self::has_clickable_links() ) {
 
 
 
 
 
 
99
  echo self::output_filename( $display, $filename, 0, true ); // WPCS: XSS ok.
100
  } else {
101
  echo esc_html( $display );
@@ -139,7 +146,7 @@ class QM_Output_Html_Theme extends QM_Output_Html {
139
  }
140
 
141
  echo '</ul>';
142
- } elseif ( $data['has_template_part_action'] ) {
143
  echo '<p><em>' . esc_html__( 'None', 'query-monitor' ) . '</em></p>';
144
  }
145
  }
@@ -176,6 +183,10 @@ class QM_Output_Html_Theme extends QM_Output_Html {
176
  $this->after_non_tabular_output();
177
  }
178
 
 
 
 
 
179
  public function admin_menu( array $menu ) {
180
 
181
  $data = $this->collector->get_data();
@@ -198,6 +209,10 @@ class QM_Output_Html_Theme extends QM_Output_Html {
198
 
199
  }
200
 
 
 
 
 
201
  public function panel_menu( array $menu ) {
202
  if ( isset( $menu[ $this->collector->id() ] ) ) {
203
  $menu[ $this->collector->id() ]['title'] = __( 'Template', 'query-monitor' );
@@ -208,6 +223,11 @@ class QM_Output_Html_Theme extends QM_Output_Html {
208
 
209
  }
210
 
 
 
 
 
 
211
  function register_qm_output_html_theme( array $output, QM_Collectors $collectors ) {
212
  if ( is_admin() ) {
213
  return $output;
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_Theme extends QM_Output_Html {
13
 
24
  add_filter( 'qm/output/panel_menus', array( $this, 'panel_menu' ), 60 );
25
  }
26
 
27
+ /**
28
+ * @return string
29
+ */
30
  public function name() {
31
  return __( 'Theme', 'query-monitor' );
32
  }
33
 
34
+ /**
35
+ * @return void
36
+ */
37
  public function output() {
38
  $data = $this->collector->get_data();
39
 
73
  echo '<p><em>' . esc_html__( 'Unknown', 'query-monitor' ) . '</em></p>';
74
  }
75
 
 
 
 
 
 
 
 
76
  if ( ! empty( $data['template_hierarchy'] ) ) {
77
  echo '<h3>' . esc_html__( 'Template Hierarchy', 'query-monitor' ) . '</h3>';
78
  echo '<ol class="qm-ltr"><li>' . implode( '</li><li>', array_map( 'esc_html', $data['template_hierarchy'] ) ) . '</li></ol>';
96
  foreach ( $parts as $filename => $display ) {
97
  echo '<li>';
98
 
99
+ if ( is_numeric( $filename ) ) {
100
+ printf(
101
+ '<a href="%1$s">%2$s</a>',
102
+ esc_url( get_edit_post_link( $filename ) ),
103
+ esc_html( $display )
104
+ );
105
+ } elseif ( self::has_clickable_links() ) {
106
  echo self::output_filename( $display, $filename, 0, true ); // WPCS: XSS ok.
107
  } else {
108
  echo esc_html( $display );
146
  }
147
 
148
  echo '</ul>';
149
+ } else {
150
  echo '<p><em>' . esc_html__( 'None', 'query-monitor' ) . '</em></p>';
151
  }
152
  }
183
  $this->after_non_tabular_output();
184
  }
185
 
186
+ /**
187
+ * @param array<string, mixed[]> $menu
188
+ * @return array<string, mixed[]>
189
+ */
190
  public function admin_menu( array $menu ) {
191
 
192
  $data = $this->collector->get_data();
209
 
210
  }
211
 
212
+ /**
213
+ * @param array<string, mixed[]> $menu
214
+ * @return array<string, mixed[]>
215
+ */
216
  public function panel_menu( array $menu ) {
217
  if ( isset( $menu[ $this->collector->id() ] ) ) {
218
  $menu[ $this->collector->id() ]['title'] = __( 'Template', 'query-monitor' );
223
 
224
  }
225
 
226
+ /**
227
+ * @param array<string, QM_Output> $output
228
+ * @param QM_Collectors $collectors
229
+ * @return array<string, QM_Output>
230
+ */
231
  function register_qm_output_html_theme( array $output, QM_Collectors $collectors ) {
232
  if ( is_admin() ) {
233
  return $output;
output/html/timing.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_Timing extends QM_Output_Html {
11
 
@@ -21,10 +23,16 @@ class QM_Output_Html_Timing extends QM_Output_Html {
21
  add_filter( 'qm/output/menus', array( $this, 'admin_menu' ), 15 );
22
  }
23
 
 
 
 
24
  public function name() {
25
  return __( 'Timing', 'query-monitor' );
26
  }
27
 
 
 
 
28
  public function output() {
29
 
30
  $data = $this->collector->get_data();
@@ -50,9 +58,9 @@ class QM_Output_Html_Timing extends QM_Output_Html {
50
  if ( ! empty( $data['timing'] ) ) {
51
  foreach ( $data['timing'] as $row ) {
52
 
53
- $component = $row['trace']->get_component();
54
- $trace = $row['trace']->get_filtered_trace();
55
- $file = self::output_filename( $row['function'], $trace[0]['file'], $trace[0]['line'] );
56
 
57
  echo '<tr>';
58
 
@@ -135,9 +143,9 @@ class QM_Output_Html_Timing extends QM_Output_Html {
135
  }
136
  if ( ! empty( $data['warning'] ) ) {
137
  foreach ( $data['warning'] as $row ) {
138
- $component = $row['trace']->get_component();
139
- $trace = $row['trace']->get_filtered_trace();
140
- $file = self::output_filename( $row['function'], $trace[0]['file'], $trace[0]['line'] );
141
 
142
  echo '<tr class="qm-warn">';
143
  if ( self::has_clickable_links() ) {
@@ -171,6 +179,10 @@ class QM_Output_Html_Timing extends QM_Output_Html {
171
  $this->after_tabular_output();
172
  }
173
 
 
 
 
 
174
  public function admin_menu( array $menu ) {
175
  $data = $this->collector->get_data();
176
 
@@ -198,6 +210,11 @@ class QM_Output_Html_Timing extends QM_Output_Html {
198
 
199
  }
200
 
 
 
 
 
 
201
  function register_qm_output_html_timing( array $output, QM_Collectors $collectors ) {
202
  $collector = QM_Collectors::get( 'timing' );
203
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_Timing extends QM_Output_Html {
13
 
23
  add_filter( 'qm/output/menus', array( $this, 'admin_menu' ), 15 );
24
  }
25
 
26
+ /**
27
+ * @return string
28
+ */
29
  public function name() {
30
  return __( 'Timing', 'query-monitor' );
31
  }
32
 
33
+ /**
34
+ * @return void
35
+ */
36
  public function output() {
37
 
38
  $data = $this->collector->get_data();
58
  if ( ! empty( $data['timing'] ) ) {
59
  foreach ( $data['timing'] as $row ) {
60
 
61
+ $component = $row['component'];
62
+ $trace = $row['filtered_trace'];
63
+ $file = self::output_filename( $row['function'], $trace[0]['file'], $trace[0]['line'] );
64
 
65
  echo '<tr>';
66
 
143
  }
144
  if ( ! empty( $data['warning'] ) ) {
145
  foreach ( $data['warning'] as $row ) {
146
+ $component = $row['component'];
147
+ $trace = $row['filtered_trace'];
148
+ $file = self::output_filename( $row['function'], $trace[0]['file'], $trace[0]['line'] );
149
 
150
  echo '<tr class="qm-warn">';
151
  if ( self::has_clickable_links() ) {
179
  $this->after_tabular_output();
180
  }
181
 
182
+ /**
183
+ * @param array<string, mixed[]> $menu
184
+ * @return array<string, mixed[]>
185
+ */
186
  public function admin_menu( array $menu ) {
187
  $data = $this->collector->get_data();
188
 
210
 
211
  }
212
 
213
+ /**
214
+ * @param array<string, QM_Output> $output
215
+ * @param QM_Collectors $collectors
216
+ * @return array<string, QM_Output>
217
+ */
218
  function register_qm_output_html_timing( array $output, QM_Collectors $collectors ) {
219
  $collector = QM_Collectors::get( 'timing' );
220
  if ( $collector ) {
output/html/transients.php CHANGED
@@ -5,7 +5,9 @@
5
  * @package query-monitor
6
  */
7
 
8
- defined( 'ABSPATH' ) || exit;
 
 
9
 
10
  class QM_Output_Html_Transients extends QM_Output_Html {
11
 
@@ -21,10 +23,16 @@ class QM_Output_Html_Transients extends QM_Output_Html {
21
  add_filter( 'qm/output/menus', array( $this, 'admin_menu' ), 100 );
22
  }
23
 
 
 
 
24
  public function name() {
25
  return __( 'Transients', 'query-monitor' );
26
  }
27
 
 
 
 
28
  public function output() {
29
 
30
  $data = $this->collector->get_data();
@@ -83,8 +91,8 @@ class QM_Output_Html_Transients extends QM_Output_Html {
83
 
84
  $stack = array();
85
 
86
- foreach ( $row['filtered_trace'] as $item ) {
87
- $stack[] = self::output_filename( $item['display'], $item['calling_file'], $item['calling_line'] );
88
  }
89
 
90
  $caller = array_shift( $stack );
@@ -125,9 +133,13 @@ class QM_Output_Html_Transients extends QM_Output_Html {
125
  }
126
  }
127
 
 
 
 
 
128
  public function admin_menu( array $menu ) {
129
 
130
- $data = $this->collector->get_data();
131
  $count = isset( $data['trans'] ) ? count( $data['trans'] ) : 0;
132
 
133
  $title = ( empty( $count ) )
@@ -147,6 +159,11 @@ class QM_Output_Html_Transients extends QM_Output_Html {
147
 
148
  }
149
 
 
 
 
 
 
150
  function register_qm_output_html_transients( array $output, QM_Collectors $collectors ) {
151
  $collector = QM_Collectors::get( 'transients' );
152
  if ( $collector ) {
5
  * @package query-monitor
6
  */
7
 
8
+ if ( ! defined( 'ABSPATH' ) ) {
9
+ exit;
10
+ }
11
 
12
  class QM_Output_Html_Transients extends QM_Output_Html {
13
 
23
  add_filter( 'qm/output/menus', array( $this, 'admin_menu' ), 100 );
24
  }
25
 
26
+ /**
27
+ * @return string
28
+ */
29
  public function name() {
30
  return __( 'Transients', 'query-monitor' );
31
  }
32
 
33
+ /**
34
+ * @return void
35
+ */
36
  public function output() {
37
 
38
  $data = $this->collector->get_data();
91
 
92
  $stack = array();
93
 
94
+ foreach ( $row['filtered_trace'] as $frame ) {
95
+ $stack[] = self::output_filename( $frame['display'], $frame['calling_file'], $frame['calling_line'] );
96
  }
97
 
98
  $caller = array_shift( $stack );
133
  }
134
  }
135
 
136
+ /**
137
+ * @param array<string, mixed[]> $menu
138
+ * @return array<string, mixed[]>
139
+ */
140
  public function admin_menu( array $menu ) {
141
 
142
+ $data = $this->collector->get_data();
143
  $count = isset( $data['trans'] ) ? count( $data['trans'] ) : 0;
144
 
145
  $title = ( empty( $count ) )
159
 
160
  }
161
 
162
+ /**
163
+ * @param array<string, QM_Output> $output
164
+ * @param QM_Collectors $collectors
165
+ * @return array<string, QM_Output>
166
+ */
167
  function register_qm_output_html_transients( array $output, QM_Collectors $collectors ) {
168
  $collector = QM_Collectors::get( 'transients' );
169
  if ( $collector ) {
output/raw/cache.php CHANGED
@@ -14,15 +14,21 @@ class QM_Output_Raw_Cache extends QM_Output_Raw {
14
  */
15
  protected $collector;
16
 
 
 
 
17
  public function name() {
18
  return __( 'Object Cache', 'query-monitor' );
19
  }
20
 
 
 
 
21
  public function get_output() {
22
  $output = array(
23
  'hit_percentage' => null,
24
- 'hits' => null,
25
- 'misses' => null,
26
  );
27
  $data = $this->collector->get_data();
28
 
@@ -36,6 +42,11 @@ class QM_Output_Raw_Cache extends QM_Output_Raw {
36
  }
37
  }
38
 
 
 
 
 
 
39
  function register_qm_output_raw_cache( array $output, QM_Collectors $collectors ) {
40
  $collector = QM_Collectors::get( 'cache' );
41
  if ( $collector ) {
14
  */
15
  protected $collector;
16
 
17
+ /**
18
+ * @return string
19
+ */
20
  public function name() {
21
  return __( 'Object Cache', 'query-monitor' );
22
  }
23
 
24
+ /**
25
+ * @return array<string, mixed>
26
+ */
27
  public function get_output() {
28
  $output = array(
29
  'hit_percentage' => null,
30
+ 'hits' => null,
31
+ 'misses' => null,
32
  );
33
  $data = $this->collector->get_data();
34
 
42
  }
43
  }
44
 
45
+ /**
46
+ * @param array<string, QM_Output> $output
47
+ * @param QM_Collectors $collectors
48
+ * @return array<string, QM_Output>
49
+ */
50
  function register_qm_output_raw_cache( array $output, QM_Collectors $collectors ) {
51
  $collector = QM_Collectors::get( 'cache' );
52
  if ( $collector ) {
output/raw/conditionals.php CHANGED
@@ -14,10 +14,16 @@ class QM_Output_Raw_Conditionals extends QM_Output_Raw {
14
  */
15
  protected $collector;
16
 
 
 
 
17
  public function name() {
18
  return __( 'Conditionals', 'query-monitor' );
19
  }
20
 
 
 
 
21
  public function get_output() {
22
  $data = $this->collector->get_data();
23
 
@@ -25,6 +31,11 @@ class QM_Output_Raw_Conditionals extends QM_Output_Raw {
25
  }
26
  }
27
 
 
 
 
 
 
28
  function register_qm_output_raw_conditionals( array $output, QM_Collectors $collectors ) {
29
  $collector = QM_Collectors::get( 'conditionals' );
30
  if ( $collector ) {
14
  */
15
  protected $collector;
16
 
17
+ /**
18
+ * @return string
19
+ */
20
  public function name() {
21
  return __( 'Conditionals', 'query-monitor' );
22
  }
23
 
24
+ /**
25
+ * @return mixed
26
+ */
27
  public function get_output() {
28
  $data = $this->collector->get_data();
29
 
31
  }
32
  }
33
 
34
+ /**
35
+ * @param array<string, QM_Output> $output
36
+ * @param QM_Collectors $collectors
37
+ * @return array<string, QM_Output>
38
+ */
39
  function register_qm_output_raw_conditionals( array $output, QM_Collectors $collectors ) {
40
  $collector = QM_Collectors::get( 'conditionals' );
41
  if ( $collector ) {
output/raw/db_queries.php CHANGED
@@ -14,15 +14,24 @@ class QM_Output_Raw_DB_Queries extends QM_Output_Raw {
14
  */
15
  protected $collector;
16
 
 
 
 
17
  public $query_row = 0;
18
 
 
 
 
19
  public function name() {
20
  return __( 'Database Queries', 'query-monitor' );
21
  }
22
 
 
 
 
23
  public function get_output() {
24
  $output = array();
25
- $data = $this->collector->get_data();
26
 
27
  if ( empty( $data['dbs'] ) ) {
28
  return $output;
@@ -61,6 +70,17 @@ class QM_Output_Raw_DB_Queries extends QM_Output_Raw {
61
  return $output;
62
  }
63
 
 
 
 
 
 
 
 
 
 
 
 
64
  protected function output_queries( $name, stdClass $db, array $data ) {
65
  $this->query_row = 0;
66
 
@@ -75,38 +95,46 @@ class QM_Output_Raw_DB_Queries extends QM_Output_Raw {
75
  }
76
 
77
  return array(
78
- 'total' => $db->total_qs,
79
- 'time' => (float) number_format_i18n( $db->total_time, 4 ),
80
  'queries' => $output,
81
  );
82
  }
83
 
 
 
 
 
84
  protected function output_query_row( array $row ) {
85
  $output = array();
86
 
87
- $output['i'] = ++$this->query_row;
88
- $output['sql'] = $row['sql'];
89
- $output['time'] = (float) number_format_i18n( $row['ltime'], 4 );
90
 
91
  if ( isset( $row['trace'] ) ) {
92
  $stack = array();
93
- $filtered_trace = $row['trace']->get_display_trace();
94
 
95
  foreach ( $filtered_trace as $item ) {
96
  $stack[] = $item['display'];
97
  }
98
  } else {
99
- $stack = explode( ', ', $row['stack'] );
100
- $stack = array_reverse( $stack );
101
  }
102
 
103
  $output['stack'] = $stack;
104
- $output['result'] = $row['result'];
105
 
106
  return $output;
107
  }
108
  }
109
 
 
 
 
 
 
110
  function register_qm_output_raw_db_queries( array $output, QM_Collectors $collectors ) {
111
  $collector = QM_Collectors::get( 'db_queries' );
112
  if ( $collector ) {
14
  */
15
  protected $collector;
16
 
17
+ /**
18
+ * @var int
19
+ */
20
  public $query_row = 0;
21
 
22
+ /**
23
+ * @return string
24
+ */
25
  public function name() {
26
  return __( 'Database Queries', 'query-monitor' );
27
  }
28
 
29
+ /**
30
+ * @return array<string, mixed>
31
+ */
32
  public function get_output() {
33
  $output = array();
34
+ $data = $this->collector->get_data();
35
 
36
  if ( empty( $data['dbs'] ) ) {
37
  return $output;
70
  return $output;
71
  }
72
 
73
+ /**
74
+ * @param string $name
75
+ * @param stdClass $db
76
+ * @param mixed[] $data
77
+ * @return array
78
+ * @phpstan-return array{
79
+ * total: int,
80
+ * time: float,
81
+ * queries: mixed[],
82
+ * }|array{}
83
+ */
84
  protected function output_queries( $name, stdClass $db, array $data ) {
85
  $this->query_row = 0;
86
 
95
  }
96
 
97
  return array(
98
+ 'total' => $db->total_qs,
99
+ 'time' => round( $db->total_time, 4 ),
100
  'queries' => $output,
101
  );
102
  }
103
 
104
+ /**
105
+ * @param array<string, mixed> $row
106
+ * @return array<string, mixed>
107
+ */
108
  protected function output_query_row( array $row ) {
109
  $output = array();
110
 
111
+ $output['i'] = ++$this->query_row;
112
+ $output['sql'] = $row['sql'];
113
+ $output['time'] = round( $row['ltime'], 4 );
114
 
115
  if ( isset( $row['trace'] ) ) {
116
  $stack = array();
117
+ $filtered_trace = $row['trace']->get_filtered_trace();
118
 
119
  foreach ( $filtered_trace as $item ) {
120
  $stack[] = $item['display'];
121
  }
122
  } else {
123
+ $stack = $row['stack'];
 
124
  }
125
 
126
  $output['stack'] = $stack;
127
+ $output['result'] = $row['result'];
128
 
129
  return $output;
130
  }
131
  }
132
 
133
+ /**
134
+ * @param array<string, QM_Output> $output
135
+ * @param QM_Collectors $collectors
136
+ * @return array<string, QM_Output>
137
+ */
138
  function register_qm_output_raw_db_queries( array $output, QM_Collectors $collectors ) {
139
  $collector = QM_Collectors::get( 'db_queries' );
140
  if ( $collector ) {
output/raw/http.php CHANGED
@@ -14,13 +14,19 @@ class QM_Output_Raw_HTTP extends QM_Output_Raw {
14
  */
15
  protected $collector;
16
 
 
 
 
17
  public function name() {
18
  return __( 'HTTP API Calls', 'query-monitor' );
19
  }
20
 
 
 
 
21
  public function get_output() {
22
  $output = array();
23
- $data = $this->collector->get_data();
24
 
25
  if ( empty( $data['http'] ) ) {
26
  return $output;
@@ -32,7 +38,7 @@ class QM_Output_Raw_HTTP extends QM_Output_Raw {
32
  $stack = array();
33
 
34
  if ( isset( $http['trace'] ) ) {
35
- $filtered_trace = $http['trace']->get_display_trace();
36
 
37
  foreach ( $filtered_trace as $item ) {
38
  $stack[] = $item['display'];
@@ -42,7 +48,7 @@ class QM_Output_Raw_HTTP extends QM_Output_Raw {
42
  $requests[] = array(
43
  'url' => $http['url'],
44
  'method' => $http['args']['method'],
45
- 'response' => $http['response']['response'],
46
  'time' => (float) number_format_i18n( $http['end'] - $http['start'], 4 ),
47
  'stack' => $stack,
48
  );
@@ -56,6 +62,11 @@ class QM_Output_Raw_HTTP extends QM_Output_Raw {
56
  }
57
  }
58
 
 
 
 
 
 
59
  function register_qm_output_raw_http( array $output, QM_Collectors $collectors ) {
60
  $collector = QM_Collectors::get( 'http' );
61
  if ( $collector ) {
14
  */
15
  protected $collector;
16
 
17
+ /**
18
+ * @return string
19
+ */
20
  public function name() {
21
  return __( 'HTTP API Calls', 'query-monitor' );
22
  }
23
 
24
+ /**
25
+ * @return array<string, mixed>
26
+ */
27
  public function get_output() {
28
  $output = array();
29
+ $data = $this->collector->get_data();
30
 
31
  if ( empty( $data['http'] ) ) {
32
  return $output;
38
  $stack = array();
39
 
40
  if ( isset( $http['trace'] ) ) {
41
+ $filtered_trace = $http['trace']->get_filtered_trace();
42
 
43
  foreach ( $filtered_trace as $item ) {
44
  $stack[] = $item['display'];
48
  $requests[] = array(
49
  'url' => $http['url'],
50
  'method' => $http['args']['method'],
51
+ 'response' => is_wp_error( $http['response'] ) ? $http['response']->get_error_message() : $http['response']['response'],
52
  'time' => (float) number_format_i18n( $http['end'] - $http['start'], 4 ),
53
  'stack' => $stack,
54
  );
62
  }
63
  }
64
 
65
+ /**
66
+ * @param array<string, QM_Output> $output
67
+ * @param QM_Collectors $collectors
68
+ * @return array<string, QM_Output>
69
+ */
70
  function register_qm_output_raw_http( array $output, QM_Collectors $collectors ) {
71
  $collector = QM_Collectors::get( 'http' );
72
  if ( $collector ) {
output/raw/logger.php CHANGED
@@ -14,13 +14,19 @@ class QM_Output_Raw_Logger extends QM_Output_Raw {
14
  */
15
  protected $collector;
16
 
 
 
 
17
  public function name() {
18
  return __( 'Logs', 'query-monitor' );
19
  }
20
 
 
 
 
21
  public function get_output() {
22
  $output = array();
23
- $data = $this->collector->get_data();
24
 
25
  if ( empty( $data['logs'] ) ) {
26
  return $output;
@@ -30,7 +36,7 @@ class QM_Output_Raw_Logger extends QM_Output_Raw {
30
  $stack = array();
31
 
32
  if ( isset( $log['trace'] ) ) {
33
- $filtered_trace = $log['trace']->get_display_trace();
34
 
35
  foreach ( $filtered_trace as $item ) {
36
  $stack[] = $item['display'];
@@ -47,6 +53,11 @@ class QM_Output_Raw_Logger extends QM_Output_Raw {
47
  }
48
  }
49
 
 
 
 
 
 
50
  function register_qm_output_raw_logger( array $output, QM_Collectors $collectors ) {
51
  $collector = QM_Collectors::get( 'logger' );
52
  if ( $collector ) {
14
  */
15
  protected $collector;
16
 
17
+ /**
18
+ * @return string
19
+ */
20
  public function name() {
21
  return __( 'Logs', 'query-monitor' );
22
  }
23
 
24
+ /**
25
+ * @return array<string, mixed>
26
+ */
27
  public function get_output() {
28
  $output = array();
29
+ $data = $this->collector->get_data();
30
 
31
  if ( empty( $data['logs'] ) ) {
32
  return $output;
36
  $stack = array();
37
 
38
  if ( isset( $log['trace'] ) ) {
39
+ $filtered_trace = $log['trace']->get_filtered_trace();
40
 
41
  foreach ( $filtered_trace as $item ) {
42
  $stack[] = $item['display'];
53
  }
54
  }
55
 
56
+ /**
57
+ * @param array<string, QM_Output> $output
58
+ * @param QM_Collectors $collectors
59
+ * @return array<string, QM_Output>
60
+ */
61
  function register_qm_output_raw_logger( array $output, QM_Collectors $collectors ) {
62
  $collector = QM_Collectors::get( 'logger' );
63
  if ( $collector ) {
output/raw/transients.php CHANGED
@@ -14,13 +14,19 @@ class QM_Output_Raw_Transients extends QM_Output_Raw {
14
  */
15
  protected $collector;
16
 
 
 
 
17
  public function name() {
18
  return __( 'Transients', 'query-monitor' );
19
  }
20
 
 
 
 
21
  public function get_output() {
22
  $output = array();
23
- $data = $this->collector->get_data();
24
 
25
  if ( empty( $data['trans'] ) ) {
26
  return $output;
@@ -55,6 +61,11 @@ class QM_Output_Raw_Transients extends QM_Output_Raw {
55
  }
56
  }
57
 
 
 
 
 
 
58
  function register_qm_output_raw_transients( array $output, QM_Collectors $collectors ) {
59
  $collector = QM_Collectors::get( 'transients' );
60
  if ( $collector ) {
14
  */
15
  protected $collector;
16
 
17
+ /**
18
+ * @return string
19
+ */
20
  public function name() {
21
  return __( 'Transients', 'query-monitor' );
22
  }
23
 
24
+ /**
25
+ * @return array<string, mixed>
26
+ */
27
  public function get_output() {
28
  $output = array();
29
+ $data = $this->collector->get_data();
30
 
31
  if ( empty( $data['trans'] ) ) {
32
  return $output;
61
  }
62
  }
63
 
64
+ /**
65
+ * @param array<string, QM_Output> $output
66
+ * @param QM_Collectors $collectors
67
+ * @return array<string, QM_Output>
68
+ */
69
  function register_qm_output_raw_transients( array $output, QM_Collectors $collectors ) {
70
  $collector = QM_Collectors::get( 'transients' );
71
  if ( $collector ) {
query-monitor.php CHANGED
@@ -10,7 +10,7 @@
10
  *
11
  * Plugin Name: Query Monitor
12
  * Description: The Developer Tools Panel for WordPress.
13
- * Version: 3.7.1
14
  * Plugin URI: https://querymonitor.com/
15
  * Author: John Blackbourn
16
  * Author URI: https://querymonitor.com/
@@ -29,7 +29,9 @@
29
  * GNU General Public License for more details.
30
  */
31
 
32
- defined( 'ABSPATH' ) || exit;
 
 
33
 
34
  $qm_dir = dirname( __FILE__ );
35
 
@@ -47,10 +49,6 @@ foreach ( array( 'Activation', 'Util', 'QM' ) as $qm_class ) {
47
 
48
  QM_Activation::init( __FILE__ );
49
 
50
- if ( ! QM_Plugin::php_version_met() ) {
51
- return;
52
- }
53
-
54
  if ( defined( 'WP_CLI' ) && WP_CLI ) {
55
  require_once "{$qm_dir}/classes/CLI.php";
56
  QM_CLI::init( __FILE__ );
@@ -80,4 +78,4 @@ unset(
80
  $qm_class
81
  );
82
 
83
- QueryMonitor::init( __FILE__ );
10
  *
11
  * Plugin Name: Query Monitor
12
  * Description: The Developer Tools Panel for WordPress.
13
+ * Version: 3.8.0
14
  * Plugin URI: https://querymonitor.com/
15
  * Author: John Blackbourn
16
  * Author URI: https://querymonitor.com/
29
  * GNU General Public License for more details.
30
  */
31
 
32
+ if ( ! defined( 'ABSPATH' ) ) {
33
+ exit;
34
+ }
35
 
36
  $qm_dir = dirname( __FILE__ );
37
 
49
 
50
  QM_Activation::init( __FILE__ );
51
 
 
 
 
 
52
  if ( defined( 'WP_CLI' ) && WP_CLI ) {
53
  require_once "{$qm_dir}/classes/CLI.php";
54
  QM_CLI::init( __FILE__ );
78
  $qm_class
79
  );
80
 
81
+ QueryMonitor::init( __FILE__ )->set_up();
readme.txt CHANGED
@@ -2,8 +2,8 @@
2
  Contributors: johnbillion
3
  Tags: debug, debug-bar, debugging, development, developer, performance, profiler, queries, query monitor, rest-api
4
  Requires at least: 3.7
5
- Tested up to: 5.7
6
- Stable tag: 3.7.1
7
  License: GPLv2 or later
8
  Requires PHP: 5.3
9
  Donate link: https://johnblackbourn.com/donations/
@@ -88,6 +88,16 @@ Long answer: Query Monitor has a small impact on page generation time because it
88
 
89
  Query Monitor's memory usage typically accounts for around 10% of the total memory used to generate the page.
90
 
 
 
 
 
 
 
 
 
 
 
91
  ### Are there any add-on plugins for Query Monitor?
92
 
93
  [A list of add-on plugins for Query Monitor can be found here.](https://github.com/johnbillion/query-monitor/wiki/Query-Monitor-Add-on-Plugins)
@@ -131,6 +141,18 @@ In addition, if you like the plugin then I'd love for you to [leave a review](ht
131
 
132
  ## Changelog ##
133
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  ### 3.7.1 ###
135
 
136
  * Add a fallback for timing processing during Ajax requests that are dispatched before the `shutdown` hook.
@@ -445,20 +467,3 @@ New features! Read about them here: https://querymonitor.com/blog/2019/02/new-fe
445
  * Switch back to depending on `jquery` instead of `jquery-core`.
446
  * Don't assume `php_uname()` is always callable. Add info about the host OS too.
447
  * Reset inline height attribute when the panel is closed.
448
-
449
- ### 3.0.0 ###
450
-
451
- * Brand new UI that resembles familiar web developer tools. Lots of related improvements and fixes.
452
- * Introduce some basic timing functionality in a Timings panel. See #282 for usage.
453
- * Introduce a `QM_NO_JQUERY` constant for running QM without jQuery as a dependency.
454
- * Greater resilience to JavaScript errors.
455
- * Allow the Scripts and Styles panel to be filtered by host name.
456
- * Expose information about redirects that occurred in HTTP API requests.
457
- * Expose more debugging information for HTTP API requests.
458
- * Don't enable the Capability Checks panel by default as it's very memory intensive.
459
- * Allow PHP errors to be silenced according to their component. See `qm/collect/php_error_levels` and `qm/collect/hide_silenced_php_errors` filters.
460
- * Hide all file paths and stack traces behind toggles by default.
461
- * Remove support for the AMP for WordPress plugin.
462
- * Add associative keys to the array passed to the `qm/built-in-collectors` filter.
463
- * Drop support for PHP 5.2.
464
- * Generally improve performance and reduce memory usage.
2
  Contributors: johnbillion
3
  Tags: debug, debug-bar, debugging, development, developer, performance, profiler, queries, query monitor, rest-api
4
  Requires at least: 3.7
5
+ Tested up to: 5.8
6
+ Stable tag: 3.8.0
7
  License: GPLv2 or later
8
  Requires PHP: 5.3
9
  Donate link: https://johnblackbourn.com/donations/
88
 
89
  Query Monitor's memory usage typically accounts for around 10% of the total memory used to generate the page.
90
 
91
+ ### Can I prevent Query Monitor from collecting data during long-running requests?
92
+
93
+ Yes, if anything calls `do_action( 'qm/cease' )` then Query Monitor will cease operating for the remainder of the page generation. It detaches itself from further data collection, discards any data it's collected so far, and skips the output of its information.
94
+
95
+ This is useful for long-running operations that perform a very high number of database queries, consume a lot of memory, or otherwise are of no concern to Query Monitor, for example:
96
+
97
+ * Backuping up or restoring your site
98
+ * Exporting a large amount of data
99
+ * Running security scans
100
+
101
  ### Are there any add-on plugins for Query Monitor?
102
 
103
  [A list of add-on plugins for Query Monitor can be found here.](https://github.com/johnbillion/query-monitor/wiki/Query-Monitor-Add-on-Plugins)
141
 
142
  ## Changelog ##
143
 
144
+ ### 3.8.0 ###
145
+
146
+ * Introduces the ability for a third party to cease all further data collection and output at any point by calling `do_action( 'qm/cease' )`, for example to prevent memory exhaustion during long-running operations
147
+ * Reduces the width of the admin toolbar menu item by using lower decimal precision
148
+ * Improves the Template panel information when a block theme is in use (for Full Site Editing)
149
+ * Improves the performance and accuracy of stack traces and calling function information
150
+ * Corrects some formatting of numbers and error messages in the REST API output
151
+ * Adds more useful information when a persistent object cache or opcode cache isn't in use
152
+ * Improves clarity in the Scripts and Styles panels when any of the URLs include a port number
153
+ * Introduces the `qm/component_context/{$type}` filter to complement `qm/component_name/{$type}` and `qm/component_dirs`
154
+ * Improves internal code quality, internationalisation, and further reduces overall memory usage
155
+
156
  ### 3.7.1 ###
157
 
158
  * Add a fallback for timing processing during Ajax requests that are dispatched before the `shutdown` hook.
467
  * Switch back to depending on `jquery` instead of `jquery-core`.
468
  * Don't assume `php_uname()` is always callable. Add info about the host OS too.
469
  * Reset inline height attribute when the panel is closed.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
wp-content/db.php CHANGED
@@ -12,7 +12,9 @@
12
  * @package query-monitor
13
  */
14
 
15
- defined( 'ABSPATH' ) || exit;
 
 
16
 
17
  if ( defined( 'QM_DISABLED' ) && QM_DISABLED ) {
18
  return;
@@ -30,7 +32,7 @@ if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
30
  }
31
 
32
  # No autoloaders for us. See https://github.com/johnbillion/query-monitor/issues/7
33
- $qm_dir = dirname( dirname( __FILE__ ) );
34
  $qm_plugin = "{$qm_dir}/classes/Plugin.php";
35
 
36
  if ( ! is_readable( $qm_plugin ) ) {
@@ -54,13 +56,21 @@ if ( ! defined( 'SAVEQUERIES' ) ) {
54
 
55
  class QM_DB extends wpdb {
56
 
 
 
 
 
 
 
 
 
57
  public $qm_php_vars = array(
58
- 'max_execution_time' => null,
59
- 'memory_limit' => null,
60
  'upload_max_filesize' => null,
61
- 'post_max_size' => null,
62
- 'display_errors' => null,
63
- 'log_errors' => null,
64
  );
65
 
66
  /**
@@ -86,28 +96,24 @@ class QM_DB extends wpdb {
86
  * affected/selected for all other queries. Boolean false on error.
87
  */
88
  public function query( $query ) {
89
- if ( ! $this->ready ) {
90
- if ( isset( $this->check_current_query ) ) {
91
- // This property was introduced in WP 4.2
92
- $this->check_current_query = true;
93
- }
94
- return false;
95
- }
96
-
97
  if ( $this->show_errors ) {
98
  $this->hide_errors();
99
  }
100
 
101
  $result = parent::query( $query );
102
- $i = $this->num_queries - 1;
 
 
 
 
 
 
103
 
104
  if ( ! isset( $this->queries[ $i ] ) ) {
105
  return $result;
106
  }
107
 
108
- $this->queries[ $i ]['trace'] = new QM_Backtrace( array(
109
- 'ignore_frames' => 1,
110
- ) );
111
 
112
  if ( ! isset( $this->queries[ $i ][3] ) ) {
113
  $this->queries[ $i ][3] = $this->time_start;
@@ -115,17 +121,17 @@ class QM_DB extends wpdb {
115
 
116
  if ( $this->last_error ) {
117
  $code = 'qmdb';
118
- if ( $this->use_mysqli ) {
119
- if ( $this->dbh instanceof mysqli ) {
120
- $code = mysqli_errno( $this->dbh );
121
- }
122
- } else {
123
- if ( is_resource( $this->dbh ) ) {
124
- // Please do not report this code as a PHP 7 incompatibility. Observe the surrounding logic.
125
- // phpcs:ignore
126
- $code = mysql_errno( $this->dbh );
127
- }
128
  }
 
 
 
 
 
 
 
129
  $this->queries[ $i ]['result'] = new WP_Error( $code, $this->last_error );
130
  } else {
131
  $this->queries[ $i ]['result'] = $result;
12
  * @package query-monitor
13
  */
14
 
15
+ if ( ! defined( 'ABSPATH' ) ) {
16
+ exit;
17
+ }
18
 
19
  if ( defined( 'QM_DISABLED' ) && QM_DISABLED ) {
20
  return;
32
  }
33
 
34
  # No autoloaders for us. See https://github.com/johnbillion/query-monitor/issues/7
35
+ $qm_dir = dirname( dirname( __FILE__ ) );
36
  $qm_plugin = "{$qm_dir}/classes/Plugin.php";
37
 
38
  if ( ! is_readable( $qm_plugin ) ) {
56
 
57
  class QM_DB extends wpdb {
58
 
59
+ /**
60
+ * @var float
61
+ */
62
+ public $time_start;
63
+
64
+ /**
65
+ * @var array<string, string|null>
66
+ */
67
  public $qm_php_vars = array(
68
+ 'max_execution_time' => null,
69
+ 'memory_limit' => null,
70
  'upload_max_filesize' => null,
71
+ 'post_max_size' => null,
72
+ 'display_errors' => null,
73
+ 'log_errors' => null,
74
  );
75
 
76
  /**
96
  * affected/selected for all other queries. Boolean false on error.
97
  */
98
  public function query( $query ) {
 
 
 
 
 
 
 
 
99
  if ( $this->show_errors ) {
100
  $this->hide_errors();
101
  }
102
 
103
  $result = parent::query( $query );
104
+ $i = $this->num_queries - 1;
105
+
106
+ if ( did_action( 'qm/cease' ) ) {
107
+ // It's not possible to prevent the parent class from logging queries because it reads
108
+ // the `SAVEQUERIES` constant and I don't want to override more methods than necessary.
109
+ $this->queries = array();
110
+ }
111
 
112
  if ( ! isset( $this->queries[ $i ] ) ) {
113
  return $result;
114
  }
115
 
116
+ $this->queries[ $i ]['trace'] = new QM_Backtrace();
 
 
117
 
118
  if ( ! isset( $this->queries[ $i ][3] ) ) {
119
  $this->queries[ $i ][3] = $this->time_start;
121
 
122
  if ( $this->last_error ) {
123
  $code = 'qmdb';
124
+
125
+ if ( $this->dbh instanceof mysqli ) {
126
+ $code = mysqli_errno( $this->dbh );
 
 
 
 
 
 
 
127
  }
128
+
129
+ if ( is_resource( $this->dbh ) ) {
130
+ // Please do not report this code as a PHP 7 incompatibility. Observe the surrounding logic.
131
+ // phpcs:ignore
132
+ $code = mysql_errno( $this->dbh );
133
+ }
134
+
135
  $this->queries[ $i ]['result'] = new WP_Error( $code, $this->last_error );
136
  } else {
137
  $this->queries[ $i ]['result'] = $result;