Skip to content
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
5 changes: 3 additions & 2 deletions blocks/library/freeform/old-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,10 @@ export default class OldEditor extends Component {
onSetup( editor ) {
const { attributes: { content }, setAttributes } = this.props;
const { ref } = this;
const initialContent = window.switchEditors.wpautop( content || '' );

editor.on( 'loadContent', () => editor.setContent( initialContent ) );
if ( content ) {
editor.on( 'loadContent', () => editor.setContent( content ) );
}

editor.on( 'blur', () => {
setAttributes( {
Expand Down
133 changes: 133 additions & 0 deletions lib/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,80 @@ function gutenberg_parse_blocks( $content ) {
return $parser->parse( _gutenberg_utf8_split( $content ) );
}

/**
* Given an array of parsed blocks, returns content string.
*
* @since 1.7.0
*
* @param array $blocks Parsed blocks.
*
* @return string Content string.
*/
function gutenberg_serialize_blocks( $blocks ) {
return implode( '', array_map( 'gutenberg_serialize_block', $blocks ) );
}

/**
* Given a parsed block, returns content string.
*
* @since 1.7.0
*
* @param array $block Parsed block.
*
* @return string Content string.
*/
function gutenberg_serialize_block( $block ) {
// Return content of unknown block verbatim.
if ( ! isset( $block['blockName'] ) ) {
return $block['innerHTML'];
}

// Custom formatting for specific block types.
if ( 'core/more' === $block['blockName'] ) {
$content = '<!--more';
if ( ! empty( $block['attrs']['customText'] ) ) {
$content .= ' ' . $block['attrs']['customText'];
}

$content .= '-->';
if ( ! empty( $block['attrs']['noTeaser'] ) ) {
$content .= "\n" . '<!--noteaser-->';
}

return $content;
}

// For standard blocks, return with comment-delimited wrapper.
$content = '<!-- wp:' . $block['blockName'] . ' ';

if ( ! empty( $block['attrs'] ) ) {
$attrs_json = json_encode( $block['attrs'] );

// In PHP 5.4+, we would pass the `JSON_UNESCAPED_SLASHES` option to
// `json_encode`. To support older versions, we must apply manually.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice comment!

Also, check out the JS serializer to see the other transforms we make in order to safeguard the attributes

export function serializeAttributes( attrs ) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Also, check out the JS serializer to see the other transforms we make in order to safeguard the attributes

Ah, right, will make a pass to port over this behavior and equivalent tests.

$attrs_json = str_replace( '\\/', '/', $attrs_json );

// Don't break HTML comments.
$attrs_json = str_replace( '--', '\\u002d\\u002d', $attrs_json );

// Don't break standard-non-compliant tools.
$attrs_json = str_replace( '<', '\\u003c', $attrs_json );
$attrs_json = str_replace( '>', '\\u003e', $attrs_json );
$attrs_json = str_replace( '&', '\\u0026', $attrs_json );

$content .= $attrs_json . ' ';
}

if ( empty( $block['innerHTML'] ) ) {
return $content . '/-->';
}

$content .= '-->';
$content .= $block['innerHTML'];
$content .= '<!-- /wp:' . $block['blockName'] . ' -->';
return $content;
}

/**
* Parses dynamic blocks out of `post_content` and re-renders them.
*
Expand Down Expand Up @@ -93,3 +167,62 @@ function do_blocks( $content ) {
return $content_after_blocks;
}
add_filter( 'the_content', 'do_blocks', 9 ); // BEFORE do_shortcode() and wpautop().

/**
* Given a string, returns content normalized with automatic paragraphs applied
* to text not identified as a block. Since this executes the block parser, it
* should not be used in a performance-critical flow such as content display.
* Block content will not have automatic paragraphs applied.
*
* @since 1.7.0
*
* @param string $content Original content.
* @return string Content formatted with automatic paragraphs applied
* to unknown blocks.
*/
function gutenberg_wpautop_block_content( $content ) {
$blocks = gutenberg_parse_blocks( $content );
foreach ( $blocks as $i => $block ) {
if ( isset( $block['blockName'] ) ) {
continue;
}

$content = $block['innerHTML'];

// wpautop will trim leading whitespace and return whitespace-only text
// as an empty string. Preserve to apply leading whitespace as prefix.
preg_match( '/^(\s+)/', $content, $prefix_match );
$prefix = empty( $prefix_match ) ? '' : $prefix_match[0];

$content = $prefix . wpautop( $content, false );

// To normalize as text where wpautop would not be applied, restore
// double newline to wpautop'd text if not at the end of content.
$is_last_block = ( count( $blocks ) === $i + 1 );
if ( ! $is_last_block ) {
$content = str_replace( "</p>\n", "</p>\n\n", $content );
}

$blocks[ $i ]['innerHTML'] = $content;
}

return gutenberg_serialize_blocks( $blocks );
}

/**
* Filters saved post data to apply wpautop to freeform block content.
*
* @since 1.7.0
*
* @param array $data An array of slashed post data.
* @return array An array of post data with wpautop applied to freeform
* block content.
*/
function gutenberg_wpautop_insert_post_data( $data ) {
if ( ! empty( $data['post_content'] ) && gutenberg_content_has_blocks( $data['post_content'] ) ) {
$data['post_content'] = gutenberg_wpautop_block_content( $data['post_content'] );
}

return $data;
}
add_filter( 'wp_insert_post_data', 'gutenberg_wpautop_insert_post_data' );
7 changes: 7 additions & 0 deletions lib/client-assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) {
}

// Set initial title to empty string for auto draft for duration of edit.
// Otherwise, title defaults to and displays as "Auto Draft".
$is_new_post = 'auto-draft' === $post_to_edit['status'];
if ( $is_new_post ) {
$post_to_edit['title'] = array(
Expand All @@ -690,6 +691,12 @@ function gutenberg_editor_scripts_and_styles( $hook ) {
);
}

// Set initial content to apply autop on unknown blocks, preserving this
// behavior for classic content while otherwise disabling for blocks.
if ( ! $is_new_post && is_array( $post_to_edit['content'] ) ) {
$post_to_edit['content']['raw'] = gutenberg_wpautop_block_content( $post_to_edit['content']['raw'] );
}

// Preload common data.
$preload_paths = array(
'/wp/v2/users/me?context=edit',
Expand Down
17 changes: 17 additions & 0 deletions lib/compat.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,20 @@ function gutenberg_add_rest_nonce_to_heartbeat_response_headers( $response ) {
}

add_filter( 'wp_refresh_nonces', 'gutenberg_add_rest_nonce_to_heartbeat_response_headers' );

/**
* As a substitute for the default content `wpautop` filter, applies autop
* behavior only for posts where content does not contain blocks.
*
* @param string $content Post content.
* @return string Paragraph-converted text if non-block content.
*/
function gutenberg_wpautop( $content ) {
if ( gutenberg_content_has_blocks( $content ) ) {
return $content;
}

return wpautop( $content );
}
remove_filter( 'the_content', 'wpautop' );
add_filter( 'the_content', 'gutenberg_wpautop' );
2 changes: 1 addition & 1 deletion lib/plugin-compat.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
* @return array $post Post object.
*/
function gutenberg_remove_wpcom_markdown_support( $post ) {
if ( content_has_blocks( $post->post_content ) ) {
if ( gutenberg_content_has_blocks( $post->post_content ) ) {
remove_post_type_support( 'post', 'wpcom-markdown' );
}
return $post;
Expand Down
17 changes: 13 additions & 4 deletions lib/register.php
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,12 @@ function gutenberg_can_edit_post_type( $post_type ) {
}

/**
* Determine whether a post has blocks.
* Determine whether a post has blocks. This test optimizes for performance
* rather than strict accuracy, detecting the pattern of a block but not
* validating its structure. For strict accuracy, you should use the block
* parser on post content.
*
* @see gutenberg_parse_blocks()
*
* @since 0.5.0
*
Expand All @@ -317,18 +322,22 @@ function gutenberg_can_edit_post_type( $post_type ) {
*/
function gutenberg_post_has_blocks( $post ) {
$post = get_post( $post );
return $post && content_has_blocks( $post->post_content );
return $post && gutenberg_content_has_blocks( $post->post_content );
}

/**
* Determine whether a content string contains gutenberg blocks.
* Determine whether a content string contains blocks. This test optimizes for
* performance rather than strict accuracy, detecting the pattern of a block
* but not validating its structure. For strict accuracy, you should use the
* block parser on post content.
*
* @since 1.6.0
* @see gutenberg_parse_blocks()
*
* @param string $content Content to test.
* @return bool Whether the content contains blocks.
*/
function content_has_blocks( $content ) {
function gutenberg_content_has_blocks( $content ) {
return false !== strpos( $content, '<!-- wp:' );
}

Expand Down
13 changes: 13 additions & 0 deletions phpunit/class-admin-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,19 @@ function test_gutenberg_post_has_blocks() {
$this->assertFalse( gutenberg_post_has_blocks( self::$post_without_blocks ) );
}

/**
* Tests gutenberg_content_has_blocks().
*
* @covers gutenberg_content_has_blocks
*/
function test_gutenberg_content_has_blocks() {
$content_with_blocks = get_post_field( 'post_content', self::$post_with_blocks );
$content_without_blocks = get_post_field( 'post_content', self::$post_without_blocks );

$this->assertTrue( gutenberg_content_has_blocks( $content_with_blocks ) );
$this->assertFalse( gutenberg_content_has_blocks( $content_without_blocks ) );
}

/**
* Tests gutenberg_add_gutenberg_post_state().
*
Expand Down
75 changes: 75 additions & 0 deletions phpunit/class-serializing-test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php
/**
* Block serializer (PHP code target) tests
*
* @package Gutenberg
*/

class Serializing_Test extends WP_UnitTestCase {
protected static $fixtures_dir;

function serializing_test_filenames() {
self::$fixtures_dir = dirname( dirname( __FILE__ ) ) . '/blocks/test/fixtures';

require_once dirname( dirname( __FILE__ ) ) . '/lib/parser.php';

$fixture_filenames = glob( self::$fixtures_dir . '/*.{json,html}', GLOB_BRACE );
$fixture_filenames = array_values( array_unique( array_map(
array( $this, 'clean_fixture_filename' ),
$fixture_filenames
) ) );

return array_map(
array( $this, 'pass_serializer_fixture_filenames' ),
$fixture_filenames
);
}

function clean_fixture_filename( $filename ) {
$filename = basename( $filename );
$filename = preg_replace( '/\..+$/', '', $filename );
return $filename;
}

function pass_serializer_fixture_filenames( $filename ) {
return array(
"$filename.html",
"$filename.parsed.json",
);
}

/**
* @dataProvider serializing_test_filenames
*/
function test_serializer_output( $html_filename, $parsed_json_filename ) {
$html_path = self::$fixtures_dir . '/' . $html_filename;
$parsed_json_path = self::$fixtures_dir . '/' . $parsed_json_filename;

foreach ( array( $html_path, $parsed_json_path ) as $filename ) {
if ( ! file_exists( $filename ) ) {
throw new Exception( "Missing fixture file: '$filename'" );
}
}

$parsed = json_decode( file_get_contents( $parsed_json_path ), true );
$expected_html = file_get_contents( $html_path );

$result = gutenberg_serialize_blocks( $parsed );

$this->assertEquals(
$expected_html,
$result,
"File '$html_filename' does not match expected value"
);
}

function test_serializer_escape() {
$original_html = '<!-- wp:core/test-block {"stuff":"left \\u0026 right \\u002d\\u002d but \\u003cnot\\u003e"} -->\n<p class="wp-block-test-block">Ribs & Chicken</p>\n<!-- /wp:core/test-block -->';
$parsed_block = gutenberg_parse_blocks( $original_html );

$serialized = gutenberg_serialize_blocks( $parsed_block );

$this->assertEquals( 'left & right -- but <not>', $parsed_block[0]['attrs']['stuff'] );
$this->assertEquals( $original_html, $serialized );
}
}
31 changes: 31 additions & 0 deletions phpunit/class-wpautop-test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php
/**
* Automatic paragraph applicatino tests
*
* @package Gutenberg
*/

class WPAutoP_Test extends WP_UnitTestCase {
function test_gutenberg_wpautop_block_content() {
$original_html = file_get_contents( dirname( __FILE__ ) . '/fixtures/wpautop-original.html' );
$expected_html = file_get_contents( dirname( __FILE__ ) . '/fixtures/wpautop-expected.html' );

$actual_html = gutenberg_wpautop_block_content( $original_html );

$this->assertEquals( $expected_html, $actual_html );
}

function test_gutenberg_wpautop_insert_post_data() {
$original_html = file_get_contents( dirname( __FILE__ ) . '/fixtures/wpautop-original.html' );
$expected_html = file_get_contents( dirname( __FILE__ ) . '/fixtures/wpautop-expected.html' );

$post_id = self::factory()->post->create( array(
'post_title' => 'Example',
'post_content' => $original_html,
) );

$post = get_post( $post_id );

$this->assertEquals( $expected_html, $post->post_content );
}
}
Loading