first
This commit is contained in:
141
wp-content/plugins/query-monitor/collectors/admin.php
Normal file
141
wp-content/plugins/query-monitor/collectors/admin.php
Normal file
@ -0,0 +1,141 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* Admin screen collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_Admin>
|
||||
*/
|
||||
class QM_Collector_Admin extends QM_DataCollector {
|
||||
|
||||
public $id = 'response';
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_Admin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_actions() {
|
||||
$actions = array(
|
||||
'current_screen',
|
||||
'admin_notices',
|
||||
'all_admin_notices',
|
||||
'network_admin_notices',
|
||||
'user_admin_notices',
|
||||
);
|
||||
|
||||
if ( ! empty( $this->data->list_table ) ) {
|
||||
$actions[] = $this->data->list_table['column_action'];
|
||||
}
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_filters() {
|
||||
$filters = array();
|
||||
|
||||
if ( ! empty( $this->data->list_table ) ) {
|
||||
$filters[] = $this->data->list_table['columns_filter'];
|
||||
$filters[] = $this->data->list_table['sortables_filter'];
|
||||
}
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
/**
|
||||
* @var string $pagenow
|
||||
* @var ?WP_List_Table $wp_list_table
|
||||
*/
|
||||
global $pagenow, $wp_list_table;
|
||||
|
||||
$this->data->pagenow = $pagenow;
|
||||
$this->data->typenow = $GLOBALS['typenow'] ?? '';
|
||||
$this->data->taxnow = $GLOBALS['taxnow'] ?? '';
|
||||
$this->data->hook_suffix = $GLOBALS['hook_suffix'] ?? '';
|
||||
$this->data->current_screen = get_current_screen();
|
||||
|
||||
$screens = array(
|
||||
'edit' => true,
|
||||
'edit-comments' => true,
|
||||
'edit-tags' => true,
|
||||
'link-manager' => true,
|
||||
'plugins' => true,
|
||||
'plugins-network' => true,
|
||||
'sites-network' => true,
|
||||
'themes-network' => true,
|
||||
'upload' => true,
|
||||
'users' => true,
|
||||
'users-network' => true,
|
||||
);
|
||||
|
||||
if ( empty( $this->data->current_screen ) || ! isset( $screens[ $this->data->current_screen->base ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
# And now, WordPress' legendary inconsistency comes into play:
|
||||
|
||||
$columns = $this->data->current_screen->id;
|
||||
$sortables = $this->data->current_screen->id;
|
||||
$column = $this->data->current_screen->base;
|
||||
|
||||
if ( ! empty( $this->data->current_screen->taxonomy ) ) {
|
||||
$column = $this->data->current_screen->taxonomy;
|
||||
} elseif ( ! empty( $this->data->current_screen->post_type ) ) {
|
||||
$column = $this->data->current_screen->post_type . '_posts';
|
||||
}
|
||||
|
||||
if ( ! empty( $this->data->current_screen->post_type ) && empty( $this->data->current_screen->taxonomy ) ) {
|
||||
$columns = $this->data->current_screen->post_type . '_posts';
|
||||
}
|
||||
|
||||
if ( 'edit-comments' === $column ) {
|
||||
$column = 'comments';
|
||||
} elseif ( 'upload' === $column ) {
|
||||
$column = 'media';
|
||||
} elseif ( 'link-manager' === $column ) {
|
||||
$column = 'link';
|
||||
}
|
||||
|
||||
$list_table_data = array(
|
||||
'columns_filter' => "manage_{$columns}_columns",
|
||||
'sortables_filter' => "manage_{$sortables}_sortable_columns",
|
||||
'column_action' => "manage_{$column}_custom_column",
|
||||
);
|
||||
|
||||
if ( ! empty( $wp_list_table ) ) {
|
||||
$list_table_data['class_name'] = get_class( $wp_list_table );
|
||||
}
|
||||
|
||||
$this->data->list_table = $list_table_data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, QM_Collector> $collectors
|
||||
* @param QueryMonitor $qm
|
||||
* @return array<string, QM_Collector>
|
||||
*/
|
||||
function register_qm_collector_admin( array $collectors, QueryMonitor $qm ) {
|
||||
$collectors['response'] = new QM_Collector_Admin();
|
||||
return $collectors;
|
||||
}
|
||||
|
||||
if ( is_admin() ) {
|
||||
add_filter( 'qm/collectors', 'register_qm_collector_admin', 10, 2 );
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* Enqueued scripts collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class QM_Collector_Assets_Scripts extends QM_Collector_Assets {
|
||||
|
||||
public $id = 'assets_scripts';
|
||||
|
||||
public function get_dependency_type() {
|
||||
return 'scripts';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_actions() {
|
||||
if ( is_admin() ) {
|
||||
return array(
|
||||
'admin_enqueue_scripts',
|
||||
'admin_print_footer_scripts',
|
||||
'admin_print_scripts',
|
||||
);
|
||||
} else {
|
||||
return array(
|
||||
'wp_enqueue_scripts',
|
||||
'wp_print_footer_scripts',
|
||||
'wp_print_scripts',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_filters() {
|
||||
return array(
|
||||
'print_scripts_array',
|
||||
'script_loader_src',
|
||||
'script_loader_tag',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, QM_Collector> $collectors
|
||||
* @param QueryMonitor $qm
|
||||
* @return array<string, QM_Collector>
|
||||
*/
|
||||
function register_qm_collector_assets_scripts( array $collectors, QueryMonitor $qm ) {
|
||||
$collectors['assets_scripts'] = new QM_Collector_Assets_Scripts();
|
||||
return $collectors;
|
||||
}
|
||||
|
||||
add_filter( 'qm/collectors', 'register_qm_collector_assets_scripts', 10, 2 );
|
@ -0,0 +1,42 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* Enqueued styles collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class QM_Collector_Assets_Styles extends QM_Collector_Assets {
|
||||
|
||||
public $id = 'assets_styles';
|
||||
|
||||
public function get_dependency_type() {
|
||||
return 'styles';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_filters() {
|
||||
return array(
|
||||
'print_styles_array',
|
||||
'style_loader_src',
|
||||
'style_loader_tag',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, QM_Collector> $collectors
|
||||
* @param QueryMonitor $qm
|
||||
* @return array<string, QM_Collector>
|
||||
*/
|
||||
function register_qm_collector_assets_styles( array $collectors, QueryMonitor $qm ) {
|
||||
$collectors['assets_styles'] = new QM_Collector_Assets_Styles();
|
||||
return $collectors;
|
||||
}
|
||||
|
||||
add_filter( 'qm/collectors', 'register_qm_collector_assets_styles', 10, 2 );
|
266
wp-content/plugins/query-monitor/collectors/block_editor.php
Normal file
266
wp-content/plugins/query-monitor/collectors/block_editor.php
Normal file
@ -0,0 +1,266 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* Block editor (née Gutenberg) collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_Block_Editor>
|
||||
*/
|
||||
class QM_Collector_Block_Editor extends QM_DataCollector {
|
||||
|
||||
public $id = 'block_editor';
|
||||
|
||||
/**
|
||||
* @var array<int, mixed[]>
|
||||
*/
|
||||
protected $block_context = array();
|
||||
|
||||
/**
|
||||
* @var array<int, QM_Timer|false>
|
||||
*/
|
||||
protected $block_timing = array();
|
||||
|
||||
/**
|
||||
* @var QM_Timer|null
|
||||
*/
|
||||
protected $block_timer = null;
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_Block_Editor();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function set_up() {
|
||||
parent::set_up();
|
||||
|
||||
add_filter( 'pre_render_block', array( $this, 'filter_pre_render_block' ), 9999, 2 );
|
||||
add_filter( 'render_block_context', array( $this, 'filter_render_block_context' ), -9999, 2 );
|
||||
add_filter( 'render_block_data', array( $this, 'filter_render_block_data' ), -9999 );
|
||||
add_filter( 'render_block', array( $this, 'filter_render_block' ), 9999, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function tear_down() {
|
||||
remove_filter( 'pre_render_block', array( $this, 'filter_pre_render_block' ), 9999 );
|
||||
remove_filter( 'render_block_context', array( $this, 'filter_render_block_context' ), -9999 );
|
||||
remove_filter( 'render_block_data', array( $this, 'filter_render_block_data' ), -9999 );
|
||||
remove_filter( 'render_block', array( $this, 'filter_render_block' ), 9999 );
|
||||
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_filters() {
|
||||
return array(
|
||||
'allowed_block_types',
|
||||
'allowed_block_types_all',
|
||||
'block_editor_settings_all',
|
||||
'block_type_metadata',
|
||||
'block_type_metadata_settings',
|
||||
'block_parser_class',
|
||||
'pre_render_block',
|
||||
'register_block_type_args',
|
||||
'render_block_context',
|
||||
'render_block_data',
|
||||
'render_block',
|
||||
'use_widgets_block_editor',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $pre_render
|
||||
* @param mixed[] $block
|
||||
* @return string|null
|
||||
*/
|
||||
public function filter_pre_render_block( $pre_render, array $block ) {
|
||||
if ( null !== $pre_render ) {
|
||||
$this->block_timing[] = false;
|
||||
}
|
||||
|
||||
return $pre_render;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $context
|
||||
* @param mixed[] $block
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function filter_render_block_context( array $context, array $block ) {
|
||||
$this->block_context[] = $context;
|
||||
|
||||
return $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $block
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function filter_render_block_data( array $block ) {
|
||||
$this->block_timer = new QM_Timer();
|
||||
$this->block_timer->start();
|
||||
|
||||
return $block;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $block_content
|
||||
* @param mixed[] $block
|
||||
* @return string
|
||||
*/
|
||||
public function filter_render_block( $block_content, array $block ) {
|
||||
if ( isset( $this->block_timer ) ) {
|
||||
$this->block_timing[] = $this->block_timer->stop();
|
||||
}
|
||||
|
||||
return $block_content;
|
||||
}
|
||||
|
||||
public function process() {
|
||||
global $_wp_current_template_content;
|
||||
|
||||
$this->data->block_editor_enabled = self::wp_block_editor_enabled();
|
||||
|
||||
if ( ! empty( $_wp_current_template_content ) ) {
|
||||
// Full site editor:
|
||||
$content = $_wp_current_template_content;
|
||||
} elseif ( is_singular() ) {
|
||||
// Post editor:
|
||||
$content = get_post( get_queried_object_id() )->post_content;
|
||||
} else {
|
||||
// Nada:
|
||||
return;
|
||||
}
|
||||
|
||||
$this->data->post_has_blocks = self::wp_has_blocks( $content );
|
||||
$this->data->post_blocks = self::wp_parse_blocks( $content );
|
||||
$this->data->all_dynamic_blocks = self::wp_get_dynamic_block_names();
|
||||
$this->data->total_blocks = 0;
|
||||
$this->data->has_block_context = false;
|
||||
$this->data->has_block_timing = false;
|
||||
|
||||
if ( $this->data->post_has_blocks ) {
|
||||
$this->data->post_blocks = array_values( array_filter( array_map( array( $this, 'process_block' ), $this->data->post_blocks ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $block
|
||||
* @return mixed[]|null
|
||||
*/
|
||||
protected function process_block( array $block ) {
|
||||
$context = array_shift( $this->block_context );
|
||||
$timing = array_shift( $this->block_timing );
|
||||
|
||||
// Remove empty blocks caused by two consecutive line breaks in content
|
||||
if ( ! $block['blockName'] && ! trim( $block['innerHTML'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->data->total_blocks++;
|
||||
|
||||
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
|
||||
$dynamic = false;
|
||||
$callback = null;
|
||||
|
||||
if ( $block_type && $block_type->is_dynamic() ) {
|
||||
$dynamic = true;
|
||||
$callback = QM_Util::populate_callback( array(
|
||||
'function' => $block_type->render_callback,
|
||||
) );
|
||||
}
|
||||
|
||||
$timing = array_shift( $this->block_timing );
|
||||
|
||||
$block['dynamic'] = $dynamic;
|
||||
$block['callback'] = $callback;
|
||||
$block['innerHTML'] = trim( $block['innerHTML'] );
|
||||
$block['size'] = strlen( $block['innerHTML'] );
|
||||
|
||||
if ( $context ) {
|
||||
$block['context'] = $context;
|
||||
$this->data->has_block_context = true;
|
||||
}
|
||||
|
||||
if ( $timing ) {
|
||||
$block['timing'] = $timing->get_time();
|
||||
$this->data->has_block_timing = true;
|
||||
}
|
||||
|
||||
if ( ! empty( $block['innerBlocks'] ) ) {
|
||||
$block['innerBlocks'] = array_values( array_filter( array_map( array( $this, 'process_block' ), $block['innerBlocks'] ) ) );
|
||||
}
|
||||
|
||||
return $block;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected static function wp_block_editor_enabled() {
|
||||
return ( function_exists( 'parse_blocks' ) || function_exists( 'gutenberg_parse_blocks' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @return bool
|
||||
*/
|
||||
protected static function wp_has_blocks( $content ) {
|
||||
if ( function_exists( 'has_blocks' ) ) {
|
||||
return has_blocks( $content );
|
||||
} elseif ( function_exists( 'gutenberg_has_blocks' ) ) {
|
||||
return gutenberg_has_blocks( $content );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @return array<int, mixed>|null
|
||||
*/
|
||||
protected static function wp_parse_blocks( $content ) {
|
||||
if ( function_exists( 'parse_blocks' ) ) {
|
||||
return parse_blocks( $content );
|
||||
} elseif ( function_exists( 'gutenberg_parse_blocks' ) ) {
|
||||
return gutenberg_parse_blocks( $content );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>|null
|
||||
*/
|
||||
protected static function wp_get_dynamic_block_names() {
|
||||
if ( function_exists( 'get_dynamic_block_names' ) ) {
|
||||
return get_dynamic_block_names();
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, QM_Collector> $collectors
|
||||
* @param QueryMonitor $qm
|
||||
* @return array<string, QM_Collector>
|
||||
*/
|
||||
function register_qm_collector_block_editor( array $collectors, QueryMonitor $qm ) {
|
||||
$collectors['block_editor'] = new QM_Collector_Block_Editor();
|
||||
return $collectors;
|
||||
}
|
||||
|
||||
add_filter( 'qm/collectors', 'register_qm_collector_block_editor', 10, 2 );
|
130
wp-content/plugins/query-monitor/collectors/cache.php
Normal file
130
wp-content/plugins/query-monitor/collectors/cache.php
Normal file
@ -0,0 +1,130 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* Object cache collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_Cache>
|
||||
*/
|
||||
class QM_Collector_Cache extends QM_DataCollector {
|
||||
|
||||
public $id = 'cache';
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_Cache();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
global $wp_object_cache;
|
||||
|
||||
$this->data->has_object_cache = (bool) wp_using_ext_object_cache();
|
||||
$this->data->cache_hit_percentage = 0;
|
||||
|
||||
if ( is_object( $wp_object_cache ) ) {
|
||||
$object_vars = get_object_vars( $wp_object_cache );
|
||||
|
||||
if ( array_key_exists( 'cache_hits', $object_vars ) ) {
|
||||
$this->data->stats['cache_hits'] = (int) $object_vars['cache_hits'];
|
||||
}
|
||||
|
||||
if ( array_key_exists( 'cache_misses', $object_vars ) ) {
|
||||
$this->data->stats['cache_misses'] = (int) $object_vars['cache_misses'];
|
||||
}
|
||||
|
||||
$stats = array();
|
||||
|
||||
if ( method_exists( $wp_object_cache, 'getStats' ) ) {
|
||||
$stats = $wp_object_cache->getStats();
|
||||
} elseif ( array_key_exists( 'stats', $object_vars ) && is_array( $object_vars['stats'] ) ) {
|
||||
$stats = $object_vars['stats'];
|
||||
} elseif ( function_exists( 'wp_cache_get_stats' ) ) {
|
||||
$stats = wp_cache_get_stats();
|
||||
}
|
||||
|
||||
if ( ! empty( $stats ) ) {
|
||||
if ( is_array( $stats ) && ! isset( $stats['get_hits'] ) && 1 === count( $stats ) ) {
|
||||
$first_server = reset( $stats );
|
||||
if ( isset( $first_server['get_hits'] ) ) {
|
||||
$stats = $first_server;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $stats as $key => $value ) {
|
||||
if ( ! is_scalar( $value ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( ! is_string( $key ) ) {
|
||||
continue;
|
||||
}
|
||||
$this->data->stats[ $key ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! isset( $this->data->stats['cache_hits'] ) ) {
|
||||
if ( isset( $this->data->stats['get_hits'] ) ) {
|
||||
$this->data->stats['cache_hits'] = (int) $this->data->stats['get_hits'];
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! isset( $this->data->stats['cache_misses'] ) ) {
|
||||
if ( isset( $this->data->stats['get_misses'] ) ) {
|
||||
$this->data->stats['cache_misses'] = (int) $this->data->stats['get_misses'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $this->data->stats['cache_hits'] ) ) {
|
||||
$total = $this->data->stats['cache_hits'];
|
||||
|
||||
if ( ! empty( $this->data->stats['cache_misses'] ) ) {
|
||||
$total += $this->data->stats['cache_misses'];
|
||||
}
|
||||
|
||||
$this->data->cache_hit_percentage = ( 100 / $total ) * $this->data->stats['cache_hits'];
|
||||
}
|
||||
|
||||
$this->data->display_hit_rate_warning = ( 100 === $this->data->cache_hit_percentage );
|
||||
|
||||
if ( function_exists( 'extension_loaded' ) ) {
|
||||
$this->data->object_cache_extensions = array_map( 'extension_loaded', array(
|
||||
'Afterburner' => 'afterburner',
|
||||
'APCu' => 'apcu',
|
||||
'Redis' => 'redis',
|
||||
'Relay' => 'relay',
|
||||
'Memcache' => 'memcache',
|
||||
'Memcached' => 'memcached',
|
||||
) );
|
||||
$this->data->opcode_cache_extensions = array_map( 'extension_loaded', array(
|
||||
'APC' => 'APC',
|
||||
'Zend OPcache' => 'Zend OPcache',
|
||||
) );
|
||||
} else {
|
||||
$this->data->object_cache_extensions = array();
|
||||
$this->data->opcode_cache_extensions = array();
|
||||
}
|
||||
|
||||
$this->data->has_opcode_cache = array_filter( $this->data->opcode_cache_extensions ) ? true : false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, QM_Collector> $collectors
|
||||
* @param QueryMonitor $qm
|
||||
* @return array<string, QM_Collector>
|
||||
*/
|
||||
function register_qm_collector_cache( array $collectors, QueryMonitor $qm ) {
|
||||
$collectors['cache'] = new QM_Collector_Cache();
|
||||
return $collectors;
|
||||
}
|
||||
|
||||
add_filter( 'qm/collectors', 'register_qm_collector_cache', 20, 2 );
|
311
wp-content/plugins/query-monitor/collectors/caps.php
Normal file
311
wp-content/plugins/query-monitor/collectors/caps.php
Normal file
@ -0,0 +1,311 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* User capability check collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_Caps>
|
||||
* @phpstan-type CapCheck array{
|
||||
* args: list<mixed>,
|
||||
* filtered_trace: list<array<string, mixed>>,
|
||||
* component: QM_Component,
|
||||
* result: bool,
|
||||
* }
|
||||
*/
|
||||
class QM_Collector_Caps extends QM_DataCollector {
|
||||
|
||||
public $id = 'caps';
|
||||
|
||||
/**
|
||||
* @var array<int, array<string, mixed>>
|
||||
* @phpstan-var list<CapCheck>
|
||||
*/
|
||||
private $cap_checks = array();
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_Caps();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function set_up() {
|
||||
parent::set_up();
|
||||
|
||||
if ( ! self::enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_filter( 'user_has_cap', array( $this, 'filter_user_has_cap' ), 9999, 3 );
|
||||
add_filter( 'map_meta_cap', array( $this, 'filter_map_meta_cap' ), 9999, 4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function tear_down() {
|
||||
remove_filter( 'user_has_cap', array( $this, 'filter_user_has_cap' ), 9999 );
|
||||
remove_filter( 'map_meta_cap', array( $this, 'filter_map_meta_cap' ), 9999 );
|
||||
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function enabled() {
|
||||
return ( defined( 'QM_ENABLE_CAPS_PANEL' ) && QM_ENABLE_CAPS_PANEL );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_actions() {
|
||||
return array(
|
||||
'wp_roles_init',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_filters() {
|
||||
return array(
|
||||
'map_meta_cap',
|
||||
'role_has_cap',
|
||||
'user_has_cap',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_options() {
|
||||
$blog_prefix = $GLOBALS['wpdb']->get_blog_prefix();
|
||||
|
||||
return array(
|
||||
"{$blog_prefix}user_roles",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_constants() {
|
||||
return array(
|
||||
'ALLOW_UNFILTERED_UPLOADS',
|
||||
'DISALLOW_FILE_EDIT',
|
||||
'DISALLOW_UNFILTERED_HTML',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs user capability checks.
|
||||
*
|
||||
* This does not get called for Super Admins. See filter_map_meta_cap() below.
|
||||
*
|
||||
* @param array<string, bool> $user_caps Concerned user's capabilities.
|
||||
* @param array<int, string> $caps Required primitive capabilities for the requested capability.
|
||||
* @param array<int, mixed> $args {
|
||||
* Arguments that accompany the requested capability check.
|
||||
*
|
||||
* @type string $0 Requested capability.
|
||||
* @type int $1 Concerned user ID.
|
||||
* @type mixed ...$2 Optional second and further parameters.
|
||||
* }
|
||||
* @phpstan-param array{
|
||||
* 0: string,
|
||||
* 1: int,
|
||||
* } $args
|
||||
* @return array<string, bool> Concerned user's capabilities.
|
||||
*/
|
||||
public function filter_user_has_cap( array $user_caps, array $caps, array $args ) {
|
||||
$trace = new QM_Backtrace( array(
|
||||
'ignore_hook' => array(
|
||||
current_filter() => true,
|
||||
),
|
||||
'ignore_func' => array(
|
||||
'current_user_can' => true,
|
||||
'map_meta_cap' => true,
|
||||
'user_can' => true,
|
||||
),
|
||||
'ignore_method' => array(
|
||||
'WP_User' => array(
|
||||
'has_cap' => true,
|
||||
),
|
||||
),
|
||||
) );
|
||||
$result = true;
|
||||
|
||||
foreach ( $caps as $cap ) {
|
||||
if ( empty( $user_caps[ $cap ] ) ) {
|
||||
$result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->cap_checks[] = array(
|
||||
'args' => $args,
|
||||
'filtered_trace' => $trace->get_filtered_trace(),
|
||||
'component' => $trace->get_component(),
|
||||
'result' => $result,
|
||||
);
|
||||
|
||||
return $user_caps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs user capability checks for Super Admins on Multisite.
|
||||
*
|
||||
* This is needed because the `user_has_cap` filter doesn't fire for Super Admins.
|
||||
*
|
||||
* @param array<int, string> $required_caps Required primitive capabilities for the requested capability.
|
||||
* @param string $cap Capability or meta capability being checked.
|
||||
* @param int $user_id Concerned user ID.
|
||||
* @param mixed[] $args {
|
||||
* Arguments that accompany the requested capability check.
|
||||
*
|
||||
* @type mixed ...$0 Optional second and further parameters.
|
||||
* }
|
||||
* @return array<int, string> Required capabilities for the requested action.
|
||||
*/
|
||||
public function filter_map_meta_cap( array $required_caps, $cap, $user_id, array $args ) {
|
||||
if ( ! is_multisite() ) {
|
||||
return $required_caps;
|
||||
}
|
||||
|
||||
if ( ! is_super_admin( $user_id ) ) {
|
||||
return $required_caps;
|
||||
}
|
||||
|
||||
$trace = new QM_Backtrace( array(
|
||||
'ignore_hook' => array(
|
||||
current_filter() => true,
|
||||
),
|
||||
'ignore_func' => array(
|
||||
'current_user_can' => true,
|
||||
'map_meta_cap' => true,
|
||||
'user_can' => true,
|
||||
),
|
||||
'ignore_method' => array(
|
||||
'WP_User' => array(
|
||||
'has_cap' => true,
|
||||
),
|
||||
),
|
||||
) );
|
||||
$result = ( ! in_array( 'do_not_allow', $required_caps, true ) );
|
||||
|
||||
array_unshift( $args, $user_id );
|
||||
array_unshift( $args, $cap );
|
||||
|
||||
$this->cap_checks[] = array(
|
||||
'args' => $args,
|
||||
'filtered_trace' => $trace->get_filtered_trace(),
|
||||
'component' => $trace->get_component(),
|
||||
'result' => $result,
|
||||
);
|
||||
|
||||
return $required_caps;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
if ( empty( $this->cap_checks ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$all_parts = array();
|
||||
$all_users = array();
|
||||
$components = array();
|
||||
$this->data->caps = array();
|
||||
|
||||
$this->cap_checks = array_values( array_filter( $this->cap_checks, array( $this, 'filter_remove_noise' ) ) );
|
||||
|
||||
if ( self::hide_qm() ) {
|
||||
$this->cap_checks = array_values( array_filter( $this->cap_checks, array( $this, 'filter_remove_qm' ) ) );
|
||||
}
|
||||
|
||||
foreach ( $this->cap_checks as $cap ) {
|
||||
$name = $cap['args'][0];
|
||||
|
||||
if ( ! is_string( $name ) ) {
|
||||
$name = '';
|
||||
}
|
||||
|
||||
$component = $cap['component'];
|
||||
$parts = array();
|
||||
$pieces = preg_split( '#[_/-]#', $name );
|
||||
|
||||
if ( is_array( $pieces ) ) {
|
||||
$parts = array_values( array_filter( $pieces ) );
|
||||
}
|
||||
|
||||
$capability = array_shift( $cap['args'] );
|
||||
$user_id = array_shift( $cap['args'] );
|
||||
|
||||
$cap['parts'] = $parts;
|
||||
$cap['name'] = $name;
|
||||
$cap['user'] = $user_id;
|
||||
|
||||
$this->data->caps[] = $cap;
|
||||
|
||||
$all_parts = array_merge( $all_parts, $parts );
|
||||
$all_users[] = (int) $user_id;
|
||||
$components[ $component->name ] = $component->name;
|
||||
}
|
||||
|
||||
$this->data->parts = array_values( array_unique( array_filter( $all_parts ) ) );
|
||||
$this->data->users = array_values( array_unique( array_filter( $all_users ) ) );
|
||||
$this->data->components = $components;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $cap
|
||||
* @phpstan-param CapCheck $cap
|
||||
* @return bool
|
||||
*/
|
||||
public function filter_remove_noise( array $cap ) {
|
||||
$trace = $cap['filtered_trace'];
|
||||
|
||||
$exclude_files = array(
|
||||
ABSPATH . 'wp-admin/menu.php',
|
||||
ABSPATH . 'wp-admin/includes/menu.php',
|
||||
);
|
||||
$exclude_functions = array(
|
||||
'_wp_menu_output',
|
||||
'wp_admin_bar_render',
|
||||
);
|
||||
|
||||
foreach ( $trace as $item ) {
|
||||
if ( isset( $item['file'] ) && in_array( $item['file'], $exclude_files, true ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( isset( $item['function'] ) && in_array( $item['function'], $exclude_functions, true ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, QM_Collector> $collectors
|
||||
* @param QueryMonitor $qm
|
||||
* @return array<string, QM_Collector>
|
||||
*/
|
||||
function register_qm_collector_caps( array $collectors, QueryMonitor $qm ) {
|
||||
$collectors['caps'] = new QM_Collector_Caps();
|
||||
return $collectors;
|
||||
}
|
||||
|
||||
add_filter( 'qm/collectors', 'register_qm_collector_caps', 20, 2 );
|
126
wp-content/plugins/query-monitor/collectors/conditionals.php
Normal file
126
wp-content/plugins/query-monitor/collectors/conditionals.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* Template conditionals collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_Conditionals>
|
||||
*/
|
||||
class QM_Collector_Conditionals extends QM_DataCollector {
|
||||
|
||||
public $id = 'conditionals';
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_Conditionals();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
|
||||
/**
|
||||
* Allows users to filter the names of conditional functions that are exposed by QM.
|
||||
*
|
||||
* @since 2.7.0
|
||||
*
|
||||
* @param array<int, string> $conditionals The list of conditional function names.
|
||||
*/
|
||||
$conds = apply_filters( 'qm/collect/conditionals', array(
|
||||
'is_404',
|
||||
'is_admin',
|
||||
'is_archive',
|
||||
'is_attachment',
|
||||
'is_author',
|
||||
'is_blog_admin',
|
||||
'is_category',
|
||||
'is_comment_feed',
|
||||
'is_customize_preview',
|
||||
'is_date',
|
||||
'is_day',
|
||||
'is_embed',
|
||||
'is_favicon',
|
||||
'is_feed',
|
||||
'is_front_page',
|
||||
'is_home',
|
||||
'is_main_network',
|
||||
'is_main_site',
|
||||
'is_month',
|
||||
'is_network_admin',
|
||||
'is_page',
|
||||
'is_page_template',
|
||||
'is_paged',
|
||||
'is_post_type_archive',
|
||||
'is_preview',
|
||||
'is_privacy_policy',
|
||||
'is_robots',
|
||||
'is_rtl',
|
||||
'is_search',
|
||||
'is_single',
|
||||
'is_singular',
|
||||
'is_ssl',
|
||||
'is_sticky',
|
||||
'is_tag',
|
||||
'is_tax',
|
||||
'is_time',
|
||||
'is_trackback',
|
||||
'is_user_admin',
|
||||
'is_year',
|
||||
) );
|
||||
|
||||
/**
|
||||
* This filter is deprecated. Please use `qm/collect/conditionals` instead.
|
||||
*
|
||||
* @since 2.7.0
|
||||
*
|
||||
* @param array<int, string> $conditionals The list of conditional function names.
|
||||
*/
|
||||
$conds = apply_filters( 'query_monitor_conditionals', $conds );
|
||||
|
||||
$true = array();
|
||||
$false = array();
|
||||
$na = array();
|
||||
|
||||
foreach ( $conds as $cond ) {
|
||||
if ( function_exists( $cond ) ) {
|
||||
$id = null;
|
||||
if ( ( 'is_sticky' === $cond ) && ! get_post( $id ) ) {
|
||||
# Special case for is_sticky to prevent PHP notices
|
||||
$false[] = $cond;
|
||||
} elseif ( ! is_multisite() && in_array( $cond, array( 'is_main_network', 'is_main_site' ), true ) ) {
|
||||
# Special case for multisite conditionals to prevent them from being annoying on single site installations
|
||||
$na[] = $cond;
|
||||
} else {
|
||||
if ( call_user_func( $cond ) ) {
|
||||
$true[] = $cond;
|
||||
} else {
|
||||
$false[] = $cond;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$na[] = $cond;
|
||||
}
|
||||
}
|
||||
$this->data->conds = compact( 'true', 'false', 'na' );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, QM_Collector> $collectors
|
||||
* @param QueryMonitor $qm
|
||||
* @return array<string, QM_Collector>
|
||||
*/
|
||||
function register_qm_collector_conditionals( array $collectors, QueryMonitor $qm ) {
|
||||
$collectors['conditionals'] = new QM_Collector_Conditionals();
|
||||
return $collectors;
|
||||
}
|
||||
|
||||
add_filter( 'qm/collectors', 'register_qm_collector_conditionals', 10, 2 );
|
54
wp-content/plugins/query-monitor/collectors/db_callers.php
Normal file
54
wp-content/plugins/query-monitor/collectors/db_callers.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* Database query calling function collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_DB_Callers>
|
||||
*/
|
||||
class QM_Collector_DB_Callers extends QM_DataCollector {
|
||||
|
||||
public $id = 'db_callers';
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_DB_Callers();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
/** @var QM_Collector_DB_Queries|null $dbq */
|
||||
$dbq = QM_Collectors::get( 'db_queries' );
|
||||
|
||||
if ( $dbq ) {
|
||||
/** @var QM_Data_DB_Queries $dbq_data */
|
||||
$dbq_data = $dbq->get_data();
|
||||
|
||||
$this->data->times = $dbq_data->times;
|
||||
QM_Util::rsort( $this->data->times, 'ltime' );
|
||||
|
||||
$this->data->types = $dbq_data->types;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, QM_Collector> $collectors
|
||||
* @param QueryMonitor $qm
|
||||
* @return array<string, QM_Collector>
|
||||
*/
|
||||
function register_qm_collector_db_callers( array $collectors, QueryMonitor $qm ) {
|
||||
$collectors['db_callers'] = new QM_Collector_DB_Callers();
|
||||
return $collectors;
|
||||
}
|
||||
|
||||
add_filter( 'qm/collectors', 'register_qm_collector_db_callers', 20, 2 );
|
@ -0,0 +1,54 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* Database query calling component collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_DB_Components>
|
||||
*/
|
||||
class QM_Collector_DB_Components extends QM_DataCollector {
|
||||
|
||||
public $id = 'db_components';
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_DB_Components();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
/** @var QM_Collector_DB_Queries|null $dbq */
|
||||
$dbq = QM_Collectors::get( 'db_queries' );
|
||||
|
||||
if ( $dbq ) {
|
||||
/** @var QM_Data_DB_Queries $dbq_data */
|
||||
$dbq_data = $dbq->get_data();
|
||||
|
||||
$this->data->times = $dbq_data->component_times;
|
||||
QM_Util::rsort( $this->data->times, 'ltime' );
|
||||
|
||||
$this->data->types = $dbq_data->types;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, QM_Collector> $collectors
|
||||
* @param QueryMonitor $qm
|
||||
* @return array<string, QM_Collector>
|
||||
*/
|
||||
function register_qm_collector_db_components( array $collectors, QueryMonitor $qm ) {
|
||||
$collectors['db_components'] = new QM_Collector_DB_Components();
|
||||
return $collectors;
|
||||
}
|
||||
|
||||
add_filter( 'qm/collectors', 'register_qm_collector_db_components', 20, 2 );
|
131
wp-content/plugins/query-monitor/collectors/db_dupes.php
Normal file
131
wp-content/plugins/query-monitor/collectors/db_dupes.php
Normal file
@ -0,0 +1,131 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* Duplicate database query collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_DB_Dupes>
|
||||
*/
|
||||
class QM_Collector_DB_Dupes extends QM_DataCollector {
|
||||
|
||||
public $id = 'db_dupes';
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_DB_Dupes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
/** @var QM_Collector_DB_Queries|null $dbq */
|
||||
$dbq = QM_Collectors::get( 'db_queries' );
|
||||
|
||||
if ( ! $dbq ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var QM_Data_DB_Queries $dbq_data */
|
||||
$dbq_data = $dbq->get_data();
|
||||
|
||||
if ( empty( $dbq_data->dupes ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter out SQL queries that do not have dupes
|
||||
$this->data->dupes = array_filter( $dbq_data->dupes, array( $this, 'filter_dupe_items' ) );
|
||||
|
||||
// Ignore dupes from `WP_Query->set_found_posts()`
|
||||
unset( $this->data->dupes['SELECT FOUND_ROWS()'] );
|
||||
|
||||
$stacks = array();
|
||||
$callers = array();
|
||||
$components = array();
|
||||
$times = array();
|
||||
|
||||
// Loop over all SQL queries that have dupes
|
||||
foreach ( $this->data->dupes as $sql => $query_ids ) {
|
||||
|
||||
// Loop over each query
|
||||
foreach ( $query_ids as $query_id ) {
|
||||
|
||||
if ( isset( $dbq_data->wpdb->rows[ $query_id ]['trace'] ) ) {
|
||||
/** @var QM_Backtrace */
|
||||
$trace = $dbq_data->wpdb->rows[ $query_id ]['trace'];
|
||||
$stack = array_column( $trace->get_filtered_trace(), 'id' );
|
||||
$component = $trace->get_component();
|
||||
|
||||
// Populate the component counts for this query
|
||||
if ( isset( $components[ $sql ][ $component->name ] ) ) {
|
||||
$components[ $sql ][ $component->name ]++;
|
||||
} else {
|
||||
$components[ $sql ][ $component->name ] = 1;
|
||||
}
|
||||
} else {
|
||||
/** @var array<int, string> */
|
||||
$stack = $dbq_data->wpdb->rows[ $query_id ]['stack'];
|
||||
}
|
||||
|
||||
// Populate the caller counts for this query
|
||||
if ( isset( $callers[ $sql ][ $stack[0] ] ) ) {
|
||||
$callers[ $sql ][ $stack[0] ]++;
|
||||
} else {
|
||||
$callers[ $sql ][ $stack[0] ] = 1;
|
||||
}
|
||||
|
||||
// Populate the stack for this query
|
||||
$stacks[ $sql ][] = $stack;
|
||||
|
||||
// Populate the time for this query
|
||||
if ( isset( $times[ $sql ] ) ) {
|
||||
$times[ $sql ] += $dbq->data->wpdb->rows[ $query_id ]['ltime'];
|
||||
} else {
|
||||
$times[ $sql ] = $dbq->data->wpdb->rows[ $query_id ]['ltime'];
|
||||
}
|
||||
}
|
||||
|
||||
// Get the callers which are common to all stacks for this query
|
||||
$common = call_user_func_array( 'array_intersect', $stacks[ $sql ] );
|
||||
|
||||
// Remove callers which are common to all stacks for this query
|
||||
foreach ( $stacks[ $sql ] as $i => $stack ) {
|
||||
$stacks[ $sql ][ $i ] = array_values( array_diff( $stack, $common ) );
|
||||
|
||||
// No uncommon callers within the stack? Just use the topmost caller.
|
||||
if ( empty( $stacks[ $sql ][ $i ] ) ) {
|
||||
$stacks[ $sql ][ $i ] = array_keys( $callers[ $sql ] );
|
||||
}
|
||||
}
|
||||
|
||||
// Wave a magic wand
|
||||
$sources[ $sql ] = array_count_values( array_column( $stacks[ $sql ], 0 ) );
|
||||
|
||||
}
|
||||
|
||||
if ( ! empty( $sources ) ) {
|
||||
$this->data->dupe_sources = $sources;
|
||||
$this->data->dupe_callers = $callers;
|
||||
$this->data->dupe_components = $components;
|
||||
$this->data->dupe_times = $times;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, QM_Collector> $collectors
|
||||
* @param QueryMonitor $qm
|
||||
* @return array<string, QM_Collector>
|
||||
*/
|
||||
function register_qm_collector_db_dupes( array $collectors, QueryMonitor $qm ) {
|
||||
$collectors['db_dupes'] = new QM_Collector_DB_Dupes();
|
||||
return $collectors;
|
||||
}
|
||||
|
||||
add_filter( 'qm/collectors', 'register_qm_collector_db_dupes', 25, 2 );
|
286
wp-content/plugins/query-monitor/collectors/db_queries.php
Normal file
286
wp-content/plugins/query-monitor/collectors/db_queries.php
Normal file
@ -0,0 +1,286 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* Database query collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! defined( 'SAVEQUERIES' ) ) {
|
||||
define( 'SAVEQUERIES', true );
|
||||
}
|
||||
if ( ! defined( 'QM_DB_EXPENSIVE' ) ) {
|
||||
define( 'QM_DB_EXPENSIVE', 0.05 );
|
||||
}
|
||||
|
||||
if ( SAVEQUERIES && property_exists( $GLOBALS['wpdb'], 'save_queries' ) ) {
|
||||
$GLOBALS['wpdb']->save_queries = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_DB_Queries>
|
||||
*/
|
||||
class QM_Collector_DB_Queries extends QM_DataCollector {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $id = 'db_queries';
|
||||
|
||||
/**
|
||||
* @var wpdb
|
||||
*/
|
||||
public $wpdb;
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_DB_Queries();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]|false
|
||||
*/
|
||||
public function get_errors() {
|
||||
if ( ! empty( $this->data->errors ) ) {
|
||||
return $this->data->errors;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]|false
|
||||
*/
|
||||
public function get_expensive() {
|
||||
if ( ! empty( $this->data->expensive ) ) {
|
||||
return $this->data->expensive;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $row
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_expensive( array $row ) {
|
||||
return $row['ltime'] > QM_DB_EXPENSIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
$this->data->total_qs = 0;
|
||||
$this->data->total_time = 0;
|
||||
$this->data->errors = array();
|
||||
$this->process_db_object();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $caller
|
||||
* @param float $ltime
|
||||
* @param string $type
|
||||
* @return void
|
||||
*/
|
||||
protected function log_caller( $caller, $ltime, $type ) {
|
||||
|
||||
if ( ! isset( $this->data->times[ $caller ] ) ) {
|
||||
$this->data->times[ $caller ] = array(
|
||||
'caller' => $caller,
|
||||
'ltime' => 0,
|
||||
'types' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
$this->data->times[ $caller ]['ltime'] += $ltime;
|
||||
|
||||
if ( isset( $this->data->times[ $caller ]['types'][ $type ] ) ) {
|
||||
$this->data->times[ $caller ]['types'][ $type ]++;
|
||||
} else {
|
||||
$this->data->times[ $caller ]['types'][ $type ] = 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function process_db_object() {
|
||||
global $wp_the_query, $wpdb;
|
||||
|
||||
$this->wpdb = $wpdb;
|
||||
|
||||
// With SAVEQUERIES defined as false, `wpdb::queries` is empty but `wpdb::num_queries` is not.
|
||||
if ( empty( $wpdb->queries ) ) {
|
||||
$this->data->total_qs += $wpdb->num_queries;
|
||||
return;
|
||||
}
|
||||
|
||||
$rows = array();
|
||||
$types = array();
|
||||
$total_time = 0;
|
||||
$has_result = false;
|
||||
$has_trace = false;
|
||||
$i = 0;
|
||||
$request = trim( $wp_the_query->request ?: '' );
|
||||
|
||||
if ( method_exists( $wpdb, 'remove_placeholder_escape' ) ) {
|
||||
$request = $wpdb->remove_placeholder_escape( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-var array{
|
||||
* 0: string,
|
||||
* 1: float,
|
||||
* 2: string,
|
||||
* trace?: QM_Backtrace,
|
||||
* result?: int|bool|WP_Error,
|
||||
* }|array{
|
||||
* query: string,
|
||||
* elapsed: float,
|
||||
* debug: string,
|
||||
* } $query
|
||||
*/
|
||||
foreach ( $wpdb->queries as $query ) {
|
||||
$has_trace = false;
|
||||
$has_result = false;
|
||||
$callers = array();
|
||||
|
||||
if ( isset( $query['query'], $query['elapsed'], $query['debug'] ) ) {
|
||||
// WordPress.com VIP.
|
||||
$sql = $query['query'];
|
||||
$ltime = $query['elapsed'];
|
||||
$stack = $query['debug'];
|
||||
} elseif ( isset( $query[0], $query[1], $query[2] ) ) {
|
||||
// Standard WP.
|
||||
$sql = $query[0];
|
||||
$ltime = $query[1];
|
||||
$stack = $query[2];
|
||||
|
||||
// Query Monitor db.php drop-in.
|
||||
$has_trace = isset( $query['trace'] );
|
||||
$has_result = isset( $query['result'] );
|
||||
} else {
|
||||
// ¯\_(ツ)_/¯
|
||||
continue;
|
||||
}
|
||||
|
||||
// @TODO: decide what I want to do with this:
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( false !== strpos( $stack, 'wp_admin_bar' ) && ! isset( $_REQUEST['qm_display_admin_bar'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result = $query['result'] ?? null;
|
||||
$total_time += $ltime;
|
||||
|
||||
if ( isset( $query['trace'] ) ) {
|
||||
|
||||
$trace = $query['trace'];
|
||||
$component = $query['trace']->get_component();
|
||||
$caller = $query['trace']->get_caller();
|
||||
$caller_name = $caller['display'] ?? 'Unknown';
|
||||
$caller = $caller['display'] ?? 'Unknown';
|
||||
|
||||
} else {
|
||||
|
||||
$trace = null;
|
||||
$component = null;
|
||||
$callers = array_reverse( explode( ',', $stack ) );
|
||||
$callers = array_map( 'trim', $callers );
|
||||
$callers = QM_Backtrace::get_filtered_stack( $callers );
|
||||
$caller = reset( $callers );
|
||||
$caller_name = $caller;
|
||||
|
||||
}
|
||||
|
||||
$sql = trim( $sql );
|
||||
$type = QM_Util::get_query_type( $sql );
|
||||
|
||||
$this->log_type( $type );
|
||||
$this->log_caller( $caller_name, $ltime, $type );
|
||||
|
||||
$this->maybe_log_dupe( $sql, $i );
|
||||
|
||||
if ( $component ) {
|
||||
$this->log_component( $component, $ltime, $type );
|
||||
}
|
||||
|
||||
if ( ! isset( $types[ $type ]['total'] ) ) {
|
||||
$types[ $type ]['total'] = 1;
|
||||
} else {
|
||||
$types[ $type ]['total']++;
|
||||
}
|
||||
|
||||
if ( ! isset( $types[ $type ]['callers'][ $caller ] ) ) {
|
||||
$types[ $type ]['callers'][ $caller ] = 1;
|
||||
} else {
|
||||
$types[ $type ]['callers'][ $caller ]++;
|
||||
}
|
||||
|
||||
$is_main_query = ( $request === $sql && ( false !== strpos( $stack, ' WP->main,' ) ) );
|
||||
|
||||
$row = compact( 'caller', 'caller_name', 'sql', 'ltime', 'result', 'type', 'component', 'trace', 'is_main_query' );
|
||||
|
||||
if ( ! isset( $trace ) ) {
|
||||
$row['stack'] = $callers;
|
||||
}
|
||||
|
||||
// @TODO these should store a reference ($i) instead of the whole row
|
||||
if ( $result instanceof WP_Error ) {
|
||||
$this->data->errors[] = $row;
|
||||
}
|
||||
|
||||
// @TODO these should store a reference ($i) instead of the whole row
|
||||
if ( self::is_expensive( $row ) ) {
|
||||
$this->data->expensive[] = $row;
|
||||
}
|
||||
|
||||
$rows[ $i ] = $row;
|
||||
$i++;
|
||||
|
||||
}
|
||||
|
||||
$total_qs = count( $rows );
|
||||
|
||||
$this->data->total_qs += $total_qs;
|
||||
$this->data->total_time += $total_time;
|
||||
|
||||
$has_main_query = wp_list_filter( $rows, array(
|
||||
'is_main_query' => true,
|
||||
) );
|
||||
|
||||
# @TODO put errors in here too:
|
||||
# @TODO proper class instead of (object)
|
||||
$this->data->wpdb = (object) compact( 'rows', 'types', 'has_result', 'has_trace', 'total_time', 'total_qs', 'has_main_query' );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sql
|
||||
* @param int $i
|
||||
* @return void
|
||||
*/
|
||||
protected function maybe_log_dupe( $sql, $i ) {
|
||||
$sql = str_replace( array( "\r\n", "\r", "\n" ), ' ', $sql );
|
||||
$sql = str_replace( array( "\t", '`' ), '', $sql );
|
||||
$sql = preg_replace( '/ +/', ' ', $sql );
|
||||
$sql = trim( $sql );
|
||||
$sql = rtrim( $sql, ';' );
|
||||
|
||||
$this->data->dupes[ $sql ][] = $i;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, QM_Collector> $collectors
|
||||
* @param QueryMonitor $qm
|
||||
* @return array<string, QM_Collector>
|
||||
*/
|
||||
function register_qm_collector_db_queries( array $collectors, QueryMonitor $qm ) {
|
||||
$collectors['db_queries'] = new QM_Collector_DB_Queries();
|
||||
return $collectors;
|
||||
}
|
||||
|
||||
add_filter( 'qm/collectors', 'register_qm_collector_db_queries', 10, 2 );
|
139
wp-content/plugins/query-monitor/collectors/debug_bar.php
Normal file
139
wp-content/plugins/query-monitor/collectors/debug_bar.php
Normal file
@ -0,0 +1,139 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* Mock 'Debug Bar' data collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
final class QM_Collector_Debug_Bar extends QM_Collector {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $id = 'debug_bar';
|
||||
|
||||
/**
|
||||
* @var Debug_Bar_Panel|null
|
||||
*/
|
||||
private $panel = null;
|
||||
|
||||
/**
|
||||
* @param Debug_Bar_Panel $panel
|
||||
* @return void
|
||||
*/
|
||||
public function set_panel( Debug_Bar_Panel $panel ) {
|
||||
$this->panel = $panel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Debug_Bar_Panel|null
|
||||
*/
|
||||
public function get_panel() {
|
||||
return $this->panel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
$this->get_panel()->prerender();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function is_visible() {
|
||||
return $this->get_panel()->is_visible();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function render() {
|
||||
$this->get_panel()->render();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
function register_qm_collectors_debug_bar() {
|
||||
|
||||
global $debug_bar;
|
||||
|
||||
if ( class_exists( 'Debug_Bar', false ) || qm_debug_bar_being_activated() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$collectors = QM_Collectors::init();
|
||||
|
||||
$debug_bar = new Debug_Bar();
|
||||
$redundant = array(
|
||||
'debug_bar_actions_addon_panel', // Debug Bar Actions and Filters Addon
|
||||
'debug_bar_remote_requests_panel', // Debug Bar Remote Requests
|
||||
'debug_bar_screen_info_panel', // Debug Bar Screen Info
|
||||
'ps_listdeps_debug_bar_panel', // Debug Bar List Script & Style Dependencies
|
||||
);
|
||||
|
||||
foreach ( $debug_bar->panels as $panel ) {
|
||||
$panel_id = strtolower( sanitize_html_class( get_class( $panel ) ) );
|
||||
|
||||
if ( in_array( $panel_id, $redundant, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$collector = new QM_Collector_Debug_Bar();
|
||||
$collector->set_id( "debug_bar_{$panel_id}" );
|
||||
$collector->set_panel( $panel );
|
||||
|
||||
$collectors->add( $collector );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
function qm_debug_bar_being_activated() {
|
||||
// phpcs:disable
|
||||
|
||||
if ( ! is_admin() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! isset( $_REQUEST['action'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( isset( $_GET['action'] ) ) {
|
||||
|
||||
if ( ! isset( $_GET['plugin'] ) || ! isset( $_GET['_wpnonce'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( 'activate' === $_GET['action'] && false !== strpos( wp_unslash( $_GET['plugin'] ), 'debug-bar.php' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} elseif ( isset( $_POST['action'] ) ) {
|
||||
|
||||
if ( ! isset( $_POST['checked'] ) || ! is_array( $_POST['checked'] ) || ! isset( $_POST['_wpnonce'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( 'activate-selected' === wp_unslash( $_POST['action'] ) && in_array( 'debug-bar/debug-bar.php', wp_unslash( $_POST['checked'] ), true ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
// phpcs:enable
|
||||
}
|
||||
|
||||
add_action( 'init', 'register_qm_collectors_debug_bar' );
|
341
wp-content/plugins/query-monitor/collectors/doing_it_wrong.php
Normal file
341
wp-content/plugins/query-monitor/collectors/doing_it_wrong.php
Normal file
@ -0,0 +1,341 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* Doing it Wrong collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_Doing_It_Wrong>
|
||||
*/
|
||||
class QM_Collector_Doing_It_Wrong extends QM_DataCollector {
|
||||
|
||||
public $id = 'doing_it_wrong';
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_Doing_It_Wrong();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function set_up() {
|
||||
parent::set_up();
|
||||
|
||||
add_action( 'doing_it_wrong_run', array( $this, 'action_doing_it_wrong_run' ), 10, 3 );
|
||||
add_action( 'deprecated_function_run', array( $this, 'action_deprecated_function_run' ), 10, 3 );
|
||||
add_action( 'deprecated_constructor_run', array( $this, 'action_deprecated_constructor_run' ), 10, 3 );
|
||||
add_action( 'deprecated_file_included', array( $this, 'action_deprecated_file_included' ), 10, 4 );
|
||||
add_action( 'deprecated_argument_run', array( $this, 'action_deprecated_argument_run' ), 10, 3 );
|
||||
add_action( 'deprecated_hook_run', array( $this, 'action_deprecated_hook_run' ), 10, 4 );
|
||||
|
||||
add_filter( 'deprecated_function_trigger_error', array( $this, 'maybe_prevent_error' ), 999 );
|
||||
add_filter( 'deprecated_constructor_trigger_error', array( $this, 'maybe_prevent_error' ), 999 );
|
||||
add_filter( 'deprecated_file_trigger_error', array( $this, 'maybe_prevent_error' ), 999 );
|
||||
add_filter( 'deprecated_argument_trigger_error', array( $this, 'maybe_prevent_error' ), 999 );
|
||||
add_filter( 'deprecated_hook_trigger_error', array( $this, 'maybe_prevent_error' ), 999 );
|
||||
add_filter( 'doing_it_wrong_trigger_error', array( $this, 'maybe_prevent_error' ), 999 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function tear_down() {
|
||||
parent::tear_down();
|
||||
|
||||
remove_action( 'doing_it_wrong_run', array( $this, 'action_doing_it_wrong_run' ) );
|
||||
remove_action( 'deprecated_function_run', array( $this, 'action_deprecated_function_run' ) );
|
||||
remove_action( 'deprecated_constructor_run', array( $this, 'action_deprecated_constructor_run' ) );
|
||||
remove_action( 'deprecated_file_included', array( $this, 'action_deprecated_file_included' ) );
|
||||
remove_action( 'deprecated_argument_run', array( $this, 'action_deprecated_argument_run' ) );
|
||||
remove_action( 'deprecated_hook_run', array( $this, 'action_deprecated_hook_run' ) );
|
||||
|
||||
remove_filter( 'deprecated_function_trigger_error', array( $this, 'maybe_prevent_error' ) );
|
||||
remove_filter( 'deprecated_constructor_trigger_error', array( $this, 'maybe_prevent_error' ) );
|
||||
remove_filter( 'deprecated_file_trigger_error', array( $this, 'maybe_prevent_error' ) );
|
||||
remove_filter( 'deprecated_argument_trigger_error', array( $this, 'maybe_prevent_error' ) );
|
||||
remove_filter( 'deprecated_hook_trigger_error', array( $this, 'maybe_prevent_error' ) );
|
||||
remove_filter( 'doing_it_wrong_trigger_error', array( $this, 'maybe_prevent_error' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents the PHP error (notice or deprecated) from being triggered for doing it wrong calls when the
|
||||
* current user can view Query Monitor output.
|
||||
*
|
||||
* @param bool $trigger
|
||||
* @return bool
|
||||
*/
|
||||
public function maybe_prevent_error( $trigger ) {
|
||||
if ( function_exists( 'wp_get_current_user' ) && current_user_can( 'view_query_monitor' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $trigger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_actions() {
|
||||
return array(
|
||||
'doing_it_wrong_run',
|
||||
'deprecated_function_run',
|
||||
'deprecated_constructor_run',
|
||||
'deprecated_file_included',
|
||||
'deprecated_argument_run',
|
||||
'deprecated_hook_run',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_filters() {
|
||||
return array(
|
||||
'deprecated_function_trigger_error',
|
||||
'deprecated_constructor_trigger_error',
|
||||
'deprecated_file_trigger_error',
|
||||
'deprecated_argument_trigger_error',
|
||||
'deprecated_hook_trigger_error',
|
||||
'doing_it_wrong_trigger_error',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $function_name
|
||||
* @param string $message
|
||||
* @param string $version
|
||||
* @return void
|
||||
*/
|
||||
public function action_doing_it_wrong_run( $function_name, $message, $version ) {
|
||||
$trace = new QM_Backtrace( array(
|
||||
'ignore_hook' => array(
|
||||
current_action() => true,
|
||||
),
|
||||
) );
|
||||
|
||||
if ( $version ) {
|
||||
/* translators: %s: Version number. */
|
||||
$version = sprintf( __( '(This message was added in version %s.)', 'query-monitor' ), $version );
|
||||
}
|
||||
|
||||
$this->data->actions[] = array(
|
||||
'hook' => 'doing_it_wrong_run',
|
||||
'filtered_trace' => $trace->get_filtered_trace(),
|
||||
'component' => $trace->get_component(),
|
||||
'message' => sprintf(
|
||||
/* translators: Developer debugging message. 1: PHP function name, 2: Explanatory message, 3: WordPress version number. */
|
||||
__( 'Function %1$s was called incorrectly. %2$s %3$s', 'query-monitor' ),
|
||||
$function_name,
|
||||
$message,
|
||||
$version
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $function_name
|
||||
* @param string $replacement
|
||||
* @param string $version
|
||||
* @return void
|
||||
*/
|
||||
public function action_deprecated_function_run( $function_name, $replacement, $version ) {
|
||||
$trace = new QM_Backtrace( array(
|
||||
'ignore_hook' => array(
|
||||
current_action() => true,
|
||||
),
|
||||
) );
|
||||
|
||||
$message = sprintf(
|
||||
/* translators: 1: PHP function name, 2: Version number. */
|
||||
__( 'Function %1$s is deprecated since version %2$s with no alternative available.', 'query-monitor' ),
|
||||
$function_name,
|
||||
$version
|
||||
);
|
||||
|
||||
if ( $replacement ) {
|
||||
$message = sprintf(
|
||||
/* translators: 1: PHP function name, 2: Version number, 3: Alternative function name. */
|
||||
__( 'Function %1$s is deprecated since version %2$s! Use %3$s instead.', 'query-monitor' ),
|
||||
$function_name,
|
||||
$version,
|
||||
$replacement
|
||||
);
|
||||
}
|
||||
|
||||
$this->data->actions[] = array(
|
||||
'hook' => 'deprecated_function_run',
|
||||
'filtered_trace' => $trace->get_filtered_trace(),
|
||||
'component' => $trace->get_component(),
|
||||
'message' => $message,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class_name
|
||||
* @param string $version
|
||||
* @param string $parent_class
|
||||
* @return void
|
||||
*/
|
||||
public function action_deprecated_constructor_run( $class_name, $version, $parent_class ) {
|
||||
$trace = new QM_Backtrace( array(
|
||||
'ignore_hook' => array(
|
||||
current_action() => true,
|
||||
),
|
||||
) );
|
||||
|
||||
$message = sprintf(
|
||||
/* translators: 1: PHP class name, 2: Version number, 3: __construct() method. */
|
||||
__( 'The called constructor method for %1$s class is deprecated since version %2$s! Use %3$s instead.', 'query-monitor' ),
|
||||
$class_name,
|
||||
$version,
|
||||
'<code>__construct()</code>'
|
||||
);
|
||||
|
||||
if ( $parent_class ) {
|
||||
$message = sprintf(
|
||||
/* translators: 1: PHP class name, 2: PHP parent class name, 3: Version number, 4: __construct() method. */
|
||||
__( 'The called constructor method for %1$s class in %2$s is deprecated since version %3$s! Use %4$s instead.', 'query-monitor' ),
|
||||
$class_name,
|
||||
$parent_class,
|
||||
$version,
|
||||
'<code>__construct()</code>'
|
||||
);
|
||||
}
|
||||
|
||||
$this->data->actions[] = array(
|
||||
'hook' => 'deprecated_constructor_run',
|
||||
'filtered_trace' => $trace->get_filtered_trace(),
|
||||
'component' => $trace->get_component(),
|
||||
'message' => $message,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @param string $replacement
|
||||
* @param string $version
|
||||
* @param string $message
|
||||
* @return void
|
||||
*/
|
||||
public function action_deprecated_file_included( $file, $replacement, $version, $message ) {
|
||||
$trace = new QM_Backtrace( array(
|
||||
'ignore_hook' => array(
|
||||
current_action() => true,
|
||||
),
|
||||
) );
|
||||
|
||||
if ( $replacement ) {
|
||||
$message = sprintf(
|
||||
/* translators: 1: PHP file name, 2: Version number, 3: Alternative file name, 4: Optional message regarding the change. */
|
||||
__( 'File %1$s is deprecated since version %2$s! Use %3$s instead. %4$s', 'query-monitor' ),
|
||||
$file,
|
||||
$version,
|
||||
$replacement,
|
||||
$message
|
||||
);
|
||||
} else {
|
||||
$message = sprintf(
|
||||
/* translators: 1: PHP file name, 2: Version number, 3: Optional message regarding the change. */
|
||||
__( 'File %1$s is deprecated since version %2$s with no alternative available. %3$s', 'query-monitor' ),
|
||||
$file,
|
||||
$version,
|
||||
$message
|
||||
);
|
||||
}
|
||||
|
||||
$this->data->actions[] = array(
|
||||
'hook' => 'deprecated_file_included',
|
||||
'filtered_trace' => $trace->get_filtered_trace(),
|
||||
'component' => $trace->get_component(),
|
||||
'message' => $message,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $function_name
|
||||
* @param string $message
|
||||
* @param string $version
|
||||
* @return void
|
||||
*/
|
||||
public function action_deprecated_argument_run( $function_name, $message, $version ) {
|
||||
$trace = new QM_Backtrace( array(
|
||||
'ignore_hook' => array(
|
||||
current_action() => true,
|
||||
),
|
||||
) );
|
||||
|
||||
if ( $message ) {
|
||||
$message = sprintf(
|
||||
/* translators: 1: PHP function name, 2: Version number, 3: Optional message regarding the change. */
|
||||
__( 'Function %1$s was called with an argument that is deprecated since version %2$s! %3$s', 'query-monitor' ),
|
||||
$function_name,
|
||||
$version,
|
||||
$message
|
||||
);
|
||||
} else {
|
||||
$message = sprintf(
|
||||
/* translators: 1: PHP function name, 2: Version number. */
|
||||
__( 'Function %1$s was called with an argument that is deprecated since version %2$s with no alternative available.', 'query-monitor' ),
|
||||
$function_name,
|
||||
$version
|
||||
);
|
||||
}
|
||||
|
||||
$this->data->actions[] = array(
|
||||
'hook' => 'deprecated_argument_run',
|
||||
'filtered_trace' => $trace->get_filtered_trace(),
|
||||
'component' => $trace->get_component(),
|
||||
'message' => $message,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $hook
|
||||
* @param string $replacement
|
||||
* @param string $version
|
||||
* @param string $message
|
||||
* @return void
|
||||
*/
|
||||
public function action_deprecated_hook_run( $hook, $replacement, $version, $message ) {
|
||||
$trace = new QM_Backtrace( array(
|
||||
'ignore_hook' => array(
|
||||
current_action() => true,
|
||||
),
|
||||
) );
|
||||
|
||||
if ( $replacement ) {
|
||||
$message = sprintf(
|
||||
/* translators: 1: WordPress hook name, 2: Version number, 3: Alternative hook name, 4: Optional message regarding the change. */
|
||||
__( 'Hook %1$s is deprecated since version %2$s! Use %3$s instead. %4$s', 'query-monitor' ),
|
||||
$hook,
|
||||
$version,
|
||||
$replacement,
|
||||
$message
|
||||
);
|
||||
} else {
|
||||
$message = sprintf(
|
||||
/* translators: 1: WordPress hook name, 2: Version number, 3: Optional message regarding the change. */
|
||||
__( 'Hook %1$s is deprecated since version %2$s with no alternative available. %3$s', 'query-monitor' ),
|
||||
$hook,
|
||||
$version,
|
||||
$message
|
||||
);
|
||||
}
|
||||
|
||||
$this->data->actions[] = array(
|
||||
'hook' => 'deprecated_hook_run',
|
||||
'filtered_trace' => $trace->get_filtered_trace(),
|
||||
'component' => $trace->get_component(),
|
||||
'message' => $message,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Load early to catch early actions
|
||||
QM_Collectors::add( new QM_Collector_Doing_It_Wrong() );
|
338
wp-content/plugins/query-monitor/collectors/environment.php
Normal file
338
wp-content/plugins/query-monitor/collectors/environment.php
Normal file
@ -0,0 +1,338 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* Environment data collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_Environment>
|
||||
*/
|
||||
class QM_Collector_Environment extends QM_DataCollector {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $id = 'environment';
|
||||
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $php_vars = array(
|
||||
'max_execution_time',
|
||||
'memory_limit',
|
||||
'upload_max_filesize',
|
||||
'post_max_size',
|
||||
'display_errors',
|
||||
'log_errors',
|
||||
);
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_Environment();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $error_reporting
|
||||
* @return array<string, bool>
|
||||
*/
|
||||
protected static function get_error_levels( $error_reporting ) {
|
||||
$levels = array(
|
||||
'E_ERROR' => false,
|
||||
'E_WARNING' => false,
|
||||
'E_PARSE' => false,
|
||||
'E_NOTICE' => false,
|
||||
'E_CORE_ERROR' => false,
|
||||
'E_CORE_WARNING' => false,
|
||||
'E_COMPILE_ERROR' => false,
|
||||
'E_COMPILE_WARNING' => false,
|
||||
'E_USER_ERROR' => false,
|
||||
'E_USER_WARNING' => false,
|
||||
'E_USER_NOTICE' => false,
|
||||
'E_STRICT' => false,
|
||||
'E_RECOVERABLE_ERROR' => false,
|
||||
'E_DEPRECATED' => false,
|
||||
'E_USER_DEPRECATED' => false,
|
||||
'E_ALL' => false,
|
||||
);
|
||||
|
||||
foreach ( $levels as $level => $reported ) {
|
||||
if ( defined( $level ) ) {
|
||||
$c = constant( $level );
|
||||
if ( $error_reporting & $c ) {
|
||||
$levels[ $level ] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $levels;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
|
||||
global $wp_version;
|
||||
|
||||
$mysql_vars = array(
|
||||
'key_buffer_size' => true, # Key cache size limit
|
||||
'max_allowed_packet' => false, # Individual query size limit
|
||||
'max_connections' => false, # Max number of client connections
|
||||
'query_cache_limit' => true, # Individual query cache size limit
|
||||
'query_cache_size' => true, # Total cache size limit
|
||||
'query_cache_type' => 'ON', # Query cache on or off
|
||||
'innodb_buffer_pool_size' => false, # The amount of memory allocated to the InnoDB buffer pool
|
||||
);
|
||||
|
||||
/** @var QM_Collector_DB_Queries|null */
|
||||
$dbq = QM_Collectors::get( 'db_queries' );
|
||||
|
||||
if ( $dbq ) {
|
||||
if ( method_exists( $dbq->wpdb, 'db_version' ) ) {
|
||||
$server = $dbq->wpdb->db_version();
|
||||
// query_cache_* deprecated since MySQL 5.7.20
|
||||
if ( version_compare( $server, '5.7.20', '>=' ) ) {
|
||||
unset( $mysql_vars['query_cache_limit'], $mysql_vars['query_cache_size'], $mysql_vars['query_cache_type'] );
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:disable
|
||||
/** @var array<int, stdClass>|null */
|
||||
$variables = $dbq->wpdb->get_results( "
|
||||
SHOW VARIABLES
|
||||
WHERE Variable_name IN ( '" . implode( "', '", array_keys( $mysql_vars ) ) . "' )
|
||||
" );
|
||||
// phpcs:enable
|
||||
|
||||
/** @var mysqli|false|null $dbh */
|
||||
$dbh = $dbq->wpdb->dbh;
|
||||
|
||||
if ( is_object( $dbh ) ) {
|
||||
# mysqli or PDO
|
||||
$extension = get_class( $dbh );
|
||||
} else {
|
||||
# Who knows?
|
||||
$extension = null;
|
||||
}
|
||||
|
||||
$client = mysqli_get_client_version();
|
||||
|
||||
if ( $client ) {
|
||||
$client_version = implode( '.', QM_Util::get_client_version( $client ) );
|
||||
$client_version = sprintf( '%s (%s)', $client, $client_version );
|
||||
} else {
|
||||
$client_version = null;
|
||||
}
|
||||
|
||||
$server_version = self::get_server_version( $dbq->wpdb );
|
||||
|
||||
$info = array(
|
||||
'server-version' => $server_version,
|
||||
'extension' => $extension,
|
||||
'client-version' => $client_version,
|
||||
'user' => $dbq->wpdb->dbuser,
|
||||
'host' => $dbq->wpdb->dbhost,
|
||||
'database' => $dbq->wpdb->dbname,
|
||||
);
|
||||
|
||||
$this->data->db = array(
|
||||
'info' => $info,
|
||||
'vars' => $mysql_vars,
|
||||
'variables' => $variables ?: array(),
|
||||
);
|
||||
}
|
||||
|
||||
$php_data = array(
|
||||
'variables' => array(),
|
||||
);
|
||||
|
||||
$php_data['version'] = phpversion();
|
||||
$php_data['sapi'] = php_sapi_name();
|
||||
$php_data['user'] = self::get_current_user();
|
||||
|
||||
// https://www.php.net/supported-versions.php
|
||||
$php_data['old'] = version_compare( $php_data['version'], '7.4', '<' );
|
||||
|
||||
foreach ( $this->php_vars as $setting ) {
|
||||
$php_data['variables'][ $setting ] = ini_get( $setting ) ?: null;
|
||||
}
|
||||
|
||||
if ( function_exists( 'get_loaded_extensions' ) ) {
|
||||
$extensions = get_loaded_extensions();
|
||||
sort( $extensions, SORT_STRING | SORT_FLAG_CASE );
|
||||
$php_data['extensions'] = array_combine( $extensions, array_map( array( $this, 'get_extension_version' ), $extensions ) ) ?: array();
|
||||
} else {
|
||||
$php_data['extensions'] = array();
|
||||
}
|
||||
|
||||
$php_data['error_reporting'] = error_reporting();
|
||||
$php_data['error_levels'] = self::get_error_levels( $php_data['error_reporting'] );
|
||||
|
||||
$this->data->wp['version'] = $wp_version;
|
||||
$constants = array(
|
||||
'WP_DEBUG' => self::format_bool_constant( 'WP_DEBUG' ),
|
||||
'WP_DEBUG_DISPLAY' => self::format_bool_constant( 'WP_DEBUG_DISPLAY' ),
|
||||
'WP_DEBUG_LOG' => self::format_bool_constant( 'WP_DEBUG_LOG' ),
|
||||
'SCRIPT_DEBUG' => self::format_bool_constant( 'SCRIPT_DEBUG' ),
|
||||
'WP_CACHE' => self::format_bool_constant( 'WP_CACHE' ),
|
||||
'CONCATENATE_SCRIPTS' => self::format_bool_constant( 'CONCATENATE_SCRIPTS' ),
|
||||
'COMPRESS_SCRIPTS' => self::format_bool_constant( 'COMPRESS_SCRIPTS' ),
|
||||
'COMPRESS_CSS' => self::format_bool_constant( 'COMPRESS_CSS' ),
|
||||
'WP_ENVIRONMENT_TYPE' => self::format_bool_constant( 'WP_ENVIRONMENT_TYPE' ),
|
||||
'WP_DEVELOPMENT_MODE' => self::format_bool_constant( 'WP_DEVELOPMENT_MODE' ),
|
||||
);
|
||||
|
||||
if ( function_exists( 'wp_get_environment_type' ) ) {
|
||||
$this->data->wp['environment_type'] = wp_get_environment_type();
|
||||
}
|
||||
|
||||
if ( function_exists( 'wp_get_development_mode' ) ) {
|
||||
$this->data->wp['development_mode'] = wp_get_development_mode();
|
||||
}
|
||||
|
||||
$this->data->wp['constants'] = apply_filters( 'qm/environment-constants', $constants );
|
||||
|
||||
if ( is_multisite() ) {
|
||||
$this->data->wp['constants']['SUNRISE'] = self::format_bool_constant( 'SUNRISE' );
|
||||
}
|
||||
|
||||
if ( isset( $_SERVER['SERVER_SOFTWARE'] ) ) {
|
||||
$server = explode( ' ', wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) );
|
||||
$server = explode( '/', reset( $server ) );
|
||||
} else {
|
||||
$server = array( '' );
|
||||
}
|
||||
|
||||
$server_version = $server[1] ?? null;
|
||||
|
||||
if ( isset( $_SERVER['SERVER_ADDR'] ) ) {
|
||||
$address = wp_unslash( $_SERVER['SERVER_ADDR'] );
|
||||
} else {
|
||||
$address = null;
|
||||
}
|
||||
|
||||
$this->data->php = $php_data;
|
||||
|
||||
$this->data->server = array(
|
||||
'name' => $server[0],
|
||||
'version' => $server_version,
|
||||
'address' => $address,
|
||||
'host' => null,
|
||||
'OS' => null,
|
||||
'arch' => null,
|
||||
);
|
||||
|
||||
if ( function_exists( 'php_uname' ) ) {
|
||||
$this->data->server['host'] = php_uname( 'n' );
|
||||
$this->data->server['OS'] = php_uname( 's' ) . ' ' . php_uname( 'r' );
|
||||
$this->data->server['arch'] = php_uname( 'm' );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $extension
|
||||
* @return string
|
||||
*/
|
||||
public function get_extension_version( $extension ) {
|
||||
// Nothing is simple in PHP. The exif and mysqlnd extensions (and probably others) add a bunch of
|
||||
// crap to their version number, so we need to pluck out the first numeric value in the string.
|
||||
$version = trim( phpversion( $extension ) ?: '' );
|
||||
|
||||
if ( ! $version ) {
|
||||
return $version;
|
||||
}
|
||||
|
||||
$parts = explode( ' ', $version );
|
||||
|
||||
foreach ( $parts as $part ) {
|
||||
if ( $part && is_numeric( $part[0] ) ) {
|
||||
$version = $part;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param wpdb $db
|
||||
* @return string
|
||||
*/
|
||||
protected static function get_server_version( wpdb $db ) {
|
||||
$version = null;
|
||||
|
||||
if ( method_exists( $db, 'db_server_info' ) ) {
|
||||
$version = $db->db_server_info();
|
||||
}
|
||||
|
||||
if ( ! $version ) {
|
||||
$version = $db->get_var( 'SELECT VERSION()' );
|
||||
}
|
||||
|
||||
if ( ! $version ) {
|
||||
$version = __( 'Unknown', 'query-monitor' );
|
||||
}
|
||||
|
||||
return $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected static function get_current_user() {
|
||||
|
||||
$php_u = null;
|
||||
|
||||
if ( function_exists( 'posix_getpwuid' ) && function_exists( 'posix_getuid' ) && function_exists( 'posix_getgrgid' ) ) {
|
||||
$u = posix_getpwuid( posix_getuid() );
|
||||
|
||||
if ( isset( $u['gid'], $u['name'] ) ) {
|
||||
$g = posix_getgrgid( $u['gid'] );
|
||||
|
||||
if ( isset( $g['name'] ) ) {
|
||||
$php_u = $u['name'] . ':' . $g['name'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $php_u ) && isset( $_ENV['APACHE_RUN_USER'] ) ) {
|
||||
$php_u = $_ENV['APACHE_RUN_USER'];
|
||||
if ( isset( $_ENV['APACHE_RUN_GROUP'] ) ) {
|
||||
$php_u .= ':' . $_ENV['APACHE_RUN_GROUP'];
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $php_u ) && isset( $_SERVER['USER'] ) ) {
|
||||
$php_u = wp_unslash( $_SERVER['USER'] );
|
||||
}
|
||||
|
||||
if ( empty( $php_u ) && function_exists( 'exec' ) ) {
|
||||
$php_u = exec( 'whoami' ); // phpcs:ignore
|
||||
}
|
||||
|
||||
if ( empty( $php_u ) && function_exists( 'getenv' ) ) {
|
||||
$php_u = getenv( 'USERNAME' );
|
||||
}
|
||||
|
||||
return $php_u;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, QM_Collector> $collectors
|
||||
* @param QueryMonitor $qm
|
||||
* @return array<string, QM_Collector>
|
||||
*/
|
||||
function register_qm_collector_environment( array $collectors, QueryMonitor $qm ) {
|
||||
$collectors['environment'] = new QM_Collector_Environment();
|
||||
return $collectors;
|
||||
}
|
||||
|
||||
add_filter( 'qm/collectors', 'register_qm_collector_environment', 20, 2 );
|
88
wp-content/plugins/query-monitor/collectors/hooks.php
Normal file
88
wp-content/plugins/query-monitor/collectors/hooks.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* Hooks and actions collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_Hooks>
|
||||
*/
|
||||
class QM_Collector_Hooks extends QM_DataCollector {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $id = 'hooks';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected static $hide_core;
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_Hooks();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
/**
|
||||
* @var array<string, int> $wp_actions
|
||||
* @var array<string, WP_Hook> $wp_filter
|
||||
*/
|
||||
global $wp_actions, $wp_filter;
|
||||
|
||||
self::$hide_qm = self::hide_qm();
|
||||
self::$hide_core = ( defined( 'QM_HIDE_CORE_ACTIONS' ) && QM_HIDE_CORE_ACTIONS );
|
||||
|
||||
$hooks = array();
|
||||
$all_parts = array();
|
||||
$components = array();
|
||||
|
||||
if ( has_action( 'all' ) ) {
|
||||
$hooks[] = QM_Hook::process( 'all', 'action', $wp_filter, self::$hide_qm, self::$hide_core );
|
||||
}
|
||||
|
||||
$this->data->all_hooks = defined( 'QM_SHOW_ALL_HOOKS' ) && QM_SHOW_ALL_HOOKS;
|
||||
|
||||
if ( $this->data->all_hooks ) {
|
||||
// Show all hooks
|
||||
$hook_names = array_keys( $wp_filter );
|
||||
} else {
|
||||
// Only show action hooks that have been called at least once
|
||||
$hook_names = array_keys( $wp_actions );
|
||||
}
|
||||
|
||||
foreach ( $hook_names as $name ) {
|
||||
$type = 'action';
|
||||
|
||||
if ( $this->data->all_hooks ) {
|
||||
$type = array_key_exists( $name, $wp_actions ) ? 'action' : 'filter';
|
||||
}
|
||||
|
||||
$hook = QM_Hook::process( $name, $type, $wp_filter, self::$hide_qm, self::$hide_core );
|
||||
$hooks[] = $hook;
|
||||
|
||||
$all_parts = array_merge( $all_parts, $hook['parts'] );
|
||||
$components = array_merge( $components, $hook['components'] );
|
||||
|
||||
}
|
||||
|
||||
$this->data->hooks = $hooks;
|
||||
$this->data->parts = array_unique( array_filter( $all_parts ) );
|
||||
$this->data->components = array_unique( array_filter( $components ) );
|
||||
|
||||
usort( $this->data->parts, 'strcasecmp' );
|
||||
usort( $this->data->components, 'strcasecmp' );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Load early to catch all hooks
|
||||
QM_Collectors::add( new QM_Collector_Hooks() );
|
399
wp-content/plugins/query-monitor/collectors/http.php
Normal file
399
wp-content/plugins/query-monitor/collectors/http.php
Normal file
@ -0,0 +1,399 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* HTTP API request collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_HTTP>
|
||||
*/
|
||||
class QM_Collector_HTTP extends QM_DataCollector {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $id = 'http';
|
||||
|
||||
/**
|
||||
* @var mixed|null
|
||||
*/
|
||||
private $info = null;
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, mixed>>
|
||||
* @phpstan-var array<string, array{
|
||||
* url: string,
|
||||
* start: float,
|
||||
* args: array<string, mixed>,
|
||||
* filtered_trace: list<array<string, mixed>>,
|
||||
* component: QM_Component,
|
||||
* }>
|
||||
*/
|
||||
private $http_requests = array();
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, mixed>>
|
||||
* @phpstan-var array<string, array{
|
||||
* end: float,
|
||||
* args: array<string, mixed>,
|
||||
* response: mixed[]|WP_Error,
|
||||
* info: array<string, mixed>|null,
|
||||
* }>
|
||||
*/
|
||||
private $http_responses = array();
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_HTTP();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function set_up() {
|
||||
|
||||
parent::set_up();
|
||||
|
||||
add_filter( 'http_request_args', array( $this, 'filter_http_request_args' ), 9999, 2 );
|
||||
add_filter( 'pre_http_request', array( $this, 'filter_pre_http_request' ), 9999, 3 );
|
||||
add_action( 'http_api_debug', array( $this, 'action_http_api_debug' ), 9999, 5 );
|
||||
|
||||
add_action( 'requests-curl.after_request', array( $this, 'action_curl_after_request' ), 9999, 2 );
|
||||
add_action( 'requests-fsockopen.after_request', array( $this, 'action_fsockopen_after_request' ), 9999, 2 );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function tear_down() {
|
||||
remove_filter( 'http_request_args', array( $this, 'filter_http_request_args' ), 9999 );
|
||||
remove_filter( 'pre_http_request', array( $this, 'filter_pre_http_request' ), 9999 );
|
||||
remove_action( 'http_api_debug', array( $this, 'action_http_api_debug' ), 9999 );
|
||||
|
||||
remove_action( 'requests-curl.before_request', array( $this, 'action_curl_before_request' ), 9999 );
|
||||
remove_action( 'requests-curl.after_request', array( $this, 'action_curl_after_request' ), 9999 );
|
||||
remove_action( 'requests-fsockopen.before_request', array( $this, 'action_fsockopen_before_request' ), 9999 );
|
||||
remove_action( 'requests-fsockopen.after_request', array( $this, 'action_fsockopen_after_request' ), 9999 );
|
||||
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_actions() {
|
||||
$actions = array(
|
||||
'http_api_curl',
|
||||
'requests-multiple.request.complete',
|
||||
'requests-request.progress',
|
||||
'requests-transport.internal.parse_error',
|
||||
'requests-transport.internal.parse_response',
|
||||
);
|
||||
$transports = array(
|
||||
'requests',
|
||||
'curl',
|
||||
'fsockopen',
|
||||
);
|
||||
|
||||
foreach ( $transports as $transport ) {
|
||||
$actions[] = "requests-{$transport}.after_headers";
|
||||
$actions[] = "requests-{$transport}.after_multi_exec";
|
||||
$actions[] = "requests-{$transport}.after_request";
|
||||
$actions[] = "requests-{$transport}.after_send";
|
||||
$actions[] = "requests-{$transport}.before_multi_add";
|
||||
$actions[] = "requests-{$transport}.before_multi_exec";
|
||||
$actions[] = "requests-{$transport}.before_parse";
|
||||
$actions[] = "requests-{$transport}.before_redirect";
|
||||
$actions[] = "requests-{$transport}.before_redirect_check";
|
||||
$actions[] = "requests-{$transport}.before_request";
|
||||
$actions[] = "requests-{$transport}.before_send";
|
||||
$actions[] = "requests-{$transport}.remote_host_path";
|
||||
$actions[] = "requests-{$transport}.remote_socket";
|
||||
}
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_filters() {
|
||||
return array(
|
||||
'block_local_requests',
|
||||
'http_request_args',
|
||||
'http_response',
|
||||
'https_local_ssl_verify',
|
||||
'https_ssl_verify',
|
||||
'pre_http_request',
|
||||
'use_curl_transport',
|
||||
'use_streams_transport',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_constants() {
|
||||
return array(
|
||||
'WP_PROXY_HOST',
|
||||
'WP_PROXY_PORT',
|
||||
'WP_PROXY_USERNAME',
|
||||
'WP_PROXY_PASSWORD',
|
||||
'WP_PROXY_BYPASS_HOSTS',
|
||||
'WP_HTTP_BLOCK_EXTERNAL',
|
||||
'WP_ACCESSIBLE_HOSTS',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the arguments used in an HTTP request.
|
||||
*
|
||||
* Used to log the request, and to add the logging key to the arguments array.
|
||||
*
|
||||
* @param array<string, mixed> $args HTTP request arguments.
|
||||
* @param string $url The request URL.
|
||||
* @return array<string, mixed> HTTP request arguments.
|
||||
*/
|
||||
public function filter_http_request_args( array $args, $url ) {
|
||||
$trace = new QM_Backtrace( array(
|
||||
'ignore_hook' => array(
|
||||
current_filter() => true,
|
||||
),
|
||||
'ignore_class' => array(
|
||||
'WP_Http' => true,
|
||||
),
|
||||
'ignore_func' => array(
|
||||
'wp_safe_remote_request' => true,
|
||||
'wp_safe_remote_get' => true,
|
||||
'wp_safe_remote_post' => true,
|
||||
'wp_safe_remote_head' => true,
|
||||
'wp_remote_request' => true,
|
||||
'wp_remote_get' => true,
|
||||
'wp_remote_post' => true,
|
||||
'wp_remote_head' => true,
|
||||
'wp_remote_fopen' => true,
|
||||
'download_url' => true,
|
||||
'vip_safe_wp_remote_get' => true,
|
||||
'vip_safe_wp_remote_request' => true,
|
||||
'wpcom_vip_file_get_contents' => true,
|
||||
),
|
||||
) );
|
||||
|
||||
if ( isset( $args['_qm_key'], $this->http_requests[ $args['_qm_key'] ] ) ) {
|
||||
// Something has triggered another HTTP request from within the `pre_http_request` filter
|
||||
// (eg. WordPress Beta Tester does this). This allows for one level of nested queries.
|
||||
$args['_qm_original_key'] = $args['_qm_key'];
|
||||
$start = $this->http_requests[ $args['_qm_key'] ]['start'];
|
||||
} else {
|
||||
$start = microtime( true );
|
||||
}
|
||||
|
||||
$key = microtime( true ) . $url;
|
||||
$this->http_requests[ $key ] = array(
|
||||
'url' => $url,
|
||||
'args' => $args,
|
||||
'start' => $start,
|
||||
'filtered_trace' => $trace->get_filtered_trace(),
|
||||
'component' => $trace->get_component(),
|
||||
);
|
||||
$args['_qm_key'] = $key;
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the HTTP request's response if it's being short-circuited by another plugin.
|
||||
* This is necessary due to https://core.trac.wordpress.org/ticket/25747
|
||||
*
|
||||
* $response should be one of boolean false, an array, or a `WP_Error`, but be aware that plugins
|
||||
* which short-circuit the request using this filter may (incorrectly) return data of another type.
|
||||
*
|
||||
* @param false|mixed[]|WP_Error $response The preemptive HTTP response. Default false.
|
||||
* @param array<string, mixed> $args HTTP request arguments.
|
||||
* @param string $url The request URL.
|
||||
* @return false|mixed[]|WP_Error The preemptive HTTP response.
|
||||
*/
|
||||
public function filter_pre_http_request( $response, array $args, $url ) {
|
||||
|
||||
// All is well:
|
||||
if ( false === $response ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
// Something's filtering the response, so we'll log it
|
||||
$this->log_http_response( $response, $args, $url );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Debugging action for the HTTP API.
|
||||
*
|
||||
* @param mixed $response A parameter which varies depending on $action.
|
||||
* @param string $action The debug action. Currently one of 'response' or 'transports_list'.
|
||||
* @param string $class The HTTP transport class name.
|
||||
* @param array<string, mixed> $args HTTP request arguments.
|
||||
* @param string $url The request URL.
|
||||
* @return void
|
||||
*/
|
||||
public function action_http_api_debug( $response, $action, $class, $args, $url ) {
|
||||
switch ( $action ) {
|
||||
|
||||
case 'response':
|
||||
$this->log_http_response( $response, $args, $url );
|
||||
|
||||
break;
|
||||
|
||||
case 'transports_list':
|
||||
# Nothing
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $headers
|
||||
* @param mixed[] $info
|
||||
* @return void
|
||||
*/
|
||||
public function action_curl_after_request( $headers, array $info = null ) {
|
||||
$this->info = $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $headers
|
||||
* @param mixed[] $info
|
||||
* @return void
|
||||
*/
|
||||
public function action_fsockopen_after_request( $headers, array $info = null ) {
|
||||
$this->info = $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an HTTP response.
|
||||
*
|
||||
* @param mixed[]|WP_Error $response The HTTP response.
|
||||
* @param array<string, mixed> $args HTTP request arguments.
|
||||
* @param string $url The request URL.
|
||||
* @return void
|
||||
*/
|
||||
public function log_http_response( $response, array $args, $url ) {
|
||||
/** @var string */
|
||||
$key = $args['_qm_key'];
|
||||
|
||||
$http_response = array(
|
||||
'end' => microtime( true ),
|
||||
'response' => $response,
|
||||
'args' => $args,
|
||||
'info' => $this->info,
|
||||
);
|
||||
|
||||
if ( isset( $args['_qm_original_key'] ) ) {
|
||||
/** @var string */
|
||||
$original_key = $args['_qm_original_key'];
|
||||
$this->http_responses[ $original_key ]['end'] = $this->http_requests[ $original_key ]['start'];
|
||||
$this->http_responses[ $original_key ]['response'] = new WP_Error( 'http_request_not_executed', sprintf(
|
||||
/* translators: %s: Hook name */
|
||||
__( 'Request not executed due to a filter on %s', 'query-monitor' ),
|
||||
'pre_http_request'
|
||||
) );
|
||||
}
|
||||
|
||||
$this->http_responses[ $key ] = $http_response;
|
||||
|
||||
$this->info = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
$this->data->ltime = 0;
|
||||
|
||||
if ( empty( $this->http_requests ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of HTTP API error codes to ignore.
|
||||
*
|
||||
* @since 2.7.0
|
||||
*
|
||||
* @param array $http_errors Array of HTTP errors.
|
||||
*/
|
||||
$silent = apply_filters( 'qm/collect/silent_http_errors', array(
|
||||
'http_request_not_executed',
|
||||
'airplane_mode_enabled',
|
||||
) );
|
||||
|
||||
$home_host = (string) parse_url( home_url(), PHP_URL_HOST );
|
||||
|
||||
foreach ( $this->http_requests as $key => $request ) {
|
||||
$response = $this->http_responses[ $key ];
|
||||
|
||||
if ( empty( $response['response'] ) ) {
|
||||
// Timed out
|
||||
$response['response'] = new WP_Error( 'http_request_timed_out', __( 'Request timed out', 'query-monitor' ) );
|
||||
$response['end'] = floatval( $request['start'] + $response['args']['timeout'] );
|
||||
}
|
||||
|
||||
if ( $response['response'] instanceof WP_Error ) {
|
||||
if ( ! in_array( $response['response']->get_error_code(), $silent, true ) ) {
|
||||
$this->data->errors['alert'][] = $key;
|
||||
}
|
||||
$type = 'error';
|
||||
} elseif ( ! $response['args']['blocking'] ) {
|
||||
$type = 'non-blocking';
|
||||
} else {
|
||||
$code = intval( wp_remote_retrieve_response_code( $response['response'] ) );
|
||||
$type = "http:{$code}";
|
||||
if ( ( $code >= 400 ) && ( 'HEAD' !== $request['args']['method'] ) ) {
|
||||
$this->data->errors['warning'][] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
$ltime = ( $response['end'] - $request['start'] );
|
||||
$redirected_to = null;
|
||||
|
||||
if ( isset( $response['info'] ) && ! empty( $response['info']['url'] ) && is_string( $response['info']['url'] ) ) {
|
||||
// Ignore query variables when detecting a redirect.
|
||||
$from = untrailingslashit( preg_replace( '#\?[^$]+$#', '', $request['url'] ) );
|
||||
$to = untrailingslashit( preg_replace( '#\?[^$]+$#', '', $response['info']['url'] ) );
|
||||
if ( $from !== $to ) {
|
||||
$redirected_to = $response['info']['url'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->data->ltime += $ltime;
|
||||
|
||||
$host = (string) parse_url( $request['url'], PHP_URL_HOST );
|
||||
$local = ( $host === $home_host );
|
||||
|
||||
$this->log_type( $type );
|
||||
$this->log_component( $request['component'], $ltime, $type );
|
||||
$this->data->http[ $key ] = array(
|
||||
'args' => $response['args'],
|
||||
'component' => $request['component'],
|
||||
'filtered_trace' => $request['filtered_trace'],
|
||||
'host' => $host,
|
||||
'info' => $response['info'],
|
||||
'local' => $local,
|
||||
'ltime' => $ltime,
|
||||
'redirected_to' => $redirected_to,
|
||||
'response' => $response['response'],
|
||||
'type' => $type,
|
||||
'url' => $request['url'],
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Load early in case a plugin is doing an HTTP request when it initialises instead of after the `plugins_loaded` hook
|
||||
QM_Collectors::add( new QM_Collector_HTTP() );
|
222
wp-content/plugins/query-monitor/collectors/languages.php
Normal file
222
wp-content/plugins/query-monitor/collectors/languages.php
Normal file
@ -0,0 +1,222 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* Language and locale collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_Languages>
|
||||
*/
|
||||
class QM_Collector_Languages extends QM_DataCollector {
|
||||
|
||||
public $id = 'languages';
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_Languages();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function set_up() {
|
||||
|
||||
parent::set_up();
|
||||
|
||||
add_filter( 'load_textdomain_mofile', array( $this, 'log_file_load' ), 9999, 2 );
|
||||
add_filter( 'load_script_translation_file', array( $this, 'log_script_file_load' ), 9999, 3 );
|
||||
add_action( 'init', array( $this, 'collect_locale_data' ), 9999 );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function tear_down() {
|
||||
remove_filter( 'load_textdomain_mofile', array( $this, 'log_file_load' ), 9999 );
|
||||
remove_filter( 'load_script_translation_file', array( $this, 'log_script_file_load' ), 9999 );
|
||||
remove_action( 'init', array( $this, 'collect_locale_data' ), 9999 );
|
||||
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function collect_locale_data() {
|
||||
$this->data->locale = get_locale();
|
||||
$this->data->user_locale = function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale();
|
||||
$this->data->determined_locale = function_exists( 'determine_locale' ) ? determine_locale() : get_locale();
|
||||
$this->data->language_attributes = get_language_attributes();
|
||||
|
||||
if ( function_exists( '\Inpsyde\MultilingualPress\siteLanguageTag' ) ) {
|
||||
$this->data->mlp_language = \Inpsyde\MultilingualPress\siteLanguageTag();
|
||||
}
|
||||
|
||||
if ( function_exists( 'pll_current_language' ) ) {
|
||||
$this->data->pll_language = pll_current_language();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_actions() {
|
||||
return array(
|
||||
'load_textdomain',
|
||||
'unload_textdomain',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_filters() {
|
||||
return array(
|
||||
'determine_locale',
|
||||
'gettext',
|
||||
'gettext_with_context',
|
||||
'language_attributes',
|
||||
'load_script_textdomain_relative_path',
|
||||
'load_script_translation_file',
|
||||
'load_script_translations',
|
||||
'load_textdomain_mofile',
|
||||
'locale',
|
||||
'ngettext',
|
||||
'ngettext_with_context',
|
||||
'override_load_textdomain',
|
||||
'override_unload_textdomain',
|
||||
'plugin_locale',
|
||||
'pre_determine_locale',
|
||||
'pre_load_script_translations',
|
||||
'theme_locale',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_options() {
|
||||
return array(
|
||||
'WPLANG',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_constants() {
|
||||
return array(
|
||||
'WPLANG',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
if ( empty( $this->data->languages ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->data->total_size = 0;
|
||||
|
||||
ksort( $this->data->languages );
|
||||
|
||||
foreach ( $this->data->languages as & $mofiles ) {
|
||||
foreach ( $mofiles as & $mofile ) {
|
||||
if ( $mofile['found'] ) {
|
||||
$this->data->total_size += $mofile['found'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store log data.
|
||||
*
|
||||
* @param mixed $mofile Should be a string path to the MO file, could be anything.
|
||||
* @param string $domain Text domain.
|
||||
* @return string
|
||||
*/
|
||||
public function log_file_load( $mofile, $domain ) {
|
||||
if ( 'query-monitor' === $domain && self::hide_qm() ) {
|
||||
return $mofile;
|
||||
}
|
||||
|
||||
if ( is_string( $mofile ) && isset( $this->data->languages[ $domain ][ $mofile ] ) ) {
|
||||
return $mofile;
|
||||
}
|
||||
|
||||
$trace = new QM_Backtrace( array(
|
||||
'ignore_hook' => array(
|
||||
current_filter() => true,
|
||||
),
|
||||
'ignore_func' => array(
|
||||
'load_textdomain' => ( 'default' !== $domain ),
|
||||
'load_muplugin_textdomain' => true,
|
||||
'load_plugin_textdomain' => true,
|
||||
'load_theme_textdomain' => true,
|
||||
'load_child_theme_textdomain' => true,
|
||||
'load_default_textdomain' => true,
|
||||
),
|
||||
) );
|
||||
|
||||
$found = ( is_string( $mofile ) ) && file_exists( $mofile ) ? filesize( $mofile ) : false;
|
||||
|
||||
if ( ! is_string( $mofile ) ) {
|
||||
$mofile = gettype( $mofile );
|
||||
}
|
||||
|
||||
$this->data->languages[ $domain ][ $mofile ] = array(
|
||||
'caller' => $trace->get_caller(),
|
||||
'domain' => $domain,
|
||||
'file' => $mofile,
|
||||
'found' => $found,
|
||||
'handle' => null,
|
||||
'type' => 'gettext',
|
||||
);
|
||||
|
||||
return $mofile;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the file path for loading script translations for the given script handle and textdomain.
|
||||
*
|
||||
* @param string|false $file Path to the translation file to load. False if there isn't one.
|
||||
* @param string $handle Name of the script to register a translation domain to.
|
||||
* @param string $domain The textdomain.
|
||||
*
|
||||
* @return string|false Path to the translation file to load. False if there isn't one.
|
||||
*/
|
||||
public function log_script_file_load( $file, $handle, $domain ) {
|
||||
$trace = new QM_Backtrace( array(
|
||||
'ignore_hook' => array(
|
||||
current_filter() => true,
|
||||
),
|
||||
) );
|
||||
|
||||
$found = ( $file && file_exists( $file ) ) ? filesize( $file ) : false;
|
||||
$key = $file ?: uniqid();
|
||||
|
||||
$this->data->languages[ $domain ][ $key ] = array(
|
||||
'caller' => $trace->get_caller(),
|
||||
'domain' => $domain,
|
||||
'file' => $file,
|
||||
'found' => $found,
|
||||
'handle' => $handle,
|
||||
'type' => 'jed',
|
||||
);
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Load early to catch early errors
|
||||
QM_Collectors::add( new QM_Collector_Languages() );
|
285
wp-content/plugins/query-monitor/collectors/logger.php
Normal file
285
wp-content/plugins/query-monitor/collectors/logger.php
Normal file
@ -0,0 +1,285 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* PSR-3 compatible logging collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_Logger>
|
||||
* @phpstan-type LogMessage WP_Error|Throwable|string|bool|null
|
||||
*/
|
||||
class QM_Collector_Logger extends QM_DataCollector {
|
||||
|
||||
public $id = 'logger';
|
||||
|
||||
public const EMERGENCY = 'emergency';
|
||||
public const ALERT = 'alert';
|
||||
public const CRITICAL = 'critical';
|
||||
public const ERROR = 'error';
|
||||
public const WARNING = 'warning';
|
||||
public const NOTICE = 'notice';
|
||||
public const INFO = 'info';
|
||||
public const DEBUG = 'debug';
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_Logger();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function set_up() {
|
||||
parent::set_up();
|
||||
|
||||
$this->data->counts = array_fill_keys( $this->get_levels(), 0 );
|
||||
|
||||
foreach ( $this->get_levels() as $level ) {
|
||||
add_action( "qm/{$level}", array( $this, $level ), 10, 2 );
|
||||
}
|
||||
|
||||
add_action( 'qm/log', array( $this, 'log' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function tear_down() {
|
||||
foreach ( $this->get_levels() as $level ) {
|
||||
remove_action( "qm/{$level}", array( $this, $level ), 10 );
|
||||
}
|
||||
|
||||
remove_action( 'qm/log', array( $this, 'log' ), 10 );
|
||||
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $message
|
||||
* @param array<string, mixed> $context
|
||||
* @phpstan-param LogMessage $message
|
||||
* @return void
|
||||
*/
|
||||
public function emergency( $message, array $context = array() ) {
|
||||
$this->store( self::EMERGENCY, $message, $context );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $message
|
||||
* @param array<string, mixed> $context
|
||||
* @phpstan-param LogMessage $message
|
||||
* @return void
|
||||
*/
|
||||
public function alert( $message, array $context = array() ) {
|
||||
$this->store( self::ALERT, $message, $context );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $message
|
||||
* @param array<string, mixed> $context
|
||||
* @phpstan-param LogMessage $message
|
||||
* @return void
|
||||
*/
|
||||
public function critical( $message, array $context = array() ) {
|
||||
$this->store( self::CRITICAL, $message, $context );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $message
|
||||
* @param array<string, mixed> $context
|
||||
* @phpstan-param LogMessage $message
|
||||
* @return void
|
||||
*/
|
||||
public function error( $message, array $context = array() ) {
|
||||
$this->store( self::ERROR, $message, $context );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $message
|
||||
* @param array<string, mixed> $context
|
||||
* @phpstan-param LogMessage $message
|
||||
* @return void
|
||||
*/
|
||||
public function warning( $message, array $context = array() ) {
|
||||
$this->store( self::WARNING, $message, $context );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $message
|
||||
* @param array<string, mixed> $context
|
||||
* @phpstan-param LogMessage $message
|
||||
* @return void
|
||||
*/
|
||||
public function notice( $message, array $context = array() ) {
|
||||
$this->store( self::NOTICE, $message, $context );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $message
|
||||
* @param array<string, mixed> $context
|
||||
* @phpstan-param LogMessage $message
|
||||
* @return void
|
||||
*/
|
||||
public function info( $message, array $context = array() ) {
|
||||
$this->store( self::INFO, $message, $context );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $message
|
||||
* @param array<string, mixed> $context
|
||||
* @phpstan-param LogMessage $message
|
||||
* @return void
|
||||
*/
|
||||
public function debug( $message, array $context = array() ) {
|
||||
$this->store( self::DEBUG, $message, $context );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $level
|
||||
* @param mixed $message
|
||||
* @param array<string, mixed> $context
|
||||
* @phpstan-param self::* $level
|
||||
* @phpstan-param LogMessage $message
|
||||
* @return void
|
||||
*/
|
||||
public function log( $level, $message, array $context = array() ) {
|
||||
if ( ! in_array( $level, $this->get_levels(), true ) ) {
|
||||
throw new InvalidArgumentException( 'Unsupported log level' );
|
||||
}
|
||||
|
||||
$this->store( $level, $message, $context );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $level
|
||||
* @param mixed $message
|
||||
* @param array<string, mixed> $context
|
||||
* @phpstan-param self::* $level
|
||||
* @phpstan-param LogMessage $message
|
||||
* @return void
|
||||
*/
|
||||
protected function store( $level, $message, array $context = array() ) {
|
||||
$trace = new QM_Backtrace( array(
|
||||
'ignore_hook' => array(
|
||||
current_filter() => true,
|
||||
),
|
||||
) );
|
||||
|
||||
if ( $message instanceof WP_Error ) {
|
||||
$message = sprintf(
|
||||
'WP_Error: %s (%s)',
|
||||
$message->get_error_message(),
|
||||
$message->get_error_code()
|
||||
);
|
||||
}
|
||||
|
||||
if ( $message instanceof Throwable ) {
|
||||
$message = sprintf(
|
||||
'%1$s: %2$s',
|
||||
get_class( $message ),
|
||||
$message->getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! is_string( $message ) ) {
|
||||
if ( null === $message ) {
|
||||
$message = 'null';
|
||||
} elseif ( false === $message ) {
|
||||
$message = 'false';
|
||||
} elseif ( true === $message ) {
|
||||
$message = 'true';
|
||||
}
|
||||
|
||||
$message = print_r( $message, true );
|
||||
} elseif ( '' === trim( $message ) ) {
|
||||
$message = '(Empty string)';
|
||||
}
|
||||
|
||||
$this->data->counts[ $level ]++;
|
||||
$this->data->logs[] = array(
|
||||
'message' => self::interpolate( $message, $context ),
|
||||
'filtered_trace' => $trace->get_filtered_trace(),
|
||||
'component' => $trace->get_component(),
|
||||
'level' => $level,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
* @param array<string, mixed> $context
|
||||
* @return string
|
||||
*/
|
||||
protected static function interpolate( $message, array $context = array() ) {
|
||||
// build a replacement array with braces around the context keys
|
||||
$replace = array();
|
||||
|
||||
foreach ( $context as $key => $val ) {
|
||||
// check that the value can be casted to string
|
||||
if ( is_bool( $val ) ) {
|
||||
$replace[ "{{$key}}" ] = ( $val ? 'true' : 'false' );
|
||||
} elseif ( is_scalar( $val ) ) {
|
||||
$replace[ "{{$key}}" ] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
// interpolate replacement values into the message and return
|
||||
return strtr( $message, $replace );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
if ( empty( $this->data->logs ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$components = array();
|
||||
|
||||
foreach ( $this->data->logs as $row ) {
|
||||
$component = $row['component'];
|
||||
$components[ $component->name ] = $component->name;
|
||||
}
|
||||
|
||||
$this->data->components = $components;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
* @phpstan-return list<self::*>
|
||||
*/
|
||||
public function get_levels() {
|
||||
return array(
|
||||
self::EMERGENCY,
|
||||
self::ALERT,
|
||||
self::CRITICAL,
|
||||
self::ERROR,
|
||||
self::WARNING,
|
||||
self::NOTICE,
|
||||
self::INFO,
|
||||
self::DEBUG,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
* @phpstan-return list<self::*>
|
||||
*/
|
||||
public function get_warning_levels() {
|
||||
return array(
|
||||
self::EMERGENCY,
|
||||
self::ALERT,
|
||||
self::CRITICAL,
|
||||
self::ERROR,
|
||||
self::WARNING,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Load early in case a plugin wants to log a message early in the bootstrap process
|
||||
QM_Collectors::add( new QM_Collector_Logger() );
|
64
wp-content/plugins/query-monitor/collectors/multisite.php
Normal file
64
wp-content/plugins/query-monitor/collectors/multisite.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* Multisite collector, used for monitoring use of `switch_to_blog()` and `restore_current_blog()`.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_Multisite>
|
||||
*/
|
||||
class QM_Collector_Multisite extends QM_DataCollector {
|
||||
public $id = 'multisite';
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
$this->data->switches = array();
|
||||
|
||||
add_action( 'switch_blog', array( $this, 'action_switch_blog' ), 10, 3 );
|
||||
}
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_Multisite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when the blog is switched.
|
||||
*
|
||||
* @param int $new_blog_id New blog ID.
|
||||
* @param int $prev_blog_id Previous blog ID.
|
||||
* @param string $context Additional context. Accepts 'switch' when called from switch_to_blog()
|
||||
* or 'restore' when called from restore_current_blog().
|
||||
* @return void
|
||||
*/
|
||||
public function action_switch_blog( $new_blog_id, $prev_blog_id, $context ) {
|
||||
if ( intval( $new_blog_id ) === intval( $prev_blog_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->data->switches[] = array(
|
||||
'new' => $new_blog_id,
|
||||
'prev' => $prev_blog_id,
|
||||
'to' => ( 'switch' === $context ),
|
||||
'trace' => new QM_Backtrace( array(
|
||||
'ignore_hook' => array(
|
||||
'switch_blog' => true,
|
||||
),
|
||||
'ignore_func' => array(
|
||||
'switch_to_blog' => true,
|
||||
'restore_current_blog' => true,
|
||||
),
|
||||
) ),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( is_multisite() ) {
|
||||
# Load early to detect as many happenings during the bootstrap process as possible
|
||||
QM_Collectors::add( new QM_Collector_Multisite() );
|
||||
}
|
114
wp-content/plugins/query-monitor/collectors/overview.php
Normal file
114
wp-content/plugins/query-monitor/collectors/overview.php
Normal file
@ -0,0 +1,114 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* General overview collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_Overview>
|
||||
*/
|
||||
class QM_Collector_Overview extends QM_DataCollector {
|
||||
|
||||
public $id = 'overview';
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_Overview();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function set_up() {
|
||||
parent::set_up();
|
||||
|
||||
add_action( 'shutdown', array( $this, 'process_timing' ), 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function tear_down() {
|
||||
remove_action( 'shutdown', array( $this, 'process_timing' ), 0 );
|
||||
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the timing and memory related stats as early as possible, so the
|
||||
* data isn't skewed by collectors that are processed before this one.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function process_timing() {
|
||||
$this->data->time_taken = self::timer_stop_float();
|
||||
|
||||
if ( function_exists( 'memory_get_peak_usage' ) ) {
|
||||
$this->data->memory = memory_get_peak_usage();
|
||||
} elseif ( function_exists( 'memory_get_usage' ) ) {
|
||||
$this->data->memory = memory_get_usage();
|
||||
} else {
|
||||
$this->data->memory = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
if ( ! isset( $this->data->time_taken ) ) {
|
||||
$this->process_timing();
|
||||
}
|
||||
|
||||
$this->data->time_limit = (int) ini_get( 'max_execution_time' );
|
||||
$this->data->time_start = $_SERVER['REQUEST_TIME_FLOAT'];
|
||||
|
||||
if ( ! empty( $this->data->time_limit ) ) {
|
||||
$this->data->time_usage = ( 100 / $this->data->time_limit ) * $this->data->time_taken;
|
||||
} else {
|
||||
$this->data->time_usage = 0;
|
||||
}
|
||||
|
||||
if ( is_user_logged_in() ) {
|
||||
$this->data->current_user = self::format_user( wp_get_current_user() );
|
||||
} else {
|
||||
$this->data->current_user = null;
|
||||
}
|
||||
|
||||
if ( function_exists( 'current_user_switched' ) && current_user_switched() ) {
|
||||
$this->data->switched_user = self::format_user( current_user_switched() );
|
||||
} else {
|
||||
$this->data->switched_user = null;
|
||||
}
|
||||
|
||||
$this->data->memory_limit = QM_Util::convert_hr_to_bytes( ini_get( 'memory_limit' ) ?: '0' );
|
||||
|
||||
if ( $this->data->memory_limit > 0 ) {
|
||||
$this->data->memory_usage = ( 100 / $this->data->memory_limit ) * $this->data->memory;
|
||||
} else {
|
||||
$this->data->memory_usage = 0;
|
||||
}
|
||||
|
||||
$this->data->display_time_usage_warning = ( $this->data->time_usage >= 75 );
|
||||
$this->data->display_memory_usage_warning = ( $this->data->memory_usage >= 75 );
|
||||
|
||||
$this->data->is_admin = is_admin();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, QM_Collector> $collectors
|
||||
* @param QueryMonitor $qm
|
||||
* @return array<string, QM_Collector>
|
||||
*/
|
||||
function register_qm_collector_overview( array $collectors, QueryMonitor $qm ) {
|
||||
$collectors['overview'] = new QM_Collector_Overview();
|
||||
return $collectors;
|
||||
}
|
||||
|
||||
add_filter( 'qm/collectors', 'register_qm_collector_overview', 1, 2 );
|
562
wp-content/plugins/query-monitor/collectors/php_errors.php
Normal file
562
wp-content/plugins/query-monitor/collectors/php_errors.php
Normal file
@ -0,0 +1,562 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* PHP error collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! defined( 'QM_ERROR_FATALS' ) ) {
|
||||
define( 'QM_ERROR_FATALS', E_ERROR | E_PARSE | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR );
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_PHP_Errors>
|
||||
* @phpstan-type errorLabels array{
|
||||
* warning: string,
|
||||
* notice: string,
|
||||
* strict: string,
|
||||
* deprecated: string,
|
||||
* }
|
||||
* @phpstan-import-type errorObject from QM_Data_PHP_Errors
|
||||
*/
|
||||
class QM_Collector_PHP_Errors extends QM_DataCollector {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $id = 'php_errors';
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, string>>
|
||||
* @phpstan-var array{
|
||||
* errors: errorLabels,
|
||||
* suppressed: errorLabels,
|
||||
* silenced: errorLabels,
|
||||
* }
|
||||
*/
|
||||
public $types;
|
||||
|
||||
/**
|
||||
* @var int|null
|
||||
*/
|
||||
private $error_reporting = null;
|
||||
|
||||
/**
|
||||
* @var string|false|null
|
||||
*/
|
||||
private $display_errors = null;
|
||||
|
||||
/**
|
||||
* @var callable|null
|
||||
*/
|
||||
private $previous_error_handler = null;
|
||||
|
||||
/**
|
||||
* @var callable|null
|
||||
*/
|
||||
private $previous_exception_handler = null;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
private static $unexpected_error = null;
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_PHP_Errors();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function set_up() {
|
||||
if ( defined( 'QM_DISABLE_ERROR_HANDLER' ) && QM_DISABLE_ERROR_HANDLER ) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::set_up();
|
||||
|
||||
// Capture the last error that occurred before QM loaded:
|
||||
$prior_error = error_get_last();
|
||||
|
||||
// Non-fatal error handler:
|
||||
$this->previous_error_handler = set_error_handler( array( $this, 'error_handler' ), ( E_ALL ^ QM_ERROR_FATALS ) );
|
||||
|
||||
// Fatal error and uncaught exception handler:
|
||||
$this->previous_exception_handler = set_exception_handler( array( $this, 'exception_handler' ) );
|
||||
|
||||
$this->error_reporting = error_reporting();
|
||||
$this->display_errors = ini_get( 'display_errors' );
|
||||
ini_set( 'display_errors', '0' );
|
||||
|
||||
if ( $prior_error ) {
|
||||
$this->error_handler(
|
||||
$prior_error['type'],
|
||||
$prior_error['message'],
|
||||
$prior_error['file'],
|
||||
$prior_error['line'],
|
||||
null,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function tear_down() {
|
||||
if ( defined( 'QM_DISABLE_ERROR_HANDLER' ) && QM_DISABLE_ERROR_HANDLER ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( null !== $this->previous_error_handler ) {
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
if ( null !== $this->previous_exception_handler ) {
|
||||
restore_exception_handler();
|
||||
}
|
||||
|
||||
if ( null !== $this->error_reporting ) {
|
||||
error_reporting( $this->error_reporting );
|
||||
}
|
||||
|
||||
if ( false !== $this->display_errors ) {
|
||||
ini_set( 'display_errors', $this->display_errors );
|
||||
}
|
||||
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
/**
|
||||
* Uncaught error handler.
|
||||
*
|
||||
* @param Throwable $e The error or exception.
|
||||
* @return void
|
||||
*/
|
||||
public function exception_handler( $e ) {
|
||||
$error = 'Uncaught Error';
|
||||
|
||||
if ( $e instanceof Exception ) {
|
||||
$error = 'Uncaught Exception';
|
||||
}
|
||||
|
||||
$this->output_fatal( 'Fatal error', array(
|
||||
'message' => sprintf(
|
||||
'%s: %s',
|
||||
$error,
|
||||
$e->getMessage()
|
||||
),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'trace' => $e->getTrace(),
|
||||
) );
|
||||
|
||||
// The error must be re-thrown or passed to the previously registered exception handler so that the error
|
||||
// is logged appropriately instead of discarded silently.
|
||||
if ( $this->previous_exception_handler ) {
|
||||
call_user_func( $this->previous_exception_handler, $e );
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $errno The error number.
|
||||
* @param string $message The error message.
|
||||
* @param string $file The file location.
|
||||
* @param int $line The line number.
|
||||
* @param mixed[] $context The context being passed.
|
||||
* @param bool $do_trace Whether a stack trace should be included in the logged error data.
|
||||
* @return bool
|
||||
*/
|
||||
public function error_handler( $errno, $message, $file = null, $line = null, $context = null, $do_trace = true ) {
|
||||
$type = null;
|
||||
|
||||
/**
|
||||
* Fires before logging the PHP error in Query Monitor.
|
||||
*
|
||||
* @since 2.7.0
|
||||
*
|
||||
* @param int $errno The error number.
|
||||
* @param string $message The error message.
|
||||
* @param string|null $file The file location.
|
||||
* @param int|null $line The line number.
|
||||
* @param mixed[]|null $context The context being passed.
|
||||
*/
|
||||
do_action( 'qm/collect/new_php_error', $errno, $message, $file, $line, $context );
|
||||
|
||||
switch ( $errno ) {
|
||||
|
||||
case E_WARNING:
|
||||
case E_USER_WARNING:
|
||||
$type = 'warning';
|
||||
break;
|
||||
|
||||
case E_NOTICE:
|
||||
case E_USER_NOTICE:
|
||||
$type = 'notice';
|
||||
break;
|
||||
|
||||
case E_STRICT:
|
||||
$type = 'strict';
|
||||
break;
|
||||
|
||||
case E_DEPRECATED:
|
||||
case E_USER_DEPRECATED:
|
||||
$type = 'deprecated';
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if ( null === $type ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'QM_Backtrace' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$error_group = 'errors';
|
||||
|
||||
if ( 0 === error_reporting() && 0 !== $this->error_reporting ) {
|
||||
// This is most likely an @-suppressed error
|
||||
$error_group = 'suppressed';
|
||||
}
|
||||
|
||||
if ( ! isset( self::$unexpected_error ) ) {
|
||||
// These strings are from core. They're passed through `__()` as variables so they get translated at runtime
|
||||
// but do not get seen by GlotPress when it populates its database of translatable strings for QM.
|
||||
$unexpected_error = 'An unexpected error occurred. Something may be wrong with WordPress.org or this server’s configuration. If you continue to have problems, please try the <a href="%s">support forums</a>.';
|
||||
$wordpress_forums = 'https://wordpress.org/support/forums/';
|
||||
|
||||
self::$unexpected_error = sprintf(
|
||||
call_user_func( '__', $unexpected_error ),
|
||||
call_user_func( '__', $wordpress_forums )
|
||||
);
|
||||
}
|
||||
|
||||
// Intentionally skip reporting these core warnings. They're a distraction when developing offline.
|
||||
// The failed HTTP request will still appear in QM's output so it's not a big problem hiding these warnings.
|
||||
if ( false !== strpos( $message, self::$unexpected_error ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$trace = new QM_Backtrace();
|
||||
$trace->push_frame( array(
|
||||
'file' => $file,
|
||||
'line' => $line,
|
||||
) );
|
||||
$caller = $trace->get_caller();
|
||||
|
||||
if ( $caller ) {
|
||||
$key = md5( $message . $file . $line . $caller['id'] );
|
||||
} else {
|
||||
$key = md5( $message . $file . $line );
|
||||
}
|
||||
|
||||
if ( isset( $this->data->{$error_group}[ $type ][ $key ] ) ) {
|
||||
$this->data->{$error_group}[ $type ][ $key ]['calls']++;
|
||||
} else {
|
||||
$this->data->{$error_group}[ $type ][ $key ] = array(
|
||||
'errno' => $errno,
|
||||
'type' => $type,
|
||||
'message' => wp_strip_all_tags( $message ),
|
||||
'file' => $file,
|
||||
'filename' => ( $file ? QM_Util::standard_dir( $file, '' ) : '' ),
|
||||
'line' => $line,
|
||||
'filtered_trace' => ( $do_trace ? $trace->get_filtered_trace() : null ),
|
||||
'component' => $trace->get_component(),
|
||||
'calls' => 1,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the PHP error handler return value. This can be used to control whether or not the default error
|
||||
* handler is called after Query Monitor's.
|
||||
*
|
||||
* @since 2.7.0
|
||||
*
|
||||
* @param bool $return_value Error handler return value. Default false.
|
||||
*/
|
||||
return apply_filters( 'qm/collect/php_errors_return_value', false );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $error
|
||||
* @param mixed[] $e
|
||||
* @phpstan-param array{
|
||||
* message: string,
|
||||
* file: string,
|
||||
* line: int,
|
||||
* type?: int,
|
||||
* trace?: mixed|null,
|
||||
* } $e
|
||||
* @return void
|
||||
*/
|
||||
protected function output_fatal( $error, array $e ) {
|
||||
$dispatcher = QM_Dispatchers::get( 'html' );
|
||||
|
||||
if ( empty( $dispatcher ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( empty( $this->display_errors ) && ! $dispatcher::user_can_view() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This hides the subsequent message from the fatal error handler in core. It cannot be
|
||||
// disabled by a plugin so we'll just hide its output.
|
||||
echo '<style type="text/css"> .wp-die-message { display: none; } </style>';
|
||||
|
||||
printf(
|
||||
// phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet
|
||||
'<link rel="stylesheet" href="%1$s?ver=%2$s" media="all" />',
|
||||
esc_url( QueryMonitor::init()->plugin_url( 'assets/query-monitor.css' ) ),
|
||||
esc_attr( QM_VERSION )
|
||||
);
|
||||
|
||||
// This unused wrapper with an attribute serves to help the #qm-fatal div break out of an
|
||||
// attribute if a fatal has occurred within one.
|
||||
echo '<div data-qm="qm">';
|
||||
|
||||
printf(
|
||||
'<div id="qm-fatal" data-qm-message="%1$s" data-qm-file="%2$s" data-qm-line="%3$d">',
|
||||
esc_attr( $e['message'] ),
|
||||
esc_attr( QM_Util::standard_dir( $e['file'], '' ) ),
|
||||
intval( $e['line'] )
|
||||
);
|
||||
|
||||
echo '<div class="qm-fatal-wrap">';
|
||||
|
||||
if ( QM_Output_Html::has_clickable_links() ) {
|
||||
$file = QM_Output_Html::output_filename( $e['file'], $e['file'], $e['line'], true );
|
||||
} else {
|
||||
$file = esc_html( $e['file'] );
|
||||
}
|
||||
|
||||
$warning = QueryMonitor::icon( 'warning' );
|
||||
|
||||
printf(
|
||||
'<p>%1$s <b>%2$s</b>: %3$s<br>in <b>%4$s</b> on line <b>%5$d</b></p>',
|
||||
$warning,
|
||||
esc_html( $error ),
|
||||
nl2br( esc_html( $e['message'] ), false ),
|
||||
$file,
|
||||
intval( $e['line'] )
|
||||
); // WPCS: XSS ok.
|
||||
|
||||
if ( ! empty( $e['trace'] ) ) {
|
||||
echo '<p>Call stack:</p>';
|
||||
echo '<ol>';
|
||||
foreach ( $e['trace'] as $frame ) {
|
||||
$callback = QM_Util::populate_callback( $frame );
|
||||
|
||||
if ( ! isset( $callback['name'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
printf(
|
||||
'<li>%s</li>',
|
||||
QM_Output_Html::output_filename( $callback['name'], $frame['file'], $frame['line'] )
|
||||
); // WPCS: XSS ok.
|
||||
}
|
||||
echo '</ol>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
|
||||
echo '<h2>Query Monitor</h2>';
|
||||
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs post-processing on the collected errors and updates the
|
||||
* errors collected in the data->errors property.
|
||||
*
|
||||
* Any unreportable errors are placed in the data->filtered_errors
|
||||
* property.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
$this->types = array(
|
||||
'errors' => array(
|
||||
'warning' => _x( 'Warning', 'PHP error level', 'query-monitor' ),
|
||||
'notice' => _x( 'Notice', 'PHP error level', 'query-monitor' ),
|
||||
'strict' => _x( 'Strict', 'PHP error level', 'query-monitor' ),
|
||||
'deprecated' => _x( 'Deprecated', 'PHP error level', 'query-monitor' ),
|
||||
),
|
||||
'suppressed' => array(
|
||||
'warning' => _x( 'Warning (Suppressed)', 'Suppressed PHP error level', 'query-monitor' ),
|
||||
'notice' => _x( 'Notice (Suppressed)', 'Suppressed PHP error level', 'query-monitor' ),
|
||||
'strict' => _x( 'Strict (Suppressed)', 'Suppressed PHP error level', 'query-monitor' ),
|
||||
'deprecated' => _x( 'Deprecated (Suppressed)', 'Suppressed PHP error level', 'query-monitor' ),
|
||||
),
|
||||
'silenced' => array(
|
||||
'warning' => _x( 'Warning (Silenced)', 'Silenced PHP error level', 'query-monitor' ),
|
||||
'notice' => _x( 'Notice (Silenced)', 'Silenced PHP error level', 'query-monitor' ),
|
||||
'strict' => _x( 'Strict (Silenced)', 'Silenced PHP error level', 'query-monitor' ),
|
||||
'deprecated' => _x( 'Deprecated (Silenced)', 'Silenced PHP error level', 'query-monitor' ),
|
||||
),
|
||||
);
|
||||
$components = array();
|
||||
|
||||
if ( ! empty( $this->data->errors ) ) {
|
||||
/**
|
||||
* Filters the levels used for reported PHP errors on a per-component basis.
|
||||
*
|
||||
* Error levels can be specified in order to silence certain error levels from
|
||||
* plugins or the current theme. Most commonly, you may wish to use this filter
|
||||
* in order to silence annoying notices from third party plugins that you do not
|
||||
* have control over.
|
||||
*
|
||||
* Silenced errors will still appear in Query Monitor's output, but will not
|
||||
* cause highlighting to appear in the top level admin toolbar.
|
||||
*
|
||||
* For example, to show all errors in the 'foo' plugin except PHP notices use:
|
||||
*
|
||||
* add_filter( 'qm/collect/php_error_levels', function( array $levels ) {
|
||||
* $levels['plugin']['foo'] = ( E_ALL & ~E_NOTICE );
|
||||
* return $levels;
|
||||
* } );
|
||||
*
|
||||
* Errors from themes, WordPress core, and other components can also be filtered:
|
||||
*
|
||||
* add_filter( 'qm/collect/php_error_levels', function( array $levels ) {
|
||||
* $levels['theme']['stylesheet'] = ( E_WARNING & E_USER_WARNING );
|
||||
* $levels['theme']['template'] = ( E_WARNING & E_USER_WARNING );
|
||||
* $levels['core']['core'] = ( 0 );
|
||||
* return $levels;
|
||||
* } );
|
||||
*
|
||||
* Any component which doesn't have an error level specified via this filter is
|
||||
* assumed to have the default level of `E_ALL`, which shows all errors.
|
||||
*
|
||||
* Valid PHP error level bitmasks are supported for each component, including `0`
|
||||
* to silence all errors from a component. See the PHP documentation on error
|
||||
* reporting for more info: http://php.net/manual/en/function.error-reporting.php
|
||||
*
|
||||
* @since 2.7.0
|
||||
*
|
||||
* @param array<string,array<string,int>> $levels The error levels used for each component.
|
||||
*/
|
||||
$levels = apply_filters( 'qm/collect/php_error_levels', array() );
|
||||
|
||||
array_map( array( $this, 'filter_reportable_errors' ), $levels, array_keys( $levels ) );
|
||||
|
||||
foreach ( $this->types as $error_group => $error_types ) {
|
||||
foreach ( $error_types as $type => $title ) {
|
||||
if ( isset( $this->data->{$error_group}[ $type ] ) ) {
|
||||
/**
|
||||
* @var array<string, mixed> $error
|
||||
* @phpstan-var errorObject $error
|
||||
*/
|
||||
foreach ( $this->data->{$error_group}[ $type ] as $error ) {
|
||||
$components[ $error['component']->name ] = $error['component']->name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->data->components = $components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the reportable PHP errors using the table specified. Users can customize the levels
|
||||
* using the `qm/collect/php_error_levels` filter.
|
||||
*
|
||||
* @param array<string, int> $components The error levels keyed by component name.
|
||||
* @param string $component_type The component type, for example 'plugin' or 'theme'.
|
||||
* @return void
|
||||
*/
|
||||
public function filter_reportable_errors( array $components, $component_type ) {
|
||||
$all_errors = $this->data->errors;
|
||||
|
||||
foreach ( $components as $component_context => $allowed_level ) {
|
||||
foreach ( $all_errors as $error_level => $errors ) {
|
||||
foreach ( $errors as $error_id => $error ) {
|
||||
if ( $this->is_reportable_error( $error['errno'], $allowed_level ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! $this->is_affected_component( $error['component'], $component_type, $component_context ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unset( $this->data->errors[ $error_level ][ $error_id ] );
|
||||
|
||||
$this->data->silenced[ $error_level ][ $error_id ] = $error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->data->errors = array_filter( $this->data->errors );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the component is of the given type and has the given context. This is
|
||||
* used to scope an error to a plugin or theme.
|
||||
*
|
||||
* @param QM_Component $component The component.
|
||||
* @param string $component_type The component type for comparison.
|
||||
* @param string $component_context The component context for comparison.
|
||||
* @return bool
|
||||
*/
|
||||
public function is_affected_component( $component, $component_type, $component_context ) {
|
||||
return ( $component->type === $component_type && $component->context === $component_context );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the error number specified is viewable based on the
|
||||
* flags specified.
|
||||
*
|
||||
* Eg:- If a plugin had the config flags,
|
||||
*
|
||||
* E_ALL & ~E_NOTICE
|
||||
*
|
||||
* then,
|
||||
*
|
||||
* is_reportable_error( E_NOTICE, E_ALL & ~E_NOTICE ) is false
|
||||
* is_reportable_error( E_WARNING, E_ALL & ~E_NOTICE ) is true
|
||||
*
|
||||
* If the `$flag` is null, all errors are assumed to be
|
||||
* reportable by default.
|
||||
*
|
||||
* @param int $error_no The errno from PHP
|
||||
* @param int|null $flags The config flags specified by users
|
||||
* @return bool Whether the error is reportable.
|
||||
*/
|
||||
public function is_reportable_error( $error_no, $flags ) {
|
||||
$result = true;
|
||||
|
||||
if ( null !== $flags ) {
|
||||
$result = (bool) ( $error_no & $flags );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* For testing purposes only. Sets the errors property manually.
|
||||
* Needed to test the filter since the data property is protected.
|
||||
*
|
||||
* @param array<string, mixed> $errors The list of errors
|
||||
* @return void
|
||||
*/
|
||||
public function set_php_errors( $errors ) {
|
||||
$this->data->errors = $errors;
|
||||
}
|
||||
}
|
||||
|
||||
# Load early to catch early errors
|
||||
QM_Collectors::add( new QM_Collector_PHP_Errors() );
|
97
wp-content/plugins/query-monitor/collectors/raw_request.php
Normal file
97
wp-content/plugins/query-monitor/collectors/raw_request.php
Normal file
@ -0,0 +1,97 @@
|
||||
<?php declare(strict_types = 1);
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_Raw_Request>
|
||||
*/
|
||||
class QM_Collector_Raw_Request extends QM_DataCollector {
|
||||
|
||||
public $id = 'raw_request';
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_Raw_Request();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts headers from a PHP-style $_SERVER array.
|
||||
*
|
||||
* From WP_REST_Server::get_headers()
|
||||
*
|
||||
* @param array<string, string> $server Associative array similar to `$_SERVER`.
|
||||
* @return array<string, string> Headers extracted from the input.
|
||||
*/
|
||||
protected function get_headers( array $server ) {
|
||||
$headers = array();
|
||||
|
||||
// CONTENT_* headers are not prefixed with HTTP_.
|
||||
$additional = array(
|
||||
'CONTENT_LENGTH' => true,
|
||||
'CONTENT_MD5' => true,
|
||||
'CONTENT_TYPE' => true,
|
||||
);
|
||||
|
||||
foreach ( $server as $key => $value ) {
|
||||
if ( strpos( $key, 'HTTP_' ) === 0 ) {
|
||||
$headers[ substr( $key, 5 ) ] = $value;
|
||||
} elseif ( isset( $additional[ $key ] ) ) {
|
||||
$headers[ $key ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process request and response data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
$request = array(
|
||||
'ip' => $_SERVER['REMOTE_ADDR'],
|
||||
'method' => strtoupper( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ),
|
||||
'scheme' => is_ssl() ? 'https' : 'http',
|
||||
'host' => wp_unslash( $_SERVER['HTTP_HOST'] ),
|
||||
'path' => wp_unslash( $_SERVER['REQUEST_URI'] ?? '/' ),
|
||||
'query' => wp_unslash( $_SERVER['QUERY_STRING'] ?? '' ),
|
||||
'headers' => $this->get_headers( wp_unslash( $_SERVER ) ),
|
||||
);
|
||||
|
||||
ksort( $request['headers'] );
|
||||
|
||||
$request['url'] = sprintf( '%s://%s%s', $request['scheme'], $request['host'], $request['path'] );
|
||||
|
||||
$this->data->request = $request;
|
||||
|
||||
$headers = array();
|
||||
$raw_headers = headers_list();
|
||||
foreach ( $raw_headers as $row ) {
|
||||
list( $key, $value ) = explode( ':', $row, 2 );
|
||||
$headers[ trim( $key ) ] = trim( $value );
|
||||
}
|
||||
|
||||
ksort( $headers );
|
||||
|
||||
$response = array(
|
||||
'status' => http_response_code(),
|
||||
'headers' => $headers,
|
||||
);
|
||||
|
||||
$this->data->response = $response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, QM_Collector> $collectors
|
||||
* @param QueryMonitor $qm
|
||||
* @return array<string, QM_Collector>
|
||||
*/
|
||||
function register_qm_collector_raw_request( array $collectors, QueryMonitor $qm ) {
|
||||
$collectors['raw_request'] = new QM_Collector_Raw_Request();
|
||||
return $collectors;
|
||||
}
|
||||
|
||||
add_filter( 'qm/collectors', 'register_qm_collector_raw_request', 10, 2 );
|
71
wp-content/plugins/query-monitor/collectors/redirects.php
Normal file
71
wp-content/plugins/query-monitor/collectors/redirects.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* HTTP redirect collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_Redirect>
|
||||
*/
|
||||
class QM_Collector_Redirects extends QM_DataCollector {
|
||||
|
||||
public $id = 'redirects';
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_Redirect();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function set_up() {
|
||||
parent::set_up();
|
||||
add_filter( 'wp_redirect', array( $this, 'filter_wp_redirect' ), 9999, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function tear_down() {
|
||||
remove_filter( 'wp_redirect', array( $this, 'filter_wp_redirect' ), 9999 );
|
||||
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $location
|
||||
* @param int $status
|
||||
* @return string
|
||||
*/
|
||||
public function filter_wp_redirect( $location, $status ) {
|
||||
|
||||
if ( ! $location ) {
|
||||
return $location;
|
||||
}
|
||||
|
||||
$trace = new QM_Backtrace( array(
|
||||
'ignore_hook' => array(
|
||||
current_filter() => true,
|
||||
),
|
||||
'ignore_func' => array(
|
||||
'wp_redirect' => true,
|
||||
),
|
||||
) );
|
||||
|
||||
$this->data->trace = $trace;
|
||||
$this->data->location = $location;
|
||||
$this->data->status = $status;
|
||||
|
||||
return $location;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Load early in case a plugin is doing a redirect when it initialises instead of after the `plugins_loaded` hook
|
||||
QM_Collectors::add( new QM_Collector_Redirects() );
|
332
wp-content/plugins/query-monitor/collectors/request.php
Normal file
332
wp-content/plugins/query-monitor/collectors/request.php
Normal file
@ -0,0 +1,332 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* Request collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_Request>
|
||||
*/
|
||||
class QM_Collector_Request extends QM_DataCollector {
|
||||
|
||||
public $id = 'request';
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_Request();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_actions() {
|
||||
return array(
|
||||
# Rewrites
|
||||
'generate_rewrite_rules',
|
||||
|
||||
# Everything else
|
||||
'parse_query',
|
||||
'parse_request',
|
||||
'parse_tax_query',
|
||||
'pre_get_posts',
|
||||
'send_headers',
|
||||
'the_post',
|
||||
'wp',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_filters() {
|
||||
global $wp_rewrite;
|
||||
|
||||
$filters = array(
|
||||
# Rewrite rules
|
||||
'author_rewrite_rules',
|
||||
'category_rewrite_rules',
|
||||
'comments_rewrite_rules',
|
||||
'date_rewrite_rules',
|
||||
'page_rewrite_rules',
|
||||
'post_format_rewrite_rules',
|
||||
'post_rewrite_rules',
|
||||
'root_rewrite_rules',
|
||||
'search_rewrite_rules',
|
||||
'tag_rewrite_rules',
|
||||
|
||||
# Home URL
|
||||
'home_url',
|
||||
|
||||
# Post permalinks
|
||||
'_get_page_link',
|
||||
'attachment_link',
|
||||
'page_link',
|
||||
'post_link',
|
||||
'post_type_link',
|
||||
'pre_post_link',
|
||||
'preview_post_link',
|
||||
'the_permalink',
|
||||
|
||||
# Post type archive permalinks
|
||||
'post_type_archive_link',
|
||||
|
||||
# Term permalinks
|
||||
'category_link',
|
||||
'pre_term_link',
|
||||
'tag_link',
|
||||
'term_link',
|
||||
|
||||
# User permalinks
|
||||
'author_link',
|
||||
|
||||
# Comment permalinks
|
||||
'get_comment_link',
|
||||
|
||||
# More rewrite stuff
|
||||
'iis7_url_rewrite_rules',
|
||||
'mod_rewrite_rules',
|
||||
'rewrite_rules',
|
||||
'rewrite_rules_array',
|
||||
|
||||
# Everything else
|
||||
'do_parse_request',
|
||||
'pre_handle_404',
|
||||
'query_string',
|
||||
'query_vars',
|
||||
'redirect_canonical',
|
||||
'request',
|
||||
'wp_headers',
|
||||
);
|
||||
|
||||
foreach ( $wp_rewrite->extra_permastructs as $permastructname => $struct ) {
|
||||
$filters[] = sprintf(
|
||||
'%s_rewrite_rules',
|
||||
$permastructname
|
||||
);
|
||||
}
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_options() {
|
||||
return array(
|
||||
'home',
|
||||
'permalink_structure',
|
||||
'rewrite_rules',
|
||||
'siteurl',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_constants() {
|
||||
return array(
|
||||
'WP_HOME',
|
||||
'WP_SITEURL',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
|
||||
global $wp, $wp_query, $current_blog, $current_site, $wp_rewrite;
|
||||
|
||||
$qo = get_queried_object();
|
||||
$user = wp_get_current_user();
|
||||
|
||||
if ( $user->exists() ) {
|
||||
$user_title = sprintf(
|
||||
/* translators: %d: User ID */
|
||||
__( 'Current User: #%d', 'query-monitor' ),
|
||||
$user->ID
|
||||
);
|
||||
} else {
|
||||
/* translators: No user */
|
||||
$user_title = _x( 'None', 'user', 'query-monitor' );
|
||||
}
|
||||
|
||||
$this->data->user = array(
|
||||
'title' => $user_title,
|
||||
'data' => ( $user->exists() ? $user : false ),
|
||||
);
|
||||
|
||||
if ( is_multisite() ) {
|
||||
$this->data->multisite['current_site'] = array(
|
||||
'title' => sprintf(
|
||||
/* translators: %d: Multisite site ID */
|
||||
__( 'Current Site: #%d', 'query-monitor' ),
|
||||
$current_blog->blog_id
|
||||
),
|
||||
'data' => $current_blog,
|
||||
);
|
||||
}
|
||||
|
||||
if ( QM_Util::is_multi_network() ) {
|
||||
$this->data->multisite['current_network'] = array(
|
||||
'title' => sprintf(
|
||||
/* translators: %d: Multisite network ID */
|
||||
__( 'Current Network: #%d', 'query-monitor' ),
|
||||
$current_site->id
|
||||
),
|
||||
'data' => $current_site,
|
||||
);
|
||||
}
|
||||
|
||||
if ( is_admin() ) {
|
||||
if ( isset( $_SERVER['REQUEST_URI'] ) ) {
|
||||
$path = parse_url( home_url(), PHP_URL_PATH );
|
||||
$home_path = trim( $path ?: '', '/' );
|
||||
$request = wp_unslash( $_SERVER['REQUEST_URI'] ); // phpcs:ignore
|
||||
|
||||
$this->data->request['request'] = str_replace( "/{$home_path}/", '', $request );
|
||||
} else {
|
||||
$this->data->request['request'] = '';
|
||||
}
|
||||
foreach ( array( 'query_string' ) as $item ) {
|
||||
$this->data->request[ $item ] = $wp->$item;
|
||||
}
|
||||
} else {
|
||||
foreach ( array( 'request', 'matched_rule', 'matched_query', 'query_string' ) as $item ) {
|
||||
$this->data->request[ $item ] = $wp->$item;
|
||||
}
|
||||
}
|
||||
|
||||
/** This filter is documented in wp-includes/class-wp.php */
|
||||
$plugin_qvars = array_flip( apply_filters( 'query_vars', array() ) );
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
$qvars = $wp_query->query_vars;
|
||||
$query_vars = array();
|
||||
|
||||
foreach ( $qvars as $k => $v ) {
|
||||
if ( isset( $plugin_qvars[ $k ] ) ) {
|
||||
if ( '' !== $v ) {
|
||||
$query_vars[ $k ] = $v;
|
||||
}
|
||||
} else {
|
||||
if ( ! empty( $v ) ) {
|
||||
$query_vars[ $k ] = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ksort( $query_vars );
|
||||
|
||||
# First add plugin vars to $this->data->qvars:
|
||||
foreach ( $query_vars as $k => $v ) {
|
||||
if ( isset( $plugin_qvars[ $k ] ) ) {
|
||||
$this->data->qvars[ $k ] = $v;
|
||||
$this->data->plugin_qvars[ $k ] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
# Now add all other vars to $this->data->qvars:
|
||||
foreach ( $query_vars as $k => $v ) {
|
||||
if ( ! isset( $plugin_qvars[ $k ] ) ) {
|
||||
$this->data->qvars[ $k ] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
switch ( true ) {
|
||||
|
||||
case ! is_object( $qo ):
|
||||
// Nada
|
||||
break;
|
||||
|
||||
case is_a( $qo, 'WP_Post' ):
|
||||
// Single post
|
||||
$this->data->queried_object['title'] = sprintf(
|
||||
/* translators: 1: Post type name, 2: Post ID */
|
||||
__( 'Single %1$s: #%2$d', 'query-monitor' ),
|
||||
get_post_type_object( $qo->post_type )->labels->singular_name,
|
||||
$qo->ID
|
||||
);
|
||||
break;
|
||||
|
||||
case is_a( $qo, 'WP_User' ):
|
||||
// Author archive
|
||||
$this->data->queried_object['title'] = sprintf(
|
||||
/* translators: %s: Author name */
|
||||
__( 'Author archive: %s', 'query-monitor' ),
|
||||
$qo->user_nicename
|
||||
);
|
||||
break;
|
||||
|
||||
case is_a( $qo, 'WP_Term' ):
|
||||
case property_exists( $qo, 'slug' ):
|
||||
// Term archive
|
||||
$this->data->queried_object['title'] = sprintf(
|
||||
/* translators: %s: Taxonomy term name */
|
||||
__( 'Term archive: %s', 'query-monitor' ),
|
||||
$qo->slug
|
||||
);
|
||||
break;
|
||||
|
||||
case is_a( $qo, 'WP_Post_Type' ):
|
||||
case property_exists( $qo, 'has_archive' ):
|
||||
// Post type archive
|
||||
$this->data->queried_object['title'] = sprintf(
|
||||
/* translators: %s: Post type name */
|
||||
__( 'Post type archive: %s', 'query-monitor' ),
|
||||
$qo->name
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Unknown, but we have a queried object
|
||||
$this->data->queried_object['title'] = __( 'Unknown queried object', 'query-monitor' );
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if ( $qo ) {
|
||||
$this->data->queried_object['data'] = $qo;
|
||||
}
|
||||
|
||||
if ( isset( $_SERVER['REQUEST_METHOD'] ) ) {
|
||||
$this->data->request_method = strtoupper( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ); // phpcs:ignore
|
||||
} else {
|
||||
$this->data->request_method = '';
|
||||
}
|
||||
|
||||
if ( is_admin() || QM_Util::is_async() || empty( $wp_rewrite->rules ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$matching = array();
|
||||
|
||||
/** @var array<string, string> */
|
||||
$rewrite_rules = $wp_rewrite->rules;
|
||||
|
||||
foreach ( $rewrite_rules as $match => $query ) {
|
||||
if ( preg_match( "#^{$match}#", $this->data->request['request'] ) ) {
|
||||
$matching[ $match ] = $query;
|
||||
}
|
||||
}
|
||||
|
||||
$this->data->matching_rewrites = $matching;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, QM_Collector> $collectors
|
||||
* @param QueryMonitor $qm
|
||||
* @return array<string, QM_Collector>
|
||||
*/
|
||||
function register_qm_collector_request( array $collectors, QueryMonitor $qm ) {
|
||||
$collectors['request'] = new QM_Collector_Request();
|
||||
return $collectors;
|
||||
}
|
||||
|
||||
add_filter( 'qm/collectors', 'register_qm_collector_request', 10, 2 );
|
594
wp-content/plugins/query-monitor/collectors/theme.php
Normal file
594
wp-content/plugins/query-monitor/collectors/theme.php
Normal file
@ -0,0 +1,594 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* Template and theme collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_Theme>
|
||||
*/
|
||||
class QM_Collector_Theme extends QM_DataCollector {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $id = 'response';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $got_theme_compat = false;
|
||||
|
||||
/**
|
||||
* @var array<int, mixed>
|
||||
*/
|
||||
protected $requested_template_parts = array();
|
||||
|
||||
/**
|
||||
* @var array<int, mixed>
|
||||
*/
|
||||
protected $requested_template_part_posts = array();
|
||||
|
||||
/**
|
||||
* @var array<int, mixed>
|
||||
*/
|
||||
protected $requested_template_part_files = array();
|
||||
|
||||
/**
|
||||
* @var array<int, mixed>
|
||||
*/
|
||||
protected $requested_template_part_nopes = array();
|
||||
|
||||
/**
|
||||
* @var ?WP_Block_Template
|
||||
*/
|
||||
protected $block_template = null;
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_Theme();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function set_up() {
|
||||
parent::set_up();
|
||||
|
||||
add_filter( 'body_class', array( $this, 'filter_body_class' ), 9999 );
|
||||
add_filter( 'timber/output', array( $this, 'filter_timber_output' ), 9999, 3 );
|
||||
add_action( 'template_redirect', array( $this, 'action_template_redirect' ) );
|
||||
add_action( 'get_template_part', array( $this, 'action_get_template_part' ), 10, 3 );
|
||||
add_action( 'get_header', array( $this, 'action_get_position' ) );
|
||||
add_action( 'get_sidebar', array( $this, 'action_get_position' ) );
|
||||
add_action( 'get_footer', array( $this, 'action_get_position' ) );
|
||||
add_action( 'render_block_core_template_part_post', array( $this, 'action_render_block_core_template_part_post' ), 10, 3 );
|
||||
add_action( 'render_block_core_template_part_file', array( $this, 'action_render_block_core_template_part_file' ), 10, 3 );
|
||||
add_action( 'render_block_core_template_part_none', array( $this, 'action_render_block_core_template_part_none' ), 10, 3 );
|
||||
add_action( 'gutenberg_render_block_core_template_part_post', array( $this, 'action_render_block_core_template_part_post' ), 10, 3 );
|
||||
add_action( 'gutenberg_render_block_core_template_part_file', array( $this, 'action_render_block_core_template_part_file' ), 10, 3 );
|
||||
add_action( 'gutenberg_render_block_core_template_part_none', array( $this, 'action_render_block_core_template_part_none' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function tear_down() {
|
||||
remove_filter( 'body_class', array( $this, 'filter_body_class' ), 9999 );
|
||||
remove_filter( 'timber/output', array( $this, 'filter_timber_output' ), 9999 );
|
||||
remove_action( 'template_redirect', array( $this, 'action_template_redirect' ) );
|
||||
remove_action( 'get_template_part', array( $this, 'action_get_template_part' ), 10 );
|
||||
remove_action( 'get_header', array( $this, 'action_get_position' ) );
|
||||
remove_action( 'get_sidebar', array( $this, 'action_get_position' ) );
|
||||
remove_action( 'get_footer', array( $this, 'action_get_position' ) );
|
||||
remove_action( 'render_block_core_template_part_post', array( $this, 'action_render_block_core_template_part_post' ), 10 );
|
||||
remove_action( 'render_block_core_template_part_file', array( $this, 'action_render_block_core_template_part_file' ), 10 );
|
||||
remove_action( 'render_block_core_template_part_none', array( $this, 'action_render_block_core_template_part_none' ), 10 );
|
||||
remove_action( 'gutenberg_render_block_core_template_part_post', array( $this, 'action_render_block_core_template_part_post' ), 10 );
|
||||
remove_action( 'gutenberg_render_block_core_template_part_file', array( $this, 'action_render_block_core_template_part_file' ), 10 );
|
||||
remove_action( 'gutenberg_render_block_core_template_part_none', array( $this, 'action_render_block_core_template_part_none' ), 10 );
|
||||
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires before the header/sidebar/footer template file is loaded.
|
||||
*
|
||||
* @param string|null $name Name of the specific file to use. Null for the default.
|
||||
* @return void
|
||||
*/
|
||||
public function action_get_position( $name ) {
|
||||
$filter = current_filter();
|
||||
$trace = new QM_Backtrace( array(
|
||||
'ignore_hook' => array(
|
||||
$filter => true,
|
||||
),
|
||||
) );
|
||||
|
||||
$position = str_replace( 'get_', '', $filter );
|
||||
$templates = array();
|
||||
if ( '' !== (string) $name ) {
|
||||
$templates[] = "{$position}-{$name}.php";
|
||||
}
|
||||
|
||||
$templates[] = "{$position}.php";
|
||||
|
||||
$data = array(
|
||||
'slug' => $position,
|
||||
'name' => $name,
|
||||
'templates' => $templates,
|
||||
'caller' => $trace->get_caller(),
|
||||
);
|
||||
|
||||
$this->requested_template_parts[] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_actions() {
|
||||
return array(
|
||||
'template_redirect',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_filters() {
|
||||
$filters = array(
|
||||
'stylesheet',
|
||||
'stylesheet_directory',
|
||||
'template',
|
||||
'template_directory',
|
||||
'template_include',
|
||||
);
|
||||
|
||||
foreach ( self::get_query_filter_names() as $filter ) {
|
||||
$filters[] = $filter;
|
||||
$filters[] = "{$filter}_hierarchy";
|
||||
}
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function get_concerned_options() {
|
||||
return array(
|
||||
'stylesheet',
|
||||
'template',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int|string, string>
|
||||
*/
|
||||
public static function get_query_template_names() {
|
||||
$names = array();
|
||||
|
||||
$names['embed'] = 'is_embed';
|
||||
$names['404'] = 'is_404';
|
||||
$names['search'] = 'is_search';
|
||||
$names['front_page'] = 'is_front_page';
|
||||
$names['home'] = 'is_home';
|
||||
$names['privacy_policy'] = 'is_privacy_policy';
|
||||
$names['post_type_archive'] = 'is_post_type_archive';
|
||||
$names['taxonomy'] = 'is_tax';
|
||||
$names['attachment'] = 'is_attachment';
|
||||
$names['single'] = 'is_single';
|
||||
$names['page'] = 'is_page';
|
||||
$names['singular'] = 'is_singular';
|
||||
$names['category'] = 'is_category';
|
||||
$names['tag'] = 'is_tag';
|
||||
$names['author'] = 'is_author';
|
||||
$names['date'] = 'is_date';
|
||||
$names['archive'] = 'is_archive';
|
||||
$names['index'] = '__return_true';
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int|string, string>
|
||||
*/
|
||||
public static function get_query_filter_names() {
|
||||
$names = array();
|
||||
|
||||
$names['embed'] = 'embed_template';
|
||||
$names['404'] = '404_template';
|
||||
$names['search'] = 'search_template';
|
||||
$names['front_page'] = 'frontpage_template';
|
||||
$names['home'] = 'home_template';
|
||||
$names['privacy_policy'] = 'privacypolicy_template';
|
||||
$names['taxonomy'] = 'taxonomy_template';
|
||||
$names['attachment'] = 'attachment_template';
|
||||
$names['single'] = 'single_template';
|
||||
$names['page'] = 'page_template';
|
||||
$names['singular'] = 'singular_template';
|
||||
$names['category'] = 'category_template';
|
||||
$names['tag'] = 'tag_template';
|
||||
$names['author'] = 'author_template';
|
||||
$names['date'] = 'date_template';
|
||||
$names['archive'] = 'archive_template';
|
||||
$names['index'] = 'index_template';
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function action_template_redirect() {
|
||||
add_filter( 'template_include', array( $this, 'filter_template_include' ), PHP_INT_MAX );
|
||||
|
||||
foreach ( self::get_query_template_names() as $template => $conditional ) {
|
||||
// If a matching theme-compat file is found, further conditional checks won't occur in template-loader.php
|
||||
if ( $this->got_theme_compat ) {
|
||||
break;
|
||||
}
|
||||
|
||||
$get_template = "get_{$template}_template";
|
||||
|
||||
if ( function_exists( $conditional ) && function_exists( $get_template ) && call_user_func( $conditional ) ) {
|
||||
$filter = str_replace( '_', '', "{$template}" );
|
||||
add_filter( "{$filter}_template_hierarchy", array( $this, 'filter_template_hierarchy' ), PHP_INT_MAX );
|
||||
add_filter( "{$filter}_template", array( $this, 'filter_template' ), PHP_INT_MAX, 3 );
|
||||
call_user_func( $get_template );
|
||||
remove_filter( "{$filter}_template_hierarchy", array( $this, 'filter_template_hierarchy' ), PHP_INT_MAX );
|
||||
remove_filter( "{$filter}_template", array( $this, 'filter_template' ), PHP_INT_MAX );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires before a template part is loaded.
|
||||
*
|
||||
* @param string $slug The slug name for the generic template.
|
||||
* @param string $name The name of the specialized template or an empty
|
||||
* string if there is none.
|
||||
* @param array<int, string> $templates Array of template files to search for, in order.
|
||||
* @return void
|
||||
*/
|
||||
public function action_get_template_part( $slug, $name, $templates ) {
|
||||
$part = compact( 'slug', 'name', 'templates' );
|
||||
|
||||
$trace = new QM_Backtrace( array(
|
||||
'ignore_hook' => array(
|
||||
current_filter() => true,
|
||||
),
|
||||
) );
|
||||
|
||||
$part['caller'] = $trace->get_caller();
|
||||
|
||||
$this->requested_template_parts[] = $part;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when a post is loaded for a template part block.
|
||||
*
|
||||
* @param string $template_part_id
|
||||
* @param mixed[] $attributes
|
||||
* @param WP_Post $post
|
||||
* @return void
|
||||
*/
|
||||
public function action_render_block_core_template_part_post( $template_part_id, $attributes, WP_Post $post ) {
|
||||
$part = array(
|
||||
'id' => $template_part_id,
|
||||
'attributes' => $attributes,
|
||||
'post' => $post->ID,
|
||||
);
|
||||
$this->requested_template_part_posts[] = $part;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when a file is loaded for a template part block.
|
||||
*
|
||||
* @param string $template_part_id
|
||||
* @param mixed[] $attributes
|
||||
* @param string $template_part_file_path
|
||||
* @return void
|
||||
*/
|
||||
public function action_render_block_core_template_part_file( $template_part_id, $attributes, $template_part_file_path ) {
|
||||
$part = array(
|
||||
'id' => $template_part_id,
|
||||
'attributes' => $attributes,
|
||||
'path' => $template_part_file_path,
|
||||
);
|
||||
$this->requested_template_part_files[] = $part;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when neither a post nor file is found for a template part block.
|
||||
*
|
||||
* @param string $template_part_id
|
||||
* @param mixed[] $attributes
|
||||
* @param string $template_part_file_path
|
||||
* @return void
|
||||
*/
|
||||
public function action_render_block_core_template_part_none( $template_part_id, $attributes, $template_part_file_path ) {
|
||||
$part = array(
|
||||
'id' => $template_part_id,
|
||||
'attributes' => $attributes,
|
||||
'path' => $template_part_file_path,
|
||||
);
|
||||
$this->requested_template_part_nopes[] = $part;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string> $templates
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function filter_template_hierarchy( array $templates ) {
|
||||
if ( ! isset( $this->data->template_hierarchy ) ) {
|
||||
$this->data->template_hierarchy = array();
|
||||
}
|
||||
|
||||
foreach ( $templates as $template_name ) {
|
||||
if ( file_exists( ABSPATH . WPINC . '/theme-compat/' . $template_name ) ) {
|
||||
$this->got_theme_compat = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( self::wp_is_block_theme() ) {
|
||||
$block_theme_folders = self::wp_get_block_theme_folders();
|
||||
foreach ( $templates as $template ) {
|
||||
if ( str_ends_with( $template, '.php' ) ) {
|
||||
// Standard PHP template, inject the HTML version:
|
||||
$this->data->template_hierarchy[] = $block_theme_folders['wp_template'] . '/' . str_replace( '.php', '.html', $template );
|
||||
$this->data->template_hierarchy[] = $template;
|
||||
} else {
|
||||
// Block theme custom template (eg. from `customTemplates` in theme.json), doesn't have a suffix:
|
||||
$this->data->template_hierarchy[] = $block_theme_folders['wp_template'] . '/' . $template . '.html';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->data->template_hierarchy = array_merge( $this->data->template_hierarchy, $templates );
|
||||
}
|
||||
|
||||
return $templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $template Path to the template. See locate_template().
|
||||
* @param string $type Sanitized filename without extension.
|
||||
* @param array<int, string> $templates A list of template candidates, in descending order of priority.
|
||||
* @return string Full path to template file.
|
||||
*/
|
||||
public function filter_template( $template, $type, $templates ) {
|
||||
if ( $this->data->block_template instanceof \WP_Block_Template ) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
$block_template = self::wp_resolve_block_template( $type, $templates, $template );
|
||||
|
||||
if ( $block_template ) {
|
||||
$this->data->block_template = $block_template;
|
||||
}
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string> $class
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function filter_body_class( array $class ) {
|
||||
$this->data->body_class = $class;
|
||||
return $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $template_path
|
||||
* @return string
|
||||
*/
|
||||
public function filter_template_include( $template_path ) {
|
||||
$this->data->template_path = $template_path;
|
||||
return $template_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $output
|
||||
* @param mixed $data
|
||||
* @param string $file
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function filter_timber_output( $output, $data = null, $file = null ) {
|
||||
if ( $file ) {
|
||||
$this->data->timber_files[] = $file;
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
|
||||
$stylesheet_directory = QM_Util::standard_dir( get_stylesheet_directory() );
|
||||
$template_directory = QM_Util::standard_dir( get_template_directory() );
|
||||
$theme_directory = QM_Util::standard_dir( get_theme_root() );
|
||||
|
||||
if ( isset( $this->data->template_hierarchy ) ) {
|
||||
$this->data->template_hierarchy = array_unique( $this->data->template_hierarchy );
|
||||
}
|
||||
|
||||
if ( ! empty( $this->requested_template_parts ) ) {
|
||||
$this->data->template_parts = array();
|
||||
$this->data->theme_template_parts = array();
|
||||
$this->data->count_template_parts = array();
|
||||
|
||||
foreach ( $this->requested_template_parts as $part ) {
|
||||
$file = locate_template( $part['templates'] );
|
||||
|
||||
if ( ! $file ) {
|
||||
$this->data->unsuccessful_template_parts[] = $part;
|
||||
continue;
|
||||
}
|
||||
|
||||
$file = QM_Util::standard_dir( $file );
|
||||
|
||||
if ( isset( $this->data->count_template_parts[ $file ] ) ) {
|
||||
$this->data->count_template_parts[ $file ]++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->data->count_template_parts[ $file ] = 1;
|
||||
|
||||
$filename = str_replace( array(
|
||||
$stylesheet_directory,
|
||||
$template_directory,
|
||||
), '', $file );
|
||||
|
||||
$display = trim( $filename, '/' );
|
||||
$theme_display = trim( str_replace( $theme_directory, '', $file ), '/' );
|
||||
|
||||
$this->data->template_parts[ $file ] = $display;
|
||||
$this->data->theme_template_parts[ $file ] = $theme_display;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
! empty( $this->requested_template_part_posts ) ||
|
||||
! empty( $this->requested_template_part_files ) ||
|
||||
! empty( $this->requested_template_part_nopes )
|
||||
) {
|
||||
$this->data->template_parts = array();
|
||||
$this->data->theme_template_parts = array();
|
||||
$this->data->count_template_parts = array();
|
||||
|
||||
$posts = ! empty( $this->requested_template_part_posts ) ? $this->requested_template_part_posts : array();
|
||||
$files = ! empty( $this->requested_template_part_files ) ? $this->requested_template_part_files : array();
|
||||
$nopes = ! empty( $this->requested_template_part_nopes ) ? $this->requested_template_part_nopes : array();
|
||||
|
||||
$all = array_merge( $posts, $files, $nopes );
|
||||
|
||||
foreach ( $all as $part ) {
|
||||
$file = $part['path'] ?? $part['post'];
|
||||
|
||||
if ( isset( $this->data->count_template_parts[ $file ] ) ) {
|
||||
$this->data->count_template_parts[ $file ]++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->data->count_template_parts[ $file ] = 1;
|
||||
|
||||
if ( isset( $part['post'] ) ) {
|
||||
$display = $part['id'];
|
||||
$theme_display = $display;
|
||||
} else {
|
||||
$file = QM_Util::standard_dir( $file );
|
||||
|
||||
$filename = str_replace( array(
|
||||
$stylesheet_directory,
|
||||
$template_directory,
|
||||
), '', $file );
|
||||
|
||||
$display = trim( $filename, '/' );
|
||||
$theme_display = trim( str_replace( $theme_directory, '', $file ), '/' );
|
||||
}
|
||||
|
||||
$this->data->template_parts[ $file ] = $display;
|
||||
$this->data->theme_template_parts[ $file ] = $theme_display;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $this->data->template_path ) ) {
|
||||
$template_path = QM_Util::standard_dir( $this->data->template_path );
|
||||
$template_file = str_replace( array( $stylesheet_directory, $template_directory, ABSPATH ), '', $template_path );
|
||||
$template_file = ltrim( $template_file, '/' );
|
||||
$theme_template_file = str_replace( array( $theme_directory, ABSPATH ), '', $template_path );
|
||||
$theme_template_file = ltrim( $theme_template_file, '/' );
|
||||
|
||||
$this->data->template_path = $template_path;
|
||||
$this->data->template_file = $template_file;
|
||||
$this->data->theme_template_file = $theme_template_file;
|
||||
}
|
||||
|
||||
$this->data->stylesheet = get_stylesheet();
|
||||
$this->data->template = get_template();
|
||||
$this->data->is_child_theme = ( $this->data->stylesheet !== $this->data->template );
|
||||
$this->data->theme_dirs = array(
|
||||
$this->data->stylesheet => $stylesheet_directory,
|
||||
$this->data->template => $template_directory,
|
||||
);
|
||||
|
||||
$this->data->theme_folders = self::wp_get_block_theme_folders();
|
||||
|
||||
$stylesheet_theme_json = $stylesheet_directory . '/theme.json';
|
||||
$template_theme_json = $template_directory . '/theme.json';
|
||||
|
||||
if ( is_readable( $stylesheet_theme_json ) ) {
|
||||
$this->data->stylesheet_theme_json = $stylesheet_theme_json;
|
||||
}
|
||||
|
||||
if ( is_readable( $template_theme_json ) ) {
|
||||
$this->data->template_theme_json = $template_theme_json;
|
||||
}
|
||||
|
||||
if ( isset( $this->data->body_class ) ) {
|
||||
asort( $this->data->body_class );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
protected static function wp_is_block_theme() {
|
||||
return function_exists( 'wp_is_block_theme' ) && wp_is_block_theme();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected static function wp_get_block_theme_folders() {
|
||||
if ( ! function_exists( 'get_block_theme_folders' ) ) {
|
||||
return array(
|
||||
'wp_template' => 'templates',
|
||||
'wp_template_part' => 'parts',
|
||||
);
|
||||
}
|
||||
|
||||
return get_block_theme_folders();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $template_type The current template type.
|
||||
* @param array<int, string> $template_hierarchy The current template hierarchy, ordered by priority.
|
||||
* @param string $fallback_template A PHP fallback template to use if no matching block template is found.
|
||||
* @return WP_Block_Template|null template A template object, or null if none could be found.
|
||||
*/
|
||||
protected static function wp_resolve_block_template( $template_type, $template_hierarchy, $fallback_template ) {
|
||||
if ( ! function_exists( 'resolve_block_template' ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( ! current_theme_supports( 'block-templates' ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return resolve_block_template( $template_type, $template_hierarchy, $fallback_template );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, QM_Collector> $collectors
|
||||
* @param QueryMonitor $qm
|
||||
* @return array<string, QM_Collector>
|
||||
*/
|
||||
function register_qm_collector_theme( array $collectors, QueryMonitor $qm ) {
|
||||
$collectors['response'] = new QM_Collector_Theme();
|
||||
return $collectors;
|
||||
}
|
||||
|
||||
if ( ! is_admin() ) {
|
||||
add_filter( 'qm/collectors', 'register_qm_collector_theme', 10, 2 );
|
||||
}
|
168
wp-content/plugins/query-monitor/collectors/timing.php
Normal file
168
wp-content/plugins/query-monitor/collectors/timing.php
Normal file
@ -0,0 +1,168 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* Timing and profiling collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_Timing>
|
||||
*/
|
||||
class QM_Collector_Timing extends QM_DataCollector {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $id = 'timing';
|
||||
|
||||
/**
|
||||
* @var array<string, QM_Timer>
|
||||
*/
|
||||
private $track_timer = array();
|
||||
|
||||
/**
|
||||
* @var array<string, QM_Timer>
|
||||
*/
|
||||
private $start = array();
|
||||
|
||||
/**
|
||||
* @var array<string, QM_Timer>
|
||||
*/
|
||||
private $stop = array();
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_Timing();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function set_up() {
|
||||
parent::set_up();
|
||||
|
||||
add_action( 'qm/start', array( $this, 'action_function_time_start' ), 10, 1 );
|
||||
add_action( 'qm/stop', array( $this, 'action_function_time_stop' ), 10, 1 );
|
||||
add_action( 'qm/lap', array( $this, 'action_function_time_lap' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function tear_down() {
|
||||
remove_action( 'qm/start', array( $this, 'action_function_time_start' ), 10 );
|
||||
remove_action( 'qm/stop', array( $this, 'action_function_time_stop' ), 10 );
|
||||
remove_action( 'qm/lap', array( $this, 'action_function_time_lap' ), 10 );
|
||||
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $function
|
||||
* @return void
|
||||
*/
|
||||
public function action_function_time_start( $function ) {
|
||||
$this->track_timer[ $function ] = new QM_Timer();
|
||||
$this->start[ $function ] = $this->track_timer[ $function ]->start();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $function
|
||||
* @return void
|
||||
*/
|
||||
public function action_function_time_stop( $function ) {
|
||||
if ( ! isset( $this->track_timer[ $function ] ) ) {
|
||||
$trace = new QM_Backtrace();
|
||||
$this->data->warning[] = array(
|
||||
'function' => $function,
|
||||
'message' => __( 'Timer not started', 'query-monitor' ),
|
||||
'filtered_trace' => $trace->get_filtered_trace(),
|
||||
'component' => $trace->get_component(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
$this->stop[ $function ] = $this->track_timer[ $function ]->stop();
|
||||
$this->calculate_time( $function );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $function
|
||||
* @param string $name
|
||||
* @return void
|
||||
*/
|
||||
public function action_function_time_lap( $function, $name = null ) {
|
||||
if ( ! isset( $this->track_timer[ $function ] ) ) {
|
||||
$trace = new QM_Backtrace();
|
||||
$this->data->warning[] = array(
|
||||
'function' => $function,
|
||||
'message' => __( 'Timer not started', 'query-monitor' ),
|
||||
'filtered_trace' => $trace->get_filtered_trace(),
|
||||
'component' => $trace->get_component(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
$this->track_timer[ $function ]->lap( array(), $name );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $function
|
||||
* @return void
|
||||
*/
|
||||
public function calculate_time( $function ) {
|
||||
$trace = $this->track_timer[ $function ]->get_trace();
|
||||
$function_time = $this->track_timer[ $function ]->get_time();
|
||||
$function_memory = $this->track_timer[ $function ]->get_memory();
|
||||
$function_laps = $this->track_timer[ $function ]->get_laps();
|
||||
$start_time = $this->track_timer[ $function ]->get_start_time();
|
||||
$end_time = $this->track_timer[ $function ]->get_end_time();
|
||||
|
||||
$this->data->timing[] = array(
|
||||
'function' => $function,
|
||||
'function_time' => $function_time,
|
||||
'function_memory' => $function_memory,
|
||||
'laps' => $function_laps,
|
||||
'filtered_trace' => $trace->get_filtered_trace(),
|
||||
'component' => $trace->get_component(),
|
||||
'start_time' => ( $start_time - $_SERVER['REQUEST_TIME_FLOAT'] ),
|
||||
'end_time' => ( $end_time - $_SERVER['REQUEST_TIME_FLOAT'] ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
foreach ( $this->start as $function => $value ) {
|
||||
if ( ! isset( $this->stop[ $function ] ) ) {
|
||||
$trace = $this->track_timer[ $function ]->get_trace();
|
||||
$this->data->warning[] = array(
|
||||
'function' => $function,
|
||||
'message' => __( 'Timer not stopped', 'query-monitor' ),
|
||||
'filtered_trace' => $trace->get_filtered_trace(),
|
||||
'component' => $trace->get_component(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $this->data->timing ) ) {
|
||||
usort( $this->data->timing, array( $this, 'sort_by_start_time' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $a
|
||||
* @param mixed[] $b
|
||||
* @return int
|
||||
* @phpstan-return -1|0|1
|
||||
*/
|
||||
public function sort_by_start_time( array $a, array $b ) {
|
||||
return $a['start_time'] <=> $b['start_time'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Load early in case a plugin is setting the function to be checked when it initialises instead of after the `plugins_loaded` hook
|
||||
QM_Collectors::add( new QM_Collector_Timing() );
|
111
wp-content/plugins/query-monitor/collectors/transients.php
Normal file
111
wp-content/plugins/query-monitor/collectors/transients.php
Normal file
@ -0,0 +1,111 @@
|
||||
<?php declare(strict_types = 1);
|
||||
/**
|
||||
* Transient storage collector.
|
||||
*
|
||||
* @package query-monitor
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @extends QM_DataCollector<QM_Data_Transients>
|
||||
*/
|
||||
class QM_Collector_Transients extends QM_DataCollector {
|
||||
|
||||
public $id = 'transients';
|
||||
|
||||
public function get_storage(): QM_Data {
|
||||
return new QM_Data_Transients();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function set_up() {
|
||||
parent::set_up();
|
||||
|
||||
add_action( 'setted_site_transient', array( $this, 'action_setted_site_transient' ), 10, 3 );
|
||||
add_action( 'setted_transient', array( $this, 'action_setted_blog_transient' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function tear_down() {
|
||||
remove_action( 'setted_site_transient', array( $this, 'action_setted_site_transient' ), 10 );
|
||||
remove_action( 'setted_transient', array( $this, 'action_setted_blog_transient' ), 10 );
|
||||
parent::tear_down();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $transient
|
||||
* @param mixed $value
|
||||
* @param int $expiration
|
||||
* @return void
|
||||
*/
|
||||
public function action_setted_site_transient( $transient, $value, $expiration ) {
|
||||
$this->setted_transient( $transient, 'site', $value, $expiration );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $transient
|
||||
* @param mixed $value
|
||||
* @param int $expiration
|
||||
* @return void
|
||||
*/
|
||||
public function action_setted_blog_transient( $transient, $value, $expiration ) {
|
||||
$this->setted_transient( $transient, 'blog', $value, $expiration );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $transient
|
||||
* @param string $type
|
||||
* @param mixed $value
|
||||
* @param int $expiration
|
||||
* @phpstan-param 'site'|'blog' $value
|
||||
* @return void
|
||||
*/
|
||||
public function setted_transient( $transient, $type, $value, $expiration ) {
|
||||
$trace = new QM_Backtrace( array(
|
||||
'ignore_hook' => array(
|
||||
current_filter() => true,
|
||||
),
|
||||
'ignore_func' => array(
|
||||
'set_transient' => true,
|
||||
'set_site_transient' => true,
|
||||
),
|
||||
) );
|
||||
|
||||
$name = str_replace( array(
|
||||
'_site_transient_',
|
||||
'_transient_',
|
||||
), '', $transient );
|
||||
|
||||
$size = strlen( (string) maybe_serialize( $value ) );
|
||||
|
||||
$this->data->trans[] = array(
|
||||
'name' => $name,
|
||||
'filtered_trace' => $trace->get_filtered_trace(),
|
||||
'component' => $trace->get_component(),
|
||||
'type' => $type,
|
||||
'value' => $value,
|
||||
'expiration' => $expiration,
|
||||
'exp_diff' => ( $expiration ? human_time_diff( 0, $expiration ) : '' ),
|
||||
'size' => $size,
|
||||
'size_formatted' => (string) size_format( $size ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function process() {
|
||||
$this->data->has_type = is_multisite();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Load early in case a plugin is setting transients when it initialises instead of after the `plugins_loaded` hook
|
||||
QM_Collectors::add( new QM_Collector_Transients() );
|
Reference in New Issue
Block a user