From 84134570e8d78fff968713e5c8ac8a0eb23c9cd8 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 13 Mar 2018 10:24:07 -0700 Subject: [PATCH 01/47] Add source comments around each Gutenberg block to track validation issues Remove requirement that amp_debug query var have a value --- .dev-lib | 1 + includes/class-amp-theme-support.php | 2 +- includes/utils/class-amp-validation-utils.php | 91 +++++++++++++++++++ phpunit.xml | 2 +- tests/test-class-amp-validation-utils.php | 61 ++++++++++++- 5 files changed, 154 insertions(+), 3 deletions(-) diff --git a/.dev-lib b/.dev-lib index 6549fa32c46..5ad3bc40839 100644 --- a/.dev-lib +++ b/.dev-lib @@ -6,4 +6,5 @@ PROJECT_SLUG=amp function after_wp_install { echo "Installing REST API..." svn export -q https://plugins.svn.wordpress.org/jetpack/trunk/ "$WP_CORE_DIR/src/wp-content/plugins/jetpack" + svn export -q https://plugins.svn.wordpress.org/gutenberg/trunk/ "$WP_CORE_DIR/src/wp-content/plugins/gutenberg" } diff --git a/includes/class-amp-theme-support.php b/includes/class-amp-theme-support.php index 4c319e98728..b290ce58989 100644 --- a/includes/class-amp-theme-support.php +++ b/includes/class-amp-theme-support.php @@ -939,7 +939,7 @@ public static function prepare_response( $response, $args = array() ) { return $response; } - $is_validation_debug_mode = ! empty( $_REQUEST[ AMP_Validation_Utils::DEBUG_QUERY_VAR ] ); // WPCS: csrf ok. + $is_validation_debug_mode = isset( $_REQUEST[ AMP_Validation_Utils::DEBUG_QUERY_VAR ] ); // WPCS: csrf ok. $args = array_merge( array( diff --git a/includes/utils/class-amp-validation-utils.php b/includes/utils/class-amp-validation-utils.php index bec91a67969..126fffcc130 100644 --- a/includes/utils/class-amp-validation-utils.php +++ b/includes/utils/class-amp-validation-utils.php @@ -303,6 +303,97 @@ public static function add_validation_hooks() { add_filter( 'do_shortcode_tag', array( __CLASS__, 'decorate_shortcode_source' ), -1, 2 ); add_filter( 'amp_content_sanitizers', array( __CLASS__, 'add_validation_callback' ) ); + + $is_gutenberg_active = ( + function_exists( 'do_blocks' ) + && + function_exists( 'gutenberg_parse_blocks' ) + && + function_exists( 'gutenberg_render_block' ) + && + class_exists( 'WP_Block_Type_Registry' ) + ); + if ( $is_gutenberg_active ) { + $do_blocks_priority = has_filter( 'the_content', 'do_blocks' ); + if ( false !== $do_blocks_priority ) { + remove_filter( 'the_content', 'do_blocks', $do_blocks_priority ); + add_filter( 'the_content', array( __CLASS__, 'do_blocks' ), $do_blocks_priority ); + } + } + } + + /** + * Parse blocks out of content and render with AMP source stack comments added to demarcate blocks. + * + * @see do_blocks() prior to https://github.com/WordPress/gutenberg/commit/49f05e53a4e9ceff0c08fe646f3b0bb724f58b53 + * + * @param string $content Content. + * @return string Content with rendered blocks. + */ + public static function do_blocks( $content ) { + $blocks = gutenberg_parse_blocks( $content ); + return self::render_blocks( $blocks ); + } + + /** + * Render parsed blocks. + * + * @param array $blocks Blocks. + * @return string Rendered blocks. + */ + protected static function render_blocks( $blocks ) { + $rendered_blocks = ''; + + foreach ( $blocks as $block ) { + $rendered_block = gutenberg_render_block( $block ); + + // Obtain source information for block. + $source = null; + if ( ! empty( $block['blockName'] ) ) { + $source = array( + 'block_name' => $block['blockName'], + ); + if ( ! empty( $block['attrs'] ) ) { + $source['block_attrs'] = $block['attrs']; + } + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); + if ( $block_type && $block_type->is_dynamic() ) { + $callback_source = self::get_source( $block_type->render_callback ); + if ( $callback_source ) { + $source = array_merge( + $source, + $callback_source + ); + } + } + } + + // Prepend rendered block with start source comment. + if ( $source ) { + $rendered_block = self::get_source_comment( $source, true ) . $rendered_block; + } + + // Render nested blocks. + if ( ! empty( $block['innerBlocks'] ) ) { + $rendered_inner_blocks = self::render_blocks( $block['innerBlocks'] ); + + // Inject rendered inner blocks before closing tag of outer block (or at end of string if no closing). @todo Confirm approach here. + $rendered_block = preg_replace( + '#(?=(\s*)$)#s', + $rendered_inner_blocks, + $rendered_block, + 1 + ); + } + + // Append rendered block with end source comment. + if ( $source ) { + $rendered_block .= self::get_source_comment( $source, false ); + } + + $rendered_blocks .= $rendered_block; + } + return $rendered_blocks; } /** diff --git a/phpunit.xml b/phpunit.xml index a2b80c5bb36..98adc79ceeb 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,7 @@ convertWarningsToExceptions="true" > - + diff --git a/tests/test-class-amp-validation-utils.php b/tests/test-class-amp-validation-utils.php index 4e71c1a3b6e..2c043756b1f 100644 --- a/tests/test-class-amp-validation-utils.php +++ b/tests/test-class-amp-validation-utils.php @@ -119,7 +119,7 @@ public function test_init() { } /** - * Test init. + * Test add_validation_hooks. * * @covers AMP_Validation_Utils::add_validation_hooks() */ @@ -131,6 +131,65 @@ public function test_add_validation_hooks() { $this->assertEquals( -1, has_action( 'do_shortcode_tag', array( self::TESTED_CLASS, 'decorate_shortcode_source' ) ) ); } + /** + * Test add_validation_hooks with Gutenberg active. + * + * @covers AMP_Validation_Utils::add_validation_hooks() + */ + public function test_add_validation_hooks_gutenberg() { + if ( ! function_exists( 'do_blocks' ) ) { + $this->markTestSkipped( 'Gutenberg not active.' ); + } + + $this->assertEquals( 9, has_filter( 'the_content', 'do_blocks' ) ); + AMP_Validation_Utils::add_validation_hooks(); + $this->assertFalse( has_filter( 'the_content', 'do_blocks' ) ); + } + + /** + * Get block data. + * + * @ses Test_AMP_Validation_Utils::test_do_blocks() + * @return array + */ + public function get_block_data() { + return array( + 'paragraph' => array( + "\n

Latest posts:

\n", + "\n

Latest posts:

\n", + ), + 'latest_posts' => array( + '', + '
    ', + ), + 'columns' => array( + "\n
    \n \n
    \n

    A quotation!

    Famous
    \n \n\n \n
    \n \n
    \n \n
    \n", + "\n
    \n\n\n\n\n
    \n

    A quotation!

    Famous
    \n \n
    \n \n
    \n
    \n", + ), + ); + } + + /** + * Test do_blocks. + * + * @param string $content Content. + * @param string $expected Expected content. + * @dataProvider get_block_data + * @covers AMP_Validation_Utils::do_blocks() + * @covers AMP_Validation_Utils::render_blocks + */ + public function test_do_blocks( $content, $expected ) { + if ( ! function_exists( 'do_blocks' ) ) { + $this->markTestSkipped( 'Gutenberg not active.' ); + } + + $rendered_block = AMP_Validation_Utils::do_blocks( $content ); + $this->assertEquals( + preg_replace( '/\s+/', ' ', $expected ), + preg_replace( '/\s+/', ' ', $rendered_block ) + ); + } + /** * Test add_validation_error. * From 07aeee265ad5458488120fc1e0c7402ccaad5d46 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 13 Mar 2018 20:31:46 -0700 Subject: [PATCH 02/47] Defer mustache tag replacements to right before serialization and only target template elements --- includes/utils/class-amp-dom-utils.php | 62 ++++++++++++--------- tests/test-class-amp-dom-utils.php | 65 +++++++++++++++++++++++ tests/test-class-amp-validation-utils.php | 34 ++++++++++-- 3 files changed, 132 insertions(+), 29 deletions(-) diff --git a/includes/utils/class-amp-dom-utils.php b/includes/utils/class-amp-dom-utils.php index 0602e92e20f..9e04ba9d748 100644 --- a/includes/utils/class-amp-dom-utils.php +++ b/includes/utils/class-amp-dom-utils.php @@ -67,22 +67,6 @@ public static function get_dom( $document ) { // @todo In the future consider an AMP_DOMDocument subclass that does this automatically. See . $document = self::convert_amp_bind_attributes( $document ); - /* - * Prevent amp-mustache syntax from getting URL-encoded in attributes when saveHTML is done. - * While this is applying to the entire document, it only really matters inside of