WordPress.org

Make WordPress Core

Ticket #47375: 47375-5.diff

File 47375-5.diff, 11.9 KB (added by jorgefilipecosta, 3 months ago)
  • src/wp-includes/blocks.php

     
    276276}
    277277
    278278/**
     279 * Renders an HTML-serialized form of a block object.
     280 *
     281 * @since 5.3.0
     282 *
     283 * @param array $block {
     284 *     The block being rendered.
     285 *
     286 *     @type string $blockName    The identifier of the block.
     287 *     @type array  $attrs        The attributes of the block.
     288 *     @type string $innerHTML    An HTML string, used only for raw html content.
     289 *     @type array  $innerContent Array of "chunks" part of the block. A "chunk" is a string or null when innerBlocks should be in a given position.
     290 *     @type array  $innerBlocks  Array of child block objects.
     291 * }
     292 * @return string|null The HTML-serialized form of the block.
     293 */
     294function serialize_block( $block ) {
     295        // Non-block content has no block name.
     296        if ( null === $block['blockName'] ) {
     297                if ( empty( $block['innerHTML'] ) || ! is_string( $block['innerHTML'] ) ) {
     298                        return '';
     299                }
     300                return $block['innerHTML'];
     301        }
     302        $has_attrs = ! empty( $block['attrs'] );
     303        if (
     304                ( empty( $block['blockName'] ) || ! is_string( $block['blockName'] ) ) ||
     305                ( $has_attrs && ! is_array( $block['attrs'] ) ) ||
     306                ( isset( $block['innerContent'] ) && ! is_array( $block['innerContent'] ) ) ||
     307                ( isset( $block['innerBlocks'] ) && ! is_array( $block['innerBlocks'] ) )
     308        ) {
     309                return '';
     310        }
     311
     312        $unwanted = array( '--', '<', '>', '&', '\"' );
     313        $wanted   = array( '\u002d\u002d', '\u003c', '\u003e', '\u0026', '\u0022' );
     314
     315        $name = 0 === strpos( $block['blockName'], 'core/' ) ? substr( $block['blockName'], 5 ) : $block['blockName'];
     316
     317        $attrs = $has_attrs ? str_replace( $unwanted, $wanted, wp_json_encode( $block['attrs'] ) ) : '';
     318
     319        // Early abort for void blocks holding no content.
     320        if ( empty( $block['innerContent'] ) ) {
     321                return $has_attrs
     322                        ? "<!-- wp:{$name} {$attrs} /-->"
     323                        : "<!-- wp:{$name} /-->";
     324        }
     325
     326        $output = $has_attrs
     327                ? "<!-- wp:{$name} {$attrs} -->\n"
     328                : "<!-- wp:{$name} -->\n";
     329
     330        $inner_block_index = 0;
     331        foreach ( $block['innerContent'] as $chunk ) {
     332                $output .= null === $chunk
     333                        ? serialize_block( $block['innerBlocks'][ $inner_block_index++ ] )
     334                        : $chunk;
     335
     336                $output .= "\n";
     337        }
     338
     339        $output .= "<!-- /wp:{$name} -->";
     340
     341        return $output;
     342}
     343
     344/**
     345 * Renders an HTML-serialized form of a list of block objects.
     346 *
     347 * @since 5.3.0
     348 *
     349 * @param array $blocks {
     350 *     An array of parsed block objects.
     351 *
     352 *     @type string $blockName    The identifier of the block.
     353 *     @type array  $attrs        The attributes of the block.
     354 *     @type string $innerHTML    An HTML string, used only for raw html content.
     355 *     @type array  $innerContent Array of "chunks" part of the block. A "chunk" is a string or null when innerBlocks should be in a given position.
     356 *     @type array  $innerBlocks  Array of child block objects.
     357 * }[]
     358 * @return string The HTML-serialized form of the list of blocks.
     359 */
     360function serialize_blocks( $blocks ) {
     361        return implode( "\n\n", array_map( 'serialize_block', $blocks ) );
     362}
     363
     364/**
    279365 * Parses blocks out of a content string.
    280366 *
    281367 * @since 5.0.0
  • tests/phpunit/tests/blocks/serialize.php

     
     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 * @covers ::serialize_block()
     16 *
     17 * @group blocks
     18 */
     19class WP_Test_Block_Serialize extends WP_UnitTestCase {
     20
     21        /**
     22         * The function should serialize a freeform block.
     23         *
     24         * @since 5.3.0
     25         */
     26        public function test_serializes_freeform_block() {
     27                $this->assertEquals(
     28                        '<strong>Success!</strong>',
     29                        serialize_block(
     30                                array(
     31                                        'blockName'    => null,
     32                                        'attrs'        => array(),
     33                                        'innerBlocks'  => array(),
     34                                        'innerHTML'    => '<strong>Success!</strong>',
     35                                        'innerContent' => array( '<strong>Success!</strong>' ),
     36                                )
     37                        )
     38                );
     39        }
     40
     41        /**
     42         * The function should serialize a void block without attributes.
     43         *
     44         * @since 5.3.0
     45         */
     46        public function test_serializes_void_block_without_attributes() {
     47                $this->assertEquals(
     48                        '<!-- wp:test/void /-->',
     49                        serialize_block( self::make_block( array() ) )
     50                );
     51        }
     52
     53        /**
     54         * The function should serialize a void block with attributes.
     55         *
     56         * @since 5.3.0
     57         */
     58        public function test_serializes_void_block_with_attributes() {
     59                $this->assertEquals(
     60                        '<!-- wp:test/void {"align":"left"} /-->',
     61                        serialize_block( self::make_block( array( 'attrs' => array( 'align' => 'left' ) ) ) )
     62                );
     63        }
     64
     65        /**
     66         * The function should serialize a block without attributes.
     67         *
     68         * @since 5.3.0
     69         */
     70        public function test_serializes_block_without_attributes() {
     71                $this->assertEquals(
     72                        "<!-- wp:test/test -->\nOnce I ate a cheeseburger.\n<!-- /wp:test/test -->",
     73                        serialize_block(
     74                                self::make_block(
     75                                        array(
     76                                                'blockName'    => 'test/test',
     77                                                'innerHTML'    => 'Once I ate a cheeseburger.',
     78                                                'innerContent' => array( 'Once I ate a cheeseburger.' ),
     79                                        )
     80                                )
     81                        )
     82                );
     83        }
     84
     85        /**
     86         * The function should serialize a block with attributes.
     87         *
     88         * @since 5.3.0
     89         */
     90        public function test_serializes_block_with_attributes() {
     91                $this->assertEquals(
     92                        "<!-- wp:test/test {\"count\":3} -->\nOnce I ate a cheeseburger.\n<!-- /wp:test/test -->",
     93                        serialize_block(
     94                                self::make_block(
     95                                        array(
     96                                                'blockName'    => 'test/test',
     97                                                'attrs'        => array( 'count' => 3 ),
     98                                                'innerHTML'    => 'Once I ate a cheeseburger.',
     99                                                'innerContent' => array( 'Once I ate a cheeseburger.' ),
     100                                        )
     101                                )
     102                        )
     103                );
     104        }
     105
     106        /**
     107         * The function should serialize a void inner block.
     108         *
     109         * @since 5.3.0
     110         */
     111        public function test_serializes_one_void_inner_block() {
     112                $inner_block = self::make_block( array( 'blockName' => 'test/inner' ) );
     113
     114                $this->assertEquals(
     115                        "<!-- wp:test/outer -->\n<!-- wp:test/inner /-->\n<!-- /wp:test/outer -->",
     116                        serialize_block(
     117                                self::make_block(
     118                                        array(
     119                                                'blockName'    => 'test/outer',
     120                                                'innerBlocks'  => array( $inner_block ),
     121                                                'innerContent' => array( null ),
     122                                        )
     123                                )
     124                        )
     125                );
     126        }
     127
     128        /**
     129         * The function should serialize multiple inner blocks.
     130         *
     131         * @since 5.3.0
     132         */
     133        public function test_serializes_many_inner_blocks() {
     134                $inner_block = self::make_block( array( 'blockName' => 'test/inner' ) );
     135
     136                $this->assertEquals(
     137                        "<!-- wp:test/outer -->\n<!-- wp:test/inner /-->\n<!-- wp:test/inner /-->\n<!-- /wp:test/outer -->",
     138                        serialize_block(
     139                                self::make_block(
     140                                        array(
     141                                                'blockName'    => 'test/outer',
     142                                                'innerBlocks'  => array( $inner_block, $inner_block ),
     143                                                'innerContent' => array( null, null ),
     144                                        )
     145                                )
     146                        )
     147                );
     148        }
     149
     150        /**
     151         * The function should serialize mixed content.
     152         *
     153         * @since 5.3.0
     154         */
     155        public function test_serializes_mixed_content() {
     156                $inner_block = self::make_block(
     157                        array(
     158                                'blockName'    => 'test/inner',
     159                                'innerHTML'    => 'inside',
     160                                'innerContent' => array( 'inside' ),
     161                        )
     162                );
     163
     164                $this->assertEquals(
     165                        "<!-- wp:test/outer -->\nbefore\n<!-- wp:test/inner -->\ninside\n<!-- /wp:test/inner -->\nafter\n<!-- /wp:test/outer -->",
     166                        serialize_block(
     167                                self::make_block(
     168                                        array(
     169                                                'blockName'    => 'test/outer',
     170                                                'innerBlocks'  => array( $inner_block ),
     171                                                'innerContent' => array( 'before', null, 'after' ),
     172                                                'innerHTML'    => 'beforeafter',
     173                                        )
     174                                )
     175                        )
     176                );
     177        }
     178
     179        /**
     180         * The function should serialize nested inner blocks.
     181         *
     182         * @since 5.3.0
     183         */
     184        public function test_serializes_nested_inner_blocks() {
     185                $inner_block = self::make_block(
     186                        array(
     187                                'blockName'    => 'test/inner',
     188                                'innerHTML'    => 'inside',
     189                                'innerContent' => array( 'inside' ),
     190                        )
     191                );
     192
     193                $middle_block = self::make_block(
     194                        array(
     195                                'blockName'    => 'test/middle',
     196                                'innerBlocks'  => array( $inner_block ),
     197                                'innerContent' => array( null ),
     198                        )
     199                );
     200
     201                $this->assertEquals(
     202                        "<!-- 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 -->",
     203                        serialize_block(
     204                                self::make_block(
     205                                        array(
     206                                                'blockName'    => 'test/outer',
     207                                                'innerBlocks'  => array( $middle_block ),
     208                                                'innerContent' => array( 'before', null, 'after' ),
     209                                                'innerHTML'    => 'beforeafter',
     210                                        )
     211                                )
     212                        )
     213                );
     214        }
     215
     216        /**
     217         * The function should serialize with escaping dangerous attributes.
     218         *
     219         * @since 5.3.0
     220         */
     221        public function test_serializes_dangerous_attrs() {
     222                $this->assertEquals(
     223                        '<!-- wp:test/void {"content":"\u003ctag\u003e"} /-->',
     224                        serialize_block( self::make_block( array( 'attrs' => array( 'content' => '<tag>' ) ) ) )
     225                );
     226
     227                $this->assertEquals(
     228                        '<!-- wp:test/void {"content":"\u003c!\u002d\u002d just a comment \u002d\u002d\u003e"} /-->',
     229                        serialize_block( self::make_block( array( 'attrs' => array( 'content' => '<!-- just a comment -->' ) ) ) )
     230                );
     231
     232                $this->assertEquals(
     233                        '<!-- wp:test/void {"content":"\u0022\u0026\u0022"} /-->',
     234                        serialize_block( self::make_block( array( 'attrs' => array( 'content' => '"&"' ) ) ) )
     235                );
     236
     237                $this->assertEquals(
     238                        "<!-- wp:test/test {\"content\":\"\u003ctag\u003e\"} -->\ntest\n<!-- /wp:test/test -->",
     239                        serialize_block(
     240                                self::make_block(
     241                                        array(
     242                                                'blockName'    => 'test/test',
     243                                                'attrs'        => array( 'content' => '<tag>' ),
     244                                                'innerHTML'    => 'test',
     245                                                'innerContent' => array( 'test' ),
     246                                        )
     247                                )
     248                        )
     249                );
     250
     251                $this->assertEquals(
     252                        "<!-- wp:test/test {\"content\":\"\u003c!\u002d\u002d just a comment \u002d\u002d\u003e\"} -->\ntest\n<!-- /wp:test/test -->",
     253                        serialize_block(
     254                                self::make_block(
     255                                        array(
     256                                                'blockName'    => 'test/test',
     257                                                'attrs'        => array( 'content' => '<!-- just a comment -->' ),
     258                                                'innerHTML'    => 'test',
     259                                                'innerContent' => array( 'test' ),
     260                                        )
     261                                )
     262                        )
     263                );
     264
     265                $this->assertEquals(
     266                        "<!-- wp:test/test {\"content\":\"\u0022\u0026\u0022\"} -->\ntest\n<!-- /wp:test/test -->",
     267                        serialize_block(
     268                                self::make_block(
     269                                        array(
     270                                                'blockName'    => 'test/test',
     271                                                'attrs'        => array( 'content' => '"&"' ),
     272                                                'innerHTML'    => 'test',
     273                                                'innerContent' => array( 'test' ),
     274                                        )
     275                                )
     276                        )
     277                );
     278        }
     279
     280        /**
     281         * The function should serialize and strip away core namespace.
     282         *
     283         * @since 5.3.0
     284         */
     285        public function test_serializes_and_strips_away_core_namespace() {
     286                $this->assertEquals(
     287                        '<!-- wp:test /-->',
     288                        serialize_block( self::make_block( array( 'blockName' => 'core/test' ) ) )
     289                );
     290        }
     291
     292        /**
     293         * The function should add a whitespace between blocks.
     294         *
     295         * @since 5.3.0
     296         */
     297        public function test_adds_pretty_whitespace_between_blocks() {
     298                $this->assertEquals(
     299                        "<!-- wp:first /-->\n\n<!-- wp:second /-->",
     300                        serialize_blocks(
     301                                array(
     302                                        self::make_block( array( 'blockName' => 'first' ) ),
     303                                        self::make_block( array( 'blockName' => 'second' ) ),
     304                                )
     305                        )
     306                );
     307        }
     308
     309        /**
     310         * Util function that creates a test block.
     311         *
     312         * @since 5.3.0
     313         * @param array $props The properties of the test block.
     314         * @return array The test block.
     315         */
     316        private static function make_block( $props ) {
     317                return array_merge(
     318                        array(
     319                                'blockName'    => 'test/void',
     320                                'attrs'        => array(),
     321                                'innerBlocks'  => array(),
     322                                'innerHTML'    => '',
     323                                'innerContent' => array(),
     324                        ),
     325                        $props
     326                );
     327        }
     328}