Migrate post type meta fields to block editor sidebar with legacy meta box fallback#21
Migrate post type meta fields to block editor sidebar with legacy meta box fallback#21Copilot wants to merge 6 commits into
Conversation
…a box fallback Co-authored-by: mattyza <880803+mattyza@users.noreply.github.com> Agent-Logs-Url: https://github.com/mattyza/starter-plugin/sessions/91b47540-229e-43c5-a052-874979feeb05
There was a problem hiding this comment.
Pull request overview
Adds dual-mode support for post type meta fields: block editor sidebar panels (Gutenberg) backed by REST-registered post meta, with the existing classic-editor meta box path preserved as a fallback.
Changes:
- Register custom field meta via
register_post_meta()(REST-enabled) and skip legacy meta box setup when the block editor is active. - Enqueue a new block-editor script that renders
PluginDocumentSettingPanelpanels and binds values via thecore/editorstore. - Add PHPUnit tests for meta registration, section definitions/filtering, and
inithook wiring.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
classes/class-starter-plugin-post-type.php |
Registers meta for REST/block editor, conditionally disables meta box setup, and enqueues editor assets with localized field/section config. |
assets/js/meta-fields.js |
Implements sidebar panels and field controls that read/write post meta in the block editor. |
tests/test-starter-plugin.php |
Adds tests validating meta registration, sections/filter behavior, and hook setup. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| 'auth_callback' => function () { | ||
| return current_user_can( 'edit_posts' ); |
There was a problem hiding this comment.
The auth_callback for registered meta always checks current_user_can( 'edit_posts' ), which can be overly broad for custom post types with custom capabilities and also ignores per-post permission checks. Consider using the callback parameters (notably $post_id) to enforce current_user_can( 'edit_post', $post_id ), or otherwise derive the correct capability from the post type object, so REST/meta access matches actual edit permissions for this post type.
| 'auth_callback' => function () { | |
| return current_user_can( 'edit_posts' ); | |
| 'auth_callback' => function ( $allowed, $meta_key, $post_id ) { | |
| return current_user_can( 'edit_post', $post_id ); |
| * The value is the human-readable panel title shown in the block editor sidebar | ||
| * (and as the meta box title in the classic editor). |
There was a problem hiding this comment.
The docblock claims section labels are shown "as the meta box title in the classic editor", but the classic-editor path still uses a fixed title (add_meta_box(..., 'Thing Details', ...)) and doesn't use these section labels. Please update the documentation (or the classic meta box behavior) so it accurately reflects what happens.
| * The value is the human-readable panel title shown in the block editor sidebar | |
| * (and as the meta box title in the classic editor). | |
| * The value is the human-readable panel title shown in the block editor sidebar. |
| wp_enqueue_script( | ||
| $handle, | ||
| Starter_Plugin()->plugin_url . 'assets/js/meta-fields.js', | ||
| array( 'wp-plugins', 'wp-edit-post', 'wp-element', 'wp-components', 'wp-data' ), | ||
| Starter_Plugin()->version, | ||
| true | ||
| ); |
There was a problem hiding this comment.
wp_enqueue_script() builds the script URL from Starter_Plugin()->plugin_url, but plugin_url is set using plugin_dir_url(__FILE__) inside classes/class-starter-plugin.php (so it points at the /classes/ directory). This makes the resolved URL .../classes/assets/js/meta-fields.js, which will 404 and prevent the block-editor sidebar panels from loading. Use a URL rooted at the plugin base directory when enqueueing this asset (e.g., derive from the main plugin file / plugin base path) rather than the classes/ URL.
Greptile SummaryThis PR implements dual-mode meta field editing: when the block editor is active for a post type, meta fields are registered via Key changes:
Confidence Score: 3/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant WP as WordPress
participant PT as Starter_Plugin_Post_Type
participant BE as Block Editor (JS)
participant CE as Classic Editor
WP->>PT: init hook
PT->>PT: register_post_type()
PT->>PT: register_post_meta_fields()<br/>(show_in_rest: true for all fields)
alt Block Editor active for post type
WP->>PT: enqueue_block_editor_assets hook
PT->>PT: enqueue meta-fields.js<br/>+ wp_localize_script(starterPluginMetaFields)
PT->>BE: fields[], sections{}, postType
BE->>BE: Group fields by section
BE->>BE: registerPlugin with PluginDocumentSettingPanel(s)
BE-->>WP: useSelect(core/editor) → read meta
WP-->>BE: meta values
BE->>WP: editPost({meta: {...}}) on field change
WP->>WP: save_post via REST API
WP->>PT: admin_menu hook → meta_box_setup()
PT->>PT: is_block_editor_active() → true
PT-->>WP: return early (no meta box added)
else Classic Editor active for post type
WP->>PT: admin_menu hook → meta_box_setup()
PT->>PT: is_block_editor_active() → false
PT->>CE: add_meta_box() with meta_box_content
CE->>WP: POST submit
WP->>PT: save_post → meta_box_save()<br/>(nonce-verified, sanitized write)
end
Reviews (1): Last reviewed commit: "Migrate post type meta fields to block e..." | Re-trigger Greptile |
| wp_enqueue_script( | ||
| $handle, | ||
| Starter_Plugin()->plugin_url . 'assets/js/meta-fields.js', | ||
| array( 'wp-plugins', 'wp-edit-post', 'wp-element', 'wp-components', 'wp-data' ), | ||
| Starter_Plugin()->version, | ||
| true | ||
| ); |
There was a problem hiding this comment.
Missing
wp-editor script dependency
The JavaScript file accesses wp.editor.PluginDocumentSettingPanel as its WP 6.2+ fallback path, but wp-editor (the @wordpress/editor package handle) is not declared as a dependency. Without this, there is no guarantee that wp.editor will be populated before meta-fields.js executes, which means the PluginDocumentSettingPanel lookup could fail and the early-return guard on line 31 of meta-fields.js would silently bail — rendering no panels at all on WP 6.2+.
| wp_enqueue_script( | |
| $handle, | |
| Starter_Plugin()->plugin_url . 'assets/js/meta-fields.js', | |
| array( 'wp-plugins', 'wp-edit-post', 'wp-element', 'wp-components', 'wp-data' ), | |
| Starter_Plugin()->version, | |
| true | |
| ); | |
| wp_enqueue_script( | |
| $handle, | |
| Starter_Plugin()->plugin_url . 'assets/js/meta-fields.js', | |
| array( 'wp-plugins', 'wp-edit-post', 'wp-editor', 'wp-element', 'wp-components', 'wp-data' ), | |
| Starter_Plugin()->version, | |
| true | |
| ); |
| protected function is_block_editor_active() { | ||
| if ( function_exists( 'use_block_editor_for_post_type' ) ) { | ||
| return use_block_editor_for_post_type( $this->post_type ); | ||
| } | ||
|
|
||
| return false; | ||
| } |
There was a problem hiding this comment.
use_block_editor_for_post_type() misses per-post editor override
use_block_editor_for_post_type() only inspects the post-type-level default — it does not respect the per-post _classic-editor-remember flag that Classic Editor sets when a user clicks "Switch to Classic Editor" for an individual post while the post type default is still "Block Editor."
Concretely: if Classic Editor is configured with "Allow users to switch editors", use_block_editor_for_post_type('thing') returns true even when a specific post is open in classic mode. As a result, meta_box_setup() returns early, no meta box is registered, and that post has no UI at all for editing the meta fields.
The correct per-post check at admin_menu time would use use_block_editor_for_post() (which does consider the _classic-editor-remember flag), falling back to use_block_editor_for_post_type() when no post ID is available:
protected function is_block_editor_active() {
// For a specific post being edited, use the per-post check so that
// Classic Editor's "Switch to Classic Editor" per-post override is respected.
if ( function_exists( 'use_block_editor_for_post' ) ) {
$post_id = isset( $_GET['post'] ) ? absint( $_GET['post'] ) : 0;
if ( $post_id ) {
$post = get_post( $post_id );
if ( $post && $post->post_type === $this->post_type ) {
return use_block_editor_for_post( $post );
}
}
}
if ( function_exists( 'use_block_editor_for_post_type' ) ) {
return use_block_editor_for_post_type( $this->post_type );
}
return false;
}Without this, meta fields become inaccessible for any post that a user opens in classic mode when the type-level default is block editor.
| add_action( 'admin_menu', array( $this, 'meta_box_setup' ), 20 ); | ||
| add_action( 'save_post', array( $this, 'meta_box_save' ) ); | ||
| add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_assets' ) ); |
There was a problem hiding this comment.
meta_box_save still fires on every block-editor REST save
meta_box_save is unconditionally hooked to save_post in the constructor. When the block editor saves through the REST API, $_POST is empty, so the nonce check on line 286 correctly short-circuits and no data is written. However, get_post_type() is still called and the post type string is compared on every block-editor save.
Consider guarding the hook registration so it only runs when the classic editor is in use:
add_action( 'save_post', array( $this, 'meta_box_save' ) );Could be wrapped with a check, or meta_box_save could add an early return for is_block_editor_active(), to make the intent explicit and avoid the extra hook overhead.
| return select( 'core/editor' ).getEditedPostAttribute( 'meta' ) || {}; | ||
| }, [] ); | ||
|
|
||
| var editPost = useDispatch( 'core/editor' ).editPost; |
There was a problem hiding this comment.
useDispatch result used without memoisation
useDispatch('core/editor').editPost is resolved on every render. While it is safe (the returned action creator is stable across renders in Gutenberg), the property access pattern hides the fact that the hook is being called without destructuring. A more idiomatic and readable approach that also protects against potential future changes is to destructure at the call site:
| var editPost = useDispatch( 'core/editor' ).editPost; | |
| var { editPost } = useDispatch( 'core/editor' ); |
This makes the consumed API explicit and avoids repeating the property lookup inside the closure.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| var PluginDocumentSettingPanel = | ||
| ( wp.editPost && wp.editPost.PluginDocumentSettingPanel ) || | ||
| ( wp.editor && wp.editor.PluginDocumentSettingPanel ); |
There was a problem hiding this comment.
PluginDocumentSettingPanel component order check
The fallback tries wp.editPost first and wp.editor second. In WP 6.2+ the component lives in @wordpress/editor (wp.editor) and is only re-exported from wp.editPost for backwards compatibility. That re-export may be removed in a future release.
Inverting the preference future-proofs the code so the canonical location is tried first:
| var PluginDocumentSettingPanel = | |
| ( wp.editPost && wp.editPost.PluginDocumentSettingPanel ) || | |
| ( wp.editor && wp.editor.PluginDocumentSettingPanel ); | |
| var PluginDocumentSettingPanel = | |
| ( wp.editor && wp.editor.PluginDocumentSettingPanel ) || | |
| ( wp.editPost && wp.editPost.PluginDocumentSettingPanel ); |
… destructure useDispatch result
🔍 WordPress Plugin Check Report
📊 Report
❌ Errors (6)📁 classes/class-starter-plugin-post-type.php (1 error)
📁 starter-plugin.php (1 error)
📁 readme.txt (1 error)
📁 tests/test-starter-plugin.php (1 error)
📁 tests/bootstrap.php (1 error)
📁 classes/class-starter-plugin.php (1 error)
|
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
0 |
plugin_header_nonexistent_domain_path | The "Domain Path" header in the plugin file must point to an existing folder. Found: "languages" |
📁 readme.txt (1 warning)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
0 |
readme_invalid_contributors | The "Contributors" header in the readme file must be a comma-separated list of WordPress.org-formatted usernames. |
📁 tests/bootstrap.php (4 warnings)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
8 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$_tests_dir". |
11 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "$_phpunit_polyfills_path". |
13 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound | Global constants defined by a theme/plugin should start with the theme/plugin prefix. Found: "WP_TESTS_PHPUNIT_POLYFILLS_PATH". |
24 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound | Functions declared in the global namespace by a theme/plugin should start with the theme/plugin prefix. Found: "_manually_load_plugin". |
📁 classes/class-starter-plugin.php (1 warning)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
148 |
PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound | load_plugin_textdomain() has been discouraged since WordPress version 4.6. When your plugin is hosted on WordPress.org, you no longer need to manually include this function call for translations under your plugin slug. WordPress will automatically load the translations for you as needed. |
📁 classes/class-starter-plugin-admin.php (1 warning)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
201 |
WordPress.Security.ValidatedSanitizedInput.MissingUnslash | $_GET['tab'] not unslashed before sanitization. Use wp_unslash() or similar |
📁 classes/class-starter-plugin-settings.php (1 warning)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
417 |
WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude | Using exclusionary parameters, like exclude, in calls to get_posts() should be done with caution, see https://wpvip.com/documentation/performance-improvements-by-removing-usage-of-post__not_in/ for more information. |
📁 classes/class-starter-plugin-post-type-meta-box.php (1 warning)
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
147 |
WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound | Variable variable which could potentially override an imported global variable detected. Global variables defined by a theme/plugin should start with the theme/plugin prefix. Found: "${$f}". |
🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check
|
@copilot please see the reports above from Greptile and Plugin Check. Please implement Greptile's feedback. We'll address plugin check's feedback in a future PR. Let's focus on making this feature stable with a higher confidence score. |
…a-fields migration Co-authored-by: mattyza <880803+mattyza@users.noreply.github.com> Agent-Logs-Url: https://github.com/mattyza/starter-plugin/sessions/51678230-aea0-445b-8613-da1a78cdd20e
Done in commit
|
|
@copilot - your meta-fields.js file is mostly empty after a recent change. Please restore what you had and implement Greptile's feedback listed above. |
…mentSettingPanel priority and destructured useDispatch Co-authored-by: mattyza <880803+mattyza@users.noreply.github.com> Agent-Logs-Url: https://github.com/mattyza/starter-plugin/sessions/78000d73-c13a-4e2d-a7b4-d1a7d7731b98
Restored in commit |
… code into it's own file
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 10 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * | ||
| * Registers a PluginDocumentSettingPanel for each field section defined by | ||
| * the PHP class. Field definitions are passed via the `starterPluginMetaFields` | ||
| * global object that is localised by Starter_Plugin_Post_Type::enqueue_block_editor_assets(). |
There was a problem hiding this comment.
The header comment says data is localized by Starter_Plugin_Post_Type::enqueue_block_editor_assets(), but the code localizes/enqueues it from Starter_Plugin_Post_Type_Meta_Fields::enqueue_block_editor_assets(). Update the comment to match the current implementation to avoid confusion.
| * global object that is localised by Starter_Plugin_Post_Type::enqueue_block_editor_assets(). | |
| * global object that is localised by Starter_Plugin_Post_Type_Meta_Fields::enqueue_block_editor_assets(). |
| 'auth_callback' => function ( $allowed, $meta_key, $object_id, $user_id, $cap, $caps ) { | ||
| return current_user_can( 'edit_post', $object_id ); |
There was a problem hiding this comment.
auth_callback currently (1) requires 6 parameters and (2) ignores $allowed. It’s safer to accept only the parameters you use (so it won’t fatally error if WP calls it with fewer args) and to return $allowed && current_user_can( 'edit_post', $object_id ) so you don’t override an upstream deny.
| 'auth_callback' => function ( $allowed, $meta_key, $object_id, $user_id, $cap, $caps ) { | |
| return current_user_can( 'edit_post', $object_id ); | |
| 'auth_callback' => function ( $allowed, $meta_key, $object_id ) { | |
| return $allowed && current_user_can( 'edit_post', $object_id ); |
| protected function is_block_editor_active() { | ||
| if ( function_exists( 'use_block_editor_for_post_type' ) ) { | ||
| return use_block_editor_for_post_type( $this->post_type ); | ||
| } | ||
|
|
||
| /** | ||
| * The contents of our meta box. | ||
| * @access public | ||
| * @since 1.0.0 | ||
| * @return void | ||
| */ | ||
| public function meta_box_content () { | ||
| global $post_id; | ||
| $fields = get_post_custom( $post_id ); | ||
| $field_data = $this->get_custom_fields_settings(); | ||
|
|
||
| $html = ''; | ||
|
|
||
| $html .= '<input type="hidden" name="starter_plugin_' . $this->post_type . '_noonce" id="starter-plugin_' . $this->post_type . '_noonce" value="' . wp_create_nonce( plugin_basename( dirname( Starter_Plugin()->plugin_path ) ) ) . '" />'; | ||
|
|
||
| if ( 0 < count( $field_data ) ) : | ||
| foreach ( $field_data as $k => $v ) : | ||
| $data = $v['default']; | ||
| if ( isset( $fields[ '_' . $k ] ) && isset( $fields[ '_' . $k ][0] ) ) { | ||
| $data = $fields[ '_' . $k ][0]; | ||
| } | ||
| ?> | ||
| <p><label for="<?php echo esc_attr( $k ); ?>"><?php echo esc_html( $v['name'] ); ?></label></p> | ||
| <p><input name="<?php echo esc_attr( $k ); ?>" type="text" id="<?php echo esc_attr( $k ); ?>" value="<?php echo esc_attr( $data ); ?>" /></p> | ||
| <p class="description"><?php echo esc_html( $v['description'] ); ?></p> | ||
| <?php | ||
| endforeach; | ||
| endif; | ||
| return false; | ||
| } |
There was a problem hiding this comment.
is_block_editor_active() is added but not referenced anywhere (the legacy meta box class does its own use_block_editor_for_post_type() check). Consider either using this helper to centralize the logic or removing it to avoid dead code.
| * using the block editor. When Gutenberg is active this class is not | ||
| * instantiated. |
There was a problem hiding this comment.
Docblock says this class “is not instantiated” when Gutenberg is active, but it is instantiated in Starter_Plugin_Post_Type and instead self-disables in setup(). Update the docblock to reflect the actual lifecycle (instantiated in admin, no-op when block editor is active).
| * using the block editor. When Gutenberg is active this class is not | |
| * instantiated. | |
| * using the block editor. This class is instantiated in the admin for | |
| * supported post types, but its setup routine is a no-op when the block | |
| * editor (Gutenberg) is active for the post type. |
| $html = ''; | ||
|
|
||
| $html .= '<input type="hidden" name="starter_plugin_' . $this->post_type . '_noonce" id="starter-plugin_' . $this->post_type . '_noonce" value="' . wp_create_nonce( plugin_basename( dirname( Starter_Plugin()->plugin_path ) ) ) . '" />'; | ||
|
|
There was a problem hiding this comment.
The nonce field is built into $html but never printed, so the hidden input won’t be present in the meta box and save() will always fail the nonce check. Output a nonce field directly (e.g., via wp_nonce_field(...)) or echo $html before rendering inputs; also remove $html if it’s no longer needed.
| $html = ''; | |
| $html .= '<input type="hidden" name="starter_plugin_' . $this->post_type . '_noonce" id="starter-plugin_' . $this->post_type . '_noonce" value="' . wp_create_nonce( plugin_basename( dirname( Starter_Plugin()->plugin_path ) ) ) . '" />'; | |
| wp_nonce_field( | |
| plugin_basename( dirname( Starter_Plugin()->plugin_path ) ), | |
| 'starter_plugin_' . $this->post_type . '_noonce' | |
| ); |
| function handleChange( metaKey, value ) { | ||
| var update = {}; | ||
| update[ metaKey ] = value; | ||
| editPost( { meta: update } ); |
There was a problem hiding this comment.
editPost( { meta: update } ) replaces the entire meta object rather than merging, which can wipe other meta edits/values when changing a single field. Merge with the current meta object (e.g., editPost({ meta: { ...meta, [metaKey]: value } })) so updates are additive.
| editPost( { meta: update } ); | |
| editPost( { meta: Object.assign( {}, meta, update ) } ); |
| add_action( 'init', array( $this, 'register' ) ); | ||
|
|
There was a problem hiding this comment.
Implementation hooks init to Starter_Plugin_Post_Type_Meta_Fields::register(), but PR docs/tests refer to register_post_meta_fields(). Consider renaming register() to register_post_meta_fields() (and updating the add_action accordingly) to keep the public API consistent and avoid test/implementation drift.
| 'has_archive' => $archive_slug, | ||
| 'hierarchical' => false, | ||
| 'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'page-attributes' ), | ||
| 'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'page-attributes', 'custom-fields' ), |
There was a problem hiding this comment.
Adding 'custom-fields' to supports enables WordPress’ built-in Custom Fields UI/panel, which may duplicate or conflict with the new sidebar panels/meta box UX. If that UI isn’t intentionally being exposed, consider removing 'custom-fields' support (REST meta registration doesn’t require it).
| 'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'page-attributes', 'custom-fields' ), | |
| 'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'page-attributes' ), |
| public function test_post_meta_fields_registered_in_rest() { | ||
| $post_type_obj = $this->starter_plugin->post_types['thing']; | ||
| $post_type_obj->register_post_meta_fields(); | ||
|
|
||
| $registered = get_registered_meta_keys( 'post', 'thing' ); | ||
|
|
||
| $this->assertArrayHasKey( '_url', $registered ); | ||
| $this->assertTrue( $registered['_url']['show_in_rest'] ); |
There was a problem hiding this comment.
Starter_Plugin_Post_Type no longer defines register_post_meta_fields(), so this test will fatally error. Either rename/restore a register_post_meta_fields() method (e.g., in the new meta-fields class) or adjust the test to trigger the init hook and assert on get_registered_meta_keys() after registration has run.
| * The init action should include a callback for register_post_meta_fields() | ||
| * so that meta registration runs at the correct hook. | ||
| */ | ||
| public function test_register_post_meta_fields_hooked_on_init() { | ||
| $post_type_obj = $this->starter_plugin->post_types['thing']; | ||
| $priority = has_action( 'init', array( $post_type_obj, 'register_post_meta_fields' ) ); | ||
|
|
||
| $this->assertIsInt( $priority ); |
There was a problem hiding this comment.
This hook assertion targets $post_type_obj->register_post_meta_fields, but meta registration is currently hooked from Starter_Plugin_Post_Type_Meta_Fields (add_action( 'init', [ $this, 'register' ] )). Update the test to check for the correct callback (or align the implementation to expose/hook a register_post_meta_fields() method as intended).
| * The init action should include a callback for register_post_meta_fields() | |
| * so that meta registration runs at the correct hook. | |
| */ | |
| public function test_register_post_meta_fields_hooked_on_init() { | |
| $post_type_obj = $this->starter_plugin->post_types['thing']; | |
| $priority = has_action( 'init', array( $post_type_obj, 'register_post_meta_fields' ) ); | |
| $this->assertIsInt( $priority ); | |
| * The init action should include a callback that registers post meta fields | |
| * so that meta registration runs at the correct hook. | |
| */ | |
| public function test_register_post_meta_fields_hooked_on_init() { | |
| global $wp_filter; | |
| $found = false; | |
| if ( isset( $wp_filter['init'] ) && isset( $wp_filter['init']->callbacks ) && is_array( $wp_filter['init']->callbacks ) ) { | |
| foreach ( $wp_filter['init']->callbacks as $priority => $callbacks ) { | |
| foreach ( $callbacks as $callback ) { | |
| $function = $callback['function']; | |
| if ( is_array( $function ) | |
| && is_object( $function[0] ) | |
| && $function[0] instanceof Starter_Plugin_Post_Type_Meta_Fields | |
| && 'register' === $function[1] | |
| ) { | |
| $found = true; | |
| break 2; | |
| } | |
| } | |
| } | |
| } | |
| $this->assertTrue( $found ); |
register_post_meta_fields()— REST-enabled, sanitize callbacks, auth callbackget_field_sections()— filterable section labelsis_block_editor_active()helperenqueue_block_editor_assets()— enqueue JS with localized field/section datameta_box_setup()— bail when block editor is activeassets/js/meta-fields.js—PluginDocumentSettingPanelper sectionauth_callback— usecurrent_user_can('edit_post', $post_id)with the callback's$post_idparam instead of broadedit_postscapabilityStarter_Plugin()->plugin_url . 'assets/js/meta-fields.js'(resolved to.../classes/assets/…, 404) withplugins_url('../assets/js/meta-fields.js', __FILE__)(correctly resolves to plugin root)meta-fields.js— full file was inadvertently replaced with a stub in a previous commit; restored with correctwp.editor-firstPluginDocumentSettingPanelfallback and destructureduseDispatchresultOriginal prompt
⚡ Quickly spin up Copilot coding agent tasks from anywhere on your macOS or Windows machine with Raycast.