PluginProbe
Edit Flow / 0.11.0
Edit Flow v0.11.0
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 0.11.0, at modules/custom-status/lib/class-cli.php

329 lines 8.2 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 'posts_per_page' => -1,
150 'fields' => 'ids',
151 ];
152
153 if ( ! empty( $post_type ) ) {
154 $query_args['post_type'] = $post_type;
155 } else {
156 $query_args['post_type'] = 'any';
157 }
158
159 $posts = get_posts( $query_args );
160 $count = count( $posts );
161
162 if ( 0 === $count ) {
163 WP_CLI::success( sprintf( "No posts found with status '%s'.", $from_status ) );
164 return;
165 }
166
167 if ( $dry_run ) {
168 WP_CLI::log( sprintf( "[Dry Run] Would migrate %d post(s) from '%s' to '%s'.", $count, $from_status, $to_status ) );
169
170 if ( $count <= 20 ) {
171 foreach ( $posts as $post_id ) {
172 $post = get_post( $post_id );
173 WP_CLI::log( sprintf( ' - #%d: %s (%s)', $post_id, $post->post_title, $post->post_type ) );
174 }
175 }
176 return;
177 }
178
179 // Confirmation prompt.
180 if ( ! isset( $assoc_args['yes'] ) ) {
181 WP_CLI::confirm( sprintf( "Are you sure you want to migrate %d post(s) from '%s' to '%s'?", $count, $from_status, $to_status ) );
182 }
183
184 // Perform the migration.
185 global $wpdb;
186
187 if ( ! empty( $post_type ) ) {
188 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Bulk update for CLI migration command.
189 $result = $wpdb->query(
190 $wpdb->prepare(
191 "UPDATE {$wpdb->posts} SET post_status = %s WHERE post_status = %s AND post_type = %s",
192 $to_status,
193 $from_status,
194 $post_type
195 )
196 );
197 } else {
198 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Bulk update for CLI migration command.
199 $result = $wpdb->query(
200 $wpdb->prepare(
201 "UPDATE {$wpdb->posts} SET post_status = %s WHERE post_status = %s",
202 $to_status,
203 $from_status
204 )
205 );
206 }
207
208 // Clear caches.
209 wp_cache_flush();
210 clean_post_cache( $posts );
211
212 WP_CLI::success( sprintf( "Migrated %d post(s) from '%s' to '%s'.", $count, $from_status, $to_status ) );
213 }
214
215 /**
216 * Get post counts for each status.
217 *
218 * ## OPTIONS
219 *
220 * [--post-type=<post-type>]
221 * : Only count posts of this type. Default: post.
222 *
223 * [--format=<format>]
224 * : Output format.
225 * ---
226 * default: table
227 * options:
228 * - table
229 * - csv
230 * - json
231 * - yaml
232 * ---
233 *
234 * ## EXAMPLES
235 *
236 * # Show post counts per status
237 * $ wp edit-flow custom-status counts
238 *
239 * # Show counts for pages
240 * $ wp edit-flow custom-status counts --post-type=page
241 *
242 * @subcommand counts
243 *
244 * @param array $args Positional arguments.
245 * @param array $assoc_args Associative arguments.
246 */
247 public function counts( $args, $assoc_args ) {
248 $post_type = $assoc_args['post-type'] ?? 'post';
249 $format = $assoc_args['format'] ?? 'table';
250
251 $counts = wp_count_posts( $post_type );
252 $items = [];
253
254 foreach ( $counts as $status => $count ) {
255 if ( (int) $count > 0 ) {
256 $status_obj = get_post_status_object( $status );
257 $items[] = [
258 'status' => $status,
259 'label' => $status_obj ? $status_obj->label : $status,
260 'count' => (int) $count,
261 ];
262 }
263 }
264
265 if ( empty( $items ) ) {
266 WP_CLI::warning( sprintf( 'No posts found for post type: %s', $post_type ) );
267 return;
268 }
269
270 // Sort by count descending.
271 usort( $items, function ( $a, $b ) {
272 return $b['count'] - $a['count'];
273 } );
274
275 WP_CLI\Utils\format_items( $format, $items, [ 'status', 'label', 'count' ] );
276 }
277
278 /**
279 * Get the custom status module instance.
280 *
281 * @return EF_Custom_Status|null
282 */
283 private function get_custom_status_module() {
284 global $edit_flow;
285
286 if ( isset( $edit_flow->custom_status ) ) {
287 return $edit_flow->custom_status;
288 }
289
290 return null;
291 }
292
293 /**
294 * Get post count for a specific status.
295 *
296 * @param string $status The status slug.
297 * @return int
298 */
299 private function get_post_count_for_status( $status ) {
300 global $wpdb;
301
302 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- CLI count query, caching not needed.
303 return (int) $wpdb->get_var(
304 $wpdb->prepare(
305 "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_status = %s",
306 $status
307 )
308 );
309 }
310
311 /**
312 * Get all valid status slugs (custom + core).
313 *
314 * @return array
315 */
316 private function get_all_valid_statuses() {
317 $custom_status_module = $this->get_custom_status_module();
318
319 // Fall back to the core statuses if the module is somehow unavailable.
320 if ( ! $custom_status_module ) {
321 return [ 'publish', 'pending', 'draft', 'private', 'trash', 'future' ];
322 }
323
324 return $custom_status_module->get_all_valid_statuses();
325 }
326 }
327
328 WP_CLI::add_command( 'edit-flow custom-status', 'EF_Custom_Status_CLI' );
329