diff --git a/amp.php b/amp.php index f854b6caea3..a6b381ff819 100644 --- a/amp.php +++ b/amp.php @@ -127,13 +127,25 @@ function amp_force_query_var_value( $query_vars ) { * @return void */ function amp_maybe_add_actions() { - if ( amp_is_canonical() || ! is_singular() || is_feed() ) { + + // Add hooks for when a themes that support AMP. + if ( current_theme_supports( 'amp' ) ) { + if ( amp_is_canonical() || is_amp_endpoint() ) { + AMP_Theme_Support::register_hooks(); + } else { + AMP_Frontend_Actions::register_hooks(); + } + return; + } + + // The remaining logic here is for paired mode running in themes that don't support AMP, the template system in AMP<=0.6. + if ( ! is_singular() || is_feed() ) { return; } $is_amp_endpoint = is_amp_endpoint(); - // Cannot use `get_queried_object` before canonical redirect; see https://core.trac.wordpress.org/ticket/35344 + // Cannot use `get_queried_object` before canonical redirect; see . global $wp_query; $post = $wp_query->post; diff --git a/composer.json b/composer.json index c1cbbeb69fd..df224f861e9 100644 --- a/composer.json +++ b/composer.json @@ -4,5 +4,10 @@ "homepage": "https://github.com/Automattic/amp-wp", "type": "wordpress-plugin", "license": "GPL-2.0", - "version": "0.7.0" + "version": "0.7.0", + "require-dev": { + "wp-coding-standards/wpcs": "^0.14.0", + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.4", + "wimg/php-compatibility": "^8.1" + } } diff --git a/includes/actions/class-amp-frontend-actions.php b/includes/actions/class-amp-frontend-actions.php index 8fa8ef1cfab..a64d0904ce5 100644 --- a/includes/actions/class-amp-frontend-actions.php +++ b/includes/actions/class-amp-frontend-actions.php @@ -8,7 +8,7 @@ /** * Class AMP_Frontend_Actions * - * Callbacks for adding AMP-related things to the main theme + * Callbacks for adding AMP-related things to the main theme in non-canonical mode theme. */ class AMP_Frontend_Actions { @@ -26,7 +26,38 @@ public static function add_canonical() { if ( false === apply_filters( 'add_canonical_link', true ) ) { return; } - $amp_url = amp_get_permalink( get_queried_object_id() ); - printf( '', esc_url( $amp_url ) ); + $amp_url = self::get_current_amphtml_url(); + if ( ! empty( $amp_url ) ) { + printf( '', esc_url( $amp_url ) ); + } + } + + /** + * Get the amphtml URL for the current request. + * + * @todo Put this function in includes/amp-helper-functions.php? + * @return string|null URL or null if AMP version is not available. + */ + public static function get_current_amphtml_url() { + if ( is_singular() ) { + return amp_get_permalink( get_queried_object_id() ); + } + + // @todo Get callback from get_theme_support( 'amp' ) to determine whether AMP is allowed for current request. See . + if ( ! current_theme_supports( 'amp' ) ) { + return null; + } + + $amp_url = ''; + $home_url = wp_parse_url( home_url() ); + if ( isset( $home_url['scheme'] ) ) { + $amp_url .= $home_url['scheme'] . ':'; + } + $amp_url .= '//' . $home_url['host']; + if ( ! empty( $home_url['port'] ) ) { + $amp_url .= ':' . $home_url['port']; + } + $amp_url .= esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ); + return add_query_arg( AMP_QUERY_VAR, '', $amp_url ); } } diff --git a/includes/actions/class-amp-paired-post-actions.php b/includes/actions/class-amp-paired-post-actions.php index a544ac878f6..816818dbb3a 100644 --- a/includes/actions/class-amp-paired-post-actions.php +++ b/includes/actions/class-amp-paired-post-actions.php @@ -84,9 +84,7 @@ public static function add_fonts( $amp_template ) { * @param AMP_Post_Template $amp_template Template. */ public static function add_boilerplate_css( $amp_template ) { - ?> - - 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}}'; + echo ''; +} diff --git a/includes/class-amp-autoloader.php b/includes/class-amp-autoloader.php index 082988c34a4..6ad8d1e1e77 100644 --- a/includes/class-amp-autoloader.php +++ b/includes/class-amp-autoloader.php @@ -32,6 +32,7 @@ class AMP_Autoloader { 'AMP_Actions' => 'includes/actions/class-amp-actions', 'AMP_Frontend_Actions' => 'includes/actions/class-amp-frontend-actions', 'AMP_Paired_Post_Actions' => 'includes/actions/class-amp-paired-post-actions', + 'AMP_Theme_Support' => 'includes/class-amp-theme-support', 'AMP_Template_Customizer' => 'includes/admin/class-amp-customizer', 'AMP_Post_Meta_Box' => 'includes/admin/class-amp-post-meta-box', 'AMP_Post_Type_Support' => 'includes/class-amp-post-type-support', diff --git a/includes/class-amp-theme-support.php b/includes/class-amp-theme-support.php new file mode 100644 index 00000000000..2693358cf57 --- /dev/null +++ b/includes/class-amp-theme-support.php @@ -0,0 +1,229 @@ +'; + + /** + * Register hooks. + */ + public static function register_hooks() { + + // Remove core actions which are invalid AMP. + remove_action( 'wp_head', 'locale_stylesheet' ); + remove_action( 'wp_head', 'print_emoji_detection_script', 7 ); + remove_action( 'wp_head', 'wp_print_styles', 8 ); + remove_action( 'wp_head', 'wp_print_head_scripts', 9 ); + remove_action( 'wp_head', 'wp_custom_css_cb', 101 ); + remove_action( 'wp_footer', 'wp_print_footer_scripts', 20 ); + remove_action( 'wp_print_styles', 'print_emoji_styles' ); + + // Replace core's canonical link functionality with one that outputs links for non-singular queries as well. See WP Core #18660. + remove_action( 'wp_head', 'rel_canonical' ); + add_action( 'wp_head', array( __CLASS__, 'add_canonical_link' ), 1 ); + + // @todo Add add_schemaorg_metadata(), add_analytics_data(), etc. + // Add additional markup required by AMP . + add_action( 'wp_head', array( __CLASS__, 'add_meta_charset' ), 0 ); + add_action( 'wp_head', array( __CLASS__, 'add_meta_viewport' ), 2 ); + add_action( 'wp_head', 'amp_print_boilerplate_code', 3 ); + add_action( 'wp_head', array( __CLASS__, 'add_scripts' ), 4 ); + add_action( 'wp_head', array( __CLASS__, 'add_styles' ), 5 ); + add_action( 'wp_head', array( __CLASS__, 'add_meta_generator' ), 6 ); + + /* + * Disable admin bar because admin-bar.css (28K) and Dashicons (48K) alone + * combine to surpass the 50K limit imposed for the amp-custom style. + */ + add_filter( 'show_admin_bar', '__return_false', 100 ); + + // Start output buffering at very low priority for sake of plugins and themes that use template_redirect instead of template_include. + add_action( 'template_redirect', array( __CLASS__, 'start_output_buffering' ), 0 ); + + // @todo Add output buffering. + // @todo Add character conversion. + } + + /** + * Print meta charset tag. + * + * @link https://www.ampproject.org/docs/reference/spec#chrs + */ + public static function add_meta_charset() { + echo ''; + } + + /** + * Print meta charset tag. + * + * @link https://www.ampproject.org/docs/reference/spec#vprt + */ + public static function add_meta_viewport() { + echo ''; + } + + /** + * Print AMP script and placeholder for others. + * + * @link https://www.ampproject.org/docs/reference/spec#scrpt + */ + public static function add_scripts() { + echo ''; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript + + // Replaced after output buffering with all AMP component scripts. + echo self::COMPONENT_SCRIPTS_PLACEHOLDER; // phpcs:ignore WordPress.Security.EscapeOutput, WordPress.XSS.EscapeOutput + } + + /** + * Add canonical link. + * + * Replaces `rel_canonical()` which only outputs canonical URLs for singular posts and pages. + * This can be removed once WP Core #18660 lands. + * + * @link https://www.ampproject.org/docs/reference/spec#canon. + * @link https://core.trac.wordpress.org/ticket/18660 + * + * @see rel_canonical() + * @global WP $wp + * @global WP_Rewrite $wp_rewrite + */ + public static function add_canonical_link() { + global $wp, $wp_rewrite; + + $url = null; + if ( is_singular() ) { + $url = wp_get_canonical_url(); + } + + // For non-singular queries, make use of the request URI and public query vars to determine canonical URL. + if ( empty( $url ) ) { + $added_query_vars = $wp->query_vars; + if ( ! $wp_rewrite->permalink_structure || empty( $wp->request ) ) { + $url = home_url( '/' ); + } else { + $url = home_url( user_trailingslashit( $wp->request ) ); + parse_str( $wp->matched_query, $matched_query_vars ); + foreach ( $wp->query_vars as $key => $value ) { + + // Remove query vars that were matched in the rewrite rules for the request. + if ( isset( $matched_query_vars[ $key ] ) ) { + unset( $added_query_vars[ $key ] ); + } + } + } + } + + if ( ! empty( $added_query_vars ) ) { + $url = add_query_arg( $added_query_vars, $url ); + } + + if ( ! amp_is_canonical() ) { + + // Strip endpoint. + $url = preg_replace( ':/' . preg_quote( AMP_QUERY_VAR, ':' ) . '(?=/?(\?|#|$)):', '', $url ); + + // Strip query var. + $url = remove_query_arg( AMP_QUERY_VAR, $url ); + } + + echo '' . "\n"; + } + + /** + * Print Custom AMP styles. + * + * @see wp_custom_css_cb() + */ + public static function add_styles() { + echo ''; + } + + /** + * Print AMP meta generator tag. + */ + public static function add_meta_generator() { + printf( '', esc_attr( 'AMP Plugin v' . AMP__VERSION ) ); + } + + /** + * Determine required AMP scripts. + * + * @param string $html Output HTML. + * @return string Scripts to inject into the HEAD. + */ + public static function get_required_amp_scripts( $html ) { + + // @todo This should be integrated with the existing Sanitizer classes so that duplication is not done here. + $amp_scripts = array( + 'amp-form' => array( + 'pattern' => '#<(form|input)\b#i', + 'source' => 'https://cdn.ampproject.org/v0/amp-form-0.1.js', + ), + // @todo Add more. + ); + + $scripts = ''; + foreach ( $amp_scripts as $component => $props ) { + if ( preg_match( '#<(form|input)\b#i', $html ) ) { + $scripts .= sprintf( + '', // phpcs:ignore WordPress.WP.EnqueuedResources, WordPress.XSS.EscapeOutput.OutputNotEscaped + $component, + $props['source'] + ); + } + } + + return $scripts; + } + + /** + * Start output buffering. + */ + public static function start_output_buffering() { + ob_start( array( __CLASS__, 'finish_output_buffering' ) ); + } + + /** + * Finish output buffering. + * + * @param string $output Buffered output. + * @return string Finalized output. + */ + public static function finish_output_buffering( $output ) { + $output = preg_replace( + '#' . preg_quote( self::COMPONENT_SCRIPTS_PLACEHOLDER, '#' ) . '#', + self::get_required_amp_scripts( $output ), + $output, + 1 + ); + + // @todo Add more validation checking and potentially the whitelist sanitizer. + return $output; + } +} diff --git a/phpcs.xml b/phpcs.xml index 5e27077e400..30f5ea64606 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -29,6 +29,12 @@ + + + + bin/* + + .