Initial commit

This commit is contained in:
Felix Förtsch
2020-10-20 14:39:50 +02:00
commit 648ded8896
1225 changed files with 216511 additions and 0 deletions

View File

@@ -0,0 +1,130 @@
<?php
/**
* Plugin activation handler.
*
* @package query-monitor
*/
class QM_Activation extends QM_Plugin {
protected function __construct( $file ) {
# PHP version handling
if ( ! self::php_version_met() ) {
add_action( 'all_admin_notices', array( $this, 'php_notice' ) );
return;
}
# Filters
add_filter( 'pre_update_option_active_plugins', array( $this, 'filter_active_plugins' ) );
add_filter( 'pre_update_site_option_active_sitewide_plugins', array( $this, 'filter_active_sitewide_plugins' ) );
# Activation and deactivation
register_activation_hook( $file, array( $this, 'activate' ) );
register_deactivation_hook( $file, array( $this, 'deactivate' ) );
# Parent setup:
parent::__construct( $file );
}
public function activate( $sitewide = false ) {
$db = WP_CONTENT_DIR . '/db.php';
if ( ! file_exists( $db ) && function_exists( 'symlink' ) ) {
@symlink( $this->plugin_path( 'wp-content/db.php' ), $db ); // phpcs:ignore
}
if ( $sitewide ) {
update_site_option( 'active_sitewide_plugins', get_site_option( 'active_sitewide_plugins' ) );
} else {
update_option( 'active_plugins', get_option( 'active_plugins' ) );
}
}
public function deactivate() {
$admins = QM_Util::get_admins();
// Remove legacy capability handling:
if ( $admins ) {
$admins->remove_cap( 'view_query_monitor' );
}
# Only delete db.php if it belongs to Query Monitor
if ( file_exists( WP_CONTENT_DIR . '/db.php' ) && class_exists( 'QM_DB' ) ) {
unlink( WP_CONTENT_DIR . '/db.php' ); // phpcs:ignore
}
}
public function filter_active_plugins( $plugins ) {
// this needs to run on the cli too
if ( empty( $plugins ) ) {
return $plugins;
}
$f = preg_quote( basename( $this->plugin_base() ), '/' );
return array_merge(
preg_grep( '/' . $f . '$/', $plugins ),
preg_grep( '/' . $f . '$/', $plugins, PREG_GREP_INVERT )
);
}
public function filter_active_sitewide_plugins( $plugins ) {
if ( empty( $plugins ) ) {
return $plugins;
}
$f = $this->plugin_base();
if ( isset( $plugins[ $f ] ) ) {
unset( $plugins[ $f ] );
return array_merge( array(
$f => time(),
), $plugins );
} else {
return $plugins;
}
}
public function php_notice() {
?>
<div id="qm_php_notice" class="error">
<p>
<span class="dashicons dashicons-warning" style="color:#dd3232" aria-hidden="true"></span>
<?php
echo esc_html( sprintf(
/* Translators: 1: Minimum required PHP version, 2: Current PHP version. */
__( 'The Query Monitor plugin requires PHP version %1$s or higher. This site is running version %2$s.', 'query-monitor' ),
self::$minimum_php_version,
PHP_VERSION
) );
?>
</p>
</div>
<?php
}
public static function init( $file = null ) {
static $instance = null;
if ( ! $instance ) {
$instance = new QM_Activation( $file );
}
return $instance;
}
}

View File

@@ -0,0 +1,348 @@
<?php
/**
* Function call backtrace container.
*
* @package query-monitor
*/
if ( ! class_exists( 'QM_Backtrace' ) ) {
class QM_Backtrace {
protected static $ignore_class = array(
'wpdb' => true,
'QueryMonitor' => true,
'W3_Db' => true,
'Debug_Bar_PHP' => true,
'WP_Hook' => true,
);
protected static $ignore_method = array();
protected static $ignore_func = array(
'include_once' => true,
'require_once' => true,
'include' => true,
'require' => true,
'call_user_func_array' => true,
'call_user_func' => true,
'trigger_error' => true,
'_doing_it_wrong' => true,
'_deprecated_argument' => true,
'_deprecated_file' => true,
'_deprecated_function' => true,
'dbDelta' => true,
);
protected static $show_args = array(
'do_action' => 1,
'apply_filters' => 1,
'do_action_ref_array' => 1,
'apply_filters_ref_array' => 1,
'get_template_part' => 2,
'get_extended_template_part' => 2,
'load_template' => 'dir',
'dynamic_sidebar' => 1,
'get_header' => 1,
'get_sidebar' => 1,
'get_footer' => 1,
'class_exists' => 2,
'current_user_can' => 3,
'user_can' => 4,
'current_user_can_for_blog' => 4,
'author_can' => 4,
);
protected static $filtered = false;
protected $trace = null;
protected $filtered_trace = null;
protected $calling_line = 0;
protected $calling_file = '';
public function __construct( array $args = array(), array $trace = null ) {
$this->trace = ( null === $trace ) ? debug_backtrace( false ) : $trace;
$args = array_merge( array(
'ignore_current_filter' => true,
'ignore_frames' => 0,
), $args );
$this->ignore( 1 ); # Self-awareness
/**
* If error_handler() is in the trace, QM fails later when it tries
* to get $lowest['file'] in get_filtered_trace()
*/
if ( 'error_handler' === $this->trace[0]['function'] ) {
$this->ignore( 1 );
}
if ( $args['ignore_frames'] ) {
$this->ignore( $args['ignore_frames'] );
}
if ( $args['ignore_current_filter'] ) {
$this->ignore_current_filter();
}
foreach ( $this->trace as $k => $frame ) {
if ( ! isset( $frame['args'] ) ) {
continue;
}
if ( isset( $frame['function'] ) && isset( self::$show_args[ $frame['function'] ] ) ) {
$show = self::$show_args[ $frame['function'] ];
if ( 'dir' === $show ) {
$show = 1;
}
$frame['args'] = array_slice( $frame['args'], 0, $show );
} else {
unset( $frame['args'] );
}
$this->trace[ $k ] = $frame;
}
}
public function get_stack() {
$trace = $this->get_filtered_trace();
$stack = wp_list_pluck( $trace, 'display' );
return $stack;
}
public function get_caller() {
$trace = $this->get_filtered_trace();
return reset( $trace );
}
public function get_component() {
$components = array();
foreach ( $this->trace as $frame ) {
$component = self::get_frame_component( $frame );
if ( $component ) {
if ( 'plugin' === $component->type ) {
// If the component is a plugin then it can't be anything else,
// so short-circuit and return early.
return $component;
}
$components[ $component->type ] = $component;
}
}
foreach ( QM_Util::get_file_dirs() as $type => $dir ) {
if ( isset( $components[ $type ] ) ) {
return $components[ $type ];
}
}
# This should not happen
}
public static function get_frame_component( array $frame ) {
try {
if ( isset( $frame['class'] ) ) {
if ( ! class_exists( $frame['class'], false ) ) {
return null;
}
if ( ! method_exists( $frame['class'], $frame['function'] ) ) {
return null;
}
$ref = new ReflectionMethod( $frame['class'], $frame['function'] );
$file = $ref->getFileName();
} elseif ( isset( $frame['function'] ) && function_exists( $frame['function'] ) ) {
$ref = new ReflectionFunction( $frame['function'] );
$file = $ref->getFileName();
} elseif ( isset( $frame['file'] ) ) {
$file = $frame['file'];
} else {
return null;
}
return QM_Util::get_file_component( $file );
} catch ( ReflectionException $e ) {
return null;
}
}
public function get_trace() {
return $this->trace;
}
public function get_display_trace() {
return $this->get_filtered_trace();
}
public function get_filtered_trace() {
if ( ! isset( $this->filtered_trace ) ) {
$trace = array_map( array( $this, 'filter_trace' ), $this->trace );
$trace = array_values( array_filter( $trace ) );
if ( empty( $trace ) && ! empty( $this->trace ) ) {
$lowest = $this->trace[0];
$file = QM_Util::standard_dir( $lowest['file'], '' );
$lowest['calling_file'] = $lowest['file'];
$lowest['calling_line'] = $lowest['line'];
$lowest['function'] = $file;
$lowest['display'] = $file;
$lowest['id'] = $file;
unset( $lowest['class'], $lowest['args'], $lowest['type'] );
$trace[0] = $lowest;
}
$this->filtered_trace = $trace;
}
return $this->filtered_trace;
}
public function ignore( $num ) {
for ( $i = 0; $i < $num; $i++ ) {
unset( $this->trace[ $i ] );
}
$this->trace = array_values( $this->trace );
return $this;
}
public function ignore_current_filter() {
if ( isset( $this->trace[2] ) && isset( $this->trace[2]['function'] ) ) {
if ( in_array( $this->trace[2]['function'], array( 'apply_filters', 'do_action' ), true ) ) {
$this->ignore( 3 ); # Ignore filter and action callbacks
}
}
}
public function filter_trace( array $trace ) {
if ( ! self::$filtered && function_exists( 'did_action' ) && did_action( 'plugins_loaded' ) ) {
/**
* Filters which classes to ignore when constructing user-facing call stacks.
*
* @since 2.7.0
*
* @param bool[] $ignore_class Array of class names to ignore. The array keys are class names to ignore,
* the array values are whether to ignore the class or not (usually true).
*/
self::$ignore_class = apply_filters( 'qm/trace/ignore_class', self::$ignore_class );
/**
* Filters which class methods to ignore when constructing user-facing call stacks.
*
* @since 2.7.0
*
* @param bool[] $ignore_method Array of method names to ignore. The array keys are method names to ignore,
* the array values are whether to ignore the method or not (usually true).
*/
self::$ignore_method = apply_filters( 'qm/trace/ignore_method', self::$ignore_method );
/**
* Filters which functions to ignore when constructing user-facing call stacks.
*
* @since 2.7.0
*
* @param bool[] $ignore_func Array of function names to ignore. The array keys are function names to ignore,
* the array values are whether to ignore the function or not (usually true).
*/
self::$ignore_func = apply_filters( 'qm/trace/ignore_func', self::$ignore_func );
/**
* Filters the number of argument values to show for the given function name when constructing user-facing
* call stacks.
*
* @since 2.7.0
*
* @param (int|string)[] $show_args The number of argument values to show for the given function name. The
* array keys are function names, the array values are either integers or
* "dir" to specifically treat the function argument as a directory path.
*/
self::$show_args = apply_filters( 'qm/trace/show_args', self::$show_args );
self::$filtered = true;
}
$return = $trace;
if ( isset( $trace['class'] ) ) {
if ( isset( self::$ignore_class[ $trace['class'] ] ) ) {
$return = null;
} elseif ( isset( self::$ignore_method[ $trace['class'] ][ $trace['function'] ] ) ) {
$return = null;
} elseif ( 0 === strpos( $trace['class'], 'QM' ) ) {
$return = null;
} else {
$return['id'] = $trace['class'] . $trace['type'] . $trace['function'] . '()';
$return['display'] = QM_Util::shorten_fqn( $trace['class'] . $trace['type'] . $trace['function'] ) . '()';
}
} else {
if ( isset( self::$ignore_func[ $trace['function'] ] ) ) {
$return = null;
} elseif ( isset( self::$show_args[ $trace['function'] ] ) ) {
$show = self::$show_args[ $trace['function'] ];
if ( 'dir' === $show ) {
if ( isset( $trace['args'][0] ) ) {
$arg = QM_Util::standard_dir( $trace['args'][0], '' );
$return['id'] = $trace['function'] . '()';
$return['display'] = QM_Util::shorten_fqn( $trace['function'] ) . "('{$arg}')";
}
} else {
$args = array();
for ( $i = 0; $i < $show; $i++ ) {
if ( isset( $trace['args'][ $i ] ) ) {
if ( is_string( $trace['args'][ $i ] ) ) {
$args[] = '\'' . $trace['args'][ $i ] . '\'';
} else {
$args[] = QM_Util::display_variable( $trace['args'][ $i ] );
}
}
}
$return['id'] = $trace['function'] . '()';
$return['display'] = QM_Util::shorten_fqn( $trace['function'] ) . '(' . implode( ',', $args ) . ')';
}
} else {
$return['id'] = $trace['function'] . '()';
$return['display'] = QM_Util::shorten_fqn( $trace['function'] ) . '()';
}
}
if ( $return ) {
$return['calling_file'] = $this->calling_file;
$return['calling_line'] = $this->calling_line;
}
if ( isset( $trace['line'] ) ) {
$this->calling_line = $trace['line'];
}
if ( isset( $trace['file'] ) ) {
$this->calling_file = $trace['file'];
}
return $return;
}
}
} else {
add_action( 'init', 'QueryMonitor::symlink_warning' );
}

View File

@@ -0,0 +1,59 @@
<?php
/**
* Plugin CLI command.
*
* @package query-monitor
*/
class QM_CLI extends QM_Plugin {
protected function __construct( $file ) {
# Register command
WP_CLI::add_command( 'qm enable', array( $this, 'enable' ) );
# Parent setup:
parent::__construct( $file );
}
/**
* Enable QM by creating the symlink for db.php
*/
public function enable() {
$drop_in = WP_CONTENT_DIR . '/db.php';
if ( file_exists( $drop_in ) ) {
if ( false !== strpos( file_get_contents( $drop_in ), 'class QM_DB' ) ) {
WP_CLI::success( "Query Monitor's wp-content/db.php is already in place" );
exit( 0 );
} else {
WP_CLI::error( 'Unknown wp-content/db.php already is already in place' );
}
}
if ( ! function_exists( 'symlink' ) ) {
WP_CLI::error( 'The symlink function is not available' );
}
if ( symlink( $this->plugin_path( 'wp-content/db.php' ), $drop_in ) ) {
WP_CLI::success( 'wp-content/db.php symlink created' );
exit( 0 );
} else {
WP_CLI::error( 'Failed to create wp-content/db.php symlink' );
}
}
public static function init( $file = null ) {
static $instance = null;
if ( ! $instance ) {
$instance = new QM_CLI( $file );
}
return $instance;
}
}

View File

@@ -0,0 +1,266 @@
<?php
/**
* Abstract data collector.
*
* @package query-monitor
*/
if ( ! class_exists( 'QM_Collector' ) ) {
abstract class QM_Collector {
protected $timer;
protected $data = array(
'types' => array(),
'component_times' => array(),
);
protected static $hide_qm = null;
public $concerned_actions = array();
public $concerned_filters = array();
public $concerned_constants = array();
public $tracked_hooks = array();
public function __construct() {}
final public function id() {
return "qm-{$this->id}";
}
protected function log_type( $type ) {
if ( isset( $this->data['types'][ $type ] ) ) {
$this->data['types'][ $type ]++;
} else {
$this->data['types'][ $type ] = 1;
}
}
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;
}
protected function log_component( $component, $ltime, $type ) {
if ( ! isset( $this->data['component_times'][ $component->name ] ) ) {
$this->data['component_times'][ $component->name ] = array(
'component' => $component->name,
'ltime' => 0,
'types' => array(),
);
}
$this->data['component_times'][ $component->name ]['ltime'] += $ltime;
if ( isset( $this->data['component_times'][ $component->name ]['types'][ $type ] ) ) {
$this->data['component_times'][ $component->name ]['types'][ $type ]++;
} else {
$this->data['component_times'][ $component->name ]['types'][ $type ] = 1;
}
}
public static function timer_stop_float() {
global $timestart;
return microtime( true ) - $timestart;
}
public static function format_bool_constant( $constant ) {
// @TODO this should be in QM_Util
if ( ! defined( $constant ) ) {
/* translators: Undefined PHP constant */
return __( 'undefined', 'query-monitor' );
} elseif ( is_string( constant( $constant ) ) && ! is_numeric( constant( $constant ) ) ) {
return constant( $constant );
} elseif ( ! constant( $constant ) ) {
return 'false';
} else {
return 'true';
}
}
final public function get_data() {
return $this->data;
}
final public function set_id( $id ) {
$this->id = $id;
}
final public function process_concerns() {
global $wp_filter;
$tracked = array();
$id = $this->id;
/**
* Filters the concerned actions for the given panel.
*
* The dynamic portion of the hook name, `$id`, refers to the collector ID, which is typically the `$id`
* property of the collector class.
*
* @since 3.3.0
*
* @param string[] $actions Array of action names that this panel concerns itself with.
*/
$concerned_actions = apply_filters( "qm/collect/concerned_actions/{$id}", $this->get_concerned_actions() );
/**
* Filters the concerned filters for the given panel.
*
* The dynamic portion of the hook name, `$id`, refers to the collector ID, which is typically the `$id`
* property of the collector class.
*
* @since 3.3.0
*
* @param string[] $filters Array of filter names that this panel concerns itself with.
*/
$concerned_filters = apply_filters( "qm/collect/concerned_filters/{$id}", $this->get_concerned_filters() );
/**
* Filters the concerned options for the given panel.
*
* The dynamic portion of the hook name, `$id`, refers to the collector ID, which is typically the `$id`
* property of the collector class.
*
* @since 3.3.0
*
* @param string[] $options Array of option names that this panel concerns itself with.
*/
$concerned_options = apply_filters( "qm/collect/concerned_options/{$id}", $this->get_concerned_options() );
/**
* Filters the concerned constants for the given panel.
*
* The dynamic portion of the hook name, `$id`, refers to the collector ID, which is typically the `$id`
* property of the collector class.
*
* @since 3.3.0
*
* @param string[] $constants Array of constant names that this panel concerns itself with.
*/
$concerned_constants = apply_filters( "qm/collect/concerned_constants/{$id}", $this->get_concerned_constants() );
foreach ( $concerned_actions as $action ) {
if ( has_action( $action ) ) {
$this->concerned_actions[ $action ] = QM_Hook::process( $action, $wp_filter, true, true );
}
$tracked[] = $action;
}
foreach ( $concerned_filters as $filter ) {
if ( has_filter( $filter ) ) {
$this->concerned_filters[ $filter ] = QM_Hook::process( $filter, $wp_filter, true, true );
}
$tracked[] = $filter;
}
$option_filters = array(
// Should this include the pre_delete_ and pre_update_ filters too?
'pre_option_%s',
'default_option_%s',
'option_%s',
'pre_site_option_%s',
'default_site_option_%s',
'site_option_%s',
);
foreach ( $concerned_options as $option ) {
foreach ( $option_filters as $option_filter ) {
$filter = sprintf(
$option_filter,
$option
);
if ( has_filter( $filter ) ) {
$this->concerned_filters[ $filter ] = QM_Hook::process( $filter, $wp_filter, true, true );
}
$tracked[] = $filter;
}
}
$this->concerned_actions = array_filter( $this->concerned_actions, array( $this, 'filter_concerns' ) );
$this->concerned_filters = array_filter( $this->concerned_filters, array( $this, 'filter_concerns' ) );
foreach ( $concerned_constants as $constant ) {
if ( defined( $constant ) ) {
$this->concerned_constants[ $constant ] = constant( $constant );
}
}
sort( $tracked );
$this->tracked_hooks = $tracked;
}
public function filter_concerns( $concerns ) {
return ! empty( $concerns['actions'] );
}
public static function format_user( WP_User $user_object ) {
$user = get_object_vars( $user_object->data );
unset(
$user['user_pass'],
$user['user_activation_key']
);
$user['roles'] = $user_object->roles;
return $user;
}
public static function enabled() {
return true;
}
public static function hide_qm() {
if ( null === self::$hide_qm ) {
self::$hide_qm = QM_HIDE_SELF;
}
return self::$hide_qm;
}
public function filter_remove_qm( array $item ) {
$component = $item['trace']->get_component();
return ( 'query-monitor' !== $component->context );
}
public function process() {}
public function post_process() {}
public function tear_down() {}
public function get_timer() {
return $this->timer;
}
public function set_timer( QM_Timer $timer ) {
$this->timer = $timer;
}
public function get_concerned_actions() {
return array();
}
public function get_concerned_filters() {
return array();
}
public function get_concerned_options() {
return array();
}
public function get_concerned_constants() {
return array();
}
}
}

View File

@@ -0,0 +1,74 @@
<?php
/**
* Container for data collectors.
*
* @package query-monitor
*/
if ( ! class_exists( 'QM_Collectors' ) ) {
class QM_Collectors implements IteratorAggregate {
private $items = array();
private $processed = false;
public function getIterator() {
return new ArrayIterator( $this->items );
}
public static function add( QM_Collector $collector ) {
$collectors = self::init();
$collectors->items[ $collector->id ] = $collector;
}
/**
* Fetches a collector instance.
*
* @param string $id The collector ID.
* @return QM_Collector|null The collector object.
*/
public static function get( $id ) {
$collectors = self::init();
if ( isset( $collectors->items[ $id ] ) ) {
return $collectors->items[ $id ];
}
return null;
}
public static function init() {
static $instance;
if ( ! $instance ) {
$instance = new QM_Collectors();
}
return $instance;
}
public function process() {
if ( $this->processed ) {
return;
}
foreach ( $this as $collector ) {
$collector->tear_down();
$timer = new QM_Timer();
$timer->start();
$collector->process();
$collector->process_concerns();
$collector->set_timer( $timer->stop() );
}
foreach ( $this as $collector ) {
$collector->post_process();
}
$this->processed = true;
}
}
}

View File

@@ -0,0 +1,149 @@
<?php
/**
* Abstract dispatcher.
*
* @package query-monitor
*/
if ( ! class_exists( 'QM_Dispatcher' ) ) {
abstract class QM_Dispatcher {
/**
* Outputter instances.
*
* @var QM_Output[] Array of outputters.
*/
protected $outputters = array();
/**
* Query Monitor plugin instance.
*
* @var QM_Plugin Plugin instance.
*/
protected $qm;
public function __construct( QM_Plugin $qm ) {
$this->qm = $qm;
if ( ! defined( 'QM_COOKIE' ) ) {
define( 'QM_COOKIE', 'wp-query_monitor_' . COOKIEHASH );
}
if ( ! defined( 'QM_EDITOR_COOKIE' ) ) {
define( 'QM_EDITOR_COOKIE', 'wp-query_monitor_editor_' . COOKIEHASH );
}
add_action( 'init', array( $this, 'init' ) );
}
abstract public function is_active();
final public function should_dispatch() {
$e = error_get_last();
# Don't dispatch if a fatal has occurred:
if ( ! empty( $e ) && ( $e['type'] & QM_ERROR_FATALS ) ) {
return false;
}
/**
* Allows users to disable this dispatcher.
*
* The dynamic portion of the hook name, `$this->id`, refers to the dispatcher ID.
*
* @since 2.8.0
*
* @param bool $true Whether or not the dispatcher is enabled.
*/
if ( ! apply_filters( "qm/dispatch/{$this->id}", true ) ) {
return false;
}
return $this->is_active();
}
/**
* Processes and fetches the outputters for this dispatcher.
*
* @param string $outputter_id The outputter ID.
* @return QM_Output[] Array of outputters.
*/
public function get_outputters( $outputter_id ) {
$collectors = QM_Collectors::init();
$collectors->process();
/**
* Allows users to filter what outputs.
*
* The dynamic portion of the hook name, `$outputter_id`, refers to the outputter ID.
*
* @since 2.8.0
*
* @param QM_Output[] $outputters Array of outputters.
* @param QM_Collectors $collectors List of collectors.
*/
$this->outputters = apply_filters( "qm/outputter/{$outputter_id}", array(), $collectors );
return $this->outputters;
}
public function init() {
if ( ! self::user_can_view() ) {
return;
}
if ( ! defined( 'DONOTCACHEPAGE' ) ) {
define( 'DONOTCACHEPAGE', 1 );
}
add_action( 'send_headers', 'nocache_headers' );
}
protected function before_output() {
// nothing
}
protected function after_output() {
// nothing
}
public static function user_can_view() {
if ( ! did_action( 'plugins_loaded' ) ) {
return false;
}
if ( current_user_can( 'view_query_monitor' ) ) {
return true;
}
return self::user_verified();
}
public static function user_verified() {
if ( isset( $_COOKIE[QM_COOKIE] ) ) { // phpcs:ignore
return self::verify_cookie( wp_unslash( $_COOKIE[QM_COOKIE] ) ); // phpcs:ignore
}
return false;
}
public static function editor_cookie() {
if ( defined( 'QM_EDITOR_COOKIE' ) && isset( $_COOKIE[QM_EDITOR_COOKIE] ) ) { // phpcs:ignore
return $_COOKIE[QM_EDITOR_COOKIE]; // phpcs:ignore
}
return '';
}
public static function verify_cookie( $value ) {
$old_user_id = wp_validate_auth_cookie( $value, 'logged_in' );
if ( $old_user_id ) {
return user_can( $old_user_id, 'view_query_monitor' );
}
return false;
}
}
}

View File

@@ -0,0 +1,40 @@
<?php
/**
* Container for dispatchers.
*
* @package query-monitor
*/
class QM_Dispatchers implements IteratorAggregate {
private $items = array();
public function getIterator() {
return new ArrayIterator( $this->items );
}
public static function add( QM_Dispatcher $dispatcher ) {
$dispatchers = self::init();
$dispatchers->items[ $dispatcher->id ] = $dispatcher;
}
public static function get( $id ) {
$dispatchers = self::init();
if ( isset( $dispatchers->items[ $id ] ) ) {
return $dispatchers->items[ $id ];
}
return false;
}
public static function init() {
static $instance;
if ( ! $instance ) {
$instance = new QM_Dispatchers();
}
return $instance;
}
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* Hook processor.
*
* @package query-monitor
*/
class QM_Hook {
public static function process( $name, array $wp_filter, $hide_qm = false, $hide_core = false ) {
$actions = array();
$components = array();
if ( isset( $wp_filter[ $name ] ) ) {
# http://core.trac.wordpress.org/ticket/17817
$action = $wp_filter[ $name ];
foreach ( $action as $priority => $callbacks ) {
foreach ( $callbacks as $callback ) {
$callback = QM_Util::populate_callback( $callback );
if ( isset( $callback['component'] ) ) {
if (
( $hide_qm && 'query-monitor' === $callback['component']->context )
|| ( $hide_core && 'core' === $callback['component']->context )
) {
continue;
}
$components[ $callback['component']->name ] = $callback['component']->name;
}
// This isn't used and takes up a ton of memory:
unset( $callback['function'] );
$actions[] = array(
'priority' => $priority,
'callback' => $callback,
);
}
}
}
$parts = array_values( array_filter( preg_split( '#[_/-]#', $name ) ) );
return array(
'name' => $name,
'actions' => $actions,
'parts' => $parts,
'components' => $components,
);
}
}

View File

@@ -0,0 +1,48 @@
<?php
/**
* Abstract output handler.
*
* @package query-monitor
*/
if ( ! class_exists( 'QM_Output' ) ) {
abstract class QM_Output {
/**
* Collector instance.
*
* @var QM_Collector Collector.
*/
protected $collector;
/**
* Timer instance.
*
* @var QM_Timer Timer.
*/
protected $timer;
public function __construct( QM_Collector $collector ) {
$this->collector = $collector;
}
abstract public function get_output();
public function output() {
// nothing
}
public function get_collector() {
return $this->collector;
}
final public function get_timer() {
return $this->timer;
}
final public function set_timer( QM_Timer $timer ) {
$this->timer = $timer;
}
}
}

View File

@@ -0,0 +1,111 @@
<?php
/**
* Abstract plugin wrapper.
*
* @package query-monitor
*/
if ( ! class_exists( 'QM_Plugin' ) ) {
abstract class QM_Plugin {
private $plugin = array();
public static $minimum_php_version = '5.3.6';
/**
* Class constructor
*/
protected function __construct( $file ) {
$this->file = $file;
}
/**
* Returns the URL for for a file/dir within this plugin.
*
* @param string $file The path within this plugin, e.g. '/js/clever-fx.js'
* @return string URL
*/
final public function plugin_url( $file = '' ) {
return $this->_plugin( 'url', $file );
}
/**
* Returns the filesystem path for a file/dir within this plugin.
*
* @param string $file The path within this plugin, e.g. '/js/clever-fx.js'
* @return string Filesystem path
*/
final public function plugin_path( $file = '' ) {
return $this->_plugin( 'path', $file );
}
/**
* Returns a version number for the given plugin file.
*
* @param string $file The path within this plugin, e.g. '/js/clever-fx.js'
* @return string Version
*/
final public function plugin_ver( $file ) {
return filemtime( $this->plugin_path( $file ) );
}
/**
* Returns the current plugin's basename, eg. 'my_plugin/my_plugin.php'.
*
* @return string Basename
*/
final public function plugin_base() {
return $this->_plugin( 'base' );
}
/**
* Populates and returns the current plugin info.
*/
final private function _plugin( $item, $file = '' ) {
if ( ! array_key_exists( $item, $this->plugin ) ) {
switch ( $item ) {
case 'url':
$this->plugin[ $item ] = plugin_dir_url( $this->file );
break;
case 'path':
$this->plugin[ $item ] = plugin_dir_path( $this->file );
break;
case 'base':
$this->plugin[ $item ] = plugin_basename( $this->file );
break;
}
}
return $this->plugin[ $item ] . ltrim( $file, '/' );
}
public static function php_version_met() {
static $met = null;
if ( null === $met ) {
$met = version_compare( PHP_VERSION, self::$minimum_php_version, '>=' );
}
return $met;
}
public static function php_version_nope() {
printf(
'<div id="qm-php-nope" class="notice notice-error is-dismissible"><p>%s</p></div>',
wp_kses(
sprintf(
/* translators: 1: Required PHP version number, 2: Current PHP version number, 3: URL of PHP update help page */
__( 'The Query Monitor plugin requires PHP version %1$s or higher. This site is running PHP version %2$s. <a href="%3$s">Learn about updating PHP</a>.', 'query-monitor' ),
self::$minimum_php_version,
PHP_VERSION,
'https://wordpress.org/support/update-php/'
),
array(
'a' => array(
'href' => array(),
),
)
)
);
}
}
}

View File

@@ -0,0 +1,110 @@
<?php
/**
* A convenience class for wrapping certain user-facing functionality.
*
* @package query-monitor
*/
class QM {
public static function emergency( $message, array $context = array() ) {
/**
* Fires when an `emergency` level message is logged.
*
* @since 3.1.0
*
* @param string $message The message.
* @param array $context The context passed.
*/
do_action( 'qm/emergency', $message, $context );
}
public static function alert( $message, array $context = array() ) {
/**
* Fires when an `alert` level message is logged.
*
* @since 3.1.0
*
* @param string $message The message.
* @param array $context The context passed.
*/
do_action( 'qm/alert', $message, $context );
}
public static function critical( $message, array $context = array() ) {
/**
* Fires when a `critical` level message is logged.
*
* @since 3.1.0
*
* @param string $message The message.
* @param array $context The context passed.
*/
do_action( 'qm/critical', $message, $context );
}
public static function error( $message, array $context = array() ) {
/**
* Fires when an `error` level message is logged.
*
* @since 3.1.0
*
* @param string $message The message.
* @param array $context The context passed.
*/
do_action( 'qm/error', $message, $context );
}
public static function warning( $message, array $context = array() ) {
/**
* Fires when a `warning` level message is logged.
*
* @since 3.1.0
*
* @param string $message The message.
* @param array $context The context passed.
*/
do_action( 'qm/warning', $message, $context );
}
public static function notice( $message, array $context = array() ) {
/**
* Fires when a `notice` level message is logged.
*
* @since 3.1.0
*
* @param string $message The message.
* @param array $context The context passed.
*/
do_action( 'qm/notice', $message, $context );
}
public static function info( $message, array $context = array() ) {
/**
* Fires when an `info` level message is logged.
*
* @since 3.1.0
*
* @param string $message The message.
* @param array $context The context passed.
*/
do_action( 'qm/info', $message, $context );
}
public static function debug( $message, array $context = array() ) {
/**
* Fires when a `debug` level message is logged.
*
* @since 3.1.0
*
* @param string $message The message.
* @param array $context The context passed.
*/
do_action( 'qm/debug', $message, $context );
}
public static function log( $level, $message, array $context = array() ) {
$logger = QM_Collectors::get( 'logger' );
$logger->log( $level, $message, $context );
}
}

View File

@@ -0,0 +1,217 @@
<?php
/**
* The main Query Monitor plugin class.
*
* @package query-monitor
*/
class QueryMonitor extends QM_Plugin {
protected function __construct( $file ) {
# Actions
add_action( 'plugins_loaded', array( $this, 'action_plugins_loaded' ) );
add_action( 'init', array( $this, 'action_init' ) );
add_action( 'members_register_caps', array( $this, 'action_register_members_caps' ) );
add_action( 'members_register_cap_groups', array( $this, 'action_register_members_groups' ) );
# Filters
add_filter( 'user_has_cap', array( $this, 'filter_user_has_cap' ), 10, 4 );
add_filter( 'ure_built_in_wp_caps', array( $this, 'filter_ure_caps' ) );
add_filter( 'ure_capabilities_groups_tree', array( $this, 'filter_ure_groups' ) );
add_filter( 'network_admin_plugin_action_links_query-monitor/query-monitor.php', array( $this, 'filter_plugin_action_links' ) );
add_filter( 'plugin_action_links_query-monitor/query-monitor.php', array( $this, 'filter_plugin_action_links' ) );
# Parent setup:
parent::__construct( $file );
# Load and register built-in collectors:
$collectors = array();
foreach ( glob( $this->plugin_path( 'collectors/*.php' ) ) as $file ) {
$key = basename( $file, '.php' );
$collectors[ $key ] = $file;
}
/**
* Allow filtering of built-in collector files.
*
* @since 2.14.0
*
* @param string[] $collectors Array of file paths to be loaded.
*/
foreach ( apply_filters( 'qm/built-in-collectors', $collectors ) as $file ) {
include $file;
}
}
public function filter_plugin_action_links( array $actions ) {
return array_merge( array(
'settings' => '<a href="#qm-settings">' . esc_html__( 'Settings', 'query-monitor' ) . '</a>',
'add-ons' => '<a href="https://github.com/johnbillion/query-monitor/wiki/Query-Monitor-Add-on-Plugins">' . esc_html__( 'Add-ons', 'query-monitor' ) . '</a>',
), $actions );
}
/**
* Filter a user's capabilities so they can be altered at runtime.
*
* This is used to:
* - Grant the 'view_query_monitor' capability to the user if they have the ability to manage options.
*
* This does not get called for Super Admins.
*
* @param bool[] $user_caps Array of key/value pairs where keys represent a capability name and boolean values
* represent whether the user has that capability.
* @param string[] $required_caps Required primitive capabilities for the requested capability.
* @param array $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.
* }
* @param WP_User $user Concerned user object.
* @return bool[] Concerned user's capabilities.
*/
public function filter_user_has_cap( array $user_caps, array $required_caps, array $args, WP_User $user ) {
if ( 'view_query_monitor' !== $args[0] ) {
return $user_caps;
}
if ( array_key_exists( 'view_query_monitor', $user_caps ) ) {
return $user_caps;
}
if ( ! is_multisite() && user_can( $args[1], 'manage_options' ) ) {
$user_caps['view_query_monitor'] = true;
}
return $user_caps;
}
public function action_plugins_loaded() {
// Hide QM itself from output by default:
if ( ! defined( 'QM_HIDE_SELF' ) ) {
define( 'QM_HIDE_SELF', true );
}
/**
* Filters the collectors that are being added.
*
* @since 2.11.2
*
* @param QM_Collector[] $collectors Array of collector instances.
* @param QueryMonitor $instance QueryMonitor instance.
*/
foreach ( apply_filters( 'qm/collectors', array(), $this ) as $collector ) {
QM_Collectors::add( $collector );
}
# Load dispatchers:
foreach ( glob( $this->plugin_path( 'dispatchers/*.php' ) ) as $file ) {
include $file;
}
/**
* Filters the dispatchers that are being added.
*
* @since 2.11.2
*
* @param QM_Dispatcher[] $dispatchers Array of dispatcher instances.
* @param QueryMonitor $instance QueryMonitor instance.
*/
foreach ( apply_filters( 'qm/dispatchers', array(), $this ) as $dispatcher ) {
QM_Dispatchers::add( $dispatcher );
}
}
public function action_init() {
load_plugin_textdomain( 'query-monitor', false, dirname( $this->plugin_base() ) . '/languages' );
}
public static function symlink_warning() {
$db = WP_CONTENT_DIR . '/db.php';
trigger_error( sprintf(
/* translators: %s: Symlink file location */
esc_html__( 'The symlink at %s is no longer pointing to the correct location. Please remove the symlink, then deactivate and reactivate Query Monitor.', 'query-monitor' ),
'<code>' . esc_html( $db ) . '</code>'
), E_USER_WARNING );
}
/**
* Registers the Query Monitor user capability group for the Members plugin.
*
* @link https://wordpress.org/plugins/members/
*/
public function action_register_members_groups() {
members_register_cap_group( 'query_monitor', array(
'label' => __( 'Query Monitor', 'query-monitor' ),
'caps' => array(
'view_query_monitor',
),
'icon' => 'dashicons-admin-tools',
'priority' => 30,
) );
}
/**
* Registers the View Query Monitor user capability for the Members plugin.
*
* @link https://wordpress.org/plugins/members/
*/
public function action_register_members_caps() {
members_register_cap( 'view_query_monitor', array(
'label' => _x( 'View Query Monitor', 'Human readable label for the user capability required to view Query Monitor.', 'query-monitor' ),
'group' => 'query_monitor',
) );
}
/**
* Registers the Query Monitor user capability group for the User Role Editor plugin.
*
* @link https://wordpress.org/plugins/user-role-editor/
*
* @param array[] $groups Array of existing groups.
* @return array[] Updated array of groups.
*/
public function filter_ure_groups( array $groups ) {
$groups['query_monitor'] = array(
'caption' => esc_html__( 'Query Monitor', 'query-monitor' ),
'parent' => 'custom',
'level' => 2,
);
return $groups;
}
/**
* Registers the View Query Monitor user capability for the User Role Editor plugin.
*
* @link https://wordpress.org/plugins/user-role-editor/
*
* @param array[] $caps Array of existing capabilities.
* @return array[] Updated array of capabilities.
*/
public function filter_ure_caps( array $caps ) {
$caps['view_query_monitor'] = array(
'custom',
'query_monitor',
);
return $caps;
}
public static function init( $file = null ) {
static $instance = null;
if ( ! $instance ) {
$instance = new QueryMonitor( $file );
}
return $instance;
}
}

View File

@@ -0,0 +1,109 @@
<?php
/**
* Timer that collects timing and memory usage.
*
* @package query-monitor
*/
class QM_Timer {
protected $start = null;
protected $end = null;
protected $trace = null;
protected $laps = array();
public function start( array $data = null ) {
$this->trace = new QM_Backtrace();
$this->start = array(
'time' => microtime( true ),
'memory' => memory_get_usage(),
'data' => $data,
);
return $this;
}
public function stop( array $data = null ) {
$this->end = array(
'time' => microtime( true ),
'memory' => memory_get_usage(),
'data' => $data,
);
return $this;
}
public function lap( array $data = null, $name = null ) {
$lap = array(
'time' => microtime( true ),
'memory' => memory_get_usage(),
'data' => $data,
);
if ( ! isset( $name ) ) {
/* translators: %d: Timing lap number */
$i = sprintf( __( 'Lap %d', 'query-monitor' ), count( $this->laps ) + 1 );
} else {
$i = $name;
}
$this->laps[ $i ] = $lap;
return $this;
}
public function get_laps() {
$laps = array();
$prev = $this->start;
foreach ( $this->laps as $lap_id => $lap ) {
$lap['time_used'] = $lap['time'] - $prev['time'];
$lap['memory_used'] = $lap['memory'] - $prev['memory'];
$laps[ $lap_id ] = $lap;
$prev = $lap;
}
return $laps;
}
public function get_time() {
return $this->end['time'] - $this->start['time'];
}
public function get_memory() {
return $this->end['memory'] - $this->start['memory'];
}
public function get_start_time() {
return $this->start['time'];
}
public function get_start_memory() {
return $this->start['memory'];
}
public function get_end_time() {
return $this->end['time'];
}
public function get_end_memory() {
return $this->end['memory'];
}
public function get_trace() {
return $this->trace;
}
public function end( array $data = null ) {
return $this->stop( $data );
}
}

View File

@@ -0,0 +1,518 @@
<?php
/**
* General utilities class.
*
* @package query-monitor
*/
if ( ! class_exists( 'QM_Util' ) ) {
class QM_Util {
protected static $file_components = array();
protected static $file_dirs = array();
protected static $abspath = null;
protected static $contentpath = null;
protected static $sort_field = null;
private function __construct() {}
public static function convert_hr_to_bytes( $size ) {
# Annoyingly, wp_convert_hr_to_bytes() is defined in a file that's only
# loaded in the admin area, so we'll use our own version.
# See also http://core.trac.wordpress.org/ticket/17725
$bytes = (float) $size;
if ( $bytes ) {
$last = strtolower( substr( $size, -1 ) );
$pos = strpos( ' kmg', $last, 1 );
if ( $pos ) {
$bytes *= pow( 1024, $pos );
}
$bytes = round( $bytes );
}
return $bytes;
}
public static function standard_dir( $dir, $path_replace = null ) {
$dir = self::normalize_path( $dir );
if ( is_string( $path_replace ) ) {
if ( ! self::$abspath ) {
self::$abspath = self::normalize_path( ABSPATH );
self::$contentpath = self::normalize_path( dirname( WP_CONTENT_DIR ) . '/' );
}
$dir = str_replace( array(
self::$abspath,
self::$contentpath,
), $path_replace, $dir );
}
return $dir;
}
public static function normalize_path( $path ) {
if ( function_exists( 'wp_normalize_path' ) ) {
$path = wp_normalize_path( $path );
} else {
$path = str_replace( '\\', '/', $path );
$path = str_replace( '//', '/', $path );
}
return $path;
}
public static function get_file_dirs() {
if ( empty( self::$file_dirs ) ) {
/**
* Filters the absolute directory paths that correlate to components.
*
* Note that this filter is applied before QM adds its built-in list of components. This is
* so custom registered components take precedence during component detection.
*
* See the corresponding `qm/component_name/{$type}` filter for specifying the component name.
*
* @since 3.6.0
*
* @param string[] $dirs Array of absolute directory paths keyed by component identifier.
*/
self::$file_dirs = apply_filters( 'qm/component_dirs', self::$file_dirs );
self::$file_dirs['plugin'] = WP_PLUGIN_DIR;
self::$file_dirs['mu-vendor'] = WPMU_PLUGIN_DIR . '/vendor';
self::$file_dirs['go-plugin'] = WPMU_PLUGIN_DIR . '/shared-plugins';
self::$file_dirs['mu-plugin'] = WPMU_PLUGIN_DIR;
self::$file_dirs['vip-plugin'] = get_theme_root() . '/vip/plugins';
if ( defined( 'WPCOM_VIP_CLIENT_MU_PLUGIN_DIR' ) ) {
self::$file_dirs['vip-client-mu-plugin'] = WPCOM_VIP_CLIENT_MU_PLUGIN_DIR;
}
self::$file_dirs['theme'] = null;
self::$file_dirs['stylesheet'] = get_stylesheet_directory();
self::$file_dirs['template'] = get_template_directory();
self::$file_dirs['other'] = WP_CONTENT_DIR;
self::$file_dirs['core'] = ABSPATH;
self::$file_dirs['unknown'] = null;
foreach ( self::$file_dirs as $type => $dir ) {
self::$file_dirs[ $type ] = self::standard_dir( $dir );
}
}
return self::$file_dirs;
}
public static function get_file_component( $file ) {
# @TODO turn this into a class (eg QM_File_Component)
$file = self::standard_dir( $file );
if ( isset( self::$file_components[ $file ] ) ) {
return self::$file_components[ $file ];
}
foreach ( self::get_file_dirs() as $type => $dir ) {
// this slash makes paths such as plugins-mu match mu-plugin not plugin
if ( $dir && ( 0 === strpos( $file, trailingslashit( $dir ) ) ) ) {
break;
}
}
$context = $type;
switch ( $type ) {
case 'plugin':
case 'mu-plugin':
case 'mu-vendor':
$plug = str_replace( '/vendor/', '/', $file );
$plug = plugin_basename( $plug );
if ( strpos( $plug, '/' ) ) {
$plug = explode( '/', $plug );
$plug = reset( $plug );
} else {
$plug = basename( $plug );
}
if ( 'plugin' !== $type ) {
/* translators: %s: Plugin name */
$name = sprintf( __( 'MU Plugin: %s', 'query-monitor' ), $plug );
} else {
/* translators: %s: Plugin name */
$name = sprintf( __( 'Plugin: %s', 'query-monitor' ), $plug );
}
$context = $plug;
break;
case 'go-plugin':
case 'vip-plugin':
case 'vip-client-mu-plugin':
$plug = str_replace( self::$file_dirs[ $type ], '', $file );
$plug = trim( $plug, '/' );
if ( strpos( $plug, '/' ) ) {
$plug = explode( '/', $plug );
$plug = reset( $plug );
} else {
$plug = basename( $plug );
}
if ( 'vip-client-mu-plugin' === $type ) {
/* translators: %s: Plugin name */
$name = sprintf( __( 'VIP Client MU Plugin: %s', 'query-monitor' ), $plug );
} else {
/* translators: %s: Plugin name */
$name = sprintf( __( 'VIP Plugin: %s', 'query-monitor' ), $plug );
}
$context = $plug;
break;
case 'stylesheet':
if ( is_child_theme() ) {
$name = __( 'Child Theme', 'query-monitor' );
} else {
$name = __( 'Theme', 'query-monitor' );
}
$type = 'theme';
break;
case 'template':
$name = __( 'Parent Theme', 'query-monitor' );
$type = 'theme';
break;
case 'other':
// Anything else that's within the content directory should appear as
// `wp-content/{dir}` or `wp-content/{file}`
$name = self::standard_dir( $file );
$name = str_replace( dirname( self::$file_dirs['other'] ), '', $name );
$parts = explode( '/', trim( $name, '/' ) );
$name = $parts[0] . '/' . $parts[1];
$context = $file;
break;
case 'core':
$name = __( 'Core', 'query-monitor' );
break;
case 'unknown':
default:
$name = __( 'Unknown', 'query-monitor' );
/**
* Filters the name of a custom or unknown component.
*
* The dynamic portion of the hook name, `$type`, refers to the component identifier.
*
* See the corresponding `qm/component_dirs` filter for specifying the component directories.
*
* @since 3.6.0
*
* @param string $name The component name.
* @param string $file The full file path for the file within the component.
*/
$name = apply_filters( "qm/component_name/{$type}", $name, $file );
break;
}
self::$file_components[ $file ] = (object) compact( 'type', 'name', 'context' );
return self::$file_components[ $file ];
}
public static function populate_callback( array $callback ) {
if ( is_string( $callback['function'] ) && ( false !== strpos( $callback['function'], '::' ) ) ) {
$callback['function'] = explode( '::', $callback['function'] );
}
if ( isset( $callback['class'] ) ) {
$callback['function'] = array(
$callback['class'],
$callback['function'],
);
}
try {
if ( is_array( $callback['function'] ) ) {
if ( is_object( $callback['function'][0] ) ) {
$class = get_class( $callback['function'][0] );
$access = '->';
} else {
$class = $callback['function'][0];
$access = '::';
}
$callback['name'] = self::shorten_fqn( $class . $access . $callback['function'][1] ) . '()';
$ref = new ReflectionMethod( $class, $callback['function'][1] );
} elseif ( is_object( $callback['function'] ) ) {
if ( is_a( $callback['function'], 'Closure' ) ) {
$ref = new ReflectionFunction( $callback['function'] );
$file = self::standard_dir( $ref->getFileName(), '' );
if ( 0 === strpos( $file, '/' ) ) {
$file = basename( $ref->getFileName() );
}
/* translators: 1: Line number, 2: File name */
$callback['name'] = sprintf( __( 'Closure on line %1$d of %2$s', 'query-monitor' ), $ref->getStartLine(), $file );
} else {
// the object should have a __invoke() method
$class = get_class( $callback['function'] );
$callback['name'] = self::shorten_fqn( $class ) . '->__invoke()';
$ref = new ReflectionMethod( $class, '__invoke' );
}
} else {
$callback['name'] = self::shorten_fqn( $callback['function'] ) . '()';
$ref = new ReflectionFunction( $callback['function'] );
}
$callback['file'] = $ref->getFileName();
$callback['line'] = $ref->getStartLine();
// https://github.com/facebook/hhvm/issues/5856
$name = trim( $ref->getName() );
if ( '__lambda_func' === $name || 0 === strpos( $name, 'lambda_' ) ) {
if ( preg_match( '|(?P<file>.*)\((?P<line>[0-9]+)\)|', $callback['file'], $matches ) ) {
$callback['file'] = $matches['file'];
$callback['line'] = $matches['line'];
$file = trim( self::standard_dir( $callback['file'], '' ), '/' );
/* translators: 1: Line number, 2: File name */
$callback['name'] = sprintf( __( 'Anonymous function on line %1$d of %2$s', 'query-monitor' ), $callback['line'], $file );
} else {
// https://github.com/facebook/hhvm/issues/5807
unset( $callback['line'], $callback['file'] );
$callback['name'] = $name . '()';
$callback['error'] = new WP_Error( 'unknown_lambda', __( 'Unable to determine source of lambda function', 'query-monitor' ) );
}
}
if ( ! empty( $callback['file'] ) ) {
$callback['component'] = self::get_file_component( $callback['file'] );
} else {
$callback['component'] = (object) array(
'type' => 'php',
'name' => 'PHP',
'context' => '',
);
}
} catch ( ReflectionException $e ) {
$callback['error'] = new WP_Error( 'reflection_exception', $e->getMessage() );
}
return $callback;
}
public static function is_ajax() {
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
return true;
}
return false;
}
public static function is_async() {
if ( self::is_ajax() ) {
return true;
}
if ( isset( $_SERVER['HTTP_X_REQUESTED_WITH'] ) && 'xmlhttprequest' === strtolower( $_SERVER['HTTP_X_REQUESTED_WITH'] ) ) { // phpcs:ignore
return true;
}
return false;
}
public static function get_admins() {
if ( is_multisite() ) {
return false;
} else {
return get_role( 'administrator' );
}
}
public static function is_multi_network() {
global $wpdb;
if ( function_exists( 'is_multi_network' ) ) {
return is_multi_network();
}
if ( ! is_multisite() ) {
return false;
}
// phpcs:disable
$num_sites = $wpdb->get_var( "
SELECT COUNT(*)
FROM {$wpdb->site}
" );
// phpcs:enable
return ( $num_sites > 1 );
}
public static function get_client_version( $client ) {
$client = intval( $client );
$hello = $client % 10000;
$major = intval( floor( $client / 10000 ) );
$minor = intval( floor( $hello / 100 ) );
$patch = intval( $hello % 100 );
return compact( 'major', 'minor', 'patch' );
}
public static function get_query_type( $sql ) {
// Trim leading whitespace and brackets
$sql = ltrim( $sql, ' \t\n\r\0\x0B(' );
if ( 0 === strpos( $sql, '/*' ) ) {
// Strip out leading comments such as `/*NO_SELECT_FOUND_ROWS*/` before calculating the query type
$sql = preg_replace( '|^/\*[^\*/]+\*/|', '', $sql );
}
$words = preg_split( '/\b/', trim( $sql ), 2, PREG_SPLIT_NO_EMPTY );
$type = strtoupper( $words[0] );
return $type;
}
public static function display_variable( $value ) {
if ( is_string( $value ) ) {
return $value;
} elseif ( is_bool( $value ) ) {
return ( $value ) ? 'true' : 'false';
} elseif ( is_scalar( $value ) ) {
return $value;
} elseif ( is_object( $value ) ) {
$class = get_class( $value );
switch ( true ) {
case ( $value instanceof WP_Post ):
case ( $value instanceof WP_User ):
return sprintf( '%s (ID: %s)', $class, $value->ID );
break;
case ( $value instanceof WP_Term ):
return sprintf( '%s (term_id: %s)', $class, $value->term_id );
break;
case ( $value instanceof WP_Comment ):
return sprintf( '%s (comment_ID: %s)', $class, $value->comment_ID );
break;
case ( $value instanceof WP_Error ):
return sprintf( '%s (%s)', $class, $value->get_error_code() );
break;
case ( $value instanceof WP_Role ):
case ( $value instanceof WP_Post_Type ):
case ( $value instanceof WP_Taxonomy ):
return sprintf( '%s (%s)', $class, $value->name );
break;
case ( $value instanceof WP_Network ):
return sprintf( '%s (id: %s)', $class, $value->id );
break;
case ( $value instanceof WP_Site ):
return sprintf( '%s (blog_id: %s)', $class, $value->blog_id );
break;
case ( $value instanceof WP_Theme ):
return sprintf( '%s (%s)', $class, $value->get_stylesheet() );
break;
default:
return $class;
break;
}
} else {
return gettype( $value );
}
}
/**
* Shortens a fully qualified name to reduce the length of the names of long namespaced symbols.
*
* This initialises portions that do not form the first or last portion of the name. For example:
*
* Inpsyde\Wonolog\HookListener\HookListenersRegistry->hook_callback()
*
* becomes:
*
* Inpsyde\W\H\HookListenersRegistry->hook_callback()
*
* @param string $fqn A fully qualified name.
* @return string A shortened version of the name.
*/
public static function shorten_fqn( $fqn ) {
return preg_replace_callback( '#\\\\[a-zA-Z0-9_\\\\]{4,}\\\\#', function( array $matches ) {
preg_match_all( '#\\\\([a-zA-Z0-9_])#', $matches[0], $m );
return '\\' . implode( '\\', $m[1] ) . '\\';
}, $fqn );
}
/**
* Helper function for JSON encoding data and formatting it in a consistent and compatible manner.
*
* @param mixed $data The data to be JSON encoded.
* @return string The JSON encoded data.
*/
public static function json_format( $data ) {
$json_options = JSON_PRETTY_PRINT;
if ( defined( 'JSON_UNESCAPED_SLASHES' ) ) {
// phpcs:ignore PHPCompatibility.Constants.NewConstants.json_unescaped_slashesFound
$json_options |= JSON_UNESCAPED_SLASHES;
}
$json = json_encode( $data, $json_options );
if ( ! defined( 'JSON_UNESCAPED_SLASHES' ) ) {
$json = wp_unslash( $json );
}
return $json;
}
public static function is_stringy( $data ) {
return ( is_string( $data ) || ( is_object( $data ) && method_exists( $data, '__toString' ) ) );
}
public static function sort( array &$array, $field ) {
self::$sort_field = $field;
usort( $array, array( __CLASS__, '_sort' ) );
}
public static function rsort( array &$array, $field ) {
self::$sort_field = $field;
usort( $array, array( __CLASS__, '_rsort' ) );
}
private static function _rsort( $a, $b ) {
$field = self::$sort_field;
if ( $a[ $field ] === $b[ $field ] ) {
return 0;
} else {
return ( $a[ $field ] > $b[ $field ] ) ? -1 : 1;
}
}
private static function _sort( $a, $b ) {
$field = self::$sort_field;
if ( $a[ $field ] === $b[ $field ] ) {
return 0;
} else {
return ( $a[ $field ] > $b[ $field ] ) ? 1 : -1;
}
}
}
}

View File

@@ -0,0 +1,65 @@
<?php
/**
* Mock 'Debug Bar' plugin class.
*
* @package query-monitor
*/
class Debug_Bar {
public $panels = array();
public function __construct() {
add_action( 'wp_head', array( $this, 'ensure_ajaxurl' ), 1 );
$this->enqueue();
$this->init_panels();
}
public function enqueue() {
// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
wp_register_style( 'debug-bar', false, array(
'query-monitor',
) );
// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
wp_register_script( 'debug-bar', false, array(
'query-monitor',
) );
/**
* Fires after scripts have been enqueued. This mimics the same action fired in the Debug Bar plugin.
*
* @since 2.7.0
*/
do_action( 'debug_bar_enqueue_scripts' );
}
public function init_panels() {
require_once 'debug_bar_panel.php';
/**
* Filters the debug bar panel list. This mimics the same filter called in the Debug Bar plugin.
*
* @since 2.7.0
*
* @param Debug_Bar_Panel[] $panels Array of Debug Bar panel instances.
*/
$this->panels = apply_filters( 'debug_bar_panels', array() );
}
public function ensure_ajaxurl() {
$dispatcher = QM_Dispatchers::get( 'html' );
if ( $this->panels && $dispatcher::user_can_view() ) {
?>
<script type="text/javascript">
var ajaxurl = '<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>';
</script>
<?php
}
}
public function Debug_Bar() {
self::__construct();
}
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* Mock 'Debug Bar' panel class.
*
* @package query-monitor
*/
abstract class Debug_Bar_Panel {
public $_title = '';
public $_visible = true;
public function __construct( $title = '' ) {
$this->title( $title );
if ( $this->init() === false ) {
$this->set_visible( false );
return;
}
# @TODO convert to QM classes
add_filter( 'debug_bar_classes', array( $this, 'debug_bar_classes' ) );
}
/**
* Initializes the panel.
*/
public function init() {}
public function prerender() {}
/**
* Renders the panel.
*/
public function render() {}
public function is_visible() {
return $this->_visible;
}
public function set_visible( $visible ) {
$this->_visible = $visible;
}
public function title( $title = null ) {
if ( ! isset( $title ) ) {
return $this->_title;
}
$this->_title = $title;
}
public function debug_bar_classes( $classes ) {
return $classes;
}
public function Debug_Bar_Panel( $title = '' ) {
self::__construct( $title );
}
}