diff --git a/docs/manifest.json b/docs/manifest.json index b110c5937ffb9..76346b974df7f 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -77,6 +77,12 @@ "markdown_source": "https:\/\/raw.githubusercontent.com\/WordPress\/gutenberg\/master\/docs\/themes.md", "parent": "reference" }, + { + "title": "Meta Boxes", + "slug": "meta-box", + "markdown_source": "https:\/\/raw.githubusercontent.com\/WordPress\/gutenberg\/master\/docs\/meta-box.md", + "parent": "reference" + }, { "title": "Glossary", "slug": "glossary", diff --git a/docs/meta-box.md b/docs/meta-box.md index f48dc84dab034..f957461f26432 100644 --- a/docs/meta-box.md +++ b/docs/meta-box.md @@ -5,6 +5,38 @@ the superior developer and user experience of blocks however, especially once, block templates are available, **converting PHP meta boxes to blocks is highly encouraged!** +### Testing, Converting, and Maintaining Existing Meta Boxes + +Before converting meta boxes to blocks, it may be easier to test if a meta box works with Gutenberg, and explicitly mark it as such. + +If a meta box *doesn't* work with in Gutenberg, and updating it to work correctly is not an option, the next step is to add the `__block_editor_compatible_meta_box` argument to the meta box declaration: + +```php +add_meta_box( 'my-meta-box', 'My Meta Box', 'my_meta_box_callback', + null, 'normal', 'high', + array( + '__block_editor_compatible_meta_box' => false, + ) +); +``` + +This will cause WordPress to fall back to the Classic editor, where the meta box will continue working as before. + +Explicitly setting `__block_editor_compatible_meta_box` to `true` will cause WordPress to stay in Gutenberg (assuming another meta box doesn't cause a fallback, of course). + +After a meta box is converted to a block, it can be declared as existing for backwards compatibility: + +```php +add_meta_box( 'my-meta-box', 'My Meta Box', 'my_meta_box_callback', + null, 'normal', 'high', + array( + '__back_compat_meta_box' => false, + ) +); +``` + +When Gutenberg is run, this meta box will no longer be displayed in the meta box area, as it now only exists for backwards compatibility purposes. It will continue to be displayed correctly in the Classic editor, should some other meta box cause a fallback. + ### Meta Box Data Collection On each Gutenberg page load, the global state of post.php is mimicked, this is @@ -22,7 +54,9 @@ namely `add_meta_boxes`, `add_meta_boxes_{$post->post_type}`, and `do_meta_boxes A copy of the global `$wp_meta_boxes` is made then filtered through `apply_filters( 'filter_gutenberg_meta_boxes', $_meta_boxes_copy );`, which will -strip out any core meta boxes along with standard custom taxonomy meta boxes. +strip out any core meta boxes, standard custom taxonomy meta boxes, and any meta +boxes that have declared themselves as only existing for backwards compatibility +purposes. Then each location for this particular type of meta box is checked for whether it is active. If it is not empty a value of true is stored, if it is empty a value @@ -36,7 +70,7 @@ have to do, unless we want to move `createEditorInstance()` to fire in the foote or at some point after `admin_head`. With recent changes to editor bootstrapping this might now be possible. Test with ACF to make sure. -### Redux and React Meta Box Management. +### Redux and React Meta Box Management *The Redux store by default will hold all meta boxes as inactive*. When `INITIALIZE_META_BOX_STATE` comes in, the store will update any active meta box diff --git a/lib/meta-box-partial-page.php b/lib/meta-box-partial-page.php index ce27ef205e6fd..fcc96ba9a7577 100644 --- a/lib/meta-box-partial-page.php +++ b/lib/meta-box-partial-page.php @@ -515,6 +515,10 @@ function gutenberg_filter_meta_boxes( $meta_boxes ) { if ( isset( $data['callback'] ) && in_array( $data['callback'], $taxonomy_callbacks_to_unset ) ) { unset( $meta_boxes[ $page ][ $context ][ $priority ][ $name ] ); } + // Filter out meta boxes that are just registered for back compat. + if ( isset( $data['args']['__back_compat_meta_box'] ) && $data['args']['__back_compat_meta_box'] ) { + unset( $meta_boxes[ $page ][ $context ][ $priority ][ $name ] ); + } } } } @@ -550,3 +554,115 @@ function gutenberg_is_meta_box_empty( $meta_boxes, $context, $post_type ) { } add_filter( 'filter_gutenberg_meta_boxes', 'gutenberg_filter_meta_boxes' ); + +/** + * Go through the global metaboxes, and override the render callback, so we can trigger our warning if needed. + * + * @since 1.8.0 + */ +function gutenberg_intercept_meta_box_render() { + global $wp_meta_boxes; + + foreach ( $wp_meta_boxes as $post_type => $contexts ) { + foreach ( $contexts as $context => $priorities ) { + foreach ( $priorities as $priority => $boxes ) { + foreach ( $boxes as $id => $box ) { + if ( ! is_array( $wp_meta_boxes[ $post_type ][ $context ][ $priority ][ $id ]['args'] ) ) { + $wp_meta_boxes[ $post_type ][ $context ][ $priority ][ $id ]['args'] = array(); + } + if ( ! isset( $wp_meta_boxes[ $post_type ][ $context ][ $priority ][ $id ]['args']['__original_callback'] ) ) { + $wp_meta_boxes[ $post_type ][ $context ][ $priority ][ $id ]['args']['__original_callback'] = $box['callback']; + $wp_meta_boxes[ $post_type ][ $context ][ $priority ][ $id ]['callback'] = 'gutenberg_override_meta_box_callback'; + } + } + } + } + } +} +add_action( 'submitpost_box', 'gutenberg_intercept_meta_box_render' ); +add_action( 'submitpage_box', 'gutenberg_intercept_meta_box_render' ); +add_action( 'edit_page_form', 'gutenberg_intercept_meta_box_render' ); +add_action( 'edit_form_advanced', 'gutenberg_intercept_meta_box_render' ); + +/** + * Check if this metabox only exists for back compat purposes, show a warning if it doesn't. + * + * @since 1.8.0 + * + * @param mixed $object The object being operated on, on this screen. + * @param array $box The current meta box definition. + */ +function gutenberg_override_meta_box_callback( $object, $box ) { + $callback = $box['args']['__original_callback']; + unset( $box['args']['__original_callback'] ); + + $block_compatible = true; + if ( isset( $box['args']['__block_editor_compatible_meta_box'] ) ) { + $block_compatible = (bool) $box['args']['__block_editor_compatible_meta_box']; + unset( $box['args']['__block_editor_compatible_meta_box'] ); + } + + if ( isset( $box['args']['__back_compat_meta_box'] ) ) { + $block_compatible |= (bool) $box['args']['__back_compat_meta_box']; + unset( $box['args']['__back_compat_meta_box'] ); + } + + if ( ! $block_compatible ) { + gutenberg_show_meta_box_warning( $callback ); + } + + call_user_func( $callback, $object, $box ); +} + +/** + * Display a warning in the metabox that the current plugin is causing the fallback to the old editor. + * + * @since 1.8.0 + * + * @param callable $callback The function that a plugin has defined to render a meta box. + */ +function gutenberg_show_meta_box_warning( $callback ) { + // Only show the warning when WP_DEBUG is enabled. + if ( ! WP_DEBUG ) { + return; + } + + // Don't show in the Gutenberg meta box UI. + if ( ! isset( $_REQUEST['classic-editor'] ) ) { + return; + } + + if ( is_array( $callback ) ) { + $reflection = new ReflectionMethod( $callback[0], $callback[1] ); + } else { + $reflection = new ReflectionFunction( $callback ); + } + + if ( $reflection->isInternal() ) { + return; + } + + $filename = $reflection->getFileName(); + if ( strpos( $filename, WP_PLUGIN_DIR ) !== 0 ) { + return; + } + + $filename = str_replace( WP_PLUGIN_DIR, '', $filename ); + $filename = preg_replace( '|^/([^/]*/).*$|', '\\1', $filename ); + + $plugins = get_plugins(); + foreach ( $plugins as $name => $plugin ) { + if ( strpos( $name, $filename ) === 0 ) { + ?> +
+

+ +

+
+ post_type ][ $location ] ) && gutenberg_is_meta_box_empty( $_meta_boxes_copy, $location, $post->post_type ) ) { + if ( gutenberg_is_meta_box_empty( $_meta_boxes_copy, $location, $post->post_type ) ) { $meta_box_data[ $location ] = false; } else { $meta_box_data[ $location ] = true; + $incompatible_meta_box = false; + // Check if we have a meta box that has declared itself incompatible with the block editor. + foreach ( $_meta_boxes_copy[ $post->post_type ][ $location ] as $boxes ) { + foreach ( $boxes as $box ) { + /* + * If __block_editor_compatible_meta_box is declared as a false-y value, + * the meta box is not compatible with the block editor. + */ + if ( is_array( $box['args'] ) + && isset( $box['args']['__block_editor_compatible_meta_box'] ) + && ! $box['args']['__block_editor_compatible_meta_box'] ) { + $incompatible_meta_box = true; + break 2; + } + } + } + + // Incompatible meta boxes require an immediate redirect to the classic editor. + if ( $incompatible_meta_box ) { + ?> + + assertEquals( $expected, $actual ); } + /** + * Test filtering back compat meta boxes + */ + public function test_gutenberg_filter_back_compat_meta_boxes() { + $meta_boxes = $this->meta_boxes; + + // Add in a back compat meta box. + $meta_boxes['post']['normal']['high']['some-meta-box'] = array( + 'id' => 'some-meta-box', + 'title' => 'Some Meta Box', + 'callback' => 'some_meta_box', + 'args' => array( + '__back_compat_meta_box' => true, + ), + ); + + // Add in a normal meta box. + $meta_boxes['post']['normal']['high']['some-other-meta-box'] = array( 'other-meta-box-stuff' ); + + $expected_meta_boxes = $this->meta_boxes; + // We expect to remove only core meta boxes. + $expected_meta_boxes['post']['normal']['core'] = array(); + $expected_meta_boxes['post']['side']['core'] = array(); + $expected_meta_boxes['post']['normal']['high']['some-other-meta-box'] = array( 'other-meta-box-stuff' ); + + $actual = gutenberg_filter_meta_boxes( $meta_boxes ); + $expected = $expected_meta_boxes; + + $this->assertEquals( $expected, $actual ); + } + /** * Test filtering of meta box data with taxonomy meta boxes. *