OwlCyberSecurity - MANAGER
Edit File: class-amp-options-manager.php
<?php /** * Class AMP_Options_Manager. * * @package AMP */ use AmpProject\AmpWP\Admin\ReaderThemes; use AmpProject\AmpWP\Option; /** * Class AMP_Options_Manager * * @internal */ class AMP_Options_Manager { /** * Option name. * * @var string */ const OPTION_NAME = 'amp-options'; /** * Default option values. * * @var array */ protected static $defaults = [ Option::THEME_SUPPORT => AMP_Theme_Support::READER_MODE_SLUG, Option::SUPPORTED_POST_TYPES => [ 'post', 'page' ], Option::ANALYTICS => [], Option::ALL_TEMPLATES_SUPPORTED => true, Option::SUPPORTED_TEMPLATES => [ 'is_singular' ], Option::VERSION => AMP__VERSION, Option::READER_THEME => ReaderThemes::DEFAULT_READER_THEME, Option::PAIRED_URL_STRUCTURE => Option::PAIRED_URL_STRUCTURE_QUERY_VAR, Option::PLUGIN_CONFIGURED => false, Option::DELETE_DATA_AT_UNINSTALL => true, Option::USE_NATIVE_IMG_TAG => false, ]; /** * Sets up hooks. */ public static function init() { add_action( 'admin_notices', [ __CLASS__, 'render_php_css_parser_conflict_notice' ] ); add_action( 'admin_notices', [ __CLASS__, 'insecure_connection_notice' ] ); add_action( 'admin_notices', [ __CLASS__, 'reader_theme_fallback_notice' ] ); } /** * Register settings. */ public static function register_settings() { register_setting( self::OPTION_NAME, self::OPTION_NAME, [ 'type' => 'array', 'sanitize_callback' => [ __CLASS__, 'validate_options' ], ] ); } /** * Get plugin options. * * @return array Options. */ public static function get_options() { $options = get_option( self::OPTION_NAME, [] ); if ( empty( $options ) ) { $options = []; // Ensure empty string becomes array. } $defaults = self::$defaults; $theme_support = AMP_Theme_Support::get_theme_support_args(); // Make sure the plugin is marked as being already configured if there saved options. if ( ! empty( $options ) ) { $defaults[ Option::PLUGIN_CONFIGURED ] = true; } // Migrate legacy method of specifying the mode. if ( ! isset( $options[ Option::THEME_SUPPORT ] ) && $theme_support ) { $template = get_template(); $stylesheet = get_stylesheet(); if ( // If theme support was probably explicitly added by the theme (since not core). ! in_array( $template, AMP_Core_Theme_Sanitizer::get_supported_themes(), true ) || // If it is a core theme no child theme is being used (which likely won't be AMP-compatible by default). $template === $stylesheet ) { if ( empty( $theme_support[ AMP_Theme_Support::PAIRED_FLAG ] ) ) { $defaults[ Option::THEME_SUPPORT ] = AMP_Theme_Support::STANDARD_MODE_SLUG; } else { $defaults[ Option::THEME_SUPPORT ] = AMP_Theme_Support::TRANSITIONAL_MODE_SLUG; } } } // Migrate legacy amp post type support to be reflected in the default supported_post_types value. if ( ! isset( $options[ Option::SUPPORTED_POST_TYPES ] ) ) { $defaults[ Option::SUPPORTED_POST_TYPES ] = array_merge( $defaults[ Option::SUPPORTED_POST_TYPES ], (array) get_post_types_by_support( 'amp' ) ); } // Migrate legacy method of specifying all_templates_supported. if ( ! isset( $options[ Option::ALL_TEMPLATES_SUPPORTED ] ) && isset( $theme_support['templates_supported'] ) ) { $defaults[ Option::ALL_TEMPLATES_SUPPORTED ] = ( 'all' === $theme_support['templates_supported'] ); } // Migrate legacy amp theme support to be reflected in the default supported_templates value. if ( ! isset( $options[ Option::SUPPORTED_TEMPLATES ] ) && isset( $theme_support['templates_supported'] ) && is_array( $theme_support['templates_supported'] ) ) { $defaults[ Option::SUPPORTED_TEMPLATES ] = array_merge( $defaults[ Option::SUPPORTED_TEMPLATES ], array_keys( array_filter( $theme_support['templates_supported'] ) ) ); $defaults[ Option::SUPPORTED_TEMPLATES ] = array_diff( $defaults[ Option::SUPPORTED_TEMPLATES ], array_keys( array_filter( $theme_support['templates_supported'], static function ( $supported ) { return ! $supported; } ) ) ); } $options = array_merge( $defaults, /** * Filters default options. * * @internal * @param array $defaults Default options. * @param array $current_options Current options. */ (array) apply_filters( 'amp_default_options', $defaults, $options ), $options ); // Ensure current template mode. if ( 'native' === $options[ Option::THEME_SUPPORT ] ) { // The slug 'native' is the old term for 'standard'. $options[ Option::THEME_SUPPORT ] = AMP_Theme_Support::STANDARD_MODE_SLUG; } elseif ( 'paired' === $options[ Option::THEME_SUPPORT ] ) { // The slug 'paired' is the old term for 'transitional. $options[ Option::THEME_SUPPORT ] = AMP_Theme_Support::TRANSITIONAL_MODE_SLUG; } elseif ( 'disabled' === $options[ Option::THEME_SUPPORT ] ) { /* * Prior to 1.2, the theme support slug for Reader mode was 'disabled'. This would be saved in options for * themes that had 'amp' theme support defined. Also prior to 1.2, the user could not switch between modes * when the theme had 'amp' theme support. The result is that a site running 1.1 could be AMP-first and then * upon upgrading to 1.2, be switched to Reader mode. So when migrating the old 'disabled' slug to the new * value, we need to make sure we use the default theme support slug as it has been determined above. If the * site has non-paired 'amp' theme support and the theme support slug is 'disabled' then it should here be * set to 'standard' as opposed to 'reader', and the same goes for paired 'amp' theme support, as it should * become 'transitional'. Otherwise, if the theme lacks 'amp' theme support, then this will become the * default 'reader' mode. */ $options[ Option::THEME_SUPPORT ] = $defaults[ Option::THEME_SUPPORT ]; } // Migrate options from 1.5 to 2.0. if ( isset( $options['version'] ) && version_compare( $options['version'], '2.0', '<' ) ) { // It used to be that the themes_supported flag overrode the options, so make sure the option gets updated to reflect the theme support. if ( isset( $theme_support['templates_supported'] ) ) { if ( 'all' === $theme_support['templates_supported'] ) { $options[ Option::ALL_TEMPLATES_SUPPORTED ] = true; } elseif ( is_array( $theme_support['templates_supported'] ) ) { $options[ Option::ALL_TEMPLATES_SUPPORTED ] = false; $options[ Option::SUPPORTED_TEMPLATES ] = array_merge( $options[ Option::SUPPORTED_TEMPLATES ], array_keys( array_filter( $theme_support['templates_supported'] ) ) ); $options[ Option::SUPPORTED_TEMPLATES ] = array_diff( $options[ Option::SUPPORTED_TEMPLATES ], array_keys( array_filter( $theme_support['templates_supported'], static function ( $supported ) { return ! $supported; } ) ) ); } } // Make sure programmatic post type support is persisted in the DB, as from now on the DB option is the source of truth. $options[ Option::SUPPORTED_POST_TYPES ] = array_merge( $options[ Option::SUPPORTED_POST_TYPES ], (array) get_post_types_by_support( AMP_Post_Type_Support::SLUG ) ); // Make sure that all post types get enabled if all templates were supported since they are now independently controlled. This only applies to non-Reader mode. if ( ! empty( $options[ Option::ALL_TEMPLATES_SUPPORTED ] ) && AMP_Theme_Support::READER_MODE_SLUG !== $options[ Option::THEME_SUPPORT ] ) { $options[ Option::SUPPORTED_POST_TYPES ] = array_merge( $options[ Option::SUPPORTED_POST_TYPES ], AMP_Post_Type_Support::get_eligible_post_types() ); } } unset( /** * Remove 'auto_accept_sanitization' option. * * @since 1.4.0 */ $options[ Option::AUTO_ACCEPT_SANITIZATION ], /** * Remove Story related options. * * Option::ENABLE_AMP_STORIES was added in 1.2-beta and later migrated into the `experiences` option. * * @since 1.5.0 */ $options[ Option::STORY_TEMPLATES_VERSION ], $options[ Option::STORY_EXPORT_BASE_URL ], $options[ Option::STORY_SETTINGS ], $options[ Option::ENABLE_AMP_STORIES ], /** * Remove 'experiences' option. * * @since 1.5.0 */ $options[ Option::EXPERIENCES ], /** * Remove 'enable_response_caching' option. * * @since 1.5.0 */ $options[ Option::ENABLE_RESPONSE_CACHING ] ); return $options; } /** * Get plugin option. * * @param string $option Plugin option name. * @param bool $default Default value. * * @return mixed Option value. */ public static function get_option( $option, $default = false ) { $amp_options = self::get_options(); if ( ! array_key_exists( $option, $amp_options ) ) { return $default; } return $amp_options[ $option ]; } /** * Validate options. * * @param array $new_options Plugin options. * @return array Options. */ public static function validate_options( $new_options ) { $options = self::get_options(); if ( ! current_user_can( 'manage_options' ) ) { return $options; } // Theme support. $recognized_theme_supports = [ AMP_Theme_Support::READER_MODE_SLUG, AMP_Theme_Support::TRANSITIONAL_MODE_SLUG, AMP_Theme_Support::STANDARD_MODE_SLUG, ]; if ( isset( $new_options[ Option::THEME_SUPPORT ] ) && in_array( $new_options[ Option::THEME_SUPPORT ], $recognized_theme_supports, true ) ) { $options[ Option::THEME_SUPPORT ] = $new_options[ Option::THEME_SUPPORT ]; } // Validate post type support. if ( isset( $new_options[ Option::SUPPORTED_POST_TYPES ] ) && is_array( $new_options[ Option::SUPPORTED_POST_TYPES ] ) ) { $options[ Option::SUPPORTED_POST_TYPES ] = []; foreach ( $new_options[ Option::SUPPORTED_POST_TYPES ] as $post_type ) { if ( post_type_exists( $post_type ) ) { $options[ Option::SUPPORTED_POST_TYPES ][] = $post_type; } } $options[ Option::SUPPORTED_POST_TYPES ] = array_values( array_unique( $options[ Option::SUPPORTED_POST_TYPES ] ) ); } // Update all_templates_supported. if ( isset( $new_options[ Option::ALL_TEMPLATES_SUPPORTED ] ) ) { $options[ Option::ALL_TEMPLATES_SUPPORTED ] = rest_sanitize_boolean( $new_options[ Option::ALL_TEMPLATES_SUPPORTED ] ); } // Validate supported templates. if ( isset( $new_options[ Option::SUPPORTED_TEMPLATES ] ) && is_array( $new_options[ Option::SUPPORTED_TEMPLATES ] ) ) { $supportable_templates = AMP_Theme_Support::get_supportable_templates(); $options[ Option::SUPPORTED_TEMPLATES ] = []; foreach ( $new_options[ Option::SUPPORTED_TEMPLATES ] as $template_id ) { if ( array_key_exists( $template_id, $supportable_templates ) ) { $options[ Option::SUPPORTED_TEMPLATES ][] = $template_id; } } $options[ Option::SUPPORTED_TEMPLATES ] = array_values( array_unique( $options[ Option::SUPPORTED_TEMPLATES ] ) ); } // Validate wizard completion. if ( isset( $new_options[ Option::PLUGIN_CONFIGURED ] ) ) { $options[ Option::PLUGIN_CONFIGURED ] = (bool) $new_options[ OPTION::PLUGIN_CONFIGURED ]; } if ( isset( $new_options[ Option::DELETE_DATA_AT_UNINSTALL ] ) ) { $options[ Option::DELETE_DATA_AT_UNINSTALL ] = (bool) $new_options[ OPTION::DELETE_DATA_AT_UNINSTALL ]; } if ( isset( $new_options[ Option::USE_NATIVE_IMG_TAG ] ) ) { $options[ Option::USE_NATIVE_IMG_TAG ] = (bool) $new_options[ OPTION::USE_NATIVE_IMG_TAG ]; } // Validate analytics. if ( isset( $new_options[ Option::ANALYTICS ] ) && $new_options[ Option::ANALYTICS ] !== $options[ Option::ANALYTICS ] ) { $new_analytics_option = []; foreach ( $new_options[ Option::ANALYTICS ] as $id => $data ) { if ( empty( $data['config'] ) || ! AMP_HTML_Utils::is_valid_json( $data['config'] ) ) { // Bad JSON or missing config. continue; } $new_analytics_option[ sanitize_key( $id ) ] = [ 'type' => ! empty( $data['type'] ) ? preg_replace( '/[^a-zA-Z0-9_\-]/', '', $data['type'] ) : '', 'config' => trim( $data['config'] ), ]; } $options[ OPTION::ANALYTICS ] = $new_analytics_option; } if ( isset( $new_options[ Option::READER_THEME ] ) && $new_options[ Option::READER_THEME ] !== $options[ Option::READER_THEME ] ) { $reader_theme_slugs = wp_list_pluck( ( new ReaderThemes() )->get_themes(), 'slug' ); if ( in_array( $new_options[ Option::READER_THEME ], $reader_theme_slugs, true ) ) { $options[ Option::READER_THEME ] = $new_options[ Option::READER_THEME ]; } } /** * Filter the options being updated, so services can handle the sanitization and validation of * their respective options. * * @internal * * @param array $options Existing options with already-sanitized values for updating. * @param array $new_options Unsanitized options being submitted for updating. */ $options = apply_filters( 'amp_options_updating', $options, $new_options ); // Store the current version with the options so we know the format. $options[ Option::VERSION ] = AMP__VERSION; return $options; } /** * Update plugin option. * * @param string $option Plugin option name. * @param mixed $value Plugin option value. * * @return bool Whether update succeeded. */ public static function update_option( $option, $value ) { $amp_options = self::get_options(); $amp_options[ $option ] = $value; return update_option( self::OPTION_NAME, $amp_options, false ); } /** * Update plugin options. * * @param array $options Plugin option name. * @return bool Whether update succeeded. */ public static function update_options( $options ) { $amp_options = array_merge( self::get_options(), $options ); return update_option( self::OPTION_NAME, $amp_options, false ); } /** * Render PHP-CSS-Parser conflict notice. * * @return void */ public static function render_php_css_parser_conflict_notice() { $current_screen = get_current_screen(); if ( ! ( $current_screen instanceof WP_Screen ) || 'toplevel_page_' . self::OPTION_NAME !== $current_screen->id ) { return; } if ( AMP_Style_Sanitizer::has_required_php_css_parser() ) { return; } try { $reflection = new ReflectionClass( 'Sabberworm\CSS\CSSList\CSSList' ); $source_dir = str_replace( trailingslashit( WP_CONTENT_DIR ), '', preg_replace( '#/vendor/sabberworm/.+#', '', $reflection->getFileName() ) ); printf( '<div class="notice notice-warning"><p>%s</p></div>', wp_kses( sprintf( /* translators: %s: path to the conflicting library */ __( 'A conflicting version of PHP-CSS-Parser appears to be installed by another plugin or theme (located in %s). Because of this, CSS processing will be limited, and tree shaking will not be available.', 'amp' ), '<code>' . esc_html( $source_dir ) . '</code>' ), [ 'code' => [] ] ) ); } catch ( ReflectionException $e ) { printf( '<div class="notice notice-warning"><p>%s</p></div>', esc_html__( 'PHP-CSS-Parser is not available so CSS processing will not be available.', 'amp' ) ); } } /** * Outputs an admin notice if the site is not served over HTTPS. * * @since 1.3 * * @return void */ public static function insecure_connection_notice() { $current_screen = get_current_screen(); // is_ssl() only tells us whether the admin backend uses HTTPS here, so we add a few more sanity checks. $uses_ssl = ( is_ssl() && ( strpos( get_bloginfo( 'wpurl' ), 'https' ) === 0 ) && ( strpos( get_bloginfo( 'url' ), 'https' ) === 0 ) ); if ( ! $uses_ssl && $current_screen instanceof WP_Screen && 'toplevel_page_' . self::OPTION_NAME === $current_screen->id ) { printf( '<div class="notice notice-warning"><p>%s</p></div>', wp_kses( sprintf( /* translators: %s: "Why should I use HTTPS" support URL */ __( 'Your site is not being fully served over a secure connection (using HTTPS).<br>As some AMP functionality requires a secure connection, you might experience degraded performance or broken components.<br><a href="%s">More details</a>', 'amp' ), esc_url( __( 'https://wordpress.org/support/article/why-should-i-use-https/', 'amp' ) ) ), [ 'br' => [], 'a' => [ 'href' => true ], ] ) ); } } /** * Outputs an admin notice if the AMP Legacy Reader theme is used as a fallback. */ public static function reader_theme_fallback_notice() { $current_screen = get_current_screen(); if ( ! ( $current_screen instanceof WP_Screen ) || ! in_array( $current_screen->id, [ 'themes', 'toplevel_page_' . self::OPTION_NAME ], true ) ) { return; } $reader_themes = new ReaderThemes(); if ( $reader_themes->using_fallback_theme() && current_user_can( 'manage_options' ) ) { $selected_theme = self::get_option( Option::READER_THEME ); $error_message = sprintf( /* translators: 1: slug of the Reader theme, 2: the URL for the reader theme selection UI */ __( 'The AMP Reader theme %1$s cannot be found. Your site is currently falling back to using the Legacy templates for AMP pages. Please <a href="%2$s">re-select</a> the desired Reader theme.', 'amp' ), "<code>{$selected_theme}</code>", esc_url( add_query_arg( 'page', self::OPTION_NAME, admin_url( 'admin.php' ) ) . '#reader-themes' ) ); ?> <div class="notice notice-warning"> <p> <?php echo wp_kses( $error_message, [ 'code' => [], 'a' => [ 'href' => true, ], ] ); ?> </p> </div> <?php } } }