Skip to content
Draft
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
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4696,6 +4696,62 @@ or return code 1 if it does not.



### wp post convert-to-blocks

Converts the classic (non-block) content of one or more posts to block markup.

~~~
wp post convert-to-blocks <id>... [--dry-run]
~~~

Uses the server-side block conversion provided by the Gutenberg plugin
(see https://github.com/WordPress/gutenberg/pull/82013) to turn classic
HTML content into serialized block markup. Markup that no block claims
is kept verbatim inside a Custom HTML block, so nothing is lost. Posts
that already contain blocks or have no content are skipped.

The conversion does not sanitize the markup. Saving the converted content
applies the usual kses filtering for the current user context, so a run
without `--user` is filtered as an untrusted author would be. Run the
command with `--user=<administrator>` to keep markup that requires the
`unfiltered_html` capability, such as iframes or scripts.

Requires a Gutenberg build that provides `gutenberg_html_to_block_markup()`.

**OPTIONS**

<id>...
One or more IDs of posts to convert.

[--dry-run]
Preview which posts would be converted, without saving any changes.

**EXAMPLES**

# Convert a single post.
$ wp post convert-to-blocks 123
Converted post 123.
Success: Converted 1 of 1 posts.

# Convert every post of a post type.
$ wp post list --post_type=post --format=ids | xargs wp post convert-to-blocks
Converted post 123.
Converted post 124.
Warning: Post 125 already contains blocks.
Success: Converted 2 of 3 posts (1 skipped).

# Preview a conversion without saving.
$ wp post convert-to-blocks 123 --dry-run
Would convert post 123.
Success: Would convert 1 of 1 posts.

# Run as an administrator to keep markup that requires unfiltered_html.
$ wp post convert-to-blocks 123 --user=admin
Converted post 123.
Success: Converted 1 of 1 posts.



### wp post block

Manages blocks within post content.
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@
"post url-to-id",
"post has-blocks",
"post has-block",
"post convert-to-blocks",
"post block",
"post block clone",
"post block count",
Expand Down
132 changes: 132 additions & 0 deletions features/post-convert-to-blocks.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
Feature: Convert classic post content to blocks

# The conversion depends on the server-side block conversion from
# https://github.com/WordPress/gutenberg/pull/82013 (branch
# `try/13163-php-block-conversion`), which no released Gutenberg build
# provides yet. The scenarios tagged @broken below need a Gutenberg build
# of that PR. To run them locally, build the plugin ZIP from that branch,
# remove the @broken tags, and add the following step after the
# `Given a WP install` step in each scenario:
#
# And I run `wp plugin install /path/to/gutenberg.zip --activate`

Scenario: Conversion requires the Gutenberg build with server-side block conversion
Given a WP install

When I try `wp post convert-to-blocks 1`
Then STDERR should be:
"""
Error: Server-side block conversion is not available. Activate the Gutenberg plugin from https://github.com/WordPress/gutenberg/pull/82013.
"""
And STDOUT should be empty
And the return code should be 1

@broken
Scenario: Convert a classic post to blocks
Given a WP install

When I run `wp post create --post_title='Classic post' --post_content='<h2>Title</h2><p>Text</p>' --porcelain`
Then save STDOUT as {POST_ID}

When I run `wp post convert-to-blocks {POST_ID}`
Then STDOUT should be:
"""
Converted post {POST_ID}.
Success: Converted 1 of 1 posts.
"""
And STDERR should be empty

When I run `wp post get {POST_ID} --field=post_content`
Then STDOUT should contain:
"""
<!-- wp:heading -->
"""
And STDOUT should contain:
"""
<!-- wp:paragraph -->
"""

When I run `wp post has-blocks {POST_ID}`
Then STDOUT should be:
"""
Success: Post {POST_ID} contains blocks.
"""

@broken
Scenario: Preview a conversion with --dry-run
Given a WP install

When I run `wp post create --post_title='Classic post' --post_content='<h2>Title</h2><p>Text</p>' --porcelain`
Then save STDOUT as {POST_ID}

When I run `wp post convert-to-blocks {POST_ID} --dry-run`
Then STDOUT should be:
"""
Would convert post {POST_ID}.
Success: Would convert 1 of 1 posts.
"""
And STDERR should be empty

When I run `wp post get {POST_ID} --field=post_content`
Then STDOUT should be:
"""
<h2>Title</h2><p>Text</p>
"""

@broken
Scenario: Skip posts that already contain blocks
Given a WP install

When I run `wp post create --post_title='Block post' --post_content='<!-- wp:paragraph --><p>Text</p><!-- /wp:paragraph -->' --porcelain`
Then save STDOUT as {POST_ID}

When I try `wp post convert-to-blocks {POST_ID}`
Then STDERR should be:
"""
Warning: Post {POST_ID} already contains blocks.
"""
And STDOUT should be:
"""
Success: Converted 0 of 1 posts (1 skipped).
"""
And the return code should be 0

@broken
Scenario: Skip posts without content
Given a WP install

When I run `wp post create --post_title='Empty post' --post_content='' --porcelain`
Then save STDOUT as {POST_ID}

When I try `wp post convert-to-blocks {POST_ID}`
Then STDERR should be:
"""
Warning: Post {POST_ID} has no content to convert.
"""
And STDOUT should be:
"""
Success: Converted 0 of 1 posts (1 skipped).
"""
And the return code should be 0

@broken
Scenario: Report missing posts as failures
Given a WP install

When I run `wp post create --post_title='Classic post' --post_content='<p>Text</p>' --porcelain`
Then save STDOUT as {POST_ID}

When I try `wp post convert-to-blocks {POST_ID} 99999`
Then STDERR should contain:
"""
Warning: Could not find the post with ID 99999.
"""
And STDERR should contain:
"""
Error: Only converted 1 of 2 posts (1 failed).
"""
And STDOUT should be:
"""
Converted post {POST_ID}.
"""
And the return code should be 1
128 changes: 128 additions & 0 deletions src/Post_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -1443,6 +1443,134 @@ public function has_block( $args, $assoc_args ) {
}
}

/**
* Converts the classic (non-block) content of one or more posts to block markup.
*
* Uses the server-side block conversion provided by the Gutenberg plugin
* (see https://github.com/WordPress/gutenberg/pull/82013) to turn classic
* HTML content into serialized block markup. Markup that no block claims
* is kept verbatim inside a Custom HTML block, so nothing is lost. Posts
* that already contain blocks or have no content are skipped.
*
* The conversion does not sanitize the markup. Saving the converted content
* applies the usual kses filtering for the current user context, so a run
* without `--user` is filtered as an untrusted author would be. Run the
* command with `--user=<administrator>` to keep markup that requires the
* `unfiltered_html` capability, such as iframes or scripts.
*
* Requires a Gutenberg build that provides `gutenberg_html_to_block_markup()`.
*
* ## OPTIONS
*
* <id>...
* : One or more IDs of posts to convert.
*
* [--dry-run]
* : Preview which posts would be converted, without saving any changes.
*
* ## EXAMPLES
*
* # Convert a single post.
* $ wp post convert-to-blocks 123
* Converted post 123.
* Success: Converted 1 of 1 posts.
*
* # Convert every post of a post type.
* $ wp post list --post_type=post --format=ids | xargs wp post convert-to-blocks
* Converted post 123.
* Converted post 124.
* Warning: Post 125 already contains blocks.
* Success: Converted 2 of 3 posts (1 skipped).
*
* # Preview a conversion without saving.
* $ wp post convert-to-blocks 123 --dry-run
* Would convert post 123.
* Success: Would convert 1 of 1 posts.
*
* # Run as an administrator to keep markup that requires unfiltered_html.
* $ wp post convert-to-blocks 123 --user=admin
* Converted post 123.
* Success: Converted 1 of 1 posts.
*
* @subcommand convert-to-blocks
*/
public function convert_to_blocks( $args, $assoc_args ) {
if ( ! function_exists( 'gutenberg_html_to_block_markup' ) ) {
WP_CLI::error( 'Server-side block conversion is not available. Activate the Gutenberg plugin from https://github.com/WordPress/gutenberg/pull/82013.' );
}

$dry_run = (bool) Utils\get_flag_value( $assoc_args, 'dry-run', false );

$total = count( $args );
$successes = 0;
$errors = 0;
$skips = 0;

foreach ( $args as $post_id ) {
$post = $this->fetcher->get( $post_id );

if ( ! $post ) {
WP_CLI::warning( "Could not find the post with ID {$post_id}." );
++$errors;
continue;
}

if ( '' === trim( (string) $post->post_content ) ) {
WP_CLI::warning( "Post {$post->ID} has no content to convert." );
++$skips;
continue;
}

if ( Block_Processor_Helper::has_blocks( $post->post_content ) ) {
WP_CLI::warning( "Post {$post->ID} already contains blocks." );
++$skips;
continue;
}

$markup = gutenberg_html_to_block_markup( $post->post_content );

if ( $dry_run ) {
WP_CLI::log( "Would convert post {$post->ID}." );
++$successes;
continue;
}

$result = wp_update_post(
[
'ID' => $post->ID,
'post_content' => $markup,
],
true
);

if ( is_wp_error( $result ) ) {
WP_CLI::warning( "Failed converting post {$post->ID}: " . $result->get_error_message() );
++$errors;
continue;
}

WP_CLI::log( "Converted post {$post->ID}." );
++$successes;
}

if ( ! $dry_run ) {
Utils\report_batch_operation_results( 'post', 'convert', $total, $successes, $errors, $skips );
return;
}

$skipped_message = $skips ? " ({$skips} skipped)" : '';

if ( $errors ) {
$failed_skipped_message = " ({$errors} failed" . ( $skips ? ", {$skips} skipped" : '' ) . ')';
if ( $successes ) {
WP_CLI::error( "Would only convert {$successes} of {$total} posts{$failed_skipped_message}." );
}
WP_CLI::error( "No posts would be converted{$failed_skipped_message}." );
}

WP_CLI::success( "Would convert {$successes} of {$total} posts{$skipped_message}." );
}

/**
* Convert a date-time string with a hyphen separator to a space separator.
*
Expand Down