PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /home/trave494/internetmoney.kerihosting.com/wp-content/plugins/amp/src/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : /home/trave494/internetmoney.kerihosting.com/wp-content/plugins/amp/src/PluginSuppression.php |
<?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; } }