Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block Bindings: Don't show protected fields that are bound to blocks and post meta #6197

Closed
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
105ffe1
Check if the meta field is protected
SantosGuillamot Feb 28, 2024
00b4d65
Check if the meta field is available in the REST API
SantosGuillamot Feb 28, 2024
46a29e4
Remove unnecessary `show_in_rest` conditional
SantosGuillamot Feb 28, 2024
0b106f9
Use `source_args` instead of `source_attrs`
SantosGuillamot Feb 28, 2024
1736c83
Add post meta bindings source tests
SantosGuillamot Feb 28, 2024
1fb2c47
Unregister only the meta fields containing "tests"
SantosGuillamot Feb 28, 2024
81d4e34
Add test for password protected posts
SantosGuillamot Feb 28, 2024
8d0a0be
Add test for posts that are not viewable
SantosGuillamot Feb 28, 2024
ce4aa0c
Move set up tp `wpSetUpBeforeClass`
SantosGuillamot Feb 29, 2024
f1655d6
Add test for non existing meta key
SantosGuillamot Feb 29, 2024
01d45e5
Remove filters in tests
SantosGuillamot Feb 29, 2024
7d25302
Refactor the way post content is modified
SantosGuillamot Feb 29, 2024
dfe02a5
Add test to check unsafe html is sanitized
SantosGuillamot Feb 29, 2024
6414a97
Move unsafe html test to render bindings
SantosGuillamot Feb 29, 2024
193131f
Add post meta unsafe html test again
SantosGuillamot Feb 29, 2024
a0b761b
Change how we modify the global $post variable
SantosGuillamot Feb 29, 2024
4cdf241
Use snake_case
SantosGuillamot Feb 29, 2024
6e37a4c
Use hooks helper function
SantosGuillamot Feb 29, 2024
8389270
Remove outdated function
SantosGuillamot Feb 29, 2024
953058d
Update class methods descriptions
SantosGuillamot Feb 29, 2024
bc81da4
Unset global post variable after class
SantosGuillamot Feb 29, 2024
64f0784
Add tests for bindings modifying html attribute
SantosGuillamot Feb 29, 2024
56beecc
Apply suggestions from code review
swissspidy Mar 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/wp-includes/block-bindings/post-meta.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ function _block_bindings_post_meta_get_value( array $source_args, $block_instanc
return null;
}

// Check if the meta field is protected.
if ( is_protected_meta( $source_args['key'], 'post' ) ) {
return null;
}

// Check if the meta field is registered to be shown in REST.
$meta_keys = get_registered_meta_keys( 'post', $block_instance->context['postType'] );
// Add fields registered for all subtypes.
$meta_keys = array_merge( $meta_keys, get_registered_meta_keys( 'post', '' ) );
if ( empty( $meta_keys[ $source_args['key'] ]['show_in_rest'] ) ) {
return null;
}

return get_post_meta( $post_id, $source_args['key'], true );
}

Expand Down
263 changes: 263 additions & 0 deletions tests/phpunit/tests/block-bindings/postMetaSource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
<?php
/**
* Tests for Block Bindings API "core/post-meta" source.
*
* @package WordPress
* @subpackage Blocks
* @since 6.5.0
*
* @group blocks
* @group block-bindings
*/
class Tests_Block_Bindings_Post_Meta_Source extends WP_UnitTestCase {
protected static $post;
protected static $wp_meta_keys_saved;

/**
* Modify the post content.
*
* @param string $content The new content.
*/
private function getModifiedPostContent( $content ) {
SantosGuillamot marked this conversation as resolved.
Show resolved Hide resolved
self::$post->post_content = $content;
// Update the global $post variable to ensure all tests get the correct $post context.
$this->updateGlobalPost();
return apply_filters( 'the_content', self::$post->post_content );
}

/**
* Update the global $post variable.
*/
private function updateGlobalPost() {
Copy link
Member

@gziolo gziolo Feb 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There isn't much value in having that code extracted from its method since it's used only once. It would make the code easier to follow if it was inlined.

You could also avoid adding inline comments by making the code structured like that:

global $post;
$post = self::$post;
$post->post_content = $content;
return apply_filters( 'the_content', self::$post->post_content );

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The self::$post variable is useless anyway because the whole class only operates on the global post.

We can just always work on the global post if that is required.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried using the global $post variable only for all the tests, but something is not working as I expected.

It seems that the value of the global $post variable is undefined (or the value I defined in wpSetUpBeforeClass) for the FIRST tests. For the following tests, the global $post variable becomes always null unless I redefine it.

This happens even if I remove all the logic and I just access that variable.

The way I'm solving that is storing the $post object in a private property when the post is initialized: link. And for each test I assign the global variable to that: link.

However, this feels like a workaround. Any idea if that is expected or how it could be solved?

global $post;
$post = self::$post;
}

/**
* Set up for every method.
SantosGuillamot marked this conversation as resolved.
Show resolved Hide resolved
*/
public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
self::$post = $factory->post->create_and_get();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per my other comment, the static $post variable is redundant if you make it global anyway.

self::$wp_meta_keys_saved = isset( $GLOBALS['wp_meta_keys'] ) ? $GLOBALS['wp_meta_keys'] : array();
}

/**
* Tear down for every method.
*/
SantosGuillamot marked this conversation as resolved.
Show resolved Hide resolved
public static function wpTearDownAfterClass() {
$GLOBALS['wp_meta_keys'] = self::$wp_meta_keys_saved;
SantosGuillamot marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Tests that a block connected to a custom field renders its value.
*
* @ticket 60651
*/
public function test_custom_field_value_is_rendered() {
register_meta(
'post',
'tests_custom_field',
array(
'show_in_rest' => true,
'single' => true,
'type' => 'string',
'default' => 'Custom field value',
)
);

$content = $this->getModifiedPostContent( '<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"core/post-meta","args":{"key":"tests_custom_field"}}}}} --><p>Fallback value</p><!-- /wp:paragraph -->' );
$this->assertSame(
'<p>Custom field value</p>',
$content,
'The post content should show the value of the custom field . '
);
}

/**
* Tests that a blocks connected in a password protected post don't render the value.
*
* @ticket 60651
*/
public function test_custom_field_value_is_not_shown_in_password_protected_posts() {
register_meta(
'post',
'tests_custom_field',
array(
'show_in_rest' => true,
'single' => true,
'type' => 'string',
'default' => 'Custom field value',
)
);

function wp_tests_require_post_password() {
return true;
}

add_filter(
'post_password_required',
'wp_tests_require_post_password'
);
SantosGuillamot marked this conversation as resolved.
Show resolved Hide resolved

$content = $this->getModifiedPostContent( '<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"core/post-meta","args":{"key":"tests_custom_field"}}}}} --><p>Fallback value</p><!-- /wp:paragraph -->' );

remove_filter(
gziolo marked this conversation as resolved.
Show resolved Hide resolved
'post_password_required',
'wp_tests_post_password_required'
);
SantosGuillamot marked this conversation as resolved.
Show resolved Hide resolved

$this->assertSame(
'<p>Fallback value</p>',
$content,
'The post content should show the fallback value instead of the custom field value.'
);
}

/**
* Tests that a blocks connected in a post that is not publicly viewable don't render the value.
*
* @ticket 60651
*/
public function test_custom_field_value_is_not_shown_in_non_viewable_posts() {
register_meta(
'post',
'tests_custom_field',
array(
'show_in_rest' => true,
'single' => true,
'type' => 'string',
'default' => 'Custom field value',
)
);

function wp_tests_make_post_status_not_viewable() {
return false;
}

SantosGuillamot marked this conversation as resolved.
Show resolved Hide resolved
add_filter(
'is_post_status_viewable',
'wp_tests_make_post_status_not_viewable'
);
SantosGuillamot marked this conversation as resolved.
Show resolved Hide resolved

$content = $this->getModifiedPostContent( '<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"core/post-meta","args":{"key":"tests_custom_field"}}}}} --><p>Fallback value</p><!-- /wp:paragraph -->' );

remove_filter(
'is_post_status_viewable',
'wp_tests_make_post_status_not_viewable'
);
SantosGuillamot marked this conversation as resolved.
Show resolved Hide resolved

$this->assertSame(
'<p>Fallback value</p>',
$content,
'The post content should show the fallback value instead of the custom field value.'
);
}

/**
* Tests that a block connected to a meta key that doesn't exist renders the fallback.
*
* @ticket 60651
*/
public function test_binding_to_non_existing_meta_key() {
$content = $this->getModifiedPostContent( '<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"core/post-meta","args":{"key":"tests_non_existing_field"}}}}} --><p>Fallback value</p><!-- /wp:paragraph -->' );

$this->assertSame(
'<p>Fallback value</p>',
$content,
'The post content should show the fallback value.'
);
}

/**
* Tests that a block connected without specifying the custom field renders the fallback.
*
* @ticket 60651
*/
public function test_binding_without_key_renders_the_fallback() {
$content = $this->getModifiedPostContent( '<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"core/post-meta"}}}} --><p>Fallback value</p><!-- /wp:paragraph -->' );

$this->assertSame(
'<p>Fallback value</p>',
$content,
'The post content should show the fallback value.'
);
}

/**
* Tests that a block connected to a protected field doesn't show the value.
*
* @ticket 60651
*/
public function test_protected_field_value_is_not_shown() {
register_meta(
'post',
'_tests_protected_field',
array(
'show_in_rest' => true,
'single' => true,
'type' => 'string',
'default' => 'Protected value',
)
);

$content = $this->getModifiedPostContent( '<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"core/post-meta","args":{"key":"_tests_protected_field"}}}}} --><p>Fallback value</p><!-- /wp:paragraph -->' );

$this->assertSame(
'<p>Fallback value</p>',
$content,
'The post content should show the fallback value instead of the protected value.'
);
}

/**
* Tests that a block connected to a field not exposed in the REST API doesn't show the value.
*
* @ticket 60651
*/
public function test_custom_field_not_exposed_in_rest_api_is_not_shown() {
register_meta(
'post',
'tests_show_in_rest_false_field',
array(
'show_in_rest' => false,
'single' => true,
'type' => 'string',
'default' => 'Protected value',
)
);

$content = $this->getModifiedPostContent( '<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"core/post-meta","args":{"key":"tests_show_in_rest_false_field"}}}}} --><p>Fallback value</p><!-- /wp:paragraph -->' );

$this->assertSame(
'<p>Fallback value</p>',
$content,
'The post content should show the fallback value instead of the protected value.'
);
}

/**
* Tests that meta key with unsafe HTML is sanitized.
*
* @ticket 60651
*/
public function test_custom_field_with_unsafe_html_is_sanitized() {
register_meta(
'post',
'tests_unsafe_html_field',
array(
'show_in_rest' => true,
'single' => true,
'type' => 'string',
'default' => '<script>alert("Unsafe HTML")</script>',
)
);

$content = $this->getModifiedPostContent( '<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"core/post-meta","args":{"key":"tests_unsafe_html_field"}}}}} --><p>Fallback value</p><!-- /wp:paragraph -->' );
gziolo marked this conversation as resolved.
Show resolved Hide resolved

$this->assertSame(
'<p>alert(&#8220;Unsafe HTML&#8221;)</p>',
$content,
'The post content should not include the script tag.'
);
}
}
Loading