WordPress.org

Make WordPress Core

Ticket #47375: 47375.patch

File 47375.patch, 7.6 KB (added by dmsnell, 6 months ago)

Proposed function and associated tests

  • src/wp-includes/blocks.php

    diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php
    index 6032159fb9..b84e2720b3 100644
    a b function render_block( $block ) { 
    275275        return apply_filters( 'render_block', $block_content, $block );
    276276}
    277277
     278/**
     279 * Renders an HTML-serialized form of a block object
     280 *
     281 * @since 5.3.0
     282 *
     283 * @param  array  $block The block being rendered
     284 * @return string        the HTML-serialized form of the block
     285 */
     286function serialize_block( $block ) {
     287        $unwanted  = array( '--', '<', '>', '&', '\"' );
     288        $wanted    = array( '\u002d\u002d', '\u003c', '\u003e', '\u0026', '\u0022' );
     289
     290        $name      = 0 === strpos( $block['blockName'], 'core/' ) ? substr( $block['blockName'], 5 ) : $block['blockName'];
     291        $has_attrs = ! empty( $block['attrs'] );
     292        $attrs     = $has_attrs ? str_replace( $unwanted, $wanted, wp_json_encode( $block['attrs'] ) ) : '';
     293
     294        // early abort for void blocks holding no content
     295        if ( empty( $block['innerContent'] ) ) {
     296                return $has_attrs
     297                        ? "<!-- wp:{$name} {$attrs} /-->"
     298                        : "<!-- wp:{$name} /-->";
     299        }
     300
     301        $output = $has_attrs
     302                ? "<!-- wp:{$name} {$attrs} -->\n"
     303                : "<!-- wp:{$name} -->\n";
     304
     305        $innerBlockIndex = 0;
     306        foreach ( $block['innerContent'] as $chunk ) {
     307                $output .= null === $chunk
     308                        ? serialize_block( $block['innerBlocks'][$innerBlockIndex++] )
     309                        : $chunk;
     310
     311                $output .= "\n";
     312        }
     313
     314        $output .= "<!-- /wp:{$name} -->";
     315
     316        return $output;
     317}
     318
    278319/**
    279320 * Parses blocks out of a content string.
    280321 *
  • new file tests/phpunit/tests/blocks/serialize.php

    diff --git a/tests/phpunit/tests/blocks/serialize.php b/tests/phpunit/tests/blocks/serialize.php
    new file mode 100644
    index 0000000000..d4074f4aa3
    - +  
     1<?php
     2/**
     3 * Block Serialization Tests
     4 *
     5 * @package WordPress
     6 * @subpackage Blocks
     7 * @since 5.3.0
     8 */
     9
     10/**
     11 * Tests for block serialization
     12 *
     13 * @since 5.3.0
     14 *
     15 * @group blocks
     16 */
     17class WP_Test_Block_Serialize extends WP_UnitTestCase {
     18        function test_seriliazes_void_block_without_attributes() {
     19                $this->assertEquals(
     20                        '<!-- wp:test/void /-->',
     21                        serialize_block( self::make_block( array() ) )
     22                );
     23        }
     24
     25        function test_serializes_void_block_with_attributes() {
     26                $this->assertEquals(
     27                        '<!-- wp:test/void {"align":"left"} /-->',
     28                        serialize_block( self::make_block( array( 'attrs' => array( 'align' => 'left' ) ) ) )
     29                );
     30        }
     31
     32        function test_serializes_block_without_attributes() {
     33                $this->assertEquals(
     34                        "<!-- wp:test/test -->\nOnce I ate a cheeseburger.\n<!-- /wp:test/test -->",
     35                        serialize_block( self::make_block( array(
     36                                'blockName'    => 'test/test',
     37                                'innerHTML'    => 'Once I ate a cheeseburger.',
     38                                'innerContent' => array( 'Once I ate a cheeseburger.' ),
     39                        ) ) )
     40                );
     41        }
     42
     43        function test_serializes_block_with_attributes() {
     44                $this->assertEquals(
     45                        "<!-- wp:test/test {\"count\":3} -->\nOnce I ate a cheeseburger.\n<!-- /wp:test/test -->",
     46                        serialize_block( self::make_block( array(
     47                                'blockName'    => 'test/test',
     48                                'attrs'        => array( 'count' => 3 ),
     49                                'innerHTML'    => 'Once I ate a cheeseburger.',
     50                                'innerContent' => array( 'Once I ate a cheeseburger.' ),
     51                        ) ) )
     52                );
     53        }
     54
     55        function test_serializes_one_void_inner_block() {
     56                $inner_block = self::make_block( array( 'blockName' => 'test/inner' ) );
     57
     58                $this->assertEquals(
     59                        "<!-- wp:test/outer -->\n<!-- wp:test/inner /-->\n<!-- /wp:test/outer -->",
     60                        serialize_block( self::make_block( array(
     61                                'blockName'    => 'test/outer',
     62                                'innerBlocks'  => array( $inner_block ),
     63                                'innerContent' => array( null )
     64                        ) ) )
     65                );
     66        }
     67
     68        function test_seriailizes_many_inner_blocks() {
     69                $inner_block = self::make_block( array( 'blockName' => 'test/inner' ) );
     70
     71                $this->assertEquals(
     72                        "<!-- wp:test/outer -->\n<!-- wp:test/inner /-->\n<!-- wp:test/inner /-->\n<!-- /wp:test/outer -->",
     73                        serialize_block( self::make_block( array(
     74                                'blockName'    => 'test/outer',
     75                                'innerBlocks'  => array( $inner_block, $inner_block ),
     76                                'innerContent' => array( null, null )
     77                        ) ) )
     78                );
     79        }
     80
     81        function test_serializes_mixed_content() {
     82                $inner_block = self::make_block( array(
     83                        'blockName'    => 'test/inner',
     84                        'innerHTML'    => 'inside',
     85                        'innerContent' => array( 'inside' ),
     86                ) );
     87
     88                $this->assertEquals(
     89                        "<!-- wp:test/outer -->\nbefore\n<!-- wp:test/inner -->\ninside\n<!-- /wp:test/inner -->\nafter\n<!-- /wp:test/outer -->",
     90                        serialize_block( self::make_block( array(
     91                                'blockName'    => 'test/outer',
     92                                'innerBlocks'  => array( $inner_block ),
     93                                'innerContent' => array( 'before', null, 'after' ),
     94                                'innerHTML'    => 'beforeafter',
     95                        ) ) )
     96                );
     97        }
     98
     99        function test_serializes_nested_inner_blocks() {
     100                $inner_block = self::make_block( array(
     101                        'blockName'    => 'test/inner',
     102                        'innerHTML'    => 'inside',
     103                        'innerContent' => array( 'inside' ),
     104                ) );
     105
     106                $middle_block = self::make_block( array(
     107                        'blockName'    => 'test/middle',
     108                        'innerBlocks'  => array( $inner_block ),
     109                        'innerContent' => array( null ),
     110                ) );
     111
     112                $this->assertEquals(
     113                        "<!-- wp:test/outer -->\nbefore\n<!-- wp:test/middle -->\n<!-- wp:test/inner -->\ninside\n<!-- /wp:test/inner -->\n<!-- /wp:test/middle -->\nafter\n<!-- /wp:test/outer -->",
     114                        serialize_block( self::make_block( array(
     115                                'blockName'    => 'test/outer',
     116                                'innerBlocks'  => array( $middle_block ),
     117                                'innerContent' => array( 'before', null, 'after' ),
     118                                'innerHTML'    => 'beforeafter',
     119                        ) ) )
     120                );
     121        }
     122
     123        function test_serializes_dangerous_attrs() {
     124                $this->assertEquals(
     125                        '<!-- wp:test/void {"content":"\u003ctag\u003e"} /-->',
     126                        serialize_block( self::make_block( array( 'attrs' => array( 'content' => '<tag>' ) ) ) )
     127                );
     128
     129                $this->assertEquals(
     130                        '<!-- wp:test/void {"content":"\u003c!\u002d\u002d just a comment \u002d\u002d\u003e"} /-->',
     131                        serialize_block( self::make_block( array( 'attrs' => array( 'content' => '<!-- just a comment -->' ) ) ) )
     132                );
     133
     134                $this->assertEquals(
     135                        '<!-- wp:test/void {"content":"\u0022\u0026\u0022"} /-->',
     136                        serialize_block( self::make_block( array( 'attrs' => array( 'content' => '"&"' ) ) ) )
     137                );
     138
     139                $this->assertEquals(
     140                        "<!-- wp:test/test {\"content\":\"\u003ctag\u003e\"} -->\ntest\n<!-- /wp:test/test -->",
     141                        serialize_block( self::make_block( array(
     142                                'blockName'    => 'test/test',
     143                                'attrs'        => array( 'content' => '<tag>' ),
     144                                'innerHTML'    => 'test',
     145                                'innerContent' => array( 'test' ),
     146                        ) ) )
     147                );
     148
     149                $this->assertEquals(
     150                        "<!-- wp:test/test {\"content\":\"\u003c!\u002d\u002d just a comment \u002d\u002d\u003e\"} -->\ntest\n<!-- /wp:test/test -->",
     151                        serialize_block( self::make_block( array(
     152                                'blockName'    => 'test/test',
     153                                'attrs'        => array( 'content' => '<!-- just a comment -->' ),
     154                                'innerHTML'    => 'test',
     155                                'innerContent' => array( 'test' ),
     156                        ) ) )
     157                );
     158
     159                $this->assertEquals(
     160                        "<!-- wp:test/test {\"content\":\"\u0022\u0026\u0022\"} -->\ntest\n<!-- /wp:test/test -->",
     161                        serialize_block( self::make_block( array(
     162                                'blockName'    => 'test/test',
     163                                'attrs'        => array( 'content' => '"&"' ),
     164                                'innerHTML'    => 'test',
     165                                'innerContent' => array( 'test' ),
     166                        ) ) )
     167                );
     168        }
     169
     170        function test_serializes_and_strips_away_core_namespace() {
     171                $this->assertEquals(
     172                        '<!-- wp:test /-->',
     173                        serialize_block( self::make_block( array( 'blockName' => 'core/test' ) ) )
     174                );
     175        }
     176
     177        static function make_block( $props ) {
     178                return array_merge( array(
     179                        'blockName'    => 'test/void',
     180                        'attrs'        => array(),
     181                        'innerBlocks'  => array(),
     182                        'innerHTML'    => '',
     183                        'innerContent' => array(),
     184                ), $props );
     185        }
     186}