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

Pattern CPT: Add basic content validation when saving a pattern #38

Merged
merged 4 commits into from
Feb 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
namespace WordPressdotorg\Pattern_Directory;

require_once __DIR__ . '/includes/pattern-post-type.php';
require_once __DIR__ . '/includes/pattern-validation.php';
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

namespace WordPressdotorg\Pattern_Directory\Pattern_Validation;
use const WordPressdotorg\Pattern_Directory\Pattern_Post_Type\POST_TYPE;

add_filter( 'rest_pre_insert_' . POST_TYPE, __NAMESPACE__ . '\validate_content', 10, 2 );

/**
* Validate the pattern content.
*/
function validate_content( $prepared_post, $request ) {
$content = $prepared_post->post_content;
if ( ! $content ) {
return new \WP_Error(
'rest_pattern_empty',
__( 'Pattern content cannot be empty.', 'wporg-patterns' ),
array( 'status' => 400 )
);
}

// The editor adds in linebreaks between blocks, but parse_blocks thinks those are invalid blocks.
$content = str_replace( "\n\n", '', $content );
$blocks = parse_blocks( $content );
$registry = \WP_Block_Type_Registry::get_instance();

// $blocks contains a list of the blocks in the content. By default it will always have one item, even if it's
// not valid block content. Instead, we should check that each block in the list has a blockName.
$invalid_blocks = array_filter( $blocks, function( $block ) use ( $registry ) {
$block_type = $registry->get_registered( $block['blockName'] );
return is_null( $block['blockName'] ) || is_null( $block_type );
} );
if ( count( $invalid_blocks ) ) {
return new \WP_Error(
'rest_pattern_invalid_blocks',
__( 'Pattern content contains invalid blocks.', 'wporg-patterns' ),
array( 'status' => 400 )
);
}

// Next, we should check that we have at least one non-empty block.
$real_blocks = array_filter( $blocks, function( $block ) use ( $registry ) {
$block_type = $registry->get_registered( $block['blockName'] );

// Check if the attributes are different from the default attributes.
$block_attrs = $block_type->prepare_attributes_for_render( $block['attrs'] );
$default_attrs = $block_type->prepare_attributes_for_render( array() );
if ( $block_attrs != $default_attrs ) {
return true;
}

// Try to judge whether a block has text content, or a valid image.
// First, remove class attributes, since custom class names would be caught above.
// Next, remove empty alt tags, which are present on default image blocks.
// Lastly, remove HTML tags without attributes- this regex catches opening, closing, and self-closing tags.
// After all this, any block_content left should be there intentionally by the author.
$to_replace = array( '/class="[^"]*"/', '/alt=""/', '/<\/?[a-zA-Z]+\s*\/?>/' );
$block_content = trim( preg_replace( $to_replace, '', $block['innerHTML'] ) );
if ( ! empty( $block_content ) ) {
return true;
}
return false;
} );

if ( ! count( $real_blocks ) ) {
return new \WP_Error(
'rest_pattern_empty_blocks',
__( 'Pattern content contains only empty blocks.', 'wporg-patterns' ),
array( 'status' => 400 )
);
}

return $prepared_post;
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
<?php
/**
* Test Block Pattern validation.
*/

use const WordPressdotorg\Pattern_Directory\Pattern_Post_Type\POST_TYPE;

/**
* Test pattern validation.
*/
class Pattern_Content_Validation_Test extends WP_UnitTestCase {
protected static $pattern_id;
protected static $user;

/**
* Setup fixtures that are shared across all tests.
*/
public static function wpSetUpBeforeClass( $factory ) {
self::$pattern_id = $factory->post->create(
array( 'post_type' => POST_TYPE )
);
self::$user = $factory->user->create(
array(
'role' => 'administrator',
)
);
}

/**
* Verify the pattern & API are set up correctly.
*/
public function test_pattern_directory_api() {
$request = new WP_REST_Request( 'GET', '/wp/v2/wporg-pattern/' . self::$pattern_id );
$response = rest_do_request( $request );
$this->assertFalse( $response->is_error() );
}

/**
* Helper function to handle REST requests to save the pattern.
*/
protected function save_block_content( $content ) {
$request = new WP_REST_Request( 'POST', '/wp/v2/wporg-pattern/' . self::$pattern_id );
$request->set_header( 'content-type', 'application/json' );
$request->set_body( json_encode( array(
'content' => $content,
) ) );
return rest_do_request( $request );
}

/**
* Test valid block content: simple paragraph.
*/
public function test_valid_simple_block() {
wp_set_current_user( self::$user );
$response = $this->save_block_content(
"<!-- wp:paragraph -->\n<p>This is a block.</p>\n<!-- /wp:paragraph -->"
);
$this->assertFalse( $response->is_error() );
}

/**
* Test valid block content: empty paragraph, with a class.
*/
public function test_valid_block_with_class() {
wp_set_current_user( self::$user );
$response = $this->save_block_content(
"<!-- wp:paragraph {\"className\":\"foo\"} -->\n<p class=\"foo\"></p>\n<!-- /wp:paragraph -->"
);
$this->assertFalse( $response->is_error() );
}

/**
* Test valid block content: paragraph with only an image.
*/
public function test_valid_block_with_image() {
wp_set_current_user( self::$user );
$response = $this->save_block_content(
"<!-- wp:paragraph -->\n<p><img class=\"wp-image-63\" style=\"width: 150px;\" src=\"./image.png\" alt=\"\"></p>\n<!-- /wp:paragraph -->"
);
$this->assertFalse( $response->is_error() );
}

/**
* Test valid block content: real content with an extra empty paragraph (beginning).
*/
public function test_valid_extra_empty_paragraph_initial() {
wp_set_current_user( self::$user );
$response = $this->save_block_content(
"<!-- wp:paragraph -->\n<p></p>\n<!-- /wp:paragraph --><!-- wp:paragraph -->\n<p>Some block content</p>\n<!-- /wp:paragraph -->"
);
$this->assertFalse( $response->is_error() );
}

/**
* Test valid block content: real content with an extra empty paragraph (end).
*/
public function test_valid_extra_empty_paragraph_end() {
wp_set_current_user( self::$user );
$response = $this->save_block_content(
"<!-- wp:paragraph -->\n<p>Some block content</p>\n<!-- /wp:paragraph --><!-- wp:paragraph -->\n<p></p>\n<!-- /wp:paragraph -->"
);
$this->assertFalse( $response->is_error() );
}

/**
* Test valid block content: a group block with an image.
*/
public function test_valid_group_block() {
wp_set_current_user( self::$user );
$response = $this->save_block_content(
"<!-- wp:group {\"backgroundColor\":\"black\",\"textColor\":\"cyan-bluish-gray\"} -->\n<div class=\"wp-block-group has-cyan-bluish-gray-color has-black-background-color has-text-color has-background\"><div class=\"wp-block-group__inner-container\"><!-- wp:image {\"sizeSlug\":\"large\"} -->\n<figure class=\"wp-block-image size-large\"><img src=\"https://s.w.org/style/images/wporg-logo.svg?3\" alt=\"\"/></figure>\n<!-- /wp:image --></div></div>\n<!-- /wp:group -->"
);
$this->assertFalse( $response->is_error() );
}

/**
* Test valid block content: an audio block.
*/
public function test_valid_audio_block() {
wp_set_current_user( self::$user );
$response = $this->save_block_content(
"<!-- wp:audio {\"id\":9} -->\n<figure class=\"wp-block-audio\"><audio controls src=\"./song.mp3\"></audio></figure>\n<!-- /wp:audio -->"
);
$this->assertFalse( $response->is_error() );
}

/**
* Test invalid block content: empty content.
*/
public function test_invalid_empty_content() {
wp_set_current_user( self::$user );
$response = $this->save_block_content( '' );
$this->assertTrue( $response->is_error() );
$data = $response->get_data();
$this->assertSame( 'rest_pattern_empty', $data['code'] );
}

/**
* Test invalid block content: not blocks.
*/
public function test_invalid_not_blocks() {
wp_set_current_user( self::$user );
$response = $this->save_block_content( '<p>This is not blocks.</p>' );
$this->assertTrue( $response->is_error() );
$data = $response->get_data();
$this->assertSame( 'rest_pattern_invalid_blocks', $data['code'] );
}

/**
* Test invalid block content: empty paragraph (default block).
*/
public function test_invalid_empty_paragraph() {
wp_set_current_user( self::$user );
$response = $this->save_block_content( "<!-- wp:paragraph -->\n<p></p>\n<!-- /wp:paragraph -->" );
$this->assertTrue( $response->is_error() );
$data = $response->get_data();
$this->assertSame( 'rest_pattern_empty_blocks', $data['code'] );
}

/**
* Test invalid block content: empty paragraphs (multiple).
*/
public function test_invalid_empty_paragraphs() {
wp_set_current_user( self::$user );
$response = $this->save_block_content( "<!-- wp:paragraph -->\n<p></p>\n<!-- /wp:paragraph --><!-- wp:paragraph -->\n<p></p>\n<!-- /wp:paragraph --><!-- wp:paragraph -->\n<p></p>\n<!-- /wp:paragraph -->" );
$this->assertTrue( $response->is_error() );
$data = $response->get_data();
$this->assertSame( 'rest_pattern_empty_blocks', $data['code'] );
}

/**
* Test invalid block content: empty list (not default).
*/
public function test_invalid_empty_list() {
wp_set_current_user( self::$user );
$response = $this->save_block_content( "<!-- wp:list -->\n<ul><li></li></ul>\n<!-- /wp:list -->" );
$this->assertTrue( $response->is_error() );
$data = $response->get_data();
$this->assertSame( 'rest_pattern_empty_blocks', $data['code'] );
}

/**
* Test invalid block content: empty image.
*/
public function test_invalid_empty_image() {
wp_set_current_user( self::$user );
$response = $this->save_block_content( "<!-- wp:image -->\n<figure class=\"wp-block-image\"><img alt=\"\"/></figure>\n<!-- /wp:image -->" );
$this->assertTrue( $response->is_error() );
$data = $response->get_data();
$this->assertSame( 'rest_pattern_empty_blocks', $data['code'] );
}

/**
* Test invalid block content: a group block with an image.
*/
public function test_invalid_empty_group_block() {
wp_set_current_user( self::$user );
$response = $this->save_block_content(
"<!-- wp:group -->\n<div class=\"wp-block-group\"><div class=\"wp-block-group__inner-container\"></div></div>\n<!-- /wp:group -->"
);
$this->assertTrue( $response->is_error() );
$data = $response->get_data();
$this->assertSame( 'rest_pattern_empty_blocks', $data['code'] );
}

/**
* Test invalid block content: an empty media & text block.
*/
public function test_invalid_empty_media_text_block() {
wp_set_current_user( self::$user );
$response = $this->save_block_content(
"<!-- wp:media-text -->\n<div class=\"wp-block-media-text alignwide is-stacked-on-mobile\"><figure class=\"wp-block-media-text__media\"></figure><div class=\"wp-block-media-text__content\"><!-- wp:paragraph {\"placeholder\":\"Content…\",\"fontSize\":\"large\"} -->\n<p class=\"has-large-font-size\"></p>\n<!-- /wp:paragraph --></div></div>\n<!-- /wp:media-text -->"
);
$this->assertTrue( $response->is_error() );
$data = $response->get_data();
$this->assertSame( 'rest_pattern_empty_blocks', $data['code'] );
}

/**
* Test invalid block content: an empty media & text block.
*/
public function test_invalid_fake_block() {
wp_set_current_user( self::$user );
$response = $this->save_block_content(
"<!-- wp:plugin/fake -->\n<p>This is some content.</p>\n<!-- /wp:plugin/fake -->"
);
$this->assertTrue( $response->is_error() );
$data = $response->get_data();
$this->assertSame( 'rest_pattern_invalid_blocks', $data['code'] );
}
}