PluginProbe
Redirection / 5.9.0
Redirection v5.9.0
5.10.0 5.9.0 5.8.1 5.8.0 3.7.2 3.7.3 4.0 4.0.1 4.1 4.1.1 4.2 4.2.1 4.2.2 4.2.3 4.3 4.3.1 4.3.2 4.3.3 4.4 4.4.1 4.4.2 4.5 4.5.1 4.6.2 4.7.1 All 130 releases
redirection / redirection-cli.php

redirection-cli.php in Redirection 5.9.0, at redirection-cli.php

530 lines 14.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Implements example command.
5 */
6 class Redirection_Cli extends WP_CLI_Command {
7 /**
8 * @param array<string, mixed> $extra CLI flags.
9 * @return 'import'|'ignore'|'update'
10 */
11 private function get_duplicate_mode( array $extra ) {
12 $mode = isset( $extra['duplicate-mode'] ) && is_string( $extra['duplicate-mode'] ) ? $extra['duplicate-mode'] : 'import';
13
14 if ( in_array( $mode, [ 'import', 'ignore', 'update' ], true ) ) {
15 return $mode;
16 }
17
18 WP_CLI::error( 'Invalid duplicate mode - import, ignore, or update supported' );
19 return 'import';
20 }
21
22 /**
23 * @param array<string, mixed> $extra CLI flags.
24 * @param string $flag Flag name.
25 * @return bool
26 */
27 private function get_boolean_flag( array $extra, $flag ) {
28 if ( ! isset( $extra[ $flag ] ) ) {
29 return false;
30 }
31
32 $value = $extra[ $flag ];
33 if ( $value === true || $value === false ) {
34 return $value;
35 }
36
37 if ( is_string( $value ) ) {
38 return in_array( strtolower( $value ), [ '1', 'true', 'yes' ], true );
39 }
40
41 if ( is_int( $value ) ) {
42 return $value === 1;
43 }
44
45 return false;
46 }
47
48 /**
49 * @param string $source Import source.
50 * @param string $type Import type.
51 * @param array{
52 * created: int,
53 * updated: int,
54 * ignored: int,
55 * groups_created: int,
56 * preview: array<int, array{
57 * source: string,
58 * target: string,
59 * code: int,
60 * regex: bool,
61 * group: string,
62 * result: 'created'|'updated'|'ignored',
63 * redirect_id?: int
64 * }>
65 * } $results Import results.
66 * @return void
67 */
68 private function display_import_results( $source, $type, array $results ) {
69 WP_CLI::success(
70 sprintf(
71 'Imported %d redirects from %s %s (%d created, %d updated, %d ignored, %d groups created)',
72 $results['created'] + $results['updated'],
73 $type,
74 $source,
75 $results['created'],
76 $results['updated'],
77 $results['ignored'],
78 $results['groups_created']
79 )
80 );
81 }
82
83 /**
84 * Resolve a group ID, or return the first available group.
85 *
86 * @param int $group_id Group ID, or 0 to auto-select the first group.
87 * @return int|false Group ID or false when not available.
88 */
89 private function get_group( $group_id ) {
90 if ( $group_id === 0 ) {
91 $groups = Red_Group::get_filtered( array() );
92
93 if ( count( $groups['items'] ) > 0 ) {
94 return $groups['items'][0]['id'];
95 }
96 } else {
97 $groups = Red_Group::get( $group_id );
98 if ( $groups !== false ) {
99 return $group_id;
100 }
101 }
102
103 return false;
104 }
105
106 /**
107 * Import from another plugin to Redirection.
108 *
109 * ## OPTIONS
110 *
111 * <name>
112 * : The plugin name to import from. Supported importers include wp-simple-redirect, seo-redirection, safe-redirect-manager, wordpress-old-slugs, rank-math, quick-redirects, pretty-links, seopress, slim-seo, eps-301-redirects, and fake-redirection.
113 *
114 * [--group=<groupid>]
115 * : The group ID to import into. Defaults to the first available group.
116 *
117 * [--duplicate-mode=<mode>]
118 * : Duplicate handling. One of import, ignore, or update. Defaults to import.
119 *
120 * [--delete-source]
121 * : Delete the original source data after import for importers that support it.
122 *
123 * ## EXAMPLES
124 *
125 * wp redirection plugin quick-redirects
126 *
127 * @param list<string> $args Positional arguments.
128 * @param array<string, mixed> $extra Associative flags.
129 * @return void
130 */
131 public function plugin( $args, $extra ) {
132 $name = $args[0];
133 $group = $this->get_group( isset( $extra['group'] ) ? intval( $extra['group'], 10 ) : 0 );
134 $options = [
135 'duplicate_mode' => $this->get_duplicate_mode( $extra ),
136 'delete_source' => $this->get_boolean_flag( $extra, 'delete-source' ),
137 ];
138
139 $importer = \Redirection\ImportExport\Importer\PluginRegistry::get_importer( $name );
140 if ( $importer !== false && $group !== false ) {
141 $results = $importer->import_plugin( $group, $options );
142 $this->display_import_results( $name, 'plugin', $results );
143 return;
144 }
145
146 if ( $importer === false ) {
147 WP_CLI::error( 'Invalid plugin name' );
148 return;
149 }
150
151 WP_CLI::error( 'Invalid group' );
152 }
153
154 /**
155 * Get or set a Redirection setting
156 *
157 * ## OPTIONS
158 *
159 * <name>
160 * : The setting name to get or set
161 *
162 * [--set=<value>]
163 * : The value to set. Use true/false for boolean settings, or JSON for complex values.
164 *
165 * [--verbose]
166 * : Display setting name along with value (e.g., "flag_case: true" instead of just "true")
167 *
168 * ## EXAMPLES
169 *
170 * wp redirection setting flag_case
171 * wp redirection setting flag_case --verbose
172 * wp redirection setting flag_case --set=true
173 * wp redirection setting cache_key --set=false
174 * wp redirection setting aliases --set='["example.com"]'
175 *
176 * @param list<string> $args Positional arguments.
177 * @param array<string, mixed> $extra Associative flags.
178 * @return void
179 */
180 public function setting( $args, $extra ) {
181 $name = $args[0];
182 $set = isset( $extra['set'] ) ? $extra['set'] : null;
183 $verbose = isset( $extra['verbose'] );
184
185 $options = Red_Options::get();
186
187 if ( ! array_key_exists( $name, $options ) ) {
188 WP_CLI::error( 'Unsupported setting: ' . $name );
189 return;
190 }
191
192 $old_value = $options[ $name ];
193
194 if ( $set !== null ) {
195 if ( ! is_string( $set ) ) {
196 WP_CLI::error( 'No value provided for --set; please provide a value, for example: --set=true or --set=\'["example.com"]\'.' );
197 return;
198 }
199
200 $decoded = $this->parse_setting_value( $set );
201
202 $update = [];
203 $update[ $name ] = $decoded;
204
205 $options = Red_Options::save( $update );
206 $new_value = array_key_exists( $name, $options ) ? $options[ $name ] : null;
207
208 $this->display_setting_result( $name, $old_value, $new_value );
209 return;
210 }
211
212 // Just display the current value
213 $this->display_setting_value( $name, $old_value, $verbose );
214 }
215
216 /**
217 * Parse a setting value from CLI input.
218 *
219 * @param string $value The raw CLI value.
220 * @return mixed The parsed value.
221 */
222 private function parse_setting_value( $value ) {
223 // Handle explicit boolean strings
224 if ( $value === 'true' ) {
225 return true;
226 }
227 if ( $value === 'false' ) {
228 return false;
229 }
230
231 // Try JSON decode for arrays/objects (but not null, which should be literal string "null")
232 $decoded = json_decode( $value, true );
233 if ( $decoded !== null ) {
234 return $decoded;
235 }
236
237 // Return as-is (string value, including literal "null")
238 return $value;
239 }
240
241 /**
242 * Display a setting value.
243 *
244 * @param string $name Setting name.
245 * @param mixed $value Setting value.
246 * @param bool $verbose Whether to include setting name in output.
247 * @return void
248 */
249 private function display_setting_value( $name, $value, $verbose = false ) {
250 $display = $this->format_value_for_display( $value );
251 if ( $verbose ) {
252 WP_CLI::success( sprintf( '%s: %s', $name, $display ) );
253 } else {
254 WP_CLI::success( $display );
255 }
256 }
257
258 /**
259 * Display the result of setting a value.
260 *
261 * @param string $name Setting name.
262 * @param mixed $old_value Previous value.
263 * @param mixed $new_value New value.
264 * @return void
265 */
266 private function display_setting_result( $name, $old_value, $new_value ) {
267 $old_display = $this->format_value_for_display( $old_value );
268 $new_display = $this->format_value_for_display( $new_value );
269
270 // Compare raw values to avoid issues with formatted display strings
271 if ( $old_value === $new_value ) {
272 WP_CLI::success( sprintf( '%s is already set to: %s', $name, $new_display ) );
273 } else {
274 WP_CLI::success( sprintf( '%s updated: %s → %s', $name, $old_display, $new_display ) );
275 }
276 }
277
278 /**
279 * Format a value for display in CLI output.
280 *
281 * @param mixed $value The value to format.
282 * @return string Formatted string for display.
283 */
284 private function format_value_for_display( $value ) {
285 if ( is_bool( $value ) ) {
286 return $value ? 'true' : 'false';
287 }
288 if ( is_array( $value ) ) {
289 $encoded = wp_json_encode( $value );
290 return is_string( $encoded ) ? $encoded : '[]';
291 }
292 if ( $value === '' ) {
293 return '(empty)';
294 }
295 return (string) $value;
296 }
297
298 /**
299 * Import redirections from a JSON, CSV, or .htaccess file
300 *
301 * ## OPTIONS
302 *
303 * <file>
304 * : The name of the file to import.
305 *
306 * [--group=<groupid>]
307 * : The group ID to import into. Defaults to the first available group. JSON
308 * contains it's own group
309 *
310 * [--use-groups-in-file]
311 * : For JSON imports, use the groups defined in the file instead of importing into a single group.
312 *
313 * [--format=<importformat>]
314 * : The import format - csv, apache, or json. Defaults to json
315 *
316 * [--duplicate-mode=<mode>]
317 * : Duplicate handling. One of import, ignore, or update. Defaults to import.
318 *
319 * ## EXAMPLES
320 *
321 * wp redirection import .htaccess --format=apache
322 *
323 * @param list<string> $args Positional arguments.
324 * @param array<string, mixed> $extra Associative flags.
325 * @return void
326 */
327 public function import( $args, $extra ) {
328 $format = isset( $extra['format'] ) ? $extra['format'] : 'json';
329 $formats = new \Redirection\ImportExport\FormatFactory();
330 $use_file_groups = $this->get_boolean_flag( $extra, 'use-groups-in-file' );
331 $group = $use_file_groups && $format === 'json' ? 0 : $this->get_group( isset( $extra['group'] ) ? intval( $extra['group'], 10 ) : 0 );
332
333 if ( $group === false ) {
334 WP_CLI::error( 'Invalid group' );
335 return;
336 }
337
338 $importer = $formats->create( $format );
339
340 if ( $importer === false ) {
341 WP_CLI::error( 'Invalid import format - csv, json, or apache supported' );
342 return;
343 }
344
345 if ( ! file_exists( $args[0] ) || ! is_readable( $args[0] ) ) {
346 WP_CLI::error( 'Invalid import file' );
347 return;
348 }
349
350 $file_size = filesize( $args[0] );
351 if ( $file_size === false ) {
352 WP_CLI::error( 'Invalid import file' );
353 return;
354 }
355
356 $results = ( new \Redirection\ImportExport\ImportService( $formats ) )->import(
357 $group,
358 [
359 'name' => basename( $args[0] ),
360 'tmp_name' => $args[0],
361 'type' => '',
362 'error' => 0,
363 'size' => $file_size,
364 ],
365 [
366 'format' => $format,
367 'duplicate_mode' => $this->get_duplicate_mode( $extra ),
368 ]
369 );
370
371 $this->display_import_results( $format, 'file', $results );
372 }
373
374 /**
375 * Export redirections to a CSV, JSON, .htaccess, or rewrite.rules file
376 *
377 * ## OPTIONS
378 *
379 * <module>
380 * : The module to export - wordpress, apache, nginx, or all
381 *
382 * <filename>
383 * : The file to export to, or - for stdout
384 *
385 * [--format=<exportformat>]
386 * : The export format. One of json, csv, apache, or nginx. Defaults to json
387 *
388 * ## EXAMPLES
389 *
390 * wp redirection export wordpress --format=apache
391 *
392 * @param list<string> $args Positional arguments.
393 * @param array<string, mixed> $extra Associative flags.
394 * @return void
395 */
396 public function export( $args, $extra ) {
397 $format = isset( $extra['format'] ) ? $extra['format'] : 'json';
398 $exporter = ( new \Redirection\ImportExport\FormatFactory() )->create( $format );
399
400 if ( $exporter === false ) {
401 WP_CLI::error( 'Invalid export format - json, csv, apache, or nginx supported' );
402 return;
403 }
404
405 $file = fopen( $args[1] === '-' ? 'php://stdout' : $args[1], 'w' );
406 if ( $file !== false ) {
407 $export = ( new \Redirection\ImportExport\ExportService() )->export( $args[0], $format );
408
409 if ( $export === false ) {
410 // phpcs:ignore
411 WP_CLI::error( 'Invalid module - must be wordpress, apache, nginx, or all' );
412 return;
413 }
414
415 fwrite( $file, $export['data'] );
416 fclose( $file );
417
418 WP_CLI::success( 'Exported ' . $export['total'] . ' to ' . $format );
419 } else {
420 WP_CLI::error( 'Invalid output file' );
421 }
422 }
423
424 /**
425 * Perform Redirection database actions
426 *
427 * ## OPTIONS
428 *
429 * <action>
430 * : The database action to perform: install, remove, upgrade
431 *
432 * [--skip-errors]
433 * : Skip errors and keep on upgrading
434 *
435 * ## EXAMPLES
436 *
437 * wp redirection database install
438 *
439 * @param list<string> $args Positional arguments.
440 * @param array<string, mixed> $extra Associative flags.
441 * @return void
442 */
443 public function database( $args, $extra ) {
444 $skip = isset( $extra['skip-errors'] ) ? true : false;
445
446 if ( count( $args ) === 0 || ! in_array( $args[0], array( 'install', 'remove', 'upgrade' ), true ) ) {
447 WP_CLI::error( 'Invalid database action - please use install, remove, or upgrade' );
448 return;
449 }
450
451 if ( $args[0] === 'install' ) {
452 Red_Database::apply_to_sites(
453 function () {
454 $latest = Red_Database::get_latest_database();
455 $latest->install();
456
457 WP_CLI::success( 'Site ' . get_current_blog_id() . ' database is installed' );
458 }
459 );
460
461 WP_CLI::success( 'Database install finished' );
462 } elseif ( $args[0] === 'upgrade' ) {
463 global $wpdb;
464
465 $wpdb->show_errors( false );
466
467 Red_Database::apply_to_sites(
468 function () use ( $skip ) {
469 $database = new Red_Database();
470 $status = new Red_Database_Status();
471
472 if ( ! $status->needs_updating() ) {
473 WP_CLI::success( 'Site ' . get_current_blog_id() . ' database is already the latest version' );
474 return;
475 }
476
477 $loop = 0;
478
479 while ( $loop < 50 ) {
480 $database->apply_upgrade( $status );
481 $info = $status->get_json();
482
483 if ( ! $info['inProgress'] ) {
484 break;
485 }
486
487 if ( isset( $info['result'] ) && $info['result'] === 'error' && isset( $info['reason'] ) && isset( $info['debug'] ) ) {
488 if ( $skip === false ) {
489 WP_CLI::error( 'Site ' . get_current_blog_id() . ' database failed to upgrade: ' . $info['reason'] . ' - ' . $info['debug'][0] );
490 return;
491 }
492
493 WP_CLI::warning( 'Site ' . get_current_blog_id() . ' database failed to upgrade: ' . $info['reason'] . ' - ' . $info['debug'][0] );
494 $status->set_next_stage();
495 }
496
497 $loop++;
498 }
499
500 WP_CLI::success( 'Site ' . get_current_blog_id() . ' database upgraded' );
501 }
502 );
503
504 WP_CLI::success( 'Database upgrade finished' );
505 } elseif ( $args[0] === 'remove' ) {
506 Red_Database::apply_to_sites(
507 function () {
508 $latest = Red_Database::get_latest_database();
509 $latest->remove();
510 }
511 );
512
513 WP_CLI::success( 'Database removed' );
514 }
515 }
516 }
517
518 if ( defined( 'WP_CLI' ) && WP_CLI ) {
519 // Register "redirection" as top-level command, and all public methods as sub-commands
520 WP_CLI::add_command( 'redirection', 'Redirection_Cli' );
521
522 add_action(
523 Red_Flusher::DELETE_HOOK,
524 function () {
525 $flusher = new Red_Flusher();
526 $flusher->flush();
527 }
528 );
529 }
530