Skip to content

Commit 34d09b7

Browse files
saulyzSaulius Vikertacbravobernal
authored
Block bindings : add support to list-item (#78947)
* add support of block bindings for list-item * Revert "Fix dev script path handling on Windows" This reverts commit bedda6a. * Backport changelog: Link List Item binding enablement to Core #12204 * Fix: Add missing newline at end of block-bindings.php * Tests: Cover basic List Item content binding Add phpunit coverage for binding a List Item's content with non-nested values (plain text, sanitized markup, inline image replacement). These exercise the enablement filter alone and need no inner-block preservation. --------- Co-authored-by: Saulius Vikerta <sauliusv@git.wordpress.org> Co-authored-by: Carlos Bravo <37012961+cbravobernal@users.noreply.github.com>
1 parent 5d5fb28 commit 34d09b7

5 files changed

Lines changed: 177 additions & 2 deletions

File tree

‎backport-changelog/7.1/12204.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
https://github.com/WordPress/wordpress-develop/pull/12204
2+
3+
* https://github.com/WordPress/gutenberg/pull/78947
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
/**
3+
* Code block bindings additions for WordPress 7.1.
4+
*
5+
* @since 7.1.0
6+
* @package gutenberg
7+
* @subpackage Block Bindings
8+
*/
9+
10+
// The following filter can be removed once the minimum required WordPress version is 7.1 or newer.
11+
add_filter(
12+
'block_bindings_supported_attributes',
13+
function ( $attributes, $block_type ) {
14+
if ( 'core/list-item' === $block_type && ! in_array( 'content', $attributes, true ) ) {
15+
$attributes[] = 'content';
16+
}
17+
return $attributes;
18+
},
19+
10,
20+
2
21+
);

‎lib/load.php‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ function gutenberg_is_experiment_enabled( $name ) {
7373
require __DIR__ . '/compat/wordpress-7.1/class-gutenberg-rest-view-config-controller-7-1.php';
7474
require __DIR__ . '/compat/wordpress-7.1/rest-api.php';
7575
require __DIR__ . '/compat/wordpress-7.1/collaboration.php';
76+
require __DIR__ . '/compat/wordpress-7.1/block-bindings.php';
7677

7778
// Plugin specific code.
7879
require_once __DIR__ . '/class-wp-rest-global-styles-controller-gutenberg.php';

‎phpunit/block-bindings-test.php‎

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,4 +391,152 @@ public function test_filter_block_bindings_source_value() {
391391
'The block content should show the filtered value.'
392392
);
393393
}
394+
395+
/**
396+
* Renders a list item with a source value bound to its content attribute.
397+
*
398+
* @param mixed $source_value The source value.
399+
* @param string $block_content The block content.
400+
* @return string Rendered block content.
401+
*/
402+
private function render_list_item_with_source_value( $source_value, $block_content ) {
403+
register_block_bindings_source(
404+
self::SOURCE_NAME,
405+
array(
406+
'label' => self::SOURCE_LABEL,
407+
'get_value_callback' => function () use ( $source_value ) {
408+
return $source_value;
409+
},
410+
)
411+
);
412+
413+
$parsed_blocks = parse_blocks( $block_content );
414+
$block = new WP_Block( $parsed_blocks[0] );
415+
return trim( $block->render() );
416+
}
417+
418+
/**
419+
* Tests if list item content is updated with a plain text value returned by the source.
420+
*
421+
* @covers WP_Block::render
422+
*/
423+
public function test_update_list_item_with_plain_text_value_from_source() {
424+
$block_content = <<<HTML
425+
<!-- wp:list-item {"metadata":{"bindings":{"content":{"source":"test/source"}}}} -->
426+
<li>This should not appear</li>
427+
<!-- /wp:list-item -->
428+
HTML;
429+
$result = $this->render_list_item_with_source_value( 'test source value', $block_content );
430+
431+
$this->assertSame(
432+
'<li>test source value</li>',
433+
$result,
434+
'The list item content should be replaced by the source text.'
435+
);
436+
}
437+
/**
438+
* Tests if raw nested list markup is replaced when it is not a list inner block.
439+
*
440+
* @covers WP_Block::render
441+
*/
442+
public function test_update_list_item_with_raw_nested_list_markup_without_inner_block_replaces_markup() {
443+
$block_content = <<<HTML
444+
<!-- wp:list-item {"metadata":{"bindings":{"content":{"source":"test/source"}}}} -->
445+
<li>Default content<ul><li>Raw nested list should be replaced</li></ul></li>
446+
<!-- /wp:list-item -->
447+
HTML;
448+
$result = $this->render_list_item_with_source_value( 'Bound text', $block_content );
449+
450+
$this->assertSame(
451+
'<li>Bound text</li>',
452+
$result,
453+
'Raw nested list markup should be replaced with the rest of the list item content.'
454+
);
455+
}
456+
457+
/**
458+
* Tests if source image markup renders after post KSES sanitization.
459+
*
460+
* @covers WP_Block::render
461+
*/
462+
public function test_update_list_item_source_image_renders_after_kses() {
463+
$block_content = <<<HTML
464+
<!-- wp:list-item {"metadata":{"bindings":{"content":{"source":"test/source"}}}} -->
465+
<li>This should not appear</li>
466+
<!-- /wp:list-item -->
467+
HTML;
468+
$result = $this->render_list_item_with_source_value(
469+
'Bound <img class="wp-image-42" src="https://example.com/inline-image.jpg" alt="Inline image" />',
470+
$block_content
471+
);
472+
473+
$this->assertMatchesRegularExpression(
474+
'#<li>Bound <img[^>]*class="wp-image-42"[^>]*src="https://example\.com/inline-image\.jpg"[^>]*alt="Inline image"[^>]*/?></li>#',
475+
$result,
476+
'The source image should render in the list item content after sanitization.'
477+
);
478+
}
479+
480+
/**
481+
* Tests if unsafe source markup is sanitized from list item content.
482+
*
483+
* @covers WP_Block::render
484+
*/
485+
public function test_update_list_item_unsafe_source_markup_is_sanitized() {
486+
$block_content = <<<HTML
487+
<!-- wp:list-item {"metadata":{"bindings":{"content":{"source":"test/source"}}}} -->
488+
<li>This should not appear</li>
489+
<!-- /wp:list-item -->
490+
HTML;
491+
$result = $this->render_list_item_with_source_value(
492+
'Bound <img src="https://example.com/inline-image.jpg" alt="Inline image" onerror="alert(1)" /> <script>alert("Unsafe HTML")</script>',
493+
$block_content
494+
);
495+
496+
$this->assertStringNotContainsString(
497+
'<script',
498+
$result,
499+
'Script tags should be stripped from list item source content.'
500+
);
501+
$this->assertStringNotContainsString(
502+
'onerror',
503+
$result,
504+
'Event handler attributes should be stripped from list item source content.'
505+
);
506+
$this->assertMatchesRegularExpression(
507+
'#<img[^>]*src="https://example\.com/inline-image\.jpg"[^>]*alt="Inline image"[^>]*/?>#',
508+
$result,
509+
'Safe image markup should remain after sanitization.'
510+
);
511+
}
512+
513+
/**
514+
* Tests if original inline images are replaced by bound list item text.
515+
*
516+
* @covers WP_Block::render
517+
*/
518+
public function test_update_list_item_original_inline_image_is_replaced_by_source_text() {
519+
$block_content = <<<HTML
520+
<!-- wp:list-item {"metadata":{"bindings":{"content":{"source":"test/source"}}}} -->
521+
<li>Original <img src="https://example.com/original-image.jpg" alt="Original inline image" /> content</li>
522+
<!-- /wp:list-item -->
523+
HTML;
524+
$result = $this->render_list_item_with_source_value( 'Bound text', $block_content );
525+
526+
$this->assertSame(
527+
'<li>Bound text</li>',
528+
$result,
529+
'The original inline image should be replaced with the rest of the list item content.'
530+
);
531+
$this->assertStringNotContainsString(
532+
'original-image.jpg',
533+
$result,
534+
'The original inline image source should not remain after binding replacement.'
535+
);
536+
$this->assertStringNotContainsString(
537+
'Original inline image',
538+
$result,
539+
'The original inline image alt text should not remain after binding replacement.'
540+
);
541+
}
394542
}

‎tools/build-scripts/dev.mjs‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
/**
44
* External dependencies
55
*/
6-
import { execSync } from 'child_process';
7-
import spawn from 'cross-spawn';
6+
import { execSync, spawn } from 'child_process';
87
import { fileURLToPath } from 'url';
98
import path from 'path';
109
import fs from 'fs';
@@ -29,6 +28,7 @@ function exec( command, args = [], options = {} ) {
2928
const childOptions = {
3029
cwd: ROOT_DIR,
3130
stdio: silent ? 'pipe' : 'inherit',
31+
shell: true,
3232
...spawnOptions,
3333
};
3434

@@ -89,6 +89,7 @@ function execAsync( command, args = [], options = {} ) {
8989
return spawn( command, args, {
9090
cwd: ROOT_DIR,
9191
stdio: 'inherit',
92+
shell: true,
9293
...options,
9394
} );
9495
}
@@ -209,6 +210,7 @@ async function dev() {
209210
const buildWatch = spawn( 'wp-build', [ '--watch' ], {
210211
cwd: ROOT_DIR,
211212
stdio: [ 'inherit', 'pipe', 'inherit' ],
213+
shell: true,
212214
env: { ...process.env, NODE_ENV: 'development' },
213215
} );
214216

0 commit comments

Comments
 (0)