PluginProbe
Edit Flow / trunk
Edit Flow vtrunk
0.11.1 0.11.0 0.7.2 0.7.3 0.7.4 0.7.5 0.7.6 0.8 0.8.1 0.8.2 0.9 0.9.1 0.9.2 0.9.3 0.9.4 0.9.5 0.9.6 0.9.7 0.9.8 0.9.9 trunk 0.1.5 0.10.0 0.10.1 0.10.2 All 44 releases
edit-flow / modules / custom-status / lib / class-cli.php

class-cli.php in Edit Flow trunk, at modules/custom-status/lib/class-cli.php

330 lines 8.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WP-CLI commands for Edit Flow Custom Statuses.
4 *
5 * @package EditFlow
6 * @subpackage CustomStatus
7 */
8
9 if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
10 return;
11 }
12
13 /**
14 * WP-CLI commands to manage Edit Flow custom statuses.
15 *
16 * @package EditFlow
17 */
18 class EF_Custom_Status_CLI extends WP_CLI_Command {
19
20 /**
21 * List all custom statuses.
22 *
23 * ## OPTIONS
24 *
25 * [--format=<format>]
26 * : Output format.
27 * ---
28 * default: table
29 * options:
30 * - table
31 * - csv
32 * - json
33 * - yaml
34 * ---
35 *
36 * ## EXAMPLES
37 *
38 * # List all custom statuses
39 * $ wp edit-flow custom-status list
40 *
41 * # List statuses as JSON
42 * $ wp edit-flow custom-status list --format=json
43 *
44 * @subcommand list
45 *
46 * @param array $args Positional arguments.
47 * @param array $assoc_args Associative arguments.
48 */
49 public function list_statuses( $args, $assoc_args ) {
50 $custom_status_module = $this->get_custom_status_module();
51
52 if ( ! $custom_status_module ) {
53 WP_CLI::error( 'Custom Status module is not available.' );
54 }
55
56 $statuses = $custom_status_module->get_custom_statuses();
57
58 if ( empty( $statuses ) ) {
59 WP_CLI::warning( 'No custom statuses found.' );
60 return;
61 }
62
63 $items = [];
64 foreach ( $statuses as $status ) {
65 $count = $this->get_post_count_for_status( $status->slug );
66 $items[] = [
67 'slug' => $status->slug,
68 'name' => $status->name,
69 'description' => $status->description,
70 'position' => $status->position ?? '',
71 'post_count' => $count,
72 ];
73 }
74
75 $format = $assoc_args['format'] ?? 'table';
76
77 WP_CLI\Utils\format_items( $format, $items, [ 'slug', 'name', 'description', 'position', 'post_count' ] );
78 }
79
80 /**
81 * Migrate posts from one status to another.
82 *
83 * ## OPTIONS
84 *
85 * --from=<status>
86 * : The source status slug to migrate from.
87 *
88 * --to=<status>
89 * : The target status slug to migrate to.
90 *
91 * [--post-type=<post-type>]
92 * : Only migrate posts of this type. Default: all supported post types.
93 *
94 * [--dry-run]
95 * : Show what would be migrated without making changes.
96 *
97 * [--yes]
98 * : Skip confirmation prompt.
99 *
100 * ## EXAMPLES
101 *
102 * # Migrate all posts from 'pitch' to 'draft'
103 * $ wp edit-flow custom-status migrate --from=pitch --to=draft
104 *
105 * # Preview migration without making changes
106 * $ wp edit-flow custom-status migrate --from=pitch --to=draft --dry-run
107 *
108 * # Migrate only posts of type 'post'
109 * $ wp edit-flow custom-status migrate --from=pitch --to=draft --post-type=post
110 *
111 * @subcommand migrate
112 *
113 * @param array $args Positional arguments.
114 * @param array $assoc_args Associative arguments.
115 */
116 public function migrate( $args, $assoc_args ) {
117 $custom_status_module = $this->get_custom_status_module();
118
119 if ( ! $custom_status_module ) {
120 WP_CLI::error( 'Custom Status module is not available.' );
121 }
122
123 $from_status = $assoc_args['from'] ?? '';
124 $to_status = $assoc_args['to'] ?? '';
125 $post_type = $assoc_args['post-type'] ?? '';
126 $dry_run = isset( $assoc_args['dry-run'] );
127
128 if ( empty( $from_status ) ) {
129 WP_CLI::error( 'Please specify a source status with --from=<status>' );
130 }
131
132 if ( empty( $to_status ) ) {
133 WP_CLI::error( 'Please specify a target status with --to=<status>' );
134 }
135
136 if ( $from_status === $to_status ) {
137 WP_CLI::error( 'Source and target status cannot be the same.' );
138 }
139
140 // Validate target status exists.
141 $valid_statuses = $this->get_all_valid_statuses();
142 if ( ! in_array( $to_status, $valid_statuses, true ) ) {
143 WP_CLI::error( sprintf( "Target status '%s' is not a valid status. Valid statuses: %s", $to_status, implode( ', ', $valid_statuses ) ) );
144 }
145
146 // Get posts with the source status.
147 $query_args = [
148 'post_status' => $from_status,
149 // phpcs:ignore WordPressVIPMinimum.Performance.NoPaging.posts_per_page_posts_per_page -- One-off WP-CLI migration fetching IDs only; pagination is not applicable in this context.
150 'posts_per_page' => -1,
151 'fields' => 'ids',
152 ];
153
154 if ( ! empty( $post_type ) ) {
155 $query_args['post_type'] = $post_type;
156 } else {
157 $query_args['post_type'] = 'any';
158 }
159
160 $posts = get_posts( $query_args );
161 $count = count( $posts );
162
163 if ( 0 === $count ) {
164 WP_CLI::success( sprintf( "No posts found with status '%s'.", $from_status ) );
165 return;
166 }
167
168 if ( $dry_run ) {
169 WP_CLI::log( sprintf( "[Dry Run] Would migrate %d post(s) from '%s' to '%s'.", $count, $from_status, $to_status ) );
170
171 if ( $count <= 20 ) {
172 foreach ( $posts as $post_id ) {
173 $post = get_post( $post_id );
174 WP_CLI::log( sprintf( ' - #%d: %s (%s)', $post_id, $post->post_title, $post->post_type ) );
175 }
176 }
177 return;
178 }
179
180 // Confirmation prompt.
181 if ( ! isset( $assoc_args['yes'] ) ) {
182 WP_CLI::confirm( sprintf( "Are you sure you want to migrate %d post(s) from '%s' to '%s'?", $count, $from_status, $to_status ) );
183 }
184
185 // Perform the migration.
186 global $wpdb;
187
188 if ( ! empty( $post_type ) ) {
189 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Bulk update for CLI migration command.
190 $result = $wpdb->query(
191 $wpdb->prepare(
192 "UPDATE {$wpdb->posts} SET post_status = %s WHERE post_status = %s AND post_type = %s",
193 $to_status,
194 $from_status,
195 $post_type
196 )
197 );
198 } else {
199 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Bulk update for CLI migration command.
200 $result = $wpdb->query(
201 $wpdb->prepare(
202 "UPDATE {$wpdb->posts} SET post_status = %s WHERE post_status = %s",
203 $to_status,
204 $from_status
205 )
206 );
207 }
208
209 // Clear caches.
210 wp_cache_flush();
211 clean_post_cache( $posts );
212
213 WP_CLI::success( sprintf( "Migrated %d post(s) from '%s' to '%s'.", $count, $from_status, $to_status ) );
214 }
215
216 /**
217 * Get post counts for each status.
218 *
219 * ## OPTIONS
220 *
221 * [--post-type=<post-type>]
222 * : Only count posts of this type. Default: post.
223 *
224 * [--format=<format>]
225 * : Output format.
226 * ---
227 * default: table
228 * options:
229 * - table
230 * - csv
231 * - json
232 * - yaml
233 * ---
234 *
235 * ## EXAMPLES
236 *
237 * # Show post counts per status
238 * $ wp edit-flow custom-status counts
239 *
240 * # Show counts for pages
241 * $ wp edit-flow custom-status counts --post-type=page
242 *
243 * @subcommand counts
244 *
245 * @param array $args Positional arguments.
246 * @param array $assoc_args Associative arguments.
247 */
248 public function counts( $args, $assoc_args ) {
249 $post_type = $assoc_args['post-type'] ?? 'post';
250 $format = $assoc_args['format'] ?? 'table';
251
252 $counts = wp_count_posts( $post_type );
253 $items = [];
254
255 foreach ( $counts as $status => $count ) {
256 if ( (int) $count > 0 ) {
257 $status_obj = get_post_status_object( $status );
258 $items[] = [
259 'status' => $status,
260 'label' => $status_obj ? $status_obj->label : $status,
261 'count' => (int) $count,
262 ];
263 }
264 }
265
266 if ( empty( $items ) ) {
267 WP_CLI::warning( sprintf( 'No posts found for post type: %s', $post_type ) );
268 return;
269 }
270
271 // Sort by count descending.
272 usort( $items, function ( $a, $b ) {
273 return $b['count'] - $a['count'];
274 } );
275
276 WP_CLI\Utils\format_items( $format, $items, [ 'status', 'label', 'count' ] );
277 }
278
279 /**
280 * Get the custom status module instance.
281 *
282 * @return EF_Custom_Status|null
283 */
284 private function get_custom_status_module() {
285 global $edit_flow;
286
287 if ( isset( $edit_flow->custom_status ) ) {
288 return $edit_flow->custom_status;
289 }
290
291 return null;
292 }
293
294 /**
295 * Get post count for a specific status.
296 *
297 * @param string $status The status slug.
298 * @return int
299 */
300 private function get_post_count_for_status( $status ) {
301 global $wpdb;
302
303 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- CLI count query, caching not needed.
304 return (int) $wpdb->get_var(
305 $wpdb->prepare(
306 "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_status = %s",
307 $status
308 )
309 );
310 }
311
312 /**
313 * Get all valid status slugs (custom + core).
314 *
315 * @return array
316 */
317 private function get_all_valid_statuses() {
318 $custom_status_module = $this->get_custom_status_module();
319
320 // Fall back to the core statuses if the module is somehow unavailable.
321 if ( ! $custom_status_module ) {
322 return [ 'publish', 'pending', 'draft', 'private', 'trash', 'future' ];
323 }
324
325 return $custom_status_module->get_all_valid_statuses();
326 }
327 }
328
329 WP_CLI::add_command( 'edit-flow custom-status', 'EF_Custom_Status_CLI' );
330