Query Monitor - Version 2.16.1

Version Description

Download this release

Release Info

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

Code changes from version 2.15.0 to 2.16.1

assets/query-monitor.css CHANGED
@@ -623,6 +623,8 @@ select.qm-filter {
623
  }
624
 
625
  .qm-hide,
 
 
626
  .qm-hide-name,
627
  .qm-hide-type,
628
  .qm-hide-caller,
@@ -647,43 +649,51 @@ select.qm-filter {
647
  .qm .qm-sort {
648
  color: #767676 !important;
649
  display: block !important;
650
- font-style: normal !important;
651
- font-weight: normal !important;
652
- font-size: 15px !important;
653
- line-height: 11px !important;
654
- font-family: Menlo, Monaco, Consolas, monospace !important;
655
  border: 0 !important;
656
  padding: 0 !important;
657
  width: 30px !important;
658
  background-color: transparent !important;
659
  cursor: pointer !important;
660
  margin: 0 auto !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
661
  }
662
 
663
  .qm .qm-sort-asc {
664
- margin-bottom: 1px !important;
665
  }
666
  .qm .qm-sort-asc:before {
667
- content: "\25b3" !important;
 
668
  }
669
  .qm .qm-sort-asc:hover:before,
670
  .qm .qm-sort-asc:focus:before,
671
  .qm .qm-sorted-asc .qm-sort-asc:before {
672
- color: #333 !important;
673
- content: "\25b2" !important;
674
  }
675
 
676
  .qm .qm-sort-desc {
677
- margin-top: 1px !important;
678
  }
679
  .qm .qm-sort-desc:before {
680
- content: "\25bd" !important;
 
681
  }
682
  .qm .qm-sort-desc:hover:before,
683
  .qm .qm-sort-desc:focus:before,
684
  .qm .qm-sorted-desc .qm-sort-desc:before {
685
- color: #333 !important;
686
- content: "\25bc" !important;
687
  }
688
 
689
  .qm .screen-reader-text {
623
  }
624
 
625
  .qm-hide,
626
+ .qm-hide-user,
627
+ .qm-hide-result,
628
  .qm-hide-name,
629
  .qm-hide-type,
630
  .qm-hide-caller,
649
  .qm .qm-sort {
650
  color: #767676 !important;
651
  display: block !important;
 
 
 
 
 
652
  border: 0 !important;
653
  padding: 0 !important;
654
  width: 30px !important;
655
  background-color: transparent !important;
656
  cursor: pointer !important;
657
  margin: 0 auto !important;
658
+ height: 10px !important;
659
+ }
660
+
661
+ .qm .qm-sort:before {
662
+ border-width: 7px 5px !important;
663
+ border-style: solid !important;
664
+ border-color: transparent !important;
665
+ display: block !important;
666
+ content: '' !important;
667
+ width: 0 !important;
668
+ height: 0 !important;
669
+ position: relative !important;
670
+ left: 10px !important;
671
  }
672
 
673
  .qm .qm-sort-asc {
674
+ margin-bottom: 2px !important;
675
  }
676
  .qm .qm-sort-asc:before {
677
+ border-bottom-color: #aaa !important;
678
+ top: -4px !important;
679
  }
680
  .qm .qm-sort-asc:hover:before,
681
  .qm .qm-sort-asc:focus:before,
682
  .qm .qm-sorted-asc .qm-sort-asc:before {
683
+ border-bottom-color: #333 !important;
 
684
  }
685
 
686
  .qm .qm-sort-desc {
687
+ margin-top: 2px !important;
688
  }
689
  .qm .qm-sort-desc:before {
690
+ border-top-color: #aaa !important;
691
+ top: 0px !important;
692
  }
693
  .qm .qm-sort-desc:hover:before,
694
  .qm .qm-sort-desc:focus:before,
695
  .qm .qm-sorted-desc .qm-sort-desc:before {
696
+ border-top-color: #333 !important;
 
697
  }
698
 
699
  .qm .screen-reader-text {
assets/query-monitor.js CHANGED
@@ -159,6 +159,15 @@ jQuery( function($) {
159
  hilite = $(this).attr('data-highlight'),
160
  time = 0;
161
 
 
 
 
 
 
 
 
 
 
162
  if ( hilite ) {
163
  table.find('tr').removeClass('qm-highlight');
164
  }
@@ -187,6 +196,16 @@ jQuery( function($) {
187
  results.find('.qm-items-time').text(time);
188
  });
189
 
 
 
 
 
 
 
 
 
 
 
190
  $('#qm').find('.qm-filter-trigger').on('click',function(e){
191
  var filter = $(this).data('qm-filter'),
192
  value = $(this).data('qm-value'),
159
  hilite = $(this).attr('data-highlight'),
160
  time = 0;
161
 
162
+ if ( window.localStorage ) {
163
+ key = $(this).attr('id');
164
+ if ( val ) {
165
+ localStorage.setItem( key, val );
166
+ } else {
167
+ localStorage.removeItem( key );
168
+ }
169
+ }
170
+
171
  if ( hilite ) {
172
  table.find('tr').removeClass('qm-highlight');
173
  }
196
  results.find('.qm-items-time').text(time);
197
  });
198
 
199
+ if ( window.localStorage ) {
200
+ $('#qm').find('.qm-filter').each(function () {
201
+ var key = $(this).attr('id');
202
+ var value = localStorage.getItem( key );
203
+ if ( value !== null && $(this).find('option[value="' + value + '"]').length ) {
204
+ $(this).val(value).change();
205
+ }
206
+ });
207
+ }
208
+
209
  $('#qm').find('.qm-filter-trigger').on('click',function(e){
210
  var filter = $(this).data('qm-filter'),
211
  value = $(this).data('qm-value'),
classes/Backtrace.php CHANGED
@@ -53,6 +53,10 @@ class QM_Backtrace {
53
  'get_sidebar' => 1,
54
  'get_footer' => 1,
55
  'class_exists' => 2,
 
 
 
 
56
  );
57
  protected static $filtered = false;
58
  protected $trace = null;
@@ -248,7 +252,11 @@ class QM_Backtrace {
248
  $args = array();
249
  for ( $i = 0; $i < $show; $i++ ) {
250
  if ( isset( $trace['args'][$i] ) ) {
251
- $args[] = '\'' . $trace['args'][$i] . '\'';
 
 
 
 
252
  }
253
  }
254
  $return['id'] = $trace['function'] . '()';
53
  'get_sidebar' => 1,
54
  'get_footer' => 1,
55
  'class_exists' => 2,
56
+ 'current_user_can' => 3,
57
+ 'user_can' => 4,
58
+ 'current_user_can_for_blog' => 4,
59
+ 'author_can' => 4,
60
  );
61
  protected static $filtered = false;
62
  protected $trace = null;
252
  $args = array();
253
  for ( $i = 0; $i < $show; $i++ ) {
254
  if ( isset( $trace['args'][$i] ) ) {
255
+ if ( is_string( $trace['args'][$i] ) ) {
256
+ $args[] = '\'' . $trace['args'][$i] . '\'';
257
+ } else {
258
+ $args[] = QM_Util::display_variable( $trace['args'][$i] );
259
+ }
260
  }
261
  }
262
  $return['id'] = $trace['function'] . '()';
classes/Collector.php CHANGED
@@ -21,6 +21,7 @@ abstract class QM_Collector {
21
  'types' => array(),
22
  'component_times' => array(),
23
  );
 
24
 
25
  public function __construct() {}
26
 
@@ -108,6 +109,19 @@ abstract class QM_Collector {
108
  return $user;
109
  }
110
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  public function process() {}
112
 
113
  public function tear_down() {}
21
  'types' => array(),
22
  'component_times' => array(),
23
  );
24
+ protected static $hide_qm = null;
25
 
26
  public function __construct() {}
27
 
109
  return $user;
110
  }
111
 
112
+ public static function hide_qm() {
113
+ if ( null === self::$hide_qm ) {
114
+ self::$hide_qm = ( defined( 'QM_HIDE_SELF' ) && QM_HIDE_SELF );
115
+ }
116
+
117
+ return self::$hide_qm;
118
+ }
119
+
120
+ public function filter_remove_qm( array $item ) {
121
+ $component = $item['trace']->get_component();
122
+ return ( 'query-monitor' !== $component->context );
123
+ }
124
+
125
  public function process() {}
126
 
127
  public function tear_down() {}
classes/Util.php CHANGED
@@ -157,7 +157,12 @@ class QM_Util {
157
  $name = __( 'Parent Theme', 'query-monitor' );
158
  break;
159
  case 'other':
160
- $name = self::standard_dir( $file, '' );
 
 
 
 
 
161
  $context = $file;
162
  break;
163
  case 'core':
@@ -330,6 +335,40 @@ class QM_Util {
330
  return $type;
331
  }
332
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
  public static function sort( array &$array, $field ) {
334
  self::$sort_field = $field;
335
  usort( $array, array( __CLASS__, '_sort' ) );
157
  $name = __( 'Parent Theme', 'query-monitor' );
158
  break;
159
  case 'other':
160
+ // Anything else that's within the content directory should appear as
161
+ // `wp-content/{dir}` or `wp-content/{file}`
162
+ $name = self::standard_dir( $file );
163
+ $name = str_replace( dirname( self::$file_dirs['other'] ), '', $name );
164
+ $parts = explode( '/', trim( $name, '/' ) );
165
+ $name = $parts[0] . '/' . $parts[1];
166
  $context = $file;
167
  break;
168
  case 'core':
335
  return $type;
336
  }
337
 
338
+ public static function display_variable( $value ) {
339
+ if ( is_string( $value ) ) {
340
+ return $value;
341
+ } elseif ( is_bool( $value ) ) {
342
+ return ( $value ) ? 'true' : 'false';
343
+ } elseif ( is_scalar( $value ) ) {
344
+ return $value;
345
+ } elseif ( is_object( $value ) ) {
346
+ $class = get_class( $value );
347
+ $id = false;
348
+
349
+ switch ( $class ) {
350
+
351
+ case 'WP_Post':
352
+ case 'WP_User':
353
+ $id = $value->ID;
354
+ break;
355
+
356
+ case 'WP_Term':
357
+ $id = $value->term_id;
358
+ break;
359
+
360
+ }
361
+
362
+ if ( $id ) {
363
+ return sprintf( '%s (ID:%d)', $class, $id );
364
+ }
365
+
366
+ return $class;
367
+ } else {
368
+ return gettype( $value );
369
+ }
370
+ }
371
+
372
  public static function sort( array &$array, $field ) {
373
  self::$sort_field = $field;
374
  usort( $array, array( __CLASS__, '_sort' ) );
collectors/caps.php ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Copyright 2009-2017 John Blackbourn
4
+
5
+ This program is free software; you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation; either version 2 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ */
16
+
17
+ class QM_Collector_Caps extends QM_Collector {
18
+
19
+ public $id = 'caps';
20
+
21
+ public function name() {
22
+ return __( 'Capability Checks', 'query-monitor' );
23
+ }
24
+
25
+ public function __construct() {
26
+ parent::__construct();
27
+ add_filter( 'user_has_cap', array( $this, 'filter_user_has_cap' ), 9999, 3 );
28
+ add_filter( 'map_meta_cap', array( $this, 'filter_map_meta_cap' ), 9999, 4 );
29
+ }
30
+
31
+ /**
32
+ * Logs user capability checks.
33
+ *
34
+ * This does not get called for Super Admins. See filter_map_meta_cap() below.
35
+ *
36
+ * @param bool[] $user_caps Concerned user's capabilities.
37
+ * @param string[] $required_caps Required primitive capabilities for the requested capability.
38
+ * @param array $args {
39
+ * Arguments that accompany the requested capability check.
40
+ *
41
+ * @type string $0 Requested capability.
42
+ * @type int $1 Concerned user ID.
43
+ * @type mixed ...$2 Optional second and further parameters.
44
+ * }
45
+ * @return bool[] Concerned user's capabilities.
46
+ */
47
+ public function filter_user_has_cap( array $user_caps, array $caps, array $args ) {
48
+ $trace = new QM_Backtrace;
49
+ $result = true;
50
+
51
+ foreach ( $caps as $cap ) {
52
+ if ( empty( $user_caps[ $cap ] ) ) {
53
+ $result = false;
54
+ break;
55
+ }
56
+ }
57
+
58
+ $this->data['caps'][] = array(
59
+ 'args' => $args,
60
+ 'trace' => $trace,
61
+ 'result' => $result,
62
+ );
63
+
64
+ return $user_caps;
65
+ }
66
+
67
+ /**
68
+ * Logs user capability checks for Super Admins on Multisite.
69
+ *
70
+ * This is needed because the `user_has_cap` filter doesn't fire for Super Admins.
71
+ *
72
+ * @param string[] $required_caps Required primitive capabilities for the requested capability.
73
+ * @param string $cap Capability or meta capability being checked.
74
+ * @param int $user_id Concerned user ID.
75
+ * @param array $args {
76
+ * Arguments that accompany the requested capability check.
77
+ *
78
+ * @type mixed ...$0 Optional second and further parameters.
79
+ * }
80
+ * @return string[] Required capabilities for the requested action.
81
+ */
82
+ public function filter_map_meta_cap( array $required_caps, $cap, $user_id, array $args ) {
83
+ if ( ! is_multisite() ) {
84
+ return $required_caps;
85
+ }
86
+
87
+ if ( ! is_super_admin( $user_id ) ) {
88
+ return $required_caps;
89
+ }
90
+
91
+ $trace = new QM_Backtrace;
92
+ $result = ( ! in_array( 'do_not_allow', $required_caps, true ) );
93
+
94
+ array_unshift( $args, $user_id );
95
+ array_unshift( $args, $cap );
96
+
97
+ $this->data['caps'][] = array(
98
+ 'args' => $args,
99
+ 'trace' => $trace,
100
+ 'result' => $result,
101
+ );
102
+
103
+ return $required_caps;
104
+ }
105
+
106
+ public function process() {
107
+ if ( empty( $this->data['caps'] ) ) {
108
+ return;
109
+ }
110
+
111
+ $all_parts = array();
112
+ $all_users = array();
113
+ $components = array();
114
+
115
+ $this->data['caps'] = array_filter( $this->data['caps'], array( $this, 'filter_remove_noise' ) );
116
+
117
+ if ( self::hide_qm() ) {
118
+ $this->data['caps'] = array_filter( $this->data['caps'], array( $this, 'filter_remove_qm' ) );
119
+ }
120
+
121
+ foreach ( $this->data['caps'] as $i => $cap ) {
122
+ $name = $cap['args'][0];
123
+ $parts = array_filter( preg_split( '#[_/-]#', $name ) );
124
+ $this->data['caps'][ $i ]['parts'] = $parts;
125
+ $this->data['caps'][ $i ]['name'] = $name;
126
+ $this->data['caps'][ $i ]['user'] = $cap['args'][1];
127
+ $this->data['caps'][ $i ]['args'] = array_slice( $cap['args'], 2 );
128
+ $all_parts = array_merge( $all_parts, $parts );
129
+ $all_users[] = $cap['args'][1];
130
+ $component = $cap['trace']->get_component();
131
+ $components[$component->name] = $component->name;
132
+ }
133
+
134
+ $this->data['parts'] = array_unique( array_filter( $all_parts ) );
135
+ $this->data['users'] = array_unique( array_filter( $all_users ) );
136
+ $this->data['components'] = $components;
137
+ }
138
+
139
+ public function filter_remove_noise( array $cap ) {
140
+ $trace = $cap['trace']->get_trace();
141
+
142
+ $exclude_files = array(
143
+ ABSPATH . 'wp-admin/menu.php',
144
+ ABSPATH . 'wp-admin/includes/menu.php',
145
+ );
146
+ $exclude_functions = array(
147
+ '_wp_menu_output',
148
+ 'wp_admin_bar_render',
149
+ );
150
+
151
+ foreach ( $trace as $item ) {
152
+ if ( isset( $item['file'] ) && in_array( $item['file'], $exclude_files, true ) ) {
153
+ return false;
154
+ }
155
+ if ( isset( $item['function'] ) && in_array( $item['function'], $exclude_functions, true ) ) {
156
+ return false;
157
+ }
158
+ }
159
+
160
+ return true;
161
+ }
162
+
163
+ }
164
+
165
+ function register_qm_collector_caps( array $collectors, QueryMonitor $qm ) {
166
+ $collectors['caps'] = new QM_Collector_Caps;
167
+ return $collectors;
168
+ }
169
+
170
+ add_filter( 'qm/collectors', 'register_qm_collector_caps', 20, 2 );
collectors/hooks.php CHANGED
@@ -18,7 +18,6 @@ class QM_Collector_Hooks extends QM_Collector {
18
 
19
  public $id = 'hooks';
20
  protected static $hide_core;
21
- protected static $hide_qm;
22
 
23
  public function name() {
24
  return __( 'Hooks & Actions', 'query-monitor' );
@@ -28,7 +27,7 @@ class QM_Collector_Hooks extends QM_Collector {
28
 
29
  global $wp_actions, $wp_filter;
30
 
31
- self::$hide_qm = ( defined( 'QM_HIDE_SELF' ) && QM_HIDE_SELF );
32
  self::$hide_core = ( defined( 'QM_HIDE_CORE_HOOKS' ) && QM_HIDE_CORE_HOOKS );
33
 
34
  if ( is_admin() and ( $admin = QM_Collectors::get( 'admin' ) ) ) {
18
 
19
  public $id = 'hooks';
20
  protected static $hide_core;
 
21
 
22
  public function name() {
23
  return __( 'Hooks & Actions', 'query-monitor' );
27
 
28
  global $wp_actions, $wp_filter;
29
 
30
+ self::$hide_qm = self::hide_qm();
31
  self::$hide_core = ( defined( 'QM_HIDE_CORE_HOOKS' ) && QM_HIDE_CORE_HOOKS );
32
 
33
  if ( is_admin() and ( $admin = QM_Collectors::get( 'admin' ) ) ) {
output/Html.php CHANGED
@@ -97,6 +97,10 @@ abstract class QM_Output_Html extends QM_Output {
97
  'prepend' => array(),
98
  ), $args );
99
 
 
 
 
 
100
  usort( $values, 'strcasecmp' );
101
 
102
  $filter_id = 'qm-filter-' . $this->collector->id . '-' . $name;
97
  'prepend' => array(),
98
  ), $args );
99
 
100
+ if ( 'component' === $name ) {
101
+ $args['prepend']['non-core'] = __( 'Non-Core', 'query-monitor' );
102
+ }
103
+
104
  usort( $values, 'strcasecmp' );
105
 
106
  $filter_id = 'qm-filter-' . $this->collector->id . '-' . $name;
output/html/caps.php ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /*
3
+ Copyright 2009-2017 John Blackbourn
4
+
5
+ This program is free software; you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation; either version 2 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ */
16
+
17
+ class QM_Output_Html_Caps 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' ), 105 );
22
+ }
23
+
24
+ public function output() {
25
+
26
+ $data = $this->collector->get_data();
27
+
28
+ echo '<div class="qm" id="' . esc_attr( $this->collector->id() ) . '">';
29
+ echo '<table cellspacing="0">';
30
+
31
+ if ( ! empty( $data['caps'] ) ) {
32
+
33
+ $results = array(
34
+ 'true',
35
+ 'false',
36
+ );
37
+ $show_user = ( count( $data['users'] ) > 1 );
38
+
39
+ echo '<caption class="screen-reader-text">' . esc_html( $this->collector->name() ) . '</caption>';
40
+
41
+ echo '<thead>';
42
+ echo '<tr>';
43
+ echo '<th scope="col">';
44
+ echo $this->build_filter( 'name', $data['parts'], __( 'Capability Check', 'query-monitor' ) ); // WPCS: XSS ok;
45
+ echo '</th>';
46
+
47
+ if ( $show_user ) {
48
+ echo '<th scope="col">';
49
+ echo $this->build_filter( 'user', $data['users'], __( 'User', 'query-monitor' ) ); // WPCS: XSS ok;
50
+ echo '</th>';
51
+ }
52
+
53
+ echo '<th scope="col">';
54
+ echo $this->build_filter( 'result', $results, __( 'Result', 'query-monitor' ) ); // WPCS: XSS ok;
55
+ echo '</th>';
56
+ echo '<th scope="col">' . esc_html__( 'Caller', 'query-monitor' ) . '</th>';
57
+ echo '<th scope="col">';
58
+ echo $this->build_filter( 'component', $data['components'], __( 'Component', 'query-monitor' ) ); // WPCS: XSS ok.
59
+ echo '</th>';
60
+ echo '</tr>';
61
+ echo '</thead>';
62
+
63
+ echo '<tbody>';
64
+
65
+ foreach ( $data['caps'] as $row ) {
66
+ $component = $row['trace']->get_component();
67
+
68
+ $row_attr = array();
69
+ $row_attr['data-qm-name'] = implode( ' ', $row['parts'] );
70
+ $row_attr['data-qm-user'] = $row['user'];
71
+ $row_attr['data-qm-component'] = $component->name;
72
+ $row_attr['data-qm-result'] = ( $row['result'] ) ? 'true' : 'false';
73
+
74
+ if ( 'core' !== $component->context ) {
75
+ $row_attr['data-qm-component'] .= ' non-core';
76
+ }
77
+
78
+ $attr = '';
79
+
80
+ foreach ( $row_attr as $a => $v ) {
81
+ $attr .= ' ' . $a . '="' . esc_attr( $v ) . '"';
82
+ }
83
+
84
+ printf( // WPCS: XSS ok.
85
+ '<tr %s>',
86
+ $attr
87
+ );
88
+
89
+ $name = esc_html( $row['name'] );
90
+
91
+ if ( ! empty( $row['args'] ) ) {
92
+ foreach ( $row['args'] as $arg ) {
93
+ $name .= '<br>' . esc_html( QM_Util::display_variable( $arg ) );
94
+ }
95
+ }
96
+
97
+ printf( // WPCS: XSS ok.
98
+ '<td class="qm-ltr qm-nowrap">%s</td>',
99
+ $name
100
+ );
101
+
102
+ if ( $show_user ) {
103
+ printf(
104
+ '<td class="qm-num">%s</td>',
105
+ esc_html( $row['user'] )
106
+ );
107
+ }
108
+
109
+ $result = ( $row['result'] ) ? '<span class="qm-true">true&nbsp;&#x2713;</span>' : '<span class="qm-false">false</span>';
110
+ printf( // WPCS: XSS ok.
111
+ '<td class="qm-ltr qm-nowrap">%s</td>',
112
+ $result
113
+ );
114
+
115
+ $stack = array();
116
+ $trace = $row['trace']->get_trace();
117
+ $filtered_trace = $row['trace']->get_display_trace();
118
+
119
+ array_pop( $filtered_trace ); // remove the WP_User->has_cap() call
120
+ array_pop( $filtered_trace ); // remove the *_user_can() call
121
+
122
+ if ( ! count( $filtered_trace ) ) {
123
+ $responsible_name = QM_Util::standard_dir( $trace[1]['file'], '' ) . ':' . $trace[1]['line'];
124
+
125
+ $responsible_item = $trace[1];
126
+ $responsible_item['display'] = $responsible_name;
127
+ $responsible_item['calling_file'] = $trace[1]['file'];
128
+ $responsible_item['calling_line'] = $trace[1]['line'];
129
+ array_unshift( $filtered_trace, $responsible_item );
130
+ }
131
+
132
+ foreach ( $filtered_trace as $item ) {
133
+ $stack[] = self::output_filename( $item['display'], $item['calling_file'], $item['calling_line'] );
134
+ }
135
+
136
+ echo '<td class="qm-has-toggle qm-nowrap qm-ltr"><ol class="qm-toggler qm-numbered">';
137
+
138
+ $caller = array_pop( $stack );
139
+
140
+ if ( ! empty( $stack ) ) {
141
+ echo '<button class="qm-toggle" data-on="+" data-off="-">+</button>';
142
+ echo '<div class="qm-toggled"><li>' . implode( '</li><li>', $stack ) . '</li></div>'; // WPCS: XSS ok.
143
+ }
144
+
145
+ echo "<li>{$caller}</li>"; // WPCS: XSS ok.
146
+ echo '</ol></td>';
147
+
148
+ printf(
149
+ '<td class="qm-nowrap">%s</td>',
150
+ esc_html( $component->name )
151
+ );
152
+
153
+ echo '</tr>';
154
+
155
+ }
156
+
157
+ echo '</tbody>';
158
+
159
+ } else {
160
+
161
+ echo '<caption>' . esc_html( $this->collector->name() ) . '</caption>';
162
+
163
+ echo '<tbody>';
164
+ echo '<tr>';
165
+ echo '<td>';
166
+ esc_html_e( 'No capability checks were recorded.', 'query-monitor' );
167
+ echo '</td>';
168
+ echo '</tr>';
169
+ echo '</tbody>';
170
+
171
+ }
172
+
173
+ echo '</table>';
174
+ echo '</div>';
175
+
176
+ }
177
+
178
+ public function admin_menu( array $menu ) {
179
+ $menu[] = $this->menu( array(
180
+ 'title' => $this->collector->name(),
181
+ ) );
182
+ return $menu;
183
+
184
+ }
185
+
186
+ }
187
+
188
+ function register_qm_output_html_caps( array $output, QM_Collectors $collectors ) {
189
+ if ( $collector = QM_Collectors::get( 'caps' ) ) {
190
+ $output['caps'] = new QM_Output_Html_Caps( $collector );
191
+ }
192
+ return $output;
193
+ }
194
+
195
+ add_filter( 'qm/outputter/html', 'register_qm_output_html_caps', 105, 2 );
output/html/db_queries.php CHANGED
@@ -79,13 +79,14 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
79
  echo '<th scope="col">' . esc_html__( 'Query', 'query-monitor' ) . '</th>';
80
  echo '<th scope="col">' . esc_html__( 'Call Stack', 'query-monitor' ) . '</th>';
81
  echo '<th scope="col">' . esc_html__( 'Component', 'query-monitor' ) . '</th>';
82
- echo '<th scope="col">' . esc_html__( 'Error', 'query-monitor' ) . '</th>';
 
83
  echo '</tr>';
84
  echo '</thead>';
85
  echo '<tbody>';
86
 
87
  foreach ( $errors as $row ) {
88
- $this->output_query_row( $row, array( 'sql', 'stack', 'component', 'result' ) );
89
  }
90
 
91
  echo '</tbody>';
@@ -185,12 +186,23 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
185
  echo '</tr>';
186
  }
187
 
 
 
 
 
 
 
 
 
 
 
 
188
  echo '<tr>';
189
  echo '<th scope="col" class="qm-sorted-asc">&nbsp;';
190
  echo $this->build_sorter(); // WPCS: XSS ok;
191
  echo '</th>';
192
  echo '<th scope="col">';
193
- echo $this->build_filter( 'type', array_keys( $db->types ), __( 'Query', 'query-monitor' ) ); // WPCS: XSS ok;
194
  echo '</th>';
195
  echo '<th scope="col">';
196
 
@@ -293,7 +305,7 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
293
  unset( $cols['component'] );
294
  }
295
  if ( !isset( $row['result'] ) ) {
296
- unset( $cols['result'] );
297
  }
298
  if ( !isset( $row['stack'] ) ) {
299
  unset( $cols['stack'] );
@@ -337,9 +349,16 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
337
  }
338
  if ( isset( $cols['sql'] ) ) {
339
  $row_attr['data-qm-type'] = $row['type'];
 
 
 
340
  }
341
  if ( isset( $cols['component'] ) && $row['component'] ) {
342
  $row_attr['data-qm-component'] = $row['component']->name;
 
 
 
 
343
  }
344
  if ( isset( $cols['caller'] ) ) {
345
  $row_attr['data-qm-caller'] = $row['caller_name'];
@@ -393,9 +412,11 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
393
  }
394
 
395
  if ( isset( $cols['stack'] ) ) {
396
- echo '<td class="qm-row-caller qm-row-stack qm-nowrap qm-ltr"><ol>';
 
 
 
397
  echo "<li>{$caller_name}</li>"; // WPCS: XSS ok.
398
- echo '<li>' . implode( '</li><li>', $stack ) . '</li>'; // WPCS: XSS ok.
399
  echo '</ol></td>';
400
  }
401
 
@@ -407,6 +428,10 @@ class QM_Output_Html_DB_Queries extends QM_Output_Html {
407
  }
408
  }
409
 
 
 
 
 
410
  if ( isset( $cols['result'] ) ) {
411
  if ( is_wp_error( $row['result'] ) ) {
412
  echo "<td class='qm-row-result qm-row-error'>" . esc_html( $row['result']->get_error_message() ) . "</td>\n";
79
  echo '<th scope="col">' . esc_html__( 'Query', 'query-monitor' ) . '</th>';
80
  echo '<th scope="col">' . esc_html__( 'Call Stack', 'query-monitor' ) . '</th>';
81
  echo '<th scope="col">' . esc_html__( 'Component', 'query-monitor' ) . '</th>';
82
+ echo '<th scope="col">' . esc_html__( 'Error Code', 'query-monitor' ) . '</th>';
83
+ echo '<th scope="col">' . esc_html__( 'Error Message', 'query-monitor' ) . '</th>';
84
  echo '</tr>';
85
  echo '</thead>';
86
  echo '<tbody>';
87
 
88
  foreach ( $errors as $row ) {
89
+ $this->output_query_row( $row, array( 'sql', 'stack', 'component', 'errno', 'result' ) );
90
  }
91
 
92
  echo '</tbody>';
186
  echo '</tr>';
187
  }
188
 
189
+ $types = array_keys( $db->types );
190
+ $prepend = array();
191
+
192
+ if ( count( $types ) > 1 ) {
193
+ $prepend['non-select'] = __( 'Non-SELECT', 'query-monitor' );
194
+ }
195
+
196
+ $args = array(
197
+ 'prepend' => $prepend,
198
+ );
199
+
200
  echo '<tr>';
201
  echo '<th scope="col" class="qm-sorted-asc">&nbsp;';
202
  echo $this->build_sorter(); // WPCS: XSS ok;
203
  echo '</th>';
204
  echo '<th scope="col">';
205
+ echo $this->build_filter( 'type', $types, __( 'Query', 'query-monitor' ), $args ); // WPCS: XSS ok;
206
  echo '</th>';
207
  echo '<th scope="col">';
208
 
305
  unset( $cols['component'] );
306
  }
307
  if ( !isset( $row['result'] ) ) {
308
+ unset( $cols['result'], $cols['errno'] );
309
  }
310
  if ( !isset( $row['stack'] ) ) {
311
  unset( $cols['stack'] );
349
  }
350
  if ( isset( $cols['sql'] ) ) {
351
  $row_attr['data-qm-type'] = $row['type'];
352
+ if ( 'SELECT' !== $row['type'] ) {
353
+ $row_attr['data-qm-type'] .= ' non-select';
354
+ }
355
  }
356
  if ( isset( $cols['component'] ) && $row['component'] ) {
357
  $row_attr['data-qm-component'] = $row['component']->name;
358
+
359
+ if ( 'core' !== $row['component']->context ) {
360
+ $row_attr['data-qm-component'] .= ' non-core';
361
+ }
362
  }
363
  if ( isset( $cols['caller'] ) ) {
364
  $row_attr['data-qm-caller'] = $row['caller_name'];
412
  }
413
 
414
  if ( isset( $cols['stack'] ) ) {
415
+ echo '<td class="qm-row-caller qm-row-stack qm-nowrap qm-ltr"><ol class="qm-numbered">';
416
+ if ( ! empty( $stack ) ) {
417
+ echo '<li>' . implode( '</li><li>', $stack ) . '</li>'; // WPCS: XSS ok.
418
+ }
419
  echo "<li>{$caller_name}</li>"; // WPCS: XSS ok.
 
420
  echo '</ol></td>';
421
  }
422
 
428
  }
429
  }
430
 
431
+ if ( isset( $cols['errno'] ) && is_wp_error( $row['result'] ) ) {
432
+ echo "<td class='qm-row-result qm-row-error'>" . esc_html( $row['result']->get_error_code() ) . "</td>\n";
433
+ }
434
+
435
  if ( isset( $cols['result'] ) ) {
436
  if ( is_wp_error( $row['result'] ) ) {
437
  echo "<td class='qm-row-result qm-row-error'>" . esc_html( $row['result']->get_error_message() ) . "</td>\n";
output/html/environment.php CHANGED
@@ -109,7 +109,7 @@ class QM_Output_Html_Environment extends QM_Output_Html {
109
  );
110
 
111
  echo '<div class="qm-toggled">';
112
- echo "<ul class='qm-info qm-supplemental'><li>{$error_levels}</li></ul>"; // WPCS: XSS ok.
113
  echo '</div>';
114
 
115
  echo '</div></td>';
@@ -125,7 +125,7 @@ class QM_Output_Html_Environment extends QM_Output_Html {
125
  esc_html( number_format_i18n( count( $data['php']['extensions'] ) ) )
126
  );
127
 
128
- echo '<div class="qm-toggled"><ul class="qm-info qm-supplemental"><li>';
129
  echo implode( '</li><li>', array_map( 'esc_html', $data['php']['extensions'] ) );
130
  echo '</li></ul></div>';
131
 
109
  );
110
 
111
  echo '<div class="qm-toggled">';
112
+ echo "<ul class='qm-supplemental'><li>{$error_levels}</li></ul>"; // WPCS: XSS ok.
113
  echo '</div>';
114
 
115
  echo '</div></td>';
125
  esc_html( number_format_i18n( count( $data['php']['extensions'] ) ) )
126
  );
127
 
128
+ echo '<div class="qm-toggled"><ul class="qm-supplemental"><li>';
129
  echo implode( '</li><li>', array_map( 'esc_html', $data['php']['extensions'] ) );
130
  echo '</li></ul></div>';
131
 
output/html/hooks.php CHANGED
@@ -80,6 +80,10 @@ class QM_Output_Html_Hooks extends QM_Output_Html {
80
  $row_attr['data-qm-name'] = implode( ' ', $hook['parts'] );
81
  $row_attr['data-qm-component'] = implode( ' ', $hook['components'] );
82
 
 
 
 
 
83
  $attr = '';
84
 
85
  if ( !empty( $hook['actions'] ) ) {
80
  $row_attr['data-qm-name'] = implode( ' ', $hook['parts'] );
81
  $row_attr['data-qm-component'] = implode( ' ', $hook['components'] );
82
 
83
+ if ( ! empty( $row_attr['data-qm-component'] ) && 'Core' !== $row_attr['data-qm-component'] ) {
84
+ $row_attr['data-qm-component'] .= ' non-core';
85
+ }
86
+
87
  $attr = '';
88
 
89
  if ( !empty( $hook['actions'] ) ) {
output/html/http.php CHANGED
@@ -52,7 +52,7 @@ class QM_Output_Html_HTTP extends QM_Output_Html {
52
  echo '<th scope="col">';
53
  echo $this->build_filter( 'type', array_keys( $data['types'] ), __( 'Status', 'query-monitor' ) ); // WPCS: XSS ok.
54
  echo '</th>';
55
- echo '<th scope="col">' . esc_html__( 'Call Stack', 'query-monitor' ) . '</th>';
56
  echo '<th scope="col">';
57
  echo $this->build_filter( 'component', wp_list_pluck( $data['component_times'], 'component' ), __( 'Component', 'query-monitor' ) ); // WPCS: XSS ok.
58
  echo '</th>';
@@ -123,7 +123,9 @@ class QM_Output_Html_HTTP extends QM_Output_Html {
123
 
124
  $stack = array();
125
  $filtered_trace = $row['trace']->get_display_trace();
126
- array_pop( $filtered_trace );
 
 
127
 
128
  foreach ( $filtered_trace as $item ) {
129
  $stack[] = self::output_filename( $item['display'], $item['calling_file'], $item['calling_line'] );
@@ -132,6 +134,10 @@ class QM_Output_Html_HTTP extends QM_Output_Html {
132
  $row_attr['data-qm-component'] = $component->name;
133
  $row_attr['data-qm-type'] = $row['type'];
134
 
 
 
 
 
135
  $attr = '';
136
  foreach ( $row_attr as $a => $v ) {
137
  $attr .= ' ' . $a . '="' . esc_attr( $v ) . '"';
@@ -155,10 +161,19 @@ class QM_Output_Html_HTTP extends QM_Output_Html {
155
  '<td>%s</td>',
156
  esc_html( $response )
157
  );
158
- printf( // WPCS: XSS ok.
159
- '<td class="qm-nowrap qm-ltr"><ol class="qm-numbered"><li>%s</li></ol></td>',
160
- implode( '</li><li>', $stack )
161
- );
 
 
 
 
 
 
 
 
 
162
  printf(
163
  '<td class="qm-nowrap">%s</td>',
164
  esc_html( $component->name )
52
  echo '<th scope="col">';
53
  echo $this->build_filter( 'type', array_keys( $data['types'] ), __( 'Status', 'query-monitor' ) ); // WPCS: XSS ok.
54
  echo '</th>';
55
+ echo '<th scope="col">' . esc_html__( 'Caller', 'query-monitor' ) . '</th>';
56
  echo '<th scope="col">';
57
  echo $this->build_filter( 'component', wp_list_pluck( $data['component_times'], 'component' ), __( 'Component', 'query-monitor' ) ); // WPCS: XSS ok.
58
  echo '</th>';
123
 
124
  $stack = array();
125
  $filtered_trace = $row['trace']->get_display_trace();
126
+ array_pop( $filtered_trace ); // remove WP_Http->request()
127
+ array_pop( $filtered_trace ); // remove WP_Http->{$method}()
128
+ array_pop( $filtered_trace ); // remove wp_remote_{$method}()
129
 
130
  foreach ( $filtered_trace as $item ) {
131
  $stack[] = self::output_filename( $item['display'], $item['calling_file'], $item['calling_line'] );
134
  $row_attr['data-qm-component'] = $component->name;
135
  $row_attr['data-qm-type'] = $row['type'];
136
 
137
+ if ( 'core' !== $component->context ) {
138
+ $row_attr['data-qm-component'] .= ' non-core';
139
+ }
140
+
141
  $attr = '';
142
  foreach ( $row_attr as $a => $v ) {
143
  $attr .= ' ' . $a . '="' . esc_attr( $v ) . '"';
161
  '<td>%s</td>',
162
  esc_html( $response )
163
  );
164
+
165
+ echo '<td class="qm-has-toggle qm-nowrap qm-ltr"><ol class="qm-toggler qm-numbered">';
166
+
167
+ $caller = array_pop( $stack );
168
+
169
+ if ( ! empty( $stack ) ) {
170
+ echo '<button class="qm-toggle" data-on="+" data-off="-">+</button>';
171
+ echo '<div class="qm-toggled"><li>' . implode( '</li><li>', $stack ) . '</li></div>'; // WPCS: XSS ok.
172
+ }
173
+
174
+ echo "<li>{$caller}</li>"; // WPCS: XSS ok.
175
+ echo '</ol></td>';
176
+
177
  printf(
178
  '<td class="qm-nowrap">%s</td>',
179
  esc_html( $component->name )
output/html/transients.php CHANGED
@@ -40,7 +40,7 @@ class QM_Output_Html_Transients extends QM_Output_Html {
40
  }
41
  echo '<th scope="col">' . esc_html__( 'Expiration', 'query-monitor' ) . '</th>';
42
  echo '<th scope="col">' . esc_html_x( 'Size', 'size of transient value', 'query-monitor' ) . '</th>';
43
- echo '<th scope="col">' . esc_html__( 'Call Stack', 'query-monitor' ) . '</th>';
44
  echo '<th scope="col">' . esc_html__( 'Component', 'query-monitor' ) . '</th>';
45
  echo '</tr>';
46
  echo '</thead>';
@@ -86,16 +86,25 @@ class QM_Output_Html_Transients extends QM_Output_Html {
86
 
87
  $stack = array();
88
  $filtered_trace = $row['trace']->get_display_trace();
89
- array_pop( $filtered_trace );
 
90
 
91
  foreach ( $filtered_trace as $item ) {
92
  $stack[] = self::output_filename( $item['display'], $item['calling_file'], $item['calling_line'] );
93
  }
94
 
95
- printf( // WPCS: XSS ok.
96
- '<td class="qm-nowrap qm-ltr"><ol class="qm-numbered"><li>%s</li></ol></td>',
97
- implode( '</li><li>', $stack )
98
- );
 
 
 
 
 
 
 
 
99
  printf(
100
  '<td class="qm-nowrap">%s</td>',
101
  esc_html( $component->name )
40
  }
41
  echo '<th scope="col">' . esc_html__( 'Expiration', 'query-monitor' ) . '</th>';
42
  echo '<th scope="col">' . esc_html_x( 'Size', 'size of transient value', 'query-monitor' ) . '</th>';
43
+ echo '<th scope="col">' . esc_html__( 'Caller', 'query-monitor' ) . '</th>';
44
  echo '<th scope="col">' . esc_html__( 'Component', 'query-monitor' ) . '</th>';
45
  echo '</tr>';
46
  echo '</thead>';
86
 
87
  $stack = array();
88
  $filtered_trace = $row['trace']->get_display_trace();
89
+ array_pop( $filtered_trace ); // remove do_action('setted_(site_)?transient')
90
+ array_pop( $filtered_trace ); // remove set_(site_)?transient()
91
 
92
  foreach ( $filtered_trace as $item ) {
93
  $stack[] = self::output_filename( $item['display'], $item['calling_file'], $item['calling_line'] );
94
  }
95
 
96
+ echo '<td class="qm-has-toggle qm-nowrap qm-ltr"><ol class="qm-toggler qm-numbered">';
97
+
98
+ $caller = array_pop( $stack );
99
+
100
+ if ( ! empty( $stack ) ) {
101
+ echo '<button class="qm-toggle" data-on="+" data-off="-">+</button>';
102
+ echo '<div class="qm-toggled"><li>' . implode( '</li><li>', $stack ) . '</li></div>'; // WPCS: XSS ok.
103
+ }
104
+
105
+ echo "<li>{$caller}</li>"; // WPCS: XSS ok.
106
+ echo '</ol></td>';
107
+
108
  printf(
109
  '<td class="qm-nowrap">%s</td>',
110
  esc_html( $component->name )
query-monitor.php CHANGED
@@ -10,7 +10,7 @@
10
  *
11
  * Plugin Name: Query Monitor
12
  * Description: Monitoring of database queries, hooks, conditionals and more.
13
- * Version: 2.15.0
14
  * Plugin URI: https://github.com/johnbillion/query-monitor
15
  * Author: John Blackbourn
16
  * Author URI: https://johnblackbourn.com/
10
  *
11
  * Plugin Name: Query Monitor
12
  * Description: Monitoring of database queries, hooks, conditionals and more.
13
+ * Version: 2.16.1
14
  * Plugin URI: https://github.com/johnbillion/query-monitor
15
  * Author: John Blackbourn
16
  * Author URI: https://johnblackbourn.com/
readme.txt CHANGED
@@ -2,8 +2,8 @@
2
  Contributors: johnbillion
3
  Tags: ajax, debug, debug-bar, debugging, development, developer, performance, profiler, profiling, queries, query monitor, rest-api
4
  Requires at least: 3.7
5
- Tested up to: 4.8
6
- Stable tag: 2.15.0
7
  License: GPLv2 or later
8
 
9
  View debugging and performance information on database queries, hooks, conditionals, HTTP requests, redirects and more.
@@ -75,6 +75,10 @@ Filtering queries by component or calling function makes it easy to see which pl
75
  * Shows the response code, call stack, component, timeout, and time taken
76
  * Highlights erroneous responses, such as failed requests and anything without a `200` response code
77
 
 
 
 
 
78
  = Redirects =
79
 
80
  * Whenever a redirect occurs, Query Monitor adds an `X-QM-Redirect` HTTP header containing the call stack, so you can use your favourite HTTP inspector or browser developer tools to easily trace where a redirect has come from
2
  Contributors: johnbillion
3
  Tags: ajax, debug, debug-bar, debugging, development, developer, performance, profiler, profiling, queries, query monitor, rest-api
4
  Requires at least: 3.7
5
+ Tested up to: 4.9
6
+ Stable tag: 2.16.1
7
  License: GPLv2 or later
8
 
9
  View debugging and performance information on database queries, hooks, conditionals, HTTP requests, redirects and more.
75
  * Shows the response code, call stack, component, timeout, and time taken
76
  * Highlights erroneous responses, such as failed requests and anything without a `200` response code
77
 
78
+ = User Capability Checks =
79
+
80
+ * Shows every user capability check that is performed on the page, along with the result and any parameters passed along with the capability check.
81
+
82
  = Redirects =
83
 
84
  * Whenever a redirect occurs, Query Monitor adds an `X-QM-Redirect` HTTP header containing the call stack, so you can use your favourite HTTP inspector or browser developer tools to easily trace where a redirect has come from
wp-content/db.php CHANGED
@@ -109,7 +109,17 @@ class QM_DB extends wpdb {
109
  ) );
110
 
111
  if ( $this->last_error ) {
112
- $this->queries[$i]['result'] = new WP_Error( 'qmdb', $this->last_error );
 
 
 
 
 
 
 
 
 
 
113
  } else {
114
  $this->queries[$i]['result'] = $result;
115
  }
109
  ) );
110
 
111
  if ( $this->last_error ) {
112
+ $code = 'qmdb';
113
+ if ( $this->use_mysqli ) {
114
+ if ( $this->dbh instanceof mysqli ) {
115
+ $code = mysqli_errno( $this->dbh );
116
+ }
117
+ } else {
118
+ if ( is_resource( $this->dbh ) ) {
119
+ $code = mysql_errno( $this->dbh );
120
+ }
121
+ }
122
+ $this->queries[$i]['result'] = new WP_Error( $code, $this->last_error );
123
  } else {
124
  $this->queries[$i]['result'] = $result;
125
  }