Skip to content

Commit

Permalink
Merge branch 'develop' into 1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
westonruter committed Nov 5, 2018
2 parents 79519f5 + 6862373 commit 54186f6
Show file tree
Hide file tree
Showing 12 changed files with 253 additions and 67 deletions.
21 changes: 19 additions & 2 deletions amp.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Plugin URI: https://github.com/automattic/amp-wp
* Author: WordPress.com VIP, XWP, Google, and contributors
* Author URI: https://github.com/Automattic/amp-wp/graphs/contributors
* Version: 1.0-RC2
* Version: 1.1-alpha
* Text Domain: amp
* Domain Path: /languages/
* License: GPLv2 or later
Expand All @@ -30,6 +30,23 @@ function _amp_print_php_version_admin_notice() {
return;
}

/**
* Print admin notice regarding DOM extension is not installed.
*
* @since 1.1
*/
function _amp_print_php_dom_document_notice() {
?>
<div class="notice notice-error">
<p><?php esc_html_e( 'The AMP plugin requires DOM extension in PHP. Please contact your host to install DOM extension.', 'amp' ); ?></p>
</div>
<?php
}
if ( ! class_exists( 'DOMDocument' ) ) {
add_action( 'admin_notices', '_amp_print_php_dom_document_notice' );
return;
}

/**
* Print admin notice when composer install has not been performed.
*
Expand All @@ -49,7 +66,7 @@ function _amp_print_composer_install_admin_notice() {

define( 'AMP__FILE__', __FILE__ );
define( 'AMP__DIR__', dirname( __FILE__ ) );
define( 'AMP__VERSION', '1.0-RC2' );
define( 'AMP__VERSION', '1.1-alpha' );

require_once AMP__DIR__ . '/includes/class-amp-autoloader.php';
AMP_Autoloader::register();
Expand Down
115 changes: 105 additions & 10 deletions includes/class-amp-theme-support.php
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,9 @@ public static function get_supportable_templates() {
*/
public static function add_hooks() {

// Let the AMP plugin manage service worker streaming in the PWA plugin.
remove_action( 'template_redirect', 'WP_Service_Worker_Navigation_Routing_Component::start_output_buffering_stream_fragment', PHP_INT_MAX );

// Remove core actions which are invalid AMP.
remove_action( 'wp_head', 'wp_post_preview_js', 1 );
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
Expand Down Expand Up @@ -1508,6 +1511,7 @@ public static function filter_customize_partial_render( $partial ) {
*/
public static function prepare_response( $response, $args = array() ) {
global $content_width;
$prepare_response_start = microtime( true );

if ( isset( $args['validation_error_callback'] ) ) {
_doing_it_wrong( __METHOD__, 'Do not supply validation_error_callback arg.', '1.0' );
Expand All @@ -1523,6 +1527,12 @@ public static function prepare_response( $response, $args = array() ) {
return $response;
}

// Dependencies on the PWA plugin for service worker streaming.
$stream_fragment = null;
if ( class_exists( 'WP_Service_Worker_Navigation_Routing_Component' ) && current_theme_supports( WP_Service_Worker_Navigation_Routing_Component::STREAM_THEME_SUPPORT ) ) {
$stream_fragment = WP_Service_Worker_Navigation_Routing_Component::get_stream_fragment_query_var();
}

$args = array_merge(
array(
'content_max_width' => ! empty( $content_width ) ? $content_width : AMP_Post_Template::CONTENT_MAX_WIDTH, // Back-compat.
Expand All @@ -1537,6 +1547,7 @@ public static function prepare_response( $response, $args = array() ) {
! is_customize_preview()
),
'user_can_validate' => AMP_Validation_Manager::has_cap(),
'stream_fragment' => $stream_fragment,
),
$args
);
Expand All @@ -1551,18 +1562,38 @@ public static function prepare_response( $response, $args = array() ) {
$args['enable_response_caching'] = ! $disable_response_caching;
}

/*
* Set response cache hash, the data values dictates whether a new hash key should be generated or not.
* This is also used as the ETag.
*/
$response_cache_key = md5( wp_json_encode( array(
$args,
$response,
self::$sanitizer_classes,
self::$embed_handlers,
AMP__VERSION,
) ) );

/*
* Per rfc7232:
* "The server generating a 304 response MUST generate any of the
* following header fields that would have been sent in a 200 (OK)
* response to the same request: Cache-Control, Content-Location, Date,
* ETag, Expires, and Vary." The only one of these headers which would
* not have been set yet during the WordPress template generation is
* the ETag. The AMP plugin sends a Vary header at amp_init.
*/
AMP_HTTP::send_header( 'ETag', $response_cache_key );

// Handle responses that are cached by the browser.
if ( isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) && $_SERVER['HTTP_IF_NONE_MATCH'] === $response_cache_key ) {
status_header( 304 );
return '';
}

// Return cache if enabled and found.
$cache_response = null;
if ( true === $args['enable_response_caching'] ) {
// Set response cache hash, the data values dictates whether a new hash key should be generated or not.
$response_cache_key = md5( wp_json_encode( array(
$args,
$response,
self::$sanitizer_classes,
self::$embed_handlers,
AMP__VERSION,
) ) );

$response_cache = wp_cache_get( $response_cache_key, self::RESPONSE_CACHE_GROUP );

// Make sure that all of the validation errors should be sanitized in the same way; if not, then the cached body should be discarded.
Expand All @@ -1583,6 +1614,18 @@ public static function prepare_response( $response, $args = array() ) {
// Short-circuit response with cached body.
if ( isset( $response_cache['body'] ) ) {

// Re-send the headers that were sent before when the response was first cached.
if ( isset( $response_cache['headers'] ) ) {
foreach ( $response_cache['headers'] as $header ) {
if ( in_array( $header, AMP_HTTP::$headers_sent, true ) ) {
continue; // Skip sending headers that were already sent prior to post-processing.
}
AMP_HTTP::send_header( $header['name'], $header['value'], wp_array_slice_assoc( $header, array( 'replace', 'status_code' ) ) );
}
}

AMP_HTTP::send_server_timing( 'amp_processor_cache_hit', -$prepare_response_start );

// Redirect to non-AMP version.
if ( ! amp_is_canonical() && $blocking_error_count > 0 ) {
if ( AMP_Validation_Manager::has_cap() ) {
Expand All @@ -1604,7 +1647,11 @@ public static function prepare_response( $response, $args = array() ) {

return wp_cache_set(
$response_cache_key,
compact( 'body', 'validation_results' ),
array(
'headers' => AMP_HTTP::$headers_sent,
'body' => $body,
'validation_results' => $validation_results,
),
AMP_Theme_Support::RESPONSE_CACHE_GROUP,
MONTH_IN_SECONDS
);
Expand Down Expand Up @@ -1632,6 +1679,21 @@ public static function prepare_response( $response, $args = array() ) {
$dom = AMP_DOM_Utils::get_dom( $response );
$head = $dom->getElementsByTagName( 'head' )->item( 0 );

// Remove scripts that are being added for PWA service worker streaming for restoration later.
$stream_combine_script_define_element = null;
$stream_combine_script_define_placeholder = null;
$stream_combine_script_invoke_element = null;
$stream_combine_script_invoke_placeholder = null;
if ( 'header' === $stream_fragment ) {
$stream_combine_script_define_element = $dom->getElementById( WP_Service_Worker_Navigation_Routing_Component::STREAM_COMBINE_DEFINE_SCRIPT_ID );
if ( $stream_combine_script_define_element ) {
$stream_combine_script_define_placeholder = $dom->createComment( WP_Service_Worker_Navigation_Routing_Component::STREAM_COMBINE_DEFINE_SCRIPT_ID );
$stream_combine_script_define_element->parentNode->replaceChild( $stream_combine_script_define_placeholder, $stream_combine_script_define_element );
}
} elseif ( 'body' === $stream_fragment ) {
$stream_combine_script_invoke_placeholder = $dom->getElementById( WP_Service_Worker_Navigation_Routing_Component::STREAM_FRAGMENT_BOUNDARY_ELEMENT_ID );
}

// Move anything after </html>, such as Query Monitor output added at shutdown, to be moved before </body>.
$body = $dom->getElementsByTagName( 'body' )->item( 0 );
if ( $body ) {
Expand Down Expand Up @@ -1746,9 +1808,42 @@ public static function prepare_response( $response, $args = array() ) {
'remove_source_comments' => ! isset( $_GET['amp_preserve_source_comments'] ), // WPCS: CSRF.
) );

// For service worker streaming, restore the script that was removed above and obtain the script that should be added to the body fragment.
$truncate_after_comment = null;
$truncate_before_comment = null;
if ( $stream_fragment ) {
if ( $stream_combine_script_define_placeholder && $stream_combine_script_define_element ) {
$stream_combine_script_define_placeholder->parentNode->replaceChild( $stream_combine_script_define_element, $stream_combine_script_define_placeholder );
$truncate_after_comment = $dom->createComment( 'AMP_TRUNCATE_RESPONSE_FOR_STREAM_HEADER' );
$stream_combine_script_define_element->parentNode->insertBefore( $truncate_after_comment, $stream_combine_script_define_element->nextSibling );
}
if ( $stream_combine_script_invoke_placeholder ) {
$stream_combine_script_invoke_element = WP_Service_Worker_Navigation_Routing_Component::get_header_combine_invoke_script( $dom, false );
$stream_combine_script_invoke_placeholder->parentNode->replaceChild( $stream_combine_script_invoke_element, $stream_combine_script_invoke_placeholder );
$truncate_before_comment = $dom->createComment( 'AMP_TRUNCATE_RESPONSE_FOR_STREAM_BODY' );
$stream_combine_script_invoke_element->parentNode->insertBefore( $truncate_before_comment, $stream_combine_script_invoke_element );
}
}

$response = "<!DOCTYPE html>\n";
$response .= AMP_DOM_Utils::get_content_from_dom_node( $dom, $dom->documentElement );

// For service worker streaming, make sure that the header response doesn't contain closing tags, and that the body fragment starts with the required script tag.
if ( $truncate_after_comment ) {
$search = sprintf( '<!--%s-->', $truncate_after_comment->nodeValue );
$position = strpos( $response, $search );
if ( false !== $position ) {
$response = substr( $response, 0, $position );
}
}
if ( $truncate_before_comment ) {
$search = sprintf( '<!--%s-->', $truncate_before_comment->nodeValue );
$position = strpos( $response, $search );
if ( false !== $position ) {
$response = substr( $response, $position + strlen( $search ) );
}
}

AMP_HTTP::send_server_timing( 'amp_dom_serialize', -$dom_serialize_start, 'AMP DOM Serialize' );

// Cache response if enabled.
Expand Down
18 changes: 5 additions & 13 deletions includes/sanitizers/class-amp-iframe-sanitizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,18 +103,7 @@ public function sanitize() {
$new_node->appendChild( $placeholder_node );
}

$parent_node = $node->parentNode;
if ( 'p' !== strtolower( $parent_node->tagName ) ) {
$parent_node->replaceChild( $new_node, $node );
} else {
// AMP does not like iframes in <p> tags.
$parent_node->removeChild( $node );
$parent_node->parentNode->insertBefore( $new_node, $parent_node->nextSibling );

if ( AMP_DOM_Utils::is_node_empty( $parent_node ) ) {
$parent_node->parentNode->removeChild( $parent_node );
}
}
$node->parentNode->replaceChild( $new_node, $node );
}
}

Expand Down Expand Up @@ -182,6 +171,9 @@ private function normalize_attributes( $attributes ) {
/**
* Builds a DOMElement to use as a placeholder for an <iframe>.
*
* Important: The element returned must not be block-level (e.g. div) as the PHP DOM parser
* will move it out from inside any containing paragraph. So this is why a span is used.
*
* @since 0.2
*
* @param string[] $parent_attributes {
Expand All @@ -193,7 +185,7 @@ private function normalize_attributes( $attributes ) {
* @return DOMElement|false
*/
private function build_placeholder( $parent_attributes ) {
$placeholder_node = AMP_DOM_Utils::create_node( $this->dom, 'div', array(
$placeholder_node = AMP_DOM_Utils::create_node( $this->dom, 'span', array(
'placeholder' => '',
'class' => 'amp-wp-iframe-placeholder',
) );
Expand Down
33 changes: 22 additions & 11 deletions includes/validation/class-amp-validated-url-post-type.php
Original file line number Diff line number Diff line change
Expand Up @@ -942,27 +942,34 @@ public static function render_sources_column( $error_summary, $post_id ) {

$sources = $error_summary[ AMP_Validation_Error_Taxonomy::SOURCES_INVALID_OUTPUT ];
$output = array();

if ( isset( $sources['plugin'] ) ) {
$plugins = get_plugins();
foreach ( wp_array_slice_assoc( $sources, array( 'plugin', 'mu-plugin' ) ) as $type => $slugs ) {
$plugin_names = array();
$plugin_slugs = array_unique( $sources['plugin'] );
$plugins = get_plugins();
$plugin_slugs = array_unique( $slugs );
foreach ( $plugin_slugs as $plugin_slug ) {
$name = $plugin_slug;
foreach ( $plugins as $plugin_file => $plugin_data ) {
if ( strtok( $plugin_file, '/' ) === $plugin_slug ) {
$name = $plugin_data['Name'];
break;
if ( 'mu-plugin' === $type ) {
$plugin_names[] = $plugin_slug;
} else {
$name = $plugin_slug;
foreach ( $plugins as $plugin_file => $plugin_data ) {
if ( strtok( $plugin_file, '/' ) === $plugin_slug ) {
$name = $plugin_data['Name'];
break;
}
}
$plugin_names[] = $name;
}
$plugin_names[] = $name;
}
$count = count( $plugin_names );
if ( 1 === $count ) {
$output[] = sprintf( '<strong class="source"><span class="dashicons dashicons-admin-plugins"></span>%s</strong>', esc_html( $plugin_names[0] ) );
} else {
$output[] = '<details class="source">';
$output[] = sprintf( '<summary class="details-attributes__summary"><strong><span class="dashicons dashicons-admin-plugins"></span>%s (%d)</strong></summary>', esc_html__( 'Plugins', 'amp' ), $count );
$output[] = sprintf(
'<summary class="details-attributes__summary"><strong><span class="dashicons dashicons-admin-plugins"></span>%s (%d)</strong></summary>',
'mu-plugin' === $type ? esc_html__( 'Must-Use Plugins', 'amp' ) : esc_html__( 'Plugins', 'amp' ),
$count
);
$output[] = '<div>';
$output[] = implode( '<br/>', array_unique( $plugin_names ) );
$output[] = '</div>';
Expand Down Expand Up @@ -999,6 +1006,10 @@ public static function render_sources_column( $error_summary, $post_id ) {
}
}

if ( empty( $output ) && ! empty( $sources['embed'] ) ) {
$output[] = sprintf( '<strong class="source"><span class="dashicons dashicons-wordpress-alt"></span>%s</strong>', esc_html( 'Embed' ) );
}

if ( empty( $output ) && ! empty( $sources['hook'] ) ) {
$output[] = sprintf( '<strong class="source"><span class="dashicons dashicons-wordpress-alt"></span>%s</strong>', esc_html( $sources['hook'] ) );
}
Expand Down
2 changes: 2 additions & 0 deletions includes/validation/class-amp-validation-error-taxonomy.php
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,8 @@ public static function summarize_validation_errors( $validation_errors ) {
}
if ( isset( $source['type'], $source['name'] ) ) {
$invalid_sources[ $source['type'] ][] = $source['name'];
} elseif ( isset( $source['embed'] ) ) {
$invalid_sources['embed'] = true;
}
}

Expand Down
25 changes: 24 additions & 1 deletion includes/validation/class-amp-validation-manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,8 @@ public static function add_validation_error_sourcing() {
add_filter( $wrapped_filter, array( __CLASS__, 'decorate_filter_source' ), PHP_INT_MAX );
}

add_filter( 'do_shortcode_tag', array( __CLASS__, 'decorate_shortcode_source' ), -1, 2 );
add_filter( 'do_shortcode_tag', array( __CLASS__, 'decorate_shortcode_source' ), PHP_INT_MAX, 2 );
add_filter( 'embed_oembed_html', array( __CLASS__, 'decorate_embed_source' ), PHP_INT_MAX, 3 );

$do_blocks_priority = has_filter( 'the_content', 'do_blocks' );
$is_gutenberg_active = (
Expand Down Expand Up @@ -1237,6 +1238,28 @@ public static function decorate_shortcode_source( $output, $tag ) {
return $output;
}

/**
* Filters the output created by embeds.
*
* @since 1.0
*
* @param string $output Embed output.
* @param string $url URL.
* @param array $attr Attributes.
* @return string Output.
*/
public static function decorate_embed_source( $output, $url, $attr ) {
$source = array(
'embed' => $url,
'attr' => $attr,
);
return implode( '', array(
self::get_source_comment( $source, true ),
trim( $output ),
self::get_source_comment( $source, false ),
) );
}

/**
* Wraps output of a filter to add source stack comments.
*
Expand Down
3 changes: 3 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ Follow along with or [contribute](https://github.com/Automattic/amp-wp/blob/deve

## Changelog ##

### 1.1 (unreleased) ###
...

### 1.0 (unreleased) ###
To learn how to use the new features in this release, please see the wiki pages for [Adding Theme Support](https://github.com/Automattic/amp-wp/wiki/Adding-Theme-Support) and [Implementing Interactivity](https://github.com/Automattic/amp-wp/wiki/Implementing-Interactivity).

Expand Down
4 changes: 4 additions & 0 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ Follow along with or [contribute](https://github.com/Automattic/amp-wp/blob/deve

== Changelog ==

= 1.1 (unreleased) =

...

= 1.0 (unreleased) =

To learn how to use the new features in this release, please see the wiki pages for [Adding Theme Support](https://github.com/Automattic/amp-wp/wiki/Adding-Theme-Support) and [Implementing Interactivity](https://github.com/Automattic/amp-wp/wiki/Implementing-Interactivity).
Expand Down

0 comments on commit 54186f6

Please sign in to comment.