|
Server : LiteSpeed System : Linux srv104790275 5.15.0-161-generic #171-Ubuntu SMP Sat Oct 11 08:17:01 UTC 2025 x86_64 User : dewac4139 ( 1077) PHP Version : 8.0.30 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare, Directory : /home/planetslotlogin.com/public_html/wp-content/plugins/amp/src/ |
Upload File : |
<?php
/**
* Class PluginSuppression.
*
* @package AmpProject\AmpWP
*/
namespace AmpProject\AmpWP;
use AMP_Options_Manager;
use AMP_Theme_Support;
use AMP_Validation_Error_Taxonomy;
use AMP_Validation_Manager;
use AMP_Validated_URL_Post_Type;
use AmpProject\AmpWP\Admin\ReaderThemes;
use AmpProject\AmpWP\DevTools\CallbackReflection;
use AmpProject\AmpWP\Infrastructure\Registerable;
use AmpProject\AmpWP\Infrastructure\Service;
use WP_Block_Type_Registry;
use WP_Hook;
use WP_Term;
use WP_Widget;
use WP_User;
/**
* Suppress plugins from running by removing their hooks and nullifying their shortcodes, widgets, and blocks.
*
* @package AmpProject\AmpWP
* @internal
* @since 2.0
*/
final class PluginSuppression implements Service, Registerable {
/**
* Plugin registry to use.
*
* @var PluginRegistry
*/
private $plugin_registry;
/**
* Callback reflector to use.
*
* @var CallbackReflection
*/
private $callback_reflection;
/**
* Paired Routing.
*
* @var PairedRouting
*/
private $paired_routing;
/**
* Original render callbacks for blocks.
*
* Populated via the `register_block_type_args` filter at the moment the block is first registered. This is useful
* to detect a suppressed plugin's blocks which had their `render_callback` wrapped by another function before
* plugin suppression is started at the `wp` action.
*
* @see gutenberg_current_parsed_block_tracking()
* @var array
*/
private $original_block_render_callbacks = [];
/**
* Instantiate the plugin suppression service.
*
* @param PluginRegistry $plugin_registry Plugin registry to use.
* @param CallbackReflection $callback_reflection Callback reflector to use.
* @param PairedRouting $paired_routing Paired routing service to use.
*/
public function __construct( PluginRegistry $plugin_registry, CallbackReflection $callback_reflection, PairedRouting $paired_routing ) {
$this->plugin_registry = $plugin_registry;
$this->callback_reflection = $callback_reflection;
$this->paired_routing = $paired_routing;
}
/**
* Register the service with the system.
*/
public function register() {
add_filter( 'amp_default_options', [ $this, 'filter_default_options' ] );
add_filter( 'amp_options_updating', [ $this, 'sanitize_options' ], 10, 2 );
add_filter( 'plugin_row_meta', [ $this, 'filter_plugin_row_meta' ], 10, 2 );
add_filter(
'register_block_type_args',
function ( $props, $block_name ) {
if ( isset( $props['render_callback'] ) ) {
$this->original_block_render_callbacks[ $block_name ] = $props['render_callback'];
}
return $props;
},
~PHP_INT_MAX,
2
);
// Priority 8 needed to run before ReaderThemeLoader::override_theme() at priority 9.
add_action( 'plugins_loaded', [ $this, 'initialize' ], 8 );
}
/**
* Initialize.
*/
public function initialize() {
// When a Reader theme is selected and an AMP request is being made, start suppressing as early as possible.
// This can be done because we know it is an AMP page due to the query parameter, but it also _has_ to be done
// specifically for the case of accessing the AMP Customizer (in which customize.php is requested with the query
// parameter) in order to prevent the registration of Customizer controls from suppressed plugins. Suppression
// could be done early for Transitional mode as well since a query parameter is also used for frontend requests
// but there is no similar need to suppress the registration of Customizer controls in Transitional mode since
// there is no separate Customizer for AMP in Transitional mode (or legacy Reader mode).
// @todo This check could be replaced with ( ! amp_is_canonical() && $this->paired_routing->has_endpoint() ).
if ( $this->is_reader_theme_request() ) {
$this->suppress_plugins();
} else {
$min_priority = defined( 'PHP_INT_MIN' ) ? PHP_INT_MIN : ~PHP_INT_MAX; // phpcs:ignore PHPCompatibility.Constants.NewConstants.php_int_minFound
// In Standard mode we _have_ to wait for the wp action because with the absence of a query parameter
// we have to rely on amp_is_request() and the WP_Query to determine whether a plugin should be suppressed.
add_action( 'wp', [ $this, 'maybe_suppress_plugins' ], $min_priority );
}
}
/**
* Is reader theme request.
*
* @return bool
*/
public function is_reader_theme_request() {
return (
AMP_Theme_Support::READER_MODE_SLUG === AMP_Options_Manager::get_option( Option::THEME_SUPPORT )
&&
ReaderThemes::DEFAULT_READER_THEME !== AMP_Options_Manager::get_option( Option::READER_THEME )
&&
$this->paired_routing->has_endpoint()
);
}
/**
* Add default option.
*
* @param array $defaults Default options.
* @return array Defaults.
*/
public function filter_default_options( $defaults ) {
$defaults[ Option::SUPPRESSED_PLUGINS ] = [];
return $defaults;
}
/**
* Suppress plugins if on an AMP endpoint.
*
* @return bool Whether plugins are being suppressed.
*/
public function maybe_suppress_plugins() {
if ( amp_is_request() ) {
return $this->suppress_plugins();
}
return false;
}
/**
* Suppress plugins.
*
* @return bool Whether plugins are being suppressed.
*/
public function suppress_plugins() {
$suppressed = AMP_Options_Manager::get_option( Option::SUPPRESSED_PLUGINS );
if ( empty( $suppressed ) ) {
return false;
}
$suppressed_plugin_slugs = array_keys( $suppressed );
$this->suppress_hooks( $suppressed_plugin_slugs );
$this->suppress_shortcodes( $suppressed_plugin_slugs );
$this->suppress_blocks( $suppressed_plugin_slugs );
$this->suppress_widgets( $suppressed_plugin_slugs );
return true;
}
/**
* Sanitize options.
*
* @param array $options Existing options with already-sanitized values for updating.
* @param array $new_options Unsanitized options being submitted for updating.
*
* @return array Sanitized options.
*/
public function sanitize_options( $options, $new_options ) {
if ( ! isset( $new_options[ Option::SUPPRESSED_PLUGINS ] ) ) {
return $options;
}
$option = $options[ Option::SUPPRESSED_PLUGINS ];
$posted_suppressed_plugins = $new_options[ Option::SUPPRESSED_PLUGINS ];
$plugins = $this->plugin_registry->get_plugins( true );
foreach ( $posted_suppressed_plugins as $plugin_slug => $suppressed ) {
if ( ! isset( $plugins[ $plugin_slug ] ) ) {
unset( $option[ $plugin_slug ] );
continue;
}
$suppressed = rest_sanitize_boolean( $suppressed );
if ( isset( $option[ $plugin_slug ] ) && ! $suppressed ) {
// Remove the plugin from being suppressed.
unset( $option[ $plugin_slug ] );
} elseif ( ! isset( $option[ $plugin_slug ] ) && $suppressed && array_key_exists( $plugin_slug, $plugins ) ) {
$user = wp_get_current_user();
$option[ $plugin_slug ] = [
// Note that we store the version that was suppressed so that we can alert the user when to check again.
Option::SUPPRESSED_PLUGINS_LAST_VERSION => $plugins[ $plugin_slug ]['Version'],
Option::SUPPRESSED_PLUGINS_TIMESTAMP => time(),
Option::SUPPRESSED_PLUGINS_USERNAME => $user->ID ? $user->user_nicename : null,
];
}
}
$options[ Option::SUPPRESSED_PLUGINS ] = $option;
return $options;
}
/**
* Provides validation errors for a plugin specified by slug.
*
* @param string $plugin_slug Plugin slug.
* @return array Validation errors.
*/
private function get_sorted_plugin_validation_errors( $plugin_slug ) {
$errors_by_source = AMP_Validated_URL_Post_Type::get_recent_validation_errors_by_source();
if ( ! isset( $errors_by_source['plugin'][ $plugin_slug ] ) ) {
return [];
}
$validation_errors = $errors_by_source['plugin'][ $plugin_slug ];
usort(
$validation_errors,
static function ( $a, $b ) {
/** @var WP_Term */
$a_term = $a['term'];
/** @var WP_Term */
$b_term = $b['term'];
$a_reviewed = ( (int) $a_term->term_group & AMP_Validation_Error_Taxonomy::ACKNOWLEDGED_VALIDATION_ERROR_BIT_MASK );
$b_reviewed = ( (int) $b_term->term_group & AMP_Validation_Error_Taxonomy::ACKNOWLEDGED_VALIDATION_ERROR_BIT_MASK );
if ( $a_reviewed !== $b_reviewed ) {
return (int) $a_reviewed - (int) $b_reviewed;
}
$a_removed = ( (int) $a_term->term_group & AMP_Validation_Error_Taxonomy::ACCEPTED_VALIDATION_ERROR_BIT_MASK );
$b_removed = ( (int) $b_term->term_group & AMP_Validation_Error_Taxonomy::ACCEPTED_VALIDATION_ERROR_BIT_MASK );
return (int) $a_removed - (int) $b_removed;
}
);
// Because this data will be accessed via REST, add additional fields that are not easily rendered in JS.
$validation_errors = array_map(
static function( $validation_error ) {
$term = $validation_error['term'];
$validation_error['edit_url'] = admin_url(
add_query_arg(
[
AMP_Validation_Error_Taxonomy::TAXONOMY_SLUG => $term->name,
'post_type' => AMP_Validated_URL_Post_Type::POST_TYPE_SLUG,
],
'edit.php'
)
);
$validation_error['title'] = AMP_Validation_Error_Taxonomy::get_error_title_from_code( $validation_error['data'] );
$validation_error['is_removed'] = (bool) ( (int) $term->term_group & AMP_Validation_Error_Taxonomy::ACCEPTED_VALIDATION_ERROR_BIT_MASK );
$validation_error['is_reviewed'] = (bool) ( (int) $term->term_group & AMP_Validation_Error_Taxonomy::ACKNOWLEDGED_VALIDATION_ERROR_BIT_MASK );
$validation_error['tooltip'] = sprintf(
/* translators: %1 is whether validation error is 'removed' or 'kept', %2 is whether validation error is 'reviewed' or 'unreviewed' */
__( 'Invalid markup causing the validation error is %1$s and %2$s. See all validated URL(s) with this validation error.', 'amp' ),
$validation_error['is_removed'] ? __( 'removed', 'amp' ) : __( 'kept', 'amp' ),
$validation_error['is_reviewed'] ? __( 'reviewed', 'amp' ) : __( 'unreviewed', 'amp' )
);
return $validation_error;
},
$validation_errors
);
return $validation_errors;
}
/**
* Provides a keyed array of active plugins with keys being slugs and values being plugin info plus validation error details.
*
* Plugins are sorted by validation error count, in descending order.
*
* @return array Plugins.
*/
public function get_suppressible_plugins_with_details() {
$plugins = [];
foreach ( $this->plugin_registry->get_plugins( true ) as $slug => $plugin ) {
$plugin['validation_errors'] = $this->get_sorted_plugin_validation_errors( $slug );
$plugins[ $slug ] = $plugin;
}
return $plugins;
}
/**
* Prepare suppressed plugins for response.
*
* Augment the suppressed plugins data with additional information.
*
* @param array $suppressed_plugins Suppressed plugins.
* @return array Prepared suppressed plugins.
*/
public function prepare_suppressed_plugins_for_response( $suppressed_plugins ) {
return array_map(
function ( $suppressed_plugin ) {
if ( ! is_array( $suppressed_plugin ) ) {
return $suppressed_plugin;
}
if ( isset( $suppressed_plugin['username'] ) ) {
$username = $suppressed_plugin['username'];
unset( $suppressed_plugin['username'] );
$suppressed_plugin['user'] = $this->prepare_user_for_response( $username );
}
return $suppressed_plugin;
},
$suppressed_plugins
);
}
/**
* Prepare user for response.
*
* @param string $username Username.
* @return array User data.
*/
private function prepare_user_for_response( $username ) {
$response = [
'slug' => $username,
];
$user = get_user_by( 'slug', $username );
if ( $user ) {
$response['name'] = $user->display_name;
}
return $response;
}
/**
* Suppress plugin hooks.
*
* @param string[] $suppressed_plugins Suppressed plugins.
* @global WP_Hook[] $wp_filter
*/
private function suppress_hooks( $suppressed_plugins ) {
global $wp_filter;
/** @var WP_Hook $filter */
foreach ( $wp_filter as $tag => $filter ) {
foreach ( $filter->callbacks as $priority => $prioritized_callbacks ) {
foreach ( $prioritized_callbacks as $callback ) {
if ( $this->is_callback_plugin_suppressed( $callback['function'], $suppressed_plugins ) ) {
// Obtain the original function which is necessary to pass into remove_filter() so that the
// expected key will be generated by _wp_filter_build_unique_id(). Alternatively, this could
// just below do `unset( $filter->callbacks[ $priority ][ $function_key ] )` but it seems safer
// to re-use all the existing logic in remove_filter().
$original_function = $this->callback_reflection->get_unwrapped_callback( $callback['function'] );
$filter->remove_filter( $tag, $original_function, $priority );
}
}
}
}
}
/**
* Suppress plugin shortcodes.
*
* @param string[] $suppressed_plugins Suppressed plugins.
* @global array $shortcode_tags
*/
private function suppress_shortcodes( $suppressed_plugins ) {
global $shortcode_tags;
foreach ( array_keys( $shortcode_tags ) as $tag ) {
if ( $this->is_callback_plugin_suppressed( $shortcode_tags[ $tag ], $suppressed_plugins ) ) {
add_shortcode( $tag, '__return_empty_string' );
}
}
}
/**
* Suppress plugin blocks.
*
* @todo What about static blocks added?
*
* @param string[] $suppressed_plugins Suppressed plugins.
*/
private function suppress_blocks( $suppressed_plugins ) {
if ( ! class_exists( 'WP_Block_Type_Registry' ) ) {
return;
}
$registry = WP_Block_Type_Registry::get_instance();
foreach ( $registry->get_all_registered() as $block_type ) {
if ( ! $block_type->is_dynamic() ) {
continue;
}
if (
$this->is_callback_plugin_suppressed( $block_type->render_callback, $suppressed_plugins )
||
(
isset( $this->original_block_render_callbacks[ $block_type->name ] )
&&
$this->is_callback_plugin_suppressed( $this->original_block_render_callbacks[ $block_type->name ], $suppressed_plugins )
)
) {
unset( $block_type->script, $block_type->style );
$block_type->render_callback = '__return_empty_string';
}
}
}
/**
* Suppress plugin widgets.
*
* @see AMP_Validation_Manager::wrap_widget_callbacks() Which needs to run after this.
*
* @param string[] $suppressed_plugins Suppressed plugins.
* @global array $wp_registered_widgets
*/
private function suppress_widgets( $suppressed_plugins ) {
global $wp_registered_widgets;
foreach ( $wp_registered_widgets as &$registered_widget ) {
if ( $this->is_callback_plugin_suppressed( $registered_widget['callback'], $suppressed_plugins ) ) {
// This is primarily needed for widgets registered without WP_Widget.
$registered_widget['callback'] = '__return_null';
}
}
// The above will ensure that widgets registered via WP_Widget or wp_register_sidebar_widget() will both be
// suppressed from being output. One additional case, which also applies to WP_Widget, is when the_widget()
// is used to render a widget. For that, the 'widget_display_callback' filter below is used (in WP>=5.3).
add_filter(
'widget_display_callback',
/**
* Prevent WP_Widgets from suppressed plugins from being rendered in sidebars and via the_widget().
*
* @param array $instance The current widget instance's settings.
* @param WP_Widget $widget_obj The current widget instance.
* @return array|false Instance or false if suppressed.
*/
function ( $instance, $widget_obj ) use ( $suppressed_plugins ) {
if ( $this->is_callback_plugin_suppressed( [ $widget_obj, 'display_callback' ], $suppressed_plugins ) ) {
$instance = false;
}
return $instance;
},
PHP_INT_MAX,
2
);
}
/**
* Determine whether callback is from a suppressed plugin.
*
* @param callable $callback Callback.
* @param string[] $suppressed_plugins Suppressed plugins.
* @return bool Whether from suppressed plugin.
*/
private function is_callback_plugin_suppressed( $callback, $suppressed_plugins ) {
$source = $this->callback_reflection->get_source( $callback );
return (
isset( $source['type'], $source['name'] ) &&
'plugin' === $source['type'] &&
in_array( $source['name'], $suppressed_plugins, true )
);
}
/**
* Add meta if plugin is suppressed in AMP page.
*
* @param array $plugin_meta An array of the plugin's metadata.
* @param string $plugin_file Path to the plugin file relative to the plugins directory.
*
* @return array An array of the plugin's metadata
*/
public function filter_plugin_row_meta( $plugin_meta, $plugin_file ) {
// Do not show on the network plugins screen.
if ( is_network_admin() ) {
return $plugin_meta;
}
$suppressed_plugins = AMP_Options_Manager::get_option( 'suppressed_plugins' );
$plugin_slug = $this->plugin_registry->get_plugin_slug_from_file( $plugin_file );
if ( isset( $suppressed_plugins[ $plugin_slug ] ) ) {
$plugin_meta[] = sprintf(
'<a href="%s" aria-label="%s">%s</a>',
esc_url( admin_url( 'admin.php?page=amp-options' ) . '#plugin-suppression' ),
esc_attr__( 'Visit AMP Settings', 'amp' ),
esc_html__( 'Suppressed on AMP Pages', 'amp' )
);
}
return $plugin_meta;
}
}