ProShell v2.0
Dashboard
Server Info
Server: 158.106.128.192
PHP: 8.2.33
home
planet5
public_html
bansibaba.com
wp-includes
2026-09-12 17:36:29
Editing: amp-helper-functions.php
Cancel
Save Changes
<?php /** * AMP Helper Functions * * @package AMP */ use AmpProject\AmpWP\Admin\ReaderThemes; use AmpProject\AmpWP\AmpSlugCustomizationWatcher; use AmpProject\AmpWP\AmpWpPluginFactory; use AmpProject\AmpWP\Exception\InvalidService; use AmpProject\AmpWP\Icon; use AmpProject\AmpWP\Option; use AmpProject\AmpWP\QueryVar; use AmpProject\AmpWP\Services; /** * Determine whether AMP is enabled on the current site. * * @since 2.1.1 * @internal * * @return bool Whether enabled. */ function amp_is_enabled() { /** * Filters whether AMP is enabled on the current site. * * Useful if the plugin is network activated and you want to turn it off on select sites. * * @since 0.2 * @since 2.0 Filter now runs earlier at plugins_loaded (with earliest priority) rather than at the after_setup_theme action. * * @param bool $enabled Whether the AMP plugin's functionality should be enabled. */ return (bool) apply_filters( 'amp_is_enabled', true ); } /** * Handle activation of plugin. * * @since 0.2 * @internal * * @param bool $network_wide Whether the activation was done network-wide. */ function amp_activate( $network_wide = false ) { if ( amp_is_enabled() ) { AmpWpPluginFactory::create()->activate( $network_wide ); } } /** * Handle deactivation of plugin. * * @since 0.2 * @internal * * @param bool $network_wide Whether the activation was done network-wide. */ function amp_deactivate( $network_wide = false ) { if ( amp_is_enabled() ) { AmpWpPluginFactory::create()->deactivate( $network_wide ); } } /** * Bootstrap plugin. * * @since 1.5 * @internal */ function amp_bootstrap_plugin() { if ( ! amp_is_enabled() ) { return; } AmpWpPluginFactory::create()->register(); // The amp_bootstrap_plugin() function is called at the plugins_loaded action with the earliest priority. This is // the earliest we can run this since that is when pluggable.php has been required and wp_hash() is available. AMP_Validation_Manager::init_validate_request(); /* * Register AMP scripts regardless of whether AMP is enabled or it is the AMP endpoint * for the sake of being able to use AMP components on non-AMP documents ("dirty AMP"). */ add_action( 'wp_default_scripts', 'amp_register_default_scripts' ); add_action( 'wp_default_styles', 'amp_register_default_styles' ); // Ensure async and custom-element/custom-template attributes are present on script tags. add_filter( 'script_loader_tag', 'amp_filter_script_loader_tag', PHP_INT_MAX, 2 ); // Ensure crossorigin=anonymous is added to font links. add_filter( 'style_loader_tag', 'amp_filter_font_style_loader_tag_with_crossorigin_anonymous', 10, 4 ); add_action( 'after_setup_theme', 'amp_after_setup_theme', 5 ); add_action( 'plugins_loaded', '_amp_bootstrap_customizer', 9 ); // Should be hooked before priority 10 on 'plugins_loaded' to properly unhook core panels. // @todo Eliminate this once https://core.trac.wordpress.org/ticket/20578 has finally landed. add_filter( 'all_plugins', 'amp_modify_plugin_description' ); } /** * Init AMP. * * @since 0.1 * @internal */ function amp_init() { /** * Triggers on init when AMP plugin is active. * * @since 0.3 */ do_action( 'amp_init' ); add_filter( 'allowed_redirect_hosts', [ AMP_HTTP::class, 'filter_allowed_redirect_hosts' ] ); AMP_HTTP::purge_amp_query_vars(); AMP_HTTP::send_cors_headers(); AMP_HTTP::handle_xhr_request(); AMP_Theme_Support::init(); AMP_Validation_Manager::init(); AMP_Service_Worker::init(); add_action( 'admin_init', 'AMP_Options_Manager::init' ); add_action( 'admin_init', 'AMP_Options_Manager::register_settings' ); add_action( 'rest_api_init', 'AMP_Options_Manager::register_settings' ); add_action( 'wp_loaded', 'amp_bootstrap_admin' ); add_action( 'admin_bar_menu', 'amp_add_admin_bar_view_link', 100 ); add_action( 'admin_bar_init', function () { $handle = 'amp-icons'; if ( ! is_admin() && wp_style_is( $handle, 'registered' ) ) { wp_styles()->registered[ $handle ]->deps[] = 'admin-bar'; // Ensure included in dev mode. wp_enqueue_style( $handle ); } } ); add_action( 'wp_loaded', 'amp_editor_core_blocks' ); add_filter( 'request', 'amp_force_query_var_value' ); /* * Broadcast plugin updates. * Note that AMP_Options_Manager::get_option( Option::VERSION, '0.0' ) cannot be used because * version was new option added, and in that case default would never be used for a site * upgrading from a version prior to 1.0. So this is why get_option() is currently used. */ $options = get_option( AMP_Options_Manager::OPTION_NAME, [] ); $old_version = isset( $options[ Option::VERSION ] ) ? $options[ Option::VERSION ] : '0.0'; if ( AMP__VERSION !== $old_version && is_admin() && current_user_can( 'manage_options' ) ) { // This waits to happen until the very end of admin_init to ensure that amp theme support and amp post type // support have all been added, and that the settings have been registered. add_action( 'admin_init', static function () use ( $old_version ) { /** * Triggers when after amp_init when the plugin version has updated. * * @param string $old_version Old version. */ do_action( 'amp_plugin_update', $old_version ); AMP_Options_Manager::update_option( Option::VERSION, AMP__VERSION ); }, PHP_INT_MAX ); } add_action( 'rest_api_init', static function () { $reader_themes = new ReaderThemes(); $reader_theme_controller = new AMP_Reader_Theme_REST_Controller( $reader_themes ); $reader_theme_controller->register_routes(); } ); /* * Hide admin bar if the window is inside the setup wizard iframe. * * Detects whether the current window is in an iframe with the specified `name` attribute. The iframe is created * by Preview component located in <assets/src/setup/pages/save/index.js>. */ add_action( 'wp_head', 'amp_remove_admin_bar_in_phone_preview' ); add_action( 'amp_post_template_head', 'amp_remove_admin_bar_in_phone_preview' ); } /** * Remove the admin bar in phone preview of the site in AMP Onboarding Wizard. * * @since 2.2.2 * @internal */ function amp_remove_admin_bar_in_phone_preview() { if ( ! amp_is_dev_mode() || ! is_admin_bar_showing() ) { return; } ?> <script data-ampdevmode> ( () => { if ( 'amp-wizard-completion-preview' !== window.name ) { return; } /** @type {HTMLStyleElement} */ const style = document.createElement( 'style' ); style.setAttribute( 'type', 'text/css' ); style.appendChild( document.createTextNode( 'html:not(#_) { margin-top: 0 !important; } #wpadminbar { display: none !important; }' ) ); document.head.appendChild( style ); document.addEventListener( 'DOMContentLoaded', function() { const adminBar = document.getElementById( 'wpadminbar' ); if ( adminBar ) { document.body.classList.remove( 'admin-bar' ); adminBar.remove(); } }); } )(); </script> <?php } /** * When AMP plugin is active remove instruction of plugin data removal steps. * * @since 2.2 * @internal * * @param array $meta An array of plugins to display in the list table. * @return array An array of plugins to display in the list table. */ function amp_modify_plugin_description( $meta ) { if ( isset( $meta['amp/amp.php']['Description'] ) ) { $meta['amp/amp.php']['Description'] = preg_replace( ':\s*<em class=\"amp-deletion-notice\">.+?</em>:', '', $meta['amp/amp.php']['Description'] ); } return $meta; } /** * Set up AMP. * * This function must be invoked through the 'after_setup_theme' action to allow * the AMP setting to declare the post types support earlier than plugins/theme. * * @since 0.6 * @internal */ function amp_after_setup_theme() { // Ensure AMP_QUERY_VAR is set since some plugins still try reading it instead of using amp_get_slug(). if ( ! defined( 'AMP_QUERY_VAR' ) ) { define( 'AMP_QUERY_VAR', amp_get_slug() ); } /** This filter is documented in includes/amp-helper-functions.php */ if ( false === apply_filters( 'amp_is_enabled', true ) ) { _doing_it_wrong( 'add_filter', esc_html( sprintf( /* translators: 1: amp_is_enabled filter name, 2: plugins_loaded action */ __( 'Filter for "%1$s" added too late. To disable AMP, this filter must be added before the "%2$s" action.', 'amp' ), 'amp_is_enabled', 'plugins_loaded' ) ), '2.0' ); } add_action( 'init', 'amp_init', 0 ); // Must be 0 because widgets_init happens at init priority 1. } /** * Make sure the `amp` query var has an explicit value. * * This avoids issues when filtering the deprecated `query_string` hook. * * @since 0.3.3 * @internal * * @param array $query_vars Query vars. * @return array Query vars. */ function amp_force_query_var_value( $query_vars ) { if ( isset( $query_vars[ amp_get_slug() ] ) && '' === $query_vars[ amp_get_slug() ] ) { $query_vars[ amp_get_slug() ] = 1; } return $query_vars; } /** * Whether this is in 'canonical mode'. * * Themes can register support for this with `add_theme_support( AMP_Theme_Support::SLUG )`: * * ```php * add_theme_support( AMP_Theme_Support::SLUG ); * ``` * * This will serve templates in AMP-first, allowing you to use AMP components in your theme templates. * If you want to make available in transitional mode, where templates are served in AMP or non-AMP documents, do: * * ```php * add_theme_support( AMP_Theme_Support::SLUG, array( * 'paired' => true, * ) ); * ``` * * Transitional mode is also implied if you define a `template_dir`: * * ```php * add_theme_support( AMP_Theme_Support::SLUG, array( * 'template_dir' => 'amp', * ) ); * ``` * * If you want to have AMP-specific templates in addition to serving AMP-first, do: * * ```php * add_theme_support( AMP_Theme_Support::SLUG, array( * 'paired' => false, * 'template_dir' => 'amp', * ) ); * ``` * * @see AMP_Theme_Support::read_theme_support() * @return boolean Whether this is in AMP 'canonical' mode, that is whether it is AMP-first and there is not a separate (paired) AMP URL. */ function amp_is_canonical() { return AMP_Theme_Support::STANDARD_MODE_SLUG === AMP_Options_Manager::get_option( Option::THEME_SUPPORT ); } /** * Determines whether the legacy AMP post templates are being used. * * @since 2.0 * @return bool */ function amp_is_legacy() { if ( AMP_Theme_Support::READER_MODE_SLUG !== AMP_Options_Manager::get_option( Option::THEME_SUPPORT ) ) { return false; } $reader_theme = AMP_Options_Manager::get_option( Option::READER_THEME ); if ( ReaderThemes::DEFAULT_READER_THEME === $reader_theme ) { return true; } return ! wp_get_theme( $reader_theme )->exists(); } /** * Determine whether AMP is available for the current URL. * * @since 2.0 * * @return bool Whether there is an AMP version for the provided URL. * @global string $pagenow * @global WP_Query $wp_query */ function amp_is_available() { global $pagenow, $wp_query; // Short-circuit for cron, CLI, admin requests or requests to non-frontend pages. if ( wp_doing_cron() || ( defined( 'WP_CLI' ) && WP_CLI ) || is_admin() || in_array( $pagenow, [ 'wp-login.php', 'wp-signup.php', 'wp-activate.php', 'repair.php' ], true ) ) { return false; } $warn = static function () { static $already_warned_sources = []; try { $likely_culprit_detector = Services::get( 'dev_tools.likely_culprit_detector' ); $closest_source = $likely_culprit_detector->analyze_backtrace(); } catch ( InvalidService $e ) { $closest_source = [ 'type' => 'exception', 'name' => 'invalid_service', ]; } $closest_source_identifier = $closest_source['type'] . ':' . $closest_source['name']; if ( in_array( $closest_source_identifier, $already_warned_sources, true ) ) { return; } $message = sprintf( /* translators: 1: amp_is_available function, 2: amp_is_request function, 3: is_amp_endpoint function */ __( '%1$s (or %2$s, formerly %3$s) was called too early and so it will not work properly.', 'amp' ), '`amp_is_available()`', '`amp_is_request()`', '`is_amp_endpoint()`' ); $current_hook = current_action(); if ( $current_hook ) { /* translators: placeholder is the current hook */ $message .= ' ' . sprintf( 'WordPress is currently doing the %s hook.', '`' . $current_hook . '`' ); } else { $message .= ' ' . __( 'WordPress is not currently doing any hook.', 'amp' ); } $message .= ' ' . sprintf( /* translators: 1: the wp action, 2: the WP_Query class, 3: the amp_skip_post() function */ __( 'Calling this function before the %1$s action means it will not have access to %2$s and the queried object to determine if it is an AMP response, thus neither the %3$s filter nor the AMP enabled toggle will be considered.', 'amp' ), '`wp`', '`WP_Query`', '`amp_skip_post()`' ); if ( ! empty( $closest_source['type'] ) && ! empty( $closest_source['name'] ) ) { $translated_string = false; switch ( $closest_source['type'] ) { case 'plugin': /* translators: placeholder is the slug of the plugin */ $translated_string = __( 'It appears the plugin with slug %s is responsible; please contact the author.', 'amp' ); break; case 'mu-plugin': /* translators: placeholder is the slug of the must-use plugin */ $translated_string = __( 'It appears the must-use plugin with slug %s is responsible; please contact the author.', 'amp' ); break; case 'theme': /* translators: placeholder is the slug of the theme */ $translated_string = __( 'It appears the theme with slug %s is responsible; please contact the author.', 'amp' ); break; case 'exception': $translated_string = __( 'The function was called too early (before the plugins_loaded action) to determine the plugin source.', 'amp' ); break; } if ( $translated_string ) { $message .= ' ' . sprintf( $translated_string, '`' . $closest_source['name'] . '`' ); } } _doing_it_wrong( 'amp_is_available', esc_html( $message ), '2.0.0' ); $already_warned_sources[] = $closest_source_identifier; }; // Make sure the parse_request action has triggered before trying to read from the REST_REQUEST constant, which is set during rest_api_loaded(). if ( ! did_action( 'parse_request' ) ) { $warn(); } elseif ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { return false; } // Make sure that the parse_query action has triggered, as this is required to initially populate the global WP_Query. if ( ! ( $wp_query instanceof WP_Query || did_action( 'parse_query' ) ) ) { $warn(); } // Always return false when requesting the service worker. // Note this is no longer strictly required because AMP_Theme_Support::prepare_response() will abort for non-HTML responses. // But it is still good to do so because it avoids needlessly output-buffering the response. if ( class_exists( 'WP_Service_Workers' ) && $wp_query instanceof WP_Query && defined( 'WP_Service_Workers::QUERY_VAR' ) && $wp_query->get( WP_Service_Workers::QUERY_VAR ) ) { return false; } // Short-circuit queries that can never have AMP responses (e.g. post embeds and feeds). // Note that these conditionals only require the parse_query action to have been run. They don't depend on the wp action having been fired. if ( $wp_query instanceof WP_Query && ( $wp_query->is_embed() || $wp_query->is_feed() || $wp_query->is_comment_feed() || $wp_query->is_trackback() || $wp_query->is_robots() || ( method_exists( $wp_query, 'is_favicon' ) && $wp_query->is_favicon() ) ) ) { return false; } // Ensure that all templates can be accessed in AMP when a Reader theme is selected. $has_reader_theme = ( 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 ) ); if ( $has_reader_theme && is_customize_preview() ) { return true; } $is_legacy = amp_is_legacy(); // If the query has not been initialized, we can only assume AMP is available if theme support is present and all templates are supported. if ( ! $wp_query instanceof WP_Query || ! did_action( 'wp' ) ) { $warn(); return ! $is_legacy && AMP_Options_Manager::get_option( Option::ALL_TEMPLATES_SUPPORTED ); } // If redirected to this page because AMP is not available due to validation errors, prevent AMP from being available (if not AMP-first). if ( ( ! amp_is_canonical() || AMP_Validation_Manager::has_cap() ) && ( isset( $_GET[ QueryVar::NOAMP ] ) && QueryVar::NOAMP_AVAILABLE === $_GET[ QueryVar::NOAMP ] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended ) { return false; } $queried_object = get_queried_object(); if ( ! $is_legacy ) { // Abort if in Transitional mode and AMP is not available for the URL. $availability = AMP_Theme_Support::get_template_availability( $wp_query ); if ( ! $availability['supported'] ) { return false; } // If not in an AMP-first mode, check if there are any validation errors with kept invalid markup for this URL. // And if so, and if the user cannot do validation (since they can always get fresh validation results), then // AMP is not available. if ( ! amp_is_canonical() && ! AMP_Validation_Manager::has_cap() ) { $validation_errors = AMP_Validated_URL_Post_Type::get_invalid_url_validation_errors( amp_get_current_url(), [ 'ignore_accepted' => true ] ); if ( count( $validation_errors ) > 0 ) { return false; } } } elseif ( ! ( $queried_object instanceof WP_Post && ( $wp_query->is_singular() || $wp_query->is_posts_page ) && amp_is_post_supported( $queried_object ) ) ) { // Abort if in legacy Reader mode and the post doesn't support AMP. return false; } return true; } /** * Bootstraps the AMP customizer. * * Uses the priority of 12 for the 'after_setup_theme' action. * Many themes run `add_theme_support()` on the 'after_setup_theme' hook, at the default priority of 10. * And that function's documentation suggests adding it to that action. * So this enables themes to `add_theme_support( AMP_Theme_Support::SLUG )`. * And `amp_init_customizer()` will be able to recognize theme support by calling `amp_is_canonical()`. * * @since 0.4 * @internal */ function _amp_bootstrap_customizer() { add_action( 'after_setup_theme', 'amp_init_customizer', 12 ); } /** * Get the slug used in AMP for the query var, endpoint, and post type support. * * The return value can be overridden by previously defining a AMP_QUERY_VAR * constant or by adding a 'amp_query_var' filter, but *warning* this ability * may be deprecated in the future. Normally the slug should be just 'amp'. * * @since 0.7 * @since 2.1 Added $ignore_late_defined_slug argument. * * @param bool $ignore_late_defined_slug Whether to ignore the late defined slug. * @return string Slug used for query var, endpoint, and post type support. */ function amp_get_slug( $ignore_late_defined_slug = false ) { // When a slug was defined late according to AmpSlugCustomizationWatcher, the slug will be stored in the // LATE_DEFINED_SLUG option by the PairedRouting service so that it can be used early. This is only needed until // the after_setup_theme action fires, because at that time the late-defined slug will have been established. if ( ! $ignore_late_defined_slug && ! did_action( AmpSlugCustomizationWatcher::LATE_DETERMINATION_ACTION ) ) { $slug = AMP_Options_Manager::get_option( Option::LATE_DEFINED_SLUG ); if ( ! empty( $slug ) && is_string( $slug ) ) { return $slug; } } /** * Filter the AMP query variable. * * Warning: This filter may become deprecated. * * @since 0.3.2 * * @param string $query_var The AMP query variable. */ return apply_filters( 'amp_query_var', defined( 'AMP_QUERY_VAR' ) ? AMP_QUERY_VAR : QueryVar::AMP ); } /** * Get the URL for the current request. * * This is essentially the REQUEST_URI prefixed by the scheme and host for the home URL. * This is needed in particular due to subdirectory installs. * * @since 1.0 * @internal * * @return string Current URL. */ function amp_get_current_url() { $parsed_url = wp_parse_url( home_url() ); if ( ! is_array( $parsed_url ) ) { $parsed_url = []; } if ( empty( $parsed_url['scheme'] ) ) { $parsed_url['scheme'] = is_ssl() ? 'https' : 'http'; } if ( ! isset( $parsed_url['host'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $parsed_url['host'] = isset( $_SERVER['HTTP_HOST'] ) ? wp_unslash( $_SERVER['HTTP_HOST'] ) : 'localhost'; } $current_url = $parsed_url['scheme'] . '://'; if ( isset( $parsed_url['user'] ) ) { $current_url .= $parsed_url['user']; if ( isset( $parsed_url['pass'] ) ) { $current_url .= ':' . $parsed_url['pass']; } $current_url .= '@'; } $current_url .= $parsed_url['host']; if ( isset( $parsed_url['port'] ) ) { $current_url .= ':' . $parsed_url['port']; } $current_url .= '/'; if ( isset( $_SERVER['REQUEST_URI'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $current_url .= ltrim( wp_unslash( $_SERVER['REQUEST_URI'] ), '/' ); } return esc_url_raw( $current_url ); } /** * Retrieves the full AMP-specific permalink for the given post ID. * * On a site in Standard mode, this is the same as `get_permalink()`. * * @since 0.1 * * @param int $post_id Post ID. * @return string AMP permalink. */ function amp_get_permalink( $post_id ) { if ( amp_is_canonical() ) { return get_permalink( $post_id ); } return amp_add_paired_endpoint( get_permalink( $post_id ) ); } /** * Remove the AMP endpoint (and query var) from a given URL. * * @since 0.7 * @since 2.1 Deprecated. * @deprecated Use amp_remove_paired_endpoint() instead. * * @param string $url URL. * @return string URL with AMP stripped. */ function amp_remove_endpoint( $url ) { return amp_remove_paired_endpoint( $url ); } /** * Add amphtml link. * * If there are known validation errors for the current URL then do not output anything. * * @since 1.0 */ function amp_add_amphtml_link() { if ( amp_is_canonical() || /** * Filters whether to show the amphtml link on the frontend. * * This is deprecated since the name was wrong and the use case is not clear. To remove this from being printed, * instead of using the filter you can rather do: * * add_action( 'template_redirect', static function () { * remove_action( 'wp_head', 'amp_add_amphtml_link' ); * } ); * * @since 0.2 * @deprecated Remove amp_add_amphtml_link() call on wp_head action instead. */ false === apply_filters_deprecated( 'amp_frontend_show_canonical', [ true ], '2.0', '', sprintf( /* translators: 1: amphtml, 2: amp_add_amphtml_link(), 3: wp_head, 4: template_redirect */ esc_html__( 'Removal of %1$s link should be done by removing %2$s from the %3$s action at %4$s.', 'amp' ), 'amphtml', __FUNCTION__ . '()', 'wp_head', 'template_redirect' ) ) ) { return; } if ( ! amp_is_available() ) { printf( '<!-- %s -->', esc_html__( 'There is no amphtml version available for this URL.', 'amp' ) ); return; } $amp_url = amp_add_paired_endpoint( amp_get_current_url() ); if ( $amp_url ) { $amp_url = remove_query_arg( QueryVar::NOAMP, $amp_url ); $sandboxing_level = amp_get_sandboxing_level(); if ( 0 === $sandboxing_level || 3 === $sandboxing_level ) { printf( '<link rel="amphtml" href="%s">', esc_url( $amp_url ) ); } } } /** * Determine whether a given post supports AMP. * * @since 2.0 Formerly known as post_supports_amp(). * @see AMP_Post_Type_Support::get_support_errors() * * @param WP_Post|int $post Post. * @return bool Whether the post supports AMP. */ function amp_is_post_supported( $post ) { return 0 === count( AMP_Post_Type_Support::get_support_errors( $post ) ); } /** * Determine whether a given post supports AMP. * * @since 0.1 * @since 0.6 Returns false when post has meta to disable AMP. * @since 2.0 Renamed to AMP-prefixed version, amp_is_post_supported(). * @deprecated Use amp_is_post_supported() instead. * * @param WP_Post $post Post. * @return bool Whether the post supports AMP. */ function post_supports_amp( $post ) { return amp_is_post_supported( $post ); } /** * Determine whether the current request is for an AMP page. * * This function cannot be called before the parse_query action because it needs to be able * to determine the queried object is able to be served as AMP. If 'amp' theme support is not * present, this function returns true just if the query var is present. If theme support is * present, then it returns true in transitional mode if an AMP template is available and the query * var is present, or else in standard mode if just the template is available. * * @since 2.0 Formerly known as is_amp_endpoint(). * * @return bool Whether it is the AMP endpoint. * @global WP_Query $wp_query */ function amp_is_request() { global $wp_query; $is_amp_url = ( amp_is_canonical() || amp_has_paired_endpoint() ); // If AMP is not available, then it's definitely not an AMP endpoint. if ( ! amp_is_available() ) { // But, if WP_Query was not available yet, then we will just assume the query is supported since at this point we do // know either that the site is in Standard mode or the URL was requested with the AMP query var. This can still // produce an undesired result when a Standard mode site has a post that opts out of AMP, but this issue will // have been flagged via _doing_it_wrong() in amp_is_available() above. if ( ! did_action( 'wp' ) || ! $wp_query instanceof WP_Query ) { return $is_amp_url && AMP_Options_Manager::get_option( Option::ALL_TEMPLATES_SUPPORTED ); } return false; } return $is_amp_url; } /** * Determine whether the current response being served as AMP. * * This function cannot be called before the parse_query action because it needs to be able * to determine the queried object is able to be served as AMP. If 'amp' theme support is not * present, this function returns true just if the query var is present. If theme support is * present, then it returns true in transitional mode if an AMP template is available and the query * var is present, or else in standard mode if just the template is available. * * @since 0.1 * @since 2.0 Renamed to AMP-prefixed version, amp_is_request(). * @deprecated Use amp_is_request() instead. * * @return bool Whether it is the AMP endpoint. */ function is_amp_endpoint() { return amp_is_request(); } /** * Get AMP asset URL. * * @since 0.1 * @internal * * @param string $file Relative path to file in assets directory. * @return string URL. */ function amp_get_asset_url( $file ) { return plugins_url( sprintf( 'assets/%s', $file ), AMP__FILE__ ); } /** * Get AMP boilerplate code. * * @since 0.7 * @internal * @link https://www.ampproject.org/docs/reference/spec#boilerplate * * @return string Boilerplate code. */ function amp_get_boilerplate_code() { $stylesheets = amp_get_boilerplate_stylesheets(); return sprintf( '<style amp-boilerplate>%s</style><noscript><style amp-boilerplate>%s</style></noscript>', $stylesheets[0], $stylesheets[1] ); } /** * Get AMP boilerplate stylesheets. * * @since 1.3 * @internal * @link https://www.ampproject.org/docs/reference/spec#boilerplate * * @return string[] Stylesheets, where first is contained in style[amp-boilerplate] and the second in noscript>style[amp-boilerplate]. */ function amp_get_boilerplate_stylesheets() { return [ 'body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}', 'body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}', ]; } /** * Add generator metadata. * * @since 0.6 * @since 1.0 Add template mode. * @since 2.0 Add reader theme. * @internal */ function amp_add_generator_metadata() { $template_mode = AMP_Options_Manager::get_option( Option::THEME_SUPPORT ); $reader_theme = AMP_Options_Manager::get_option( Option::READER_THEME ); // Account for case where the active theme has been switched to be the same as the reader theme. // In this case, the behavior of the plugin is the same as transitional mode. if ( AMP_Theme_Support::READER_MODE_SLUG === $template_mode && get_stylesheet() === $reader_theme && ! Services::get( 'reader_theme_loader' )->is_enabled() ) { $template_mode = AMP_Theme_Support::TRANSITIONAL_MODE_SLUG; } $content = sprintf( 'AMP Plugin v%s', AMP__VERSION ); $content .= sprintf( '; mode=%s', $template_mode ); if ( AMP_Theme_Support::READER_MODE_SLUG === $template_mode ) { $content .= sprintf( '; theme=%s', $reader_theme ); } printf( '<meta name="generator" content="%s">', esc_attr( $content ) ); } /** * Register default scripts for AMP components. * * @internal * * @param WP_Scripts $wp_scripts Scripts. */ function amp_register_default_scripts( $wp_scripts ) { // AMP Runtime. $handle = 'amp-runtime'; $wp_scripts->add( $handle, 'https://cdn.ampproject.org/v0.js', [], null ); $wp_scripts->add_data( $handle, 'amp_script_attributes', [ 'async' => true, ] ); // Shadow AMP API. $handle = 'amp-shadow'; $wp_scripts->add( $handle, 'https://cdn.ampproject.org/shadow-v0.js', [], null ); $wp_scripts->add_data( $handle, 'amp_script_attributes', [ 'async' => true, ] ); // Register all AMP components as defined in the spec. $extension_specs = AMP_Allowed_Tags_Generated::get_extension_specs(); if ( isset( $extension_specs['amp-carousel'] ) && '0.1' === $extension_specs['amp-carousel']['latest'] ) { /* * The latestVersion of amp-carousel is 0.1 in https://github.com/ampproject/amphtml/blob/main/build-system/compile/bundles.config.extensions.json * But we have been using 0.2 since https://github.com/ampproject/amp-wp/pull/3115 * Therefore, we override the latest version to also be 0.2 since it has been shown to be stable. */ $extension_specs['amp-carousel']['latest'] = '0.2'; } foreach ( $extension_specs as $extension_name => $extension_spec ) { $version = $extension_spec['latest']; // Skip registering the amp-gfycat extension, as gfycat have been sunset. // @TODO: Remove this once the amp-gfycat extension is removed from spec. if ( 'amp-gfycat' === $extension_name ) { continue; } $src = sprintf( 'https://cdn.ampproject.org/v0/%s-%s.js', $extension_name, $version ); $wp_scripts->add( $extension_name, $src, [ 'amp-runtime' ], null ); } /** * Register polyfill for React JSX runtime which is required by: * - amp-site-scan-notice * - amp-block-editor * - amp-block-validation * * @see <https://github.com/WordPress/wordpress-develop/pull/6678>. * @TODO: Remove this once the minimum required version of WordPress is 6.6 for the AMP plugin. */ if ( ! $wp_scripts->query( 'react-jsx-runtime', 'registered' ) ) { $wp_scripts->add( 'react-jsx-runtime', amp_get_asset_url( 'js/react-jsx-runtime.js' ), [ 'react' ], // Since it is a polyfill for react JSX runtime, it is dependent on react. AMP__VERSION ); } } /** * Register default styles. * * @since 2.0 * @internal * * @param WP_Styles $styles Styles. */ function amp_register_default_styles( WP_Styles $styles ) { $styles->add( 'amp-default', amp_get_asset_url( 'css/amp-default.css' ), [], AMP__VERSION ); $styles->add_data( 'amp-default', 'rtl', 'replace' ); $styles->add( 'amp-icons', amp_get_asset_url( 'css/amp-icons.css' ), [ 'dashicons' ], AMP__VERSION ); $styles->add_data( 'amp-icons', 'rtl', 'replace' ); } /** * Generate HTML for AMP scripts that have not yet been printed. * * This is adapted from `wp_scripts()->do_items()`, but it runs only the bare minimum required to output * the missing scripts, without allowing other filters to apply which may cause an invalid AMP response. * The HTML for the scripts is returned instead of being printed. * * @since 0.7.2 * @see WP_Scripts::do_items() * @see AMP_Base_Embed_Handler::get_scripts() * @see AMP_Base_Sanitizer::get_scripts() * @internal * * @param array $scripts Script handles mapped to URLs or true. * @return string HTML for scripts tags that have not yet been done. */ function amp_render_scripts( $scripts ) { $script_tags = ''; /* * Make sure the src is up to date. This allows for embed handlers to override the * default extension version by defining a different URL. */ foreach ( $scripts as $handle => $src ) { if ( is_string( $src ) && wp_script_is( $handle, 'registered' ) ) { wp_scripts()->registered[ $handle ]->src = $src; } } foreach ( array_diff( array_keys( $scripts ), wp_scripts()->done ) as $handle ) { if ( ! wp_script_is( $handle, 'registered' ) ) { continue; } $script_dep = wp_scripts()->registered[ $handle ]; $script_tags .= amp_filter_script_loader_tag( sprintf( "<script type='text/javascript' src='%s'></script>\n", // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript esc_url( $script_dep->src ) ), $handle ); wp_scripts()->done[] = $handle; } return $script_tags; } /** * Add AMP script attributes to enqueued scripts. * * @link https://core.trac.wordpress.org/ticket/12009 * @since 0.7 * @internal * * @param string $tag The script tag. * @param string $handle The script handle. * @return string Script loader tag. */ function amp_filter_script_loader_tag( $tag, $handle ) { $prefix = 'https://cdn.ampproject.org/'; $src = wp_scripts()->registered[ $handle ]->src; if ( 0 !== strpos( $src, $prefix ) ) { return $tag; } /* * All scripts from AMP CDN should be loaded async. * See <https://www.ampproject.org/docs/integration/pwa-amp/amp-in-pwa#include-"shadow-amp"-in-your-progressive-web-app>. */ $attributes = [ 'async' => true, ]; // Add custom-template and custom-element attributes. All component scripts look like https://cdn.ampproject.org/v0/:name-:version.js. if ( 'v0' === strtok( substr( $src, strlen( $prefix ) ), '/' ) ) { /* * Per the spec, "Most extensions are custom-elements." In fact, there is only one custom template. So we hard-code it here. * * This could also be derived by looking at the extension_type in the extension_spec. * * @link https://github.com/ampproject/amphtml/blob/cd685d4e62153557519553ffa2183aedf8c93d62/validator/validator.proto#L326-L328 * @link https://github.com/ampproject/amphtml/blob/cd685d4e62153557519553ffa2183aedf8c93d62/extensions/amp-mustache/validator-amp-mustache.protoascii#L27 */ if ( 'amp-mustache' === $handle ) { $attributes['custom-template'] = $handle; } else { $attributes['custom-element'] = $handle; } } // Add each attribute (if it hasn't already been added). foreach ( $attributes as $key => $value ) { if ( ! preg_match( ":\s$key(=|>|\s):", $tag ) ) { if ( true === $value ) { $attribute_string = sprintf( ' %s', esc_attr( $key ) ); } else { $attribute_string = sprintf( ' %s="%s"', esc_attr( $key ), esc_attr( $value ) ); } $tag = preg_replace( ':(?=></script>):', $attribute_string, $tag, 1 ); } } return $tag; } /** * Explicitly opt-in to CORS mode by adding the crossorigin attribute to font stylesheet links. * * This explicitly triggers a CORS request, and gets back a non-opaque response, ensuring that a service * worker caching the external stylesheet will not inflate the storage quota. This must be done in AMP * and non-AMP alike because in transitional mode the service worker could cache the font stylesheets in a * non-AMP document without CORS (crossorigin="anonymous") in which case the service worker could then * fail to serve the cached font resources in an AMP document with the warning: * * > The FetchEvent resulted in a network error response: an "opaque" response was used for a request whose type is not no-cors * * @since 1.0 * @link https://developers.google.com/web/tools/workbox/guides/storage-quota#beware_of_opaque_responses * @link https://developers.google.com/web/tools/workbox/guides/handle-third-party-requests#cross-origin_requests_and_opaque_responses * @todo This should be proposed for WordPress core. * @internal * * @param string $tag Link tag HTML. * @param string $handle Dependency handle. * @param string $href Link URL. * @return string Link tag HTML. */ function amp_filter_font_style_loader_tag_with_crossorigin_anonymous( $tag, $handle, $href ) { static $allowed_font_src_regex = null; if ( ! $allowed_font_src_regex ) { $spec_name = 'link rel=stylesheet for fonts'; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet foreach ( AMP_Allowed_Tags_Generated::get_allowed_tag( 'link' ) as $spec_rule ) { if ( isset( $spec_rule[ AMP_Rule_Spec::TAG_SPEC ]['spec_name'] ) && $spec_name === $spec_rule[ AMP_Rule_Spec::TAG_SPEC ]['spec_name'] ) { $allowed_font_src_regex = '@^(' . $spec_rule[ AMP_Rule_Spec::ATTR_SPEC_LIST ]['href']['value_regex'] . ')$@'; break; } } } $href = preg_replace( '#^(http:)?(?=//)#', 'https:', $href ); if ( preg_match( $allowed_font_src_regex, $href ) && false === strpos( $tag, 'crossorigin=' ) ) { $tag = preg_replace( '/(?<=<link\s)/', 'crossorigin="anonymous" ', $tag ); } return $tag; } /** * Retrieve analytics data added in backend. * * @since 0.7 * @internal * * @param array $analytics Analytics entries. * @return array Analytics. */ function amp_get_analytics( $analytics = [] ) { $analytics_entries = AMP_Options_Manager::get_option( Option::ANALYTICS, [] ); /** * Add amp-analytics tags. * * This filter allows you to easily insert any amp-analytics tags without needing much heavy lifting. * This filter should be used to alter entries for transitional mode. * * @since 0.7 * * @param array $analytics_entries An associative array of the analytics entries we want to output. Each array entry must have a unique key, and the value should be an array with the following keys: `type`, `attributes`, `config_data`. See readme for more details. */ $analytics_entries = apply_filters( 'amp_analytics_entries', $analytics_entries ); if ( ! $analytics_entries ) { return $analytics; } foreach ( $analytics_entries as $entry_id => $entry ) { if ( ! isset( $entry['attributes'] ) ) { $entry['attributes'] = []; } if ( ! isset( $entry['config_data'] ) && isset( $entry['config'] ) && is_string( $entry['config'] ) ) { $entry['config_data'] = json_decode( $entry['config'] ); } $analytics[ $entry_id ] = $entry; } return $analytics; } /** * Print analytics data. * * @since 0.7 * @internal * * @param array|string $analytics Analytics entries, or empty string when called via wp_footer action. */ function amp_print_analytics( $analytics ) { if ( '' === $analytics ) { $analytics = []; } $analytics_entries = amp_get_analytics( $analytics ); /** * Triggers before analytics entries are printed as amp-analytics tags. * * This is useful for printing additional `amp-analytics` tags to the page without having to refactor any existing * markup generation logic to use the data structure mutated by the `amp_analytics_entries` filter. For such cases, * this action should be used for printing `amp-analytics` tags as opposed to using the `wp_footer` and * `amp_post_template_footer` actions. * * @since 1.3 * @param array $analytics_entries Analytics entries, already potentially modified by the amp_analytics_entries filter. */ do_action( 'amp_print_analytics', $analytics_entries ); if ( empty( $analytics_entries ) ) { return; } // Can enter multiple configs within backend. foreach ( $analytics_entries as $id => $analytics_entry ) { if ( ! isset( $analytics_entry['attributes'], $analytics_entry['config_data'] ) ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: the analytics entry ID. 2: type. 3: attributes. 4: config_data. 5: comma-separated list of the actual entry keys. */ esc_html__( 'Analytics entry for %1$s is missing one of the following keys: `%2$s` or `%3$s` (array keys: %4$s)', 'amp' ), esc_html( $id ), 'attributes', 'config_data', esc_html( implode( ', ', array_keys( $analytics_entry ) ) ) ), '0.3.2' ); continue; } $script_element = AMP_HTML_Utils::build_tag( 'script', [ 'type' => 'application/json', ], wp_json_encode( $analytics_entry['config_data'] ) ); $amp_analytics_attr = array_merge( compact( 'id' ), $analytics_entry['attributes'] ); if ( ! empty( $analytics_entry['type'] ) ) { $amp_analytics_attr['type'] = $analytics_entry['type']; } echo AMP_HTML_Utils::build_tag( 'amp-analytics', $amp_analytics_attr, $script_element ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } } /** * Get content embed handlers. * * @since 0.7 * @internal * * @param WP_Post $post Post that the content belongs to. Deprecated when theme supports AMP, as embeds may apply * to non-post data (e.g. Text widget). * @return array Embed handlers. */ function amp_get_content_embed_handlers( $post = null ) { if ( ! amp_is_legacy() && $post ) { _deprecated_argument( __FUNCTION__, '0.7', sprintf( /* translators: %s: $post */ esc_html__( 'The %s argument is deprecated when theme supports AMP.', 'amp' ), '$post' ) ); $post = null; } /** * Filters the content embed handlers. * * @since 0.2 * @since 0.7 Deprecated $post parameter. * * @param array $handlers Handlers. * @param WP_Post $post Post. Deprecated. It will be null when `amp_is_canonical()`. */ return apply_filters( 'amp_content_embed_handlers', [ AMP_Core_Block_Handler::class => [], AMP_Twitter_Embed_Handler::class => [], AMP_YouTube_Embed_Handler::class => [], AMP_Crowdsignal_Embed_Handler::class => [], AMP_DailyMotion_Embed_Handler::class => [], AMP_Vimeo_Embed_Handler::class => [], AMP_SoundCloud_Embed_Handler::class => [], AMP_Instagram_Embed_Handler::class => [], AMP_Issuu_Embed_Handler::class => [], AMP_Meetup_Embed_Handler::class => [], AMP_Facebook_Embed_Handler::class => [], AMP_Pinterest_Embed_Handler::class => [], AMP_Playlist_Embed_Handler::class => [], AMP_Reddit_Embed_Handler::class => [], AMP_TikTok_Embed_Handler::class => [], AMP_Tumblr_Embed_Handler::class => [], AMP_Gallery_Embed_Handler::class => [], AMP_Imgur_Embed_Handler::class => [], AMP_Scribd_Embed_Handler::class => [], AMP_WordPress_Embed_Handler::class => [], AMP_WordPress_TV_Embed_Handler::class => [], ], $post ); } /** * Determine whether AMP dev mode is enabled. * * When enabled, the `<html>` element will get the data-ampdevmode attribute and the plugin will add the same attribute * to elements associated with the admin bar and other elements that are provided by the `amp_dev_mode_element_xpaths` * filter. * * @since 1.3 * * @return bool Whether AMP dev mode is enabled. */ function amp_is_dev_mode() { /** * Filters whether AMP mode is enabled. * * When enabled, the data-ampdevmode attribute will be added to the document element and it will allow the * attributes to be added to the admin bar. It will also add the attribute to all elements which match the * queries for the expressions returned by the 'amp_dev_mode_element_xpaths' filter. * * @since 1.3 * @param bool $is_dev_mode_enabled Whether AMP dev mode is enabled. */ return apply_filters( 'amp_dev_mode_enabled', ( // For the few sites that forcibly show the admin bar even when the user is logged out, only enable dev // mode if the user is actually logged in. This prevents the dev mode from being served to crawlers // when they index the AMP version. The theme support check disables dev mode in Reader mode. ( is_admin_bar_showing() && is_user_logged_in() ) || is_customize_preview() || ( ! is_ssl() && function_exists( 'wp_get_environment_type' ) && 'local' === wp_get_environment_type() && 'localhost' === wp_parse_url( home_url(), PHP_URL_HOST ) ) ) ); } /** * Determine whether native `img` should be used instead of converting to `amp-img`. * * @since 2.2 * * @return bool Whether to use `img`. */ function amp_is_native_img_used() { $use_native_img_tag = AMP_Options_Manager::get_option( Option::USE_NATIVE_IMG_TAG ); /** * Filters whether to use the native `img` element rather than convert to `amp-img`. * * This filter is a feature flag to opt-in to discontinue using `amp-img` (and `amp-anim`) which will be deprecated * in AMP in the near future. Once this lands in AMP, this filter will switch to defaulting to true instead of false. * * @since 2.2 * @link https://github.com/ampproject/amphtml/issues/30442 * * @param bool $use_native Whether to use `img`. */ return (bool) apply_filters( 'amp_native_img_used', $use_native_img_tag ); } /** * Get content sanitizers. * * @since 0.7 * @since 1.1 Added AMP_Nav_Menu_Toggle_Sanitizer and AMP_Nav_Menu_Dropdown_Sanitizer. * @internal * * @param WP_Post $post Post that the content belongs to. Deprecated when theme supports AMP, as sanitizers apply * to non-post data (e.g. Text widget). * @return array Embed handlers. */ function amp_get_content_sanitizers( $post = null ) { $theme_support_args = AMP_Theme_Support::get_theme_support_args(); if ( $post && ! amp_is_legacy() ) { _deprecated_argument( __FUNCTION__, '0.7', sprintf( /* translators: %s: $post */ esc_html__( 'The %s argument is deprecated.', 'amp' ), '$post' ) ); $post = null; } $parsed_home_url = wp_parse_url( get_home_url() ); $current_origin = $parsed_home_url['scheme'] . '://' . $parsed_home_url['host']; if ( isset( $parsed_home_url['port'] ) ) { $current_origin .= ':' . $parsed_home_url['port']; } /** * Filters whether AMP-to-AMP linking should be enabled. * * @since 1.4.0 * @param bool $amp_to_amp_linking_enabled Whether AMP-to-AMP linking should be enabled. */ $amp_to_amp_linking_enabled = (bool) apply_filters( 'amp_to_amp_linking_enabled', AMP_Theme_Support::TRANSITIONAL_MODE_SLUG === AMP_Options_Manager::get_option( Option::THEME_SUPPORT ) ); $is_dev_mode = amp_is_dev_mode(); $native_img_used = amp_is_native_img_used(); $sanitizers = [ // Embed sanitization must come first because it strips out custom scripts associated with embeds. AMP_Embed_Sanitizer::class => [ 'amp_to_amp_linking_enabled' => $amp_to_amp_linking_enabled, ], AMP_O2_Player_Sanitizer::class => [], AMP_Playbuzz_Sanitizer::class => [], AMP_Core_Theme_Sanitizer::class => [ 'template' => get_template(), 'stylesheet' => get_stylesheet(), 'theme_features' => [ 'force_svg_support' => [], // Always replace 'no-svg' class with 'svg' if it exists. ], 'native_img_used' => $native_img_used, ], AMP_Comments_Sanitizer::class => [ 'comments_live_list' => ! empty( $theme_support_args['comments_live_list'] ), ], // The AMP_PWA_Script_Sanitizer run before AMP_Script_Sanitizer, to prevent the script tags // from getting removed in PWA plugin offline/500 templates. AMP_PWA_Script_Sanitizer::class => [], // The AMP_GTag_Script_Sanitizer runs before AMP_Script_Sanitizer to mark the the Google Analytics script tags as being PX-verified. AMP_GTag_Script_Sanitizer::class => [], // The AMP_Script_Sanitizer runs here because based on whether it allows custom scripts // to be kept, it may impact the behavior of other sanitizers. For example, if custom // scripts are kept then this is a signal that tree shaking in AMP_Style_Sanitizer cannot be // performed. AMP_Script_Sanitizer::class => [], AMP_Srcset_Sanitizer::class => [], AMP_Img_Sanitizer::class => [ 'align_wide_support' => current_theme_supports( 'align-wide' ), 'native_img_used' => $native_img_used, ], AMP_Form_Sanitizer::class => [], AMP_Video_Sanitizer::class => [], AMP_Audio_Sanitizer::class => [], AMP_Object_Sanitizer::class => [], AMP_Iframe_Sanitizer::class => [ 'add_placeholder' => true, 'current_origin' => $current_origin, 'align_wide_support' => current_theme_supports( 'align-wide' ), ], AMP_Gallery_Block_Sanitizer::class => [ // Note: Gallery block sanitizer must come after image sanitizers since itÅ› logic is using the already sanitized images. 'carousel_required' => ! is_array( $theme_support_args ), // For back-compat. 'native_img_used' => $native_img_used, ], AMP_Native_Img_Attributes_Sanitizer::class => [ // Note: Native img attributes sanitizer must come after image sanitizers since its logic is sanitizing the already sanitized images attributes. 'native_img_used' => $native_img_used, ], AMP_Block_Sanitizer::class => [], // Note: Block sanitizer must come after embed / media sanitizers since its logic is using the already sanitized content. AMP_Style_Sanitizer::class => [ 'skip_tree_shaking' => is_customize_preview(), 'allow_excessive_css' => is_customize_preview(), ], AMP_Meta_Sanitizer::class => [], AMP_Layout_Sanitizer::class => [], AMP_Accessibility_Sanitizer::class => [], // Note: This validating sanitizer must come at the end to clean up any remaining issues the other sanitizers didn't catch. AMP_Tag_And_Attribute_Sanitizer::class => [ 'allow_localhost_http_protocol' => $is_dev_mode, ], ]; if ( ! empty( $theme_support_args['nav_menu_toggle'] ) ) { $sanitizers[ AMP_Nav_Menu_Toggle_Sanitizer::class ] = $theme_support_args['nav_menu_toggle']; } if ( ! empty( $theme_support_args['nav_menu_dropdown'] ) ) { $sanitizers[ AMP_Nav_Menu_Dropdown_Sanitizer::class ] = $theme_support_args['nav_menu_dropdown']; } if ( $amp_to_amp_linking_enabled && AMP_Theme_Support::STANDARD_MODE_SLUG !== AMP_Options_Manager::get_option( Option::THEME_SUPPORT ) ) { /** * Filters the list of URLs which are excluded from being included in AMP-to-AMP linking. * * This only applies when the amp_to_amp_linking_enabled filter returns true, * which it does by default in Transitional mode. This filter can be used to opt-in * when in Reader mode. This does not apply in Standard mode. * Only frontend URLs on the frontend need be excluded, as all other URLs are never made into AMP links. * * @since 1.5.0 * * @param string[] $excluded_urls The URLs to exclude from having AMP-to-AMP links. */ $excluded_urls = apply_filters( 'amp_to_amp_excluded_urls', [] ); $sanitizers[ AMP_Link_Sanitizer::class ] = array_merge( [ 'paired' => ! amp_is_canonical() ], compact( 'excluded_urls' ) ); } /** * Filters whether AMP auto-lightbox is disabled. * * When disabled, the data-amp-auto-lightbox-disable attribute is added to the body. * * @since 2.2.2 * @link https://github.com/ampproject/amphtml/blob/420bc3987f69f6d9cd36e31c013fc9eea4f1b245/docs/spec/auto-lightbox.md#disabling-treatment-explicitly * * @param bool $disabled Whether disabled. */ $is_auto_lightbox_disabled = apply_filters( 'amp_auto_lightbox_disabled', true ); if ( $is_auto_lightbox_disabled ) { $sanitizers[ AMP_Auto_Lightbox_Disable_Sanitizer::class ] = []; } /** * Filters the content sanitizers. * * @since 0.2 * @since 0.7 Deprecated $post parameter. It will be null when `amp_is_canonical()`. * * @param array $handlers Handlers. * @param WP_Post $post Post. Deprecated. */ $sanitizers = apply_filters( 'amp_content_sanitizers', $sanitizers, $post ); if ( $is_dev_mode ) { /** * Filters the XPath queries for elements that should be enabled for dev mode. * * By supplying XPath queries to this filter, the data-ampdevmode attribute will automatically be added to the * root HTML element as well as to any elements that match the expressions. The attribute is added to the * elements prior to running any of the sanitizers. * * @since 1.3 * @param string[] $element_xpaths XPath element queries. Context is the root element. */ $dev_mode_xpaths = (array) apply_filters( 'amp_dev_mode_element_xpaths', [] ); if ( is_admin_bar_showing() ) { $dev_mode_xpaths[] = '//*[ @id = "wpadminbar" ]'; $dev_mode_xpaths[] = '//*[ @id = "wpadminbar" ]//*'; $dev_mode_xpaths[] = '//style[ @id = "admin-bar-inline-css" ]'; } if ( is_customize_preview() ) { // Scripts are always needed to inject changeset UUID. $dev_mode_xpaths[] = '//script[ @src ]'; $dev_mode_xpaths[] = '//script[ not( @type ) or @type = "text/javascript" ]'; // Style needed for Additional CSS to work as intended. $dev_mode_xpaths[] = '//style[ @id = "wp-custom-css" ]'; // Styles needed for Colors customization. $dev_mode_xpaths[] = '//style[ @id = "custom-background-css" ]'; $dev_mode_xpaths[] = '//style[ @id = "custom-theme-colors" ]'; } // Mark the script output by wp_comment_form_unfiltered_html_nonce() as being in dev mode. if ( current_user_can( 'unfiltered_html' ) ) { $dev_mode_xpaths[] = '//script[ not( @src ) and preceding-sibling::input[ @name = "_wp_unfiltered_html_comment_disabled" ] and contains( text(), "_wp_unfiltered_html_comment_disabled" ) ]'; } // Mark the script output by wp_post_preview_js() as being in dev mode. if ( is_preview() && get_queried_object() instanceof WP_Post ) { $dev_mode_xpaths[] = sprintf( '//script[ not( @src ) and contains( text(), "document.location.search" ) and contains( text(), "preview=true" ) and ( contains( text(), "pagehide" ) or contains( text(), "unload" ) ) and contains( text(), "window.name" ) and contains( text(), "wp-preview-%d" ) ]', get_queried_object_id() ); } // Mark the manifest output by PWA plugin as being in dev mode. if ( ! is_ssl() && 'localhost' === $parsed_home_url['host'] ) { $dev_mode_xpaths[] = '//link[@rel="manifest" and contains(@href, "web-app-manifest")]'; } $sanitizers = array_merge( [ AMP_Dev_Mode_Sanitizer::class => [ 'element_xpaths' => $dev_mode_xpaths, ], ], $sanitizers ); } /** * Filters whether parsed CSS is allowed to be cached in transients. * * When this is filtered to be false, parsed CSS will not be stored in transients. This is important when there is * highly-variable CSS content in order to prevent filling up the wp_options table with an endless number of entries. * * @since 1.5.0 * @param bool $transient_caching_allowed Transient caching allowed. */ $sanitizers[ AMP_Style_Sanitizer::class ]['allow_transient_caching'] = apply_filters( 'amp_parsed_css_transient_caching_allowed', true ); // Force core essential sanitizers to appear at the end at the end, with non-essential and third-party sanitizers appearing before. $expected_final_sanitizer_order = [ AMP_Auto_Lightbox_Disable_Sanitizer::class, AMP_Core_Theme_Sanitizer::class, // Must come before script sanitizer since onclick attributes are removed. AMP_PWA_Script_Sanitizer::class, // Must come before script sanitizer since PWA offline page scripts are removed. AMP_GTag_Script_Sanitizer::class, // Must come before script sanitizer since gtag.js is removed. AMP_Script_Sanitizer::class, // Must come before sanitizers for images, videos, audios, comments, forms, and styles. AMP_Form_Sanitizer::class, // Must come before comments sanitizer. AMP_Comments_Sanitizer::class, // Also must come after the form sanitizer. AMP_Srcset_Sanitizer::class, AMP_Img_Sanitizer::class, AMP_Video_Sanitizer::class, AMP_Audio_Sanitizer::class, AMP_Object_Sanitizer::class, AMP_Iframe_Sanitizer::class, AMP_Gallery_Block_Sanitizer::class, AMP_Native_Img_Attributes_Sanitizer::class, // Must come after gallery block sanitizer since it sanitizes img attributes. AMP_Block_Sanitizer::class, AMP_Accessibility_Sanitizer::class, AMP_Layout_Sanitizer::class, AMP_Style_Sanitizer::class, AMP_Meta_Sanitizer::class, AMP_Tag_And_Attribute_Sanitizer::class, ]; foreach ( $expected_final_sanitizer_order as $class_name ) { if ( isset( $sanitizers[ $class_name ] ) ) { $sanitizer = $sanitizers[ $class_name ]; unset( $sanitizers[ $class_name ] ); $sanitizers[ $class_name ] = $sanitizer; } } return $sanitizers; } /** * Grabs featured image or the first attached image for the post. * * @since 0.7 This originally was located in the private method AMP_Post_Template::get_post_image_metadata(). * @internal * * @param WP_Post|int $post Post or post ID. * @return array|false $post_image_meta Post image metadata, or false if not found. */ function amp_get_post_image_metadata( $post = null ) { $post = get_post( $post ); if ( ! $post ) { return false; } $post_image_meta = null; $post_image_id = false; if ( has_post_thumbnail( $post->ID ) ) { $post_image_id = get_post_thumbnail_id( $post->ID ); } elseif ( ( 'attachment' === $post->post_type ) && wp_attachment_is( 'image', $post ) ) { $post_image_id = $post->ID; } else { $attached_image_ids = get_posts( [ 'post_parent' => $post->ID, 'post_type' => 'attachment', 'post_mime_type' => 'image', 'posts_per_page' => 1, 'orderby' => 'menu_order', 'order' => 'ASC', 'fields' => 'ids', 'suppress_filters' => false, ] ); if ( ! empty( $attached_image_ids ) ) { $post_image_id = array_shift( $attached_image_ids ); } } if ( ! $post_image_id ) { return false; } $post_image_src = wp_get_attachment_image_src( $post_image_id, 'full' ); if ( is_array( $post_image_src ) ) { $post_image_meta = [ '@type' => 'ImageObject', 'url' => $post_image_src[0], 'width' => $post_image_src[1], 'height' => $post_image_src[2], ]; } return $post_image_meta; } /** * Get the publisher logo. * * The following guidelines apply to logos used for general AMP pages. * * "The logo should be a rectangle, not a square. The logo should fit in a 60x600px rectangle., * and either be exactly 60px high (preferred), or exactly 600px wide. For example, 450x45px * would not be acceptable, even though it fits in the 600x60px rectangle." * * @since 1.2.1 * @link https://developers.google.com/search/docs/data-types/article#logo-guidelines * @internal * * @return string Publisher logo image URL. WordPress logo if no site icon or custom logo defined, and no logo provided via 'amp_site_icon_url' filter. */ function amp_get_publisher_logo() { $logo_image_url = null; /* * This should be 60x600px rectangle. It *can* be larger than this, contrary to the current documentation. * Only minimum size and ratio matters. So height should be at least 60px and width a minimum of 200px. * An aspect ratio between 200/60 (10/3) and 600:60 (10/1) should be used. A square image still be used, * but it is not preferred; a landscape logo should be provided if possible. */ $logo_width = 600; $logo_height = 60; // Use the Custom Logo if set. $custom_logo_id = get_theme_mod( 'custom_logo' ); if ( has_custom_logo() && $custom_logo_id ) { $custom_logo_img = wp_get_attachment_image_src( $custom_logo_id, [ $logo_width, $logo_height ], false ); if ( ! empty( $custom_logo_img[0] ) ) { $logo_image_url = $custom_logo_img[0]; } } // Try Site Icon if a custom logo is not set. $site_icon_id = get_option( 'site_icon' ); if ( empty( $logo_image_url ) && $site_icon_id ) { $site_icon_src = wp_get_attachment_image_src( $site_icon_id, [ $logo_width, $logo_height ], false ); if ( ! empty( $site_icon_src ) ) { $logo_image_url = $site_icon_src[0]; } } /** * Filters the publisher logo URL in the schema.org data. * * Previously, this only filtered the Site Icon, as that was the only possible schema.org publisher logo. * But the Custom Logo is now the preferred publisher logo, if it exists and its dimensions aren't too big. * * @since 0.3 * * @param string $schema_img_url URL of the publisher logo, either the Custom Logo or the Site Icon. */ $logo_image_url = apply_filters( 'amp_site_icon_url', $logo_image_url ); // Fallback to serving the WordPress logo. if ( empty( $logo_image_url ) ) { $logo_image_url = amp_get_asset_url( 'images/amp-page-fallback-wordpress-publisher-logo.png' ); } return $logo_image_url; } /** * Get schema.org metadata for the current query. * * @since 0.7 * @see AMP_Post_Template::build_post_data() Where the logic in this function originally existed. * @internal * * @return array $metadata All schema.org metadata for the post. */ function amp_get_schemaorg_metadata() { $metadata = [ '@context' => 'http://schema.org', 'publisher' => [ '@type' => 'Organization', 'name' => get_bloginfo( 'name' ), ], ]; $publisher_logo = amp_get_publisher_logo(); if ( $publisher_logo ) { $metadata['publisher']['logo'] = [ '@type' => 'ImageObject', 'url' => $publisher_logo, ]; } $queried_object = get_queried_object(); if ( $queried_object instanceof WP_Post ) { $date_published = mysql2date( 'c', $queried_object->post_date, false ); $date_modified = mysql2date( 'c', $queried_object->post_modified, false ); $metadata = array_merge( $metadata, [ '@type' => is_page() ? 'WebPage' : 'BlogPosting', 'mainEntityOfPage' => get_permalink(), 'headline' => get_the_title(), 'datePublished' => $date_published, 'dateModified' => $date_modified, ] ); $post_author = get_userdata( $queried_object->post_author ); if ( $post_author ) { $metadata['author'] = [ '@type' => 'Person', 'name' => html_entity_decode( $post_author->display_name, ENT_QUOTES, get_bloginfo( 'charset' ) ), ]; } $image_metadata = amp_get_post_image_metadata( $queried_object ); if ( $image_metadata ) { $metadata['image'] = $image_metadata['url']; } /** * Filters Schema.org metadata for a post. * * The 'post_template' in the filter name here is due to this filter originally being introduced in `AMP_Post_Template`. * In general the `amp_schemaorg_metadata` filter should be used instead. * * @since 0.3 * * @param array $metadata Metadata. * @param WP_Post $queried_object Post. */ $metadata = apply_filters( 'amp_post_template_metadata', $metadata, $queried_object ); } elseif ( is_archive() ) { $metadata['@type'] = 'CollectionPage'; } /** * Filters Schema.org metadata for a query. * * Check the the main query for the context for which metadata should be added. * * @since 0.7 * * @param array $metadata Metadata. */ $metadata = apply_filters( 'amp_schemaorg_metadata', $metadata ); return $metadata; } /** * Output schema.org metadata. * * @since 0.7 * @since 1.1 we pass `JSON_UNESCAPED_UNICODE` to `wp_json_encode`. * @see https://github.com/ampproject/amp-wp/issues/1969 * @internal */ function amp_print_schemaorg_metadata() { $metadata = amp_get_schemaorg_metadata(); if ( empty( $metadata ) ) { return; } ?> <script type="application/ld+json"><?php echo wp_json_encode( $metadata, JSON_UNESCAPED_UNICODE ); ?></script> <?php } /** * Filters content and keeps only allowable HTML elements by amp-mustache. * * @see wp_kses() * @since 1.0 * @internal * * @param string $markup Markup to sanitize. * @return string HTML markup with tags allowed by amp-mustache. */ function amp_wp_kses_mustache( $markup ) { $amp_mustache_allowed_html_tags = [ 'strong', 'b', 'em', 'i', 'u', 's', 'small', 'mark', 'del', 'ins', 'sup', 'sub' ]; return wp_kses( $markup, array_fill_keys( $amp_mustache_allowed_html_tags, [] ) ); } /** * Add "View AMP" admin bar item for Transitional/Reader mode. * * Note that when theme support is present (in Native/Transitional modes), the admin bar item will be further amended by * the `AMP_Validation_Manager::add_admin_bar_menu_items()` method. * * @see \AMP_Validation_Manager::add_admin_bar_menu_items() * @internal * * @param WP_Admin_Bar $wp_admin_bar Admin bar. */ function amp_add_admin_bar_view_link( $wp_admin_bar ) { if ( is_admin() || amp_is_canonical() || ! amp_is_available() ) { return; } $is_amp_request = amp_is_request(); $current_url = remove_query_arg( array_merge( wp_removable_query_args(), [ QueryVar::NOAMP ] ), amp_get_current_url() ); if ( $is_amp_request ) { $amp_url = $current_url; $non_amp_url = amp_remove_paired_endpoint( $current_url ); } else { $amp_url = amp_add_paired_endpoint( $current_url ); $non_amp_url = $current_url; } $icon = $is_amp_request ? Icon::logo() : Icon::link(); $attr = [ 'id' => 'amp-admin-bar-item-status-icon', 'class' => 'ab-icon', ]; $non_amp_view_title = __( 'View non-AMP version', 'amp' ); $amp_view_title = __( 'View AMP version', 'amp' ); $wp_admin_bar->add_node( [ 'id' => 'amp', 'title' => $icon->to_html( $attr ) . ' ' . esc_html__( 'AMP', 'amp' ), 'href' => esc_url( $is_amp_request ? $non_amp_url : $amp_url ), 'meta' => [ 'title' => esc_attr( $is_amp_request ? $non_amp_view_title : $amp_view_title ), ], ] ); $wp_admin_bar->add_node( [ 'parent' => 'amp', 'id' => 'amp-view', 'title' => esc_html( $is_amp_request ? $non_amp_view_title : $amp_view_title ), 'href' => esc_url( $is_amp_request ? $non_amp_url : $amp_url ), ] ); // Make sure the Customizer opens with AMP enabled. $customize_node = $wp_admin_bar->get_node( 'customize' ); if ( $customize_node && $is_amp_request && AMP_Theme_Support::READER_MODE_SLUG === AMP_Options_Manager::get_option( Option::THEME_SUPPORT ) ) { $args = get_object_vars( $customize_node ); if ( amp_is_legacy() ) { $args['href'] = add_query_arg( 'autofocus[panel]', AMP_Template_Customizer::PANEL_ID, $args['href'] ); } else { $args['href'] = add_query_arg( amp_get_slug(), '1', $args['href'] ); } $wp_admin_bar->add_node( $args ); } } /** * Generate hash for inline amp-script. * * The sha384 hash used by amp-script is represented not as hexadecimal but as base64url, which is defined in RFC 4648 * under section 5, "Base 64 Encoding with URL and Filename Safe Alphabet". It is sometimes referred to as "web safe". * * @since 1.4.0 * @link https://amp.dev/documentation/components/amp-script/#security-features * @link https://github.com/ampproject/amphtml/blob/e8707858895c2af25903af25d396e144e64690ba/extensions/amp-script/0.1/amp-script.js#L401-L425 * @link https://github.com/ampproject/amphtml/blob/27b46b9c8c0fb3711a00376668d808f413d798ed/src/service/crypto-impl.js#L67-L124 * @link https://github.com/ampproject/amphtml/blob/c4a663d0ba13d0488c6fe73c55dc8c971ac6ec0d/src/utils/base64.js#L52-L61 * @link https://tools.ietf.org/html/rfc4648#section-5 * * @param string $script Script. * @return string|null Script hash or null if the sha384 algorithm is not supported. */ function amp_generate_script_hash( $script ) { try { $sha384 = hash( 'sha384', $script, true ); } catch ( ValueError $e ) { $sha384 = false; } if ( false === $sha384 ) { return null; } $hash = str_replace( [ '+', '/', '=' ], [ '-', '_', '.' ], base64_encode( $sha384 ) // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode ); return 'sha384-' . $hash; } /** * Turn a given URL into a paired AMP URL. * * @since 2.1 * * @param string $url URL. * @return string AMP URL. */ function amp_add_paired_endpoint( $url ) { try { return Services::get( 'paired_routing' )->add_endpoint( $url ); } catch ( InvalidService $e ) { if ( ! amp_is_enabled() ) { $reason = __( 'Function called while AMP is disabled via `amp_is_enabled` filter.', 'amp' ); } else { $reason = __( 'Function cannot be called before services are registered.', 'amp' ); } _doing_it_wrong( __FUNCTION__, esc_html( $reason ) . ' ' . esc_html( $e->getMessage() ), '2.1.1' ); return $url; } } /** * Determine a given URL is for a paired AMP request. * * @since 2.1 * * @param string $url URL to examine. If empty, will use the current URL. * @return bool True if the AMP query parameter is set with the required value, false if not. */ function amp_has_paired_endpoint( $url = '' ) { try { return Services::get( 'paired_routing' )->has_endpoint( $url ); } catch ( InvalidService $e ) { if ( ! amp_is_enabled() ) { $reason = __( 'Function called while AMP is disabled via `amp_is_enabled` filter.', 'amp' ); } else { $reason = __( 'Function cannot be called before services are registered.', 'amp' ); } _doing_it_wrong( __FUNCTION__, esc_html( $reason ) . ' ' . esc_html( $e->getMessage() ), '2.1.1' ); return false; } } /** * Remove the paired AMP endpoint from a given URL. * * @since 2.1 * * @param string $url URL. * @return string URL with AMP stripped. */ function amp_remove_paired_endpoint( $url ) { try { return Services::get( 'paired_routing' )->remove_endpoint( $url ); } catch ( InvalidService $e ) { if ( ! amp_is_enabled() ) { $reason = __( 'Function called while AMP is disabled via `amp_is_enabled` filter.', 'amp' ); } else { $reason = __( 'Function cannot be called before services are registered.', 'amp' ); } _doing_it_wrong( __FUNCTION__, esc_html( $reason ) . ' ' . esc_html( $e->getMessage() ), '2.1.1' ); return $url; } } /** * Determine sandboxing level if enabled. * * @since 2.4.0 * * @return int Following values are possible: * 0: Sandbox is disabled. * 1: Sandboxing level: Loose. * 2: Sandboxing level: Moderate. * 3: Sandboxing level: Strict. */ function amp_get_sandboxing_level() { if ( ! AMP_Options_Manager::get_option( Option::SANDBOXING_ENABLED ) ) { return 0; } return AMP_Options_Manager::get_option( Option::SANDBOXING_LEVEL ); }
Upload Files
Cancel
Upload
Create New
Cancel
Create
Change Permissions
Cancel
Save
Change Date
Cancel
Save
Rename Item
Cancel
Save
Confirm Delete
Are you sure you want to delete the selected items?
Cancel
Delete