diff --git a/features/post-block.feature b/features/post-block.feature index cf2ff2301..e0b5daac2 100644 --- a/features/post-block.feature +++ b/features/post-block.feature @@ -47,6 +47,27 @@ Feature: Manage blocks in post content """ And the return code should be 1 + @less-than-wp-5.0 + Scenario: Checking for a specific block requires WordPress 5.0 + Given a WP install + When I run `wp post create --post_title="Block Post" --post_content="

Hello

" --porcelain` + Then save STDOUT as {POST_ID} + + # has-blocks goes through the bundled WP_Block_Processor polyfill, so it keeps working. + When I run `wp post has-blocks {POST_ID}` + Then STDOUT should contain: + """ + Success: Post {POST_ID} contains blocks. + """ + + # has-block relies on the has_block() function that WordPress only added in 5.0. + When I try `wp post has-block {POST_ID} core/paragraph` + Then STDERR should contain: + """ + Error: Requires WordPress 5.0 or greater. + """ + And the return code should be 1 + @require-wp-5.0 Scenario: Parse blocks in a post Given a WP install diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 1ebe3bd32..05ea1c2eb 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -19,3 +19,95 @@ parameters: - identifier: missingType.property - identifier: missingType.parameter - identifier: missingType.return + + + # johnbillion/wp-compat checks every WordPress symbol against the WordPress 4.9 + # baseline that wp-cli-tests configures. It only recognizes function_exists() and + # method_exists() guards, so it cannot see the `before_invoke` version checks in + # entity-command.php or the polyfills in src/Compat/, and reports those as errors. + # + # Every entry below matches on the message as well as the identifier, so that a call + # to something introduced later than the version a command is gated on still gets + # reported rather than swallowed by a file-wide ignore. + + # `WP_Block_Processor` and its dependencies are polyfilled in src/Compat/ and loaded + # on demand through `BlockProcessorLoader::load()`, so they are available on every + # supported WordPress version, not just 6.9+. + - + identifier: WPCompat.methodNotAvailable + message: '#^WP_Block_Processor::(allocate_and_return_parsed_attributes|extract_full_block_and_advance|get_block_type|get_delimiter_type|get_depth|get_span|next_block)\(\) is only available since WordPress version 6\.9\.0\.$#' + path: src/Block_Processor_Helper.php + + # `wp font collection|face|family` aborts on WordPress < 6.5 via `before_invoke`. + - + identifier: WPCompat.methodNotAvailable + message: '#^(WP_Font_Collection::get_data|WP_Font_Library::(get_font_collection|get_font_collections|get_instance))\(\) is only available since WordPress version 6\.5\.0\.$#' + paths: + - src/Font_Collection_Command.php + - src/Font_Family_Command.php + + # `wp user application-password` aborts on WordPress < 5.6 via `before_invoke`. + - + identifier: WPCompat.methodNotAvailable + message: '#^WP_Application_Passwords::(create_new_application_password|delete_all_application_passwords|delete_application_password|get_user_application_password|get_user_application_passwords|record_application_password_usage|update_application_password)\(\) is only available since WordPress version 5\.6\.0\.$#' + path: src/User_Application_Password_Command.php + + # The single WordPress 5.7 method is additionally behind a `wp_version_compare()` + # check in `application_name_exists_for_user()`, which reimplements it for 5.6. + - + identifier: WPCompat.methodNotAvailable + message: '#^WP_Application_Passwords::application_name_exists_for_user\(\) is only available since WordPress version 5\.7\.0\.$#' + path: src/User_Application_Password_Command.php + + # `wp user privacy-request` aborts on WordPress < 4.9.6 via `before_invoke`. + - + identifier: WPCompat.functionNotAvailable + message: '#^(_wp_privacy_completed_request|wp_create_user_request|wp_get_user_request_data|wp_privacy_exports_dir|wp_privacy_generate_personal_data_export_file|wp_send_user_request)\(\) is only available since WordPress version 4\.9\.6\.$#' + path: src/User_Privacy_Request_Command.php + + # `wp site meta` aborts via `before_invoke` unless `is_site_meta_supported()` + # exists, which is the WordPress 5.1 function that introduced site meta support + # along with the `*_site_meta()` functions used here. + - + identifier: WPCompat.functionNotAvailable + message: '#^(add_site_meta|delete_site_meta|get_site_meta|update_site_meta)\(\) is only available since WordPress version 5\.1\.0\.$#' + path: src/Site_Meta_Command.php + + # `wp post block` aborts on WordPress < 5.0 via `before_invoke`. `serialize_blocks()` + # is WordPress 5.3.1+ and is guarded separately in the mutating subcommands, so it + # must keep being reported if that guard ever goes away. + - + identifier: WPCompat.functionNotAvailable + message: '#^(has_blocks|parse_blocks|render_block)\(\) is only available since WordPress version 5\.0\.0\.$#' + path: src/Post_Block_Command.php + + # Only the uppercase `'ID'` alias for the `$field` parameter of `get_term_by()` + # is WordPress 5.5+. These call sites pass `'id'`, `'term_id'` or `'slug'`, all + # supported since WordPress 2.3. + - + identifier: WPCompat.parameterNotAvailable.gettermby.field + paths: + - src/Menu_Item_Command.php + - src/Term_Command.php + - src/WP_CLI/CommandWithTerms.php + + # WordPress 6.0 only formalized the already documented `...$args` parameter of + # `apply_filters()` by adding it to the function signature. Additional arguments + # were collected through `func_get_args()` before that. + - + identifier: WPCompat.parameterNotAvailable.applyfilters.args + path: src/Post_Block_Command.php + + # Same for `wpdb::prepare()`, where WordPress 5.3 formalized the already + # documented `...$args` parameter. Passing a single array of values is even + # handled explicitly by `wpdb::prepare()` itself. + - + identifier: WPCompat.parameterNotAvailable.wpdbprepare.args + path: src/Site_Command.php + + # The `$force_cache` parameter of `wp_load_alloptions()` is WordPress 5.3.1+, but + # older versions simply ignore the extra argument. The cache refresh in + # `wp option set-autoload` degrades to a no-op there instead of failing. + - + identifier: WPCompat.parameterNotAvailable.wploadalloptions.forcecache + path: src/Option_Command.php diff --git a/src/Post_Block_Command.php b/src/Post_Block_Command.php index ebca6e454..4607e66d1 100644 --- a/src/Post_Block_Command.php +++ b/src/Post_Block_Command.php @@ -224,6 +224,10 @@ public function get( $args, $assoc_args ) { * @subcommand update */ public function update( $args, $assoc_args ) { + if ( ! function_exists( 'serialize_blocks' ) ) { + WP_CLI::error( 'Requires WordPress 5.3.1 or greater.' ); + } + $post = $this->fetcher->get_check( $args[0] ); $index = (int) $args[1]; $blocks = parse_blocks( $post->post_content ); @@ -340,6 +344,10 @@ public function update( $args, $assoc_args ) { * @subcommand move */ public function move( $args, $assoc_args ) { + if ( ! function_exists( 'serialize_blocks' ) ) { + WP_CLI::error( 'Requires WordPress 5.3.1 or greater.' ); + } + $post = $this->fetcher->get_check( $args[0] ); $from_index = (int) $args[1]; $to_index = (int) $args[2]; @@ -607,6 +615,10 @@ function ( $block ) { * @subcommand import */ public function import( $args, $assoc_args ) { + if ( ! function_exists( 'serialize_blocks' ) ) { + WP_CLI::error( 'Requires WordPress 5.3.1 or greater.' ); + } + $post = $this->fetcher->get_check( $args[0] ); $file = Utils\get_flag_value( $assoc_args, 'file', null ); $position = Utils\get_flag_value( $assoc_args, 'position', 'end' ); @@ -914,6 +926,10 @@ public function count( $args, $assoc_args ) { * @subcommand clone */ public function clone_block( $args, $assoc_args ) { + if ( ! function_exists( 'serialize_blocks' ) ) { + WP_CLI::error( 'Requires WordPress 5.3.1 or greater.' ); + } + $post = $this->fetcher->get_check( $args[0] ); $source_index = (int) $args[1]; $position = Utils\get_flag_value( $assoc_args, 'position', 'after' ); @@ -1393,6 +1409,10 @@ public function list_( $args, $assoc_args ) { * @subcommand insert */ public function insert( $args, $assoc_args ) { + if ( ! function_exists( 'serialize_blocks' ) ) { + WP_CLI::error( 'Requires WordPress 5.3.1 or greater.' ); + } + $post = $this->fetcher->get_check( $args[0] ); $block_name = $args[1]; $content = Utils\get_flag_value( $assoc_args, 'content', '' ); @@ -1492,6 +1512,10 @@ public function insert( $args, $assoc_args ) { * @subcommand remove */ public function remove( $args, $assoc_args ) { + if ( ! function_exists( 'serialize_blocks' ) ) { + WP_CLI::error( 'Requires WordPress 5.3.1 or greater.' ); + } + $post = $this->fetcher->get_check( $args[0] ); $block_name = isset( $args[1] ) ? $args[1] : null; $indices = Utils\get_flag_value( $assoc_args, 'index', null ); @@ -1637,6 +1661,10 @@ function ( $idx ) use ( $index_map ) { * @subcommand replace */ public function replace( $args, $assoc_args ) { + if ( ! function_exists( 'serialize_blocks' ) ) { + WP_CLI::error( 'Requires WordPress 5.3.1 or greater.' ); + } + $post = $this->fetcher->get_check( $args[0] ); $old_block_name = $args[1]; $new_block_name = $args[2]; diff --git a/src/Post_Command.php b/src/Post_Command.php index 1880f24b5..540883cae 100644 --- a/src/Post_Command.php +++ b/src/Post_Command.php @@ -1430,6 +1430,10 @@ public function has_blocks( $args, $assoc_args ) { * @subcommand has-block */ public function has_block( $args, $assoc_args ) { + if ( ! function_exists( 'has_block' ) ) { + WP_CLI::error( 'Requires WordPress 5.0 or greater.' ); + } + $post = $this->fetcher->get_check( $args[0] ); $block_name = $args[1];