Skip to content

Commit 9645bec

Browse files
GaryJonesclaude
andcommitted
feat(custom-status): add status migration tool and WP-CLI commands
Adds tools to migrate posts between statuses, addressing the issue of posts becoming inaccessible when Edit Flow is deactivated while posts have custom statuses. Features: - New "Migrate" tab in Custom Status settings with UI to select source and target statuses, showing post counts for each - WP-CLI commands: - `wp edit-flow custom-status list` - list all statuses - `wp edit-flow custom-status migrate --from=X --to=Y` - migrate posts - `wp edit-flow custom-status counts` - show post counts per status - Supports --dry-run flag for preview and --post-type filter Closes #230 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent ad3d608 commit 9645bec

File tree

5 files changed

+705
-1
lines changed

5 files changed

+705
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
2020

2121
### Added
2222

23+
* feat(custom-status): add status migration tool and WP-CLI commands by @GaryJones in [#859](https://github.com/Automattic/Edit-Flow/pull/859)
2324
* feat(notifications): add Post Author and Auto-subscribed badges by @GaryJones in [#847](https://github.com/Automattic/Edit-Flow/pull/847)
2425
* feat(story-budget): improve UX with Screen Options and collapsible categories by @GaryJones in [#846](https://github.com/Automattic/Edit-Flow/pull/846)
2526

modules/custom-status/custom-status.php

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public function __construct() {
5454
'term-updated' => __( 'Post status updated.', 'edit-flow' ),
5555
'status-deleted' => __( 'Post status deleted.', 'edit-flow' ),
5656
'status-position-updated' => __( 'Status order updated.', 'edit-flow' ),
57+
'status-migrated' => __( 'Posts migrated successfully.', 'edit-flow' ),
5758
],
5859
'autoload' => false,
5960
'settings_help_tab' => [
@@ -72,6 +73,11 @@ public function __construct() {
7273
public function init() {
7374
global $edit_flow;
7475

76+
// Load WP-CLI commands.
77+
if ( defined( 'WP_CLI' ) && WP_CLI ) {
78+
require_once __DIR__ . '/lib/class-cli.php';
79+
}
80+
7581
// Register custom statuses as a taxonomy
7682
$this->register_custom_statuses();
7783

@@ -100,6 +106,7 @@ public function init() {
100106
add_action( 'admin_init', [ $this, 'handle_edit_custom_status' ] );
101107
add_action( 'admin_init', [ $this, 'handle_make_default_custom_status' ] );
102108
add_action( 'admin_init', [ $this, 'handle_delete_custom_status' ] );
109+
add_action( 'admin_init', [ $this, 'handle_migrate_status' ] );
103110
add_action( 'wp_ajax_update_status_positions', [ $this, 'handle_ajax_update_status_positions' ] );
104111
add_action( 'wp_ajax_inline_save_status', [ $this, 'ajax_inline_save_status' ] );
105112

@@ -1051,6 +1058,79 @@ public function handle_delete_custom_status() {
10511058
wp_die();
10521059
}
10531060

1061+
/**
1062+
* Handle a POST request to migrate posts between statuses.
1063+
*
1064+
* @since 0.9.10
1065+
*/
1066+
public function handle_migrate_status() {
1067+
// Check that this is our POST request.
1068+
if ( ! isset( $_POST['action'] ) || 'migrate' !== $_POST['action'] ) {
1069+
return;
1070+
}
1071+
1072+
// Verify the page.
1073+
if ( ! isset( $_GET['page'] ) || $_GET['page'] !== $this->module->settings_slug ) {
1074+
return;
1075+
}
1076+
1077+
// Check for proper nonce.
1078+
if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'custom-status-migrate-nonce' ) ) {
1079+
wp_die( esc_html__( 'Invalid nonce for submission.', 'edit-flow' ) );
1080+
}
1081+
1082+
// Only allow users with the proper caps.
1083+
if ( ! current_user_can( 'manage_options' ) ) {
1084+
wp_die( esc_html__( 'Sorry, you do not have permission to migrate posts.', 'edit-flow' ) );
1085+
}
1086+
1087+
$from_status = isset( $_POST['migrate_from'] ) ? sanitize_key( $_POST['migrate_from'] ) : '';
1088+
$to_status = isset( $_POST['migrate_to'] ) ? sanitize_key( $_POST['migrate_to'] ) : '';
1089+
1090+
// Validate inputs.
1091+
if ( empty( $from_status ) || empty( $to_status ) ) {
1092+
wp_die( esc_html__( 'Please select both a source and target status.', 'edit-flow' ) );
1093+
}
1094+
1095+
if ( $from_status === $to_status ) {
1096+
wp_die( esc_html__( 'Source and target status cannot be the same.', 'edit-flow' ) );
1097+
}
1098+
1099+
// Perform the migration.
1100+
$this->reassign_post_status( $from_status, $to_status );
1101+
1102+
// Clear caches.
1103+
wp_cache_flush();
1104+
1105+
$redirect_url = $this->get_link(
1106+
[
1107+
'action' => 'migrate-status',
1108+
'message' => 'status-migrated',
1109+
]
1110+
);
1111+
wp_redirect( $redirect_url );
1112+
exit;
1113+
}
1114+
1115+
/**
1116+
* Get count of posts with a specific status.
1117+
*
1118+
* @since 0.9.10
1119+
*
1120+
* @param string $status The status slug.
1121+
* @return int The number of posts with this status.
1122+
*/
1123+
public function get_post_count_for_status( $status ) {
1124+
global $wpdb;
1125+
1126+
return (int) $wpdb->get_var(
1127+
$wpdb->prepare(
1128+
"SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_status = %s",
1129+
$status
1130+
)
1131+
);
1132+
}
1133+
10541134
/**
10551135
* Generate a link to one of the custom status actions
10561136
*
@@ -1261,7 +1341,7 @@ public function settings_validate( $new_options ) {
12611341
*/
12621342
public function print_configure_view() {
12631343
// phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- No verification required for unprivileged URL check.
1264-
$action = isset( $_GET['action'] ) && in_array( $_GET['action'], [ 'edit-status', 'change-options' ] ) ? $_GET['action'] : '';
1344+
$action = isset( $_GET['action'] ) && in_array( $_GET['action'], [ 'edit-status', 'change-options', 'migrate-status' ] ) ? $_GET['action'] : '';
12651345

12661346
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- No verification required for unprivileged URL check.
12671347
$term_id = isset( $_GET['term-id'] ) ? absint( $_GET['term-id'] ) : false;

0 commit comments

Comments
 (0)