PluginProbe
WP-Stateless – Google Cloud Storage / 4.4.2
WP-Stateless – Google Cloud Storage v4.4.2
4.4.3 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.3.0 2.3.1 2.3.2 3.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.2.0 3.2.1 3.2.2 All 62 releases
wp-stateless / lib / cli / class-sm-cli-command.php

class-sm-cli-command.php in WP-Stateless – Google Cloud Storage 4.4.2, at lib/cli/class-sm-cli-command.php

633 lines 19.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 use wpCloud\StatelessMedia\Migrator;
4 use \wpCloud\StatelessMedia\Batch\BatchTaskManager;
5
6 /**
7 * WP CLI SM Commands
8 */
9 if (defined('WP_CLI') && WP_CLI && class_exists('WP_CLI_Command')) {
10
11 /**
12 * WP-CLI command
13 */
14 class SM_CLI_Command extends WP_CLI_Command {
15
16 public $url;
17
18 /**
19 * @param $args
20 * @param $assoc_args
21 */
22 public function __construct($args = array(), $assoc_args = array()) {
23 parent::__construct();
24
25 if (php_sapi_name() != 'cli') {
26 die('Must run from command line');
27 }
28
29 //** Setup some server settings */
30 set_time_limit(0);
31 ini_set('memory_limit', '2G');
32 //** Setup error handling */
33 ini_set('display_errors', 1);
34 ini_set('log_errors', 0);
35 ini_set('html_errors', 0);
36
37 if (!class_exists('SM_CLI_Process')) {
38 require_once(dirname(__FILE__) . '/class-sm-cli-process.php');
39 }
40
41 if (!class_exists('SM_CLI')) {
42 require_once(dirname(__FILE__) . '/class-sm-cli.php');
43 }
44
45 /** Be sure that we add url parameter to commands if we have MultiSite installation. */
46 $this->url = is_multisite() ? WP_CLI::get_runner()->config['url'] : false;
47 }
48
49 /**
50 * Sync Data
51 *
52 * ## OPTIONS
53 *
54 * <type>
55 * : Which data we want to sync. May be images or files.
56 *
57 * --url
58 * : Blog URL if multisite installation.
59 *
60 * --start
61 * : Indent (sql start). It's ignored on batches.
62 *
63 * --limit
64 * : Limit per query (sql limit)
65 *
66 * --end
67 * : Where ( on which row ) we should stop script. It's ignored on batches
68 *
69 * --batch
70 * : Number of Batch. Default is 1.
71 *
72 * --batches
73 * : General amount of batches.
74 *
75 * --b
76 * : Runs command using batches till it's done. Other parameters will be ignored. There are 10 batches by default. Batch is external command process
77 *
78 * --log
79 * : Show more information in command line
80 *
81 * --o
82 * : Process includes database optimization and transient removing.
83 *
84 * --order
85 * : Order. May be ASC or DESC
86 *
87 * ## EXAMPLES
88 *
89 * wp stateless sync images --url=example.com --b
90 * : Run process looping 10 batches. Every batch is external command 'wp stateless sync images --url=example.com --batch=<number> --batches=10'
91 *
92 * wp stateless sync images --url=example.com --b --batches=100
93 * : Run process looping 100 batches.
94 *
95 * wp stateless sync images --url=example.com --b --batches=10 --batch=2
96 * : Run second batch from 10 batches manually.
97 *
98 * wp stateless sync images --url=example.com --log
99 * : Run default process showing additional information in command line.
100 *
101 * wp stateless sync images --url=example.com --end=3000 --limit=50
102 * : Run process from 1 to 3000 row. Splits process by limiting queries to 50 rows. So, the current example does 60 queries ( 3000 / 50 = 60 )
103 *
104 * wp stateless sync images --url=example.com --start=777 --end=3000 --o
105 * : Run process from 777 to 3000 row. Also does database optimization and removes transient in the end.
106 *
107 * @synopsis <type> [--url=<val>] [--start=<val>] [--limit=<val>] [--end=<val>] [--batch=<val>] [--batches=<val>] [--b] [--log] [--o] [--order=<val>]
108 * @param $args
109 * @param $assoc_args
110 */
111 public function sync($args, $assoc_args) {
112
113 $sm_mode = ud_get_stateless_media()->get('sm.mode');
114 if ($sm_mode === 'stateless') {
115 WP_CLI::error('Sync is not supported in Stateless mode');
116 }
117 //** DB Optimization process */
118 if (isset($assoc_args['o'])) {
119 $this->_before_command_run();
120 }
121 //** Run batches */
122 if (isset($assoc_args['b'])) {
123 if (empty($args[0])) {
124 WP_CLI::error('Invalid type parameter');
125 }
126 $this->_run_batches('sync', $args[0], $assoc_args);
127 }
128 //** Or run command as is. */
129 else {
130 if (!class_exists('SM_CLI_Sync')) {
131 require_once(dirname(__FILE__) . '/class-sm-cli-sync.php');
132 }
133 if (class_exists('SM_CLI_Sync')) {
134 $object = new SM_CLI_Sync($args, $assoc_args);
135 $controller = !empty($args[0]) ? $args[0] : false;
136 if ($controller && is_callable(array($object, $controller))) {
137 call_user_func(array($object, $controller));
138 } else {
139 WP_CLI::error('Invalid type parameter');
140 }
141 } else {
142 WP_CLI::error('Class SM_CLI_Sync is undefined.');
143 }
144 }
145 //** Get rid of all transients and run DB optimization again */
146 if (isset($assoc_args['o'])) {
147 $this->_after_command_run();
148 }
149 }
150
151 /**
152 * Upgrade Data
153 *
154 * ## OPTIONS
155 *
156 * <type>
157 * : Which data we want to upgrade. Currently only 'meta' type is supported.
158 *
159 * --start
160 * : Indent (sql start). It's ignored on batches.
161 *
162 * --limit
163 * : Limit per query (sql limit)
164 *
165 * --end
166 * : Where ( on which row ) we should stop script. It's ignored on batches
167 *
168 * --batch
169 * : Number of Batch. Default is 1.
170 *
171 * --batches
172 * : General amount of batches.
173 *
174 * --b
175 * : Runs command using batches till it's done. Other parameters will be ignored. There are 10 batches by default. Batch is external command process
176 *
177 * --log
178 * : Show more information in command line
179 *
180 * --o
181 * : Process includes database optimization and transient removing.
182 *
183 * --url
184 * : Blog URL if multisite installation.
185 *
186 * ## EXAMPLES
187 *
188 * wp stateless upgrade meta --url=example.com --b
189 * : Run process looping 10 batches. Every batch is external command 'wp stateless upgrade meta --url=example.com --batch=<number> --batches=10'
190 *
191 * wp stateless upgrade meta --url=example.com --b --batches=100
192 * : Run process looping 100 batches.
193 *
194 * wp stateless upgrade meta --url=example.com --b --batches=10 --batch=2
195 * : Run second batch from 10 batches manually.
196 *
197 * wp stateless upgrade meta --url=example.com --log
198 * : Run default process showing additional information in command line.
199 *
200 * wp stateless upgrade meta --url=example.com --end=3000 --limit=50
201 * : Run process from 1 to 3000 row. Splits process by limiting queries to 50 rows. So, the current example does 60 queries ( 3000 / 50 = 60 )
202 *
203 * wp stateless upgrade meta --url=example.com --start=777 --end=3000 --o
204 * : Run process from 777 to 3000 row. Also does database optimization and removes transient in the end.
205 *
206 * @synopsis <type> [--url=<val>] [--start=<val>] [--limit=<val>] [--end=<val>] [--batch=<val>] [--batches=<val>] [--b] [--log] [--o]
207 * @param $args
208 * @param $assoc_args
209 */
210 public function upgrade($args, $assoc_args) {
211 //** DB Optimization process */
212 if (isset($assoc_args['o'])) {
213 $this->_before_command_run();
214 }
215 //** Run batches */
216 if (isset($assoc_args['b'])) {
217 if (empty($args[0])) {
218 WP_CLI::error('Invalid type parameter');
219 }
220 $this->_run_batches('upgrade', $args[0], $assoc_args);
221 }
222 //** Or run command as is. */
223 else {
224 if (!class_exists('SM_CLI_Upgrade')) {
225 require_once(dirname(__FILE__) . '/class-sm-cli-upgrade.php');
226 }
227 if (class_exists('SM_CLI_Upgrade')) {
228 $object = new SM_CLI_Upgrade($args, $assoc_args);
229 $controller = !empty($args[0]) ? $args[0] : false;
230 if ($controller && is_callable(array($object, $controller))) {
231 call_user_func(array($object, $controller));
232 } else {
233 WP_CLI::error('Invalid type parameter');
234 }
235 } else {
236 WP_CLI::error('Class SM_CLI_Upgrade is undefined.');
237 }
238 }
239 //** Get rid of all transients and run DB optimization again */
240 if (isset($assoc_args['o'])) {
241 $this->_after_command_run();
242 }
243 }
244
245 /**
246 * Run migrations
247 *
248 * ## OPTIONS
249 *
250 * [<id|auto>]
251 * : start migration by its ID, or automatically run all pending migrations (auto). Auto mode does not support '--force' parameter.
252 *
253 * --force
254 * : Force starting migration even if it is not pending
255 *
256 * --progress=<interval>
257 * : Monitor migration progress every <interval> seconds (minimum 1)
258 *
259 * --email=<email>
260 * : Send email notification to specified email when migration is finished. By default it uses email from plugin settings. You can also use a list of emails, comma separated.
261 *
262 * --url
263 * : Blog URL if multisite installation.
264 *
265 * --yes
266 * : Confirm automatically.
267 *
268 *
269 * ## EXAMPLES
270 *
271 * wp stateless migrate
272 * : List migrations information.
273 *
274 * wp stateless migrate --url=example.com
275 * : List migrations information for specific blog in multisite network.
276 *
277 * wp stateless migrate --progress=3
278 * : Display current migration progress every 3 seconds.
279 *
280 * wp stateless migrate 20240216150177
281 * : Start migration with ID 20240216150177.
282 *
283 * wp stateless migrate auto --email=mail@example.com --yes
284 * : Automatically run all pending migrations without confirmation and send notifications to mail@example.com.
285 *
286 * wp stateless migrate 20240216150177 --progress=2 --yes
287 * : Start migration with ID 20240216150177 without confirmation and display progress every 2 seconds.
288 *
289 * wp stateless migrate 20240216150177 --force --email=mail@example.com,user@domain.com --url=example.com
290 * : Start migration with ID 20240216150177 for specific blog in multisite network. Start migration even if it was already finished or failed. After finishing send email notification to mail@example.com and user@domain.com.
291 *
292 * @synopsis [<id|auto>] [--force] [--progress=<val>] [--email=<val>] [--yes] [--url=<val>]
293 * @param $args
294 * @param $assoc_args
295 */
296 public function migrate($args, $assoc_args) {
297 $id = $args[0] ?? '';
298
299 // No migration ID provided, list all migrations and exit
300 if ( empty($id) && !isset($assoc_args['progress']) ) {
301 $this->_list_migrations();
302
303 return;
304 } else if ( !empty($id) ) {
305 if ( $id === 'auto' ) {
306 if ( isset($assoc_args['force']) ) {
307 WP_CLI::error( 'The parameter --force is not supported for auto mode.' );
308
309 return;
310 }
311
312 $this->_auto_migrate($assoc_args);
313
314 return;
315 } else {
316 $this->_run_migration($id, $assoc_args);
317 }
318 }
319
320 if ( $id !== 'auto' && isset($assoc_args['progress']) ) {
321 $this->_check_progress($assoc_args['progress']);
322 }
323 }
324
325 /**
326 * Sets cacheControl meta for all files on Google Cloud Storage to the default value from WP-Stateless settings.
327 *
328 * ## OPTIONS
329 *
330 * --url
331 * : Blog URL if multisite installation.
332 *
333 * ## EXAMPLES
334 *
335 * wp stateless reset_cache_control
336 * : Sync cache control for all files on Google Cloud Storage.
337 *
338 * @synopsis [--url=<val>]
339 * @param $args
340 * @param $assoc_args
341 */
342 public function reset_cache_control($args, $assoc_args) {
343 $sm_mode = ud_get_stateless_media()->get('sm.mode');
344 if ( ud_get_stateless_media()->is_mode('stateless') ) {
345 WP_CLI::error('Sync cache control is not supported in Stateless mode');
346 }
347
348 global $wpdb;
349
350 $gs_client = ud_get_stateless_media()->get_client();
351 $cache_control = ud_get_stateless_media()->get_default_cache_control();
352 $cache_control = apply_filters('sm:item:cacheControl', ud_get_stateless_media()->get_default_cache_control() );
353
354 $table_name = ud_stateless_db()->files;
355 $names = $wpdb->get_col("
356 SELECT name
357 FROM {$table_name}
358 WHERE post_id IS NOT NULL
359 ");
360
361 foreach ($names as $name) {
362 if ( !$gs_client->media_exists($name) ) {
363 continue;
364 }
365
366 $args = [
367 'skipLocalCheck' => true,
368 'force' => false,
369 'cacheControl' => $cache_control,
370 'name' => $name,
371 ];
372
373 $gs_client->add_media($args);
374
375 WP_CLI::line("Processed file: '{$name}'");
376 }
377 }
378
379 /**
380 * Run all pending migrations
381 *
382 * @param array $assoc_args
383 */
384 private function _auto_migrate($assoc_args) {
385 $progress = $assoc_args['progress'] ?? 1;
386
387 do {
388 // We need to omit the cache and get the data directly from the db
389 $migrations = apply_filters('wp_stateless_get_migrations', []);
390
391 $keys = array_reverse( array_keys($migrations) );
392 $id = null;
393
394 // Do we have next pending migration?
395 foreach ($keys as $key) {
396 if ( $migrations[$key]['status'] === Migrator::STATUS_PENDING ) {
397 $id = $key;
398 break;
399 }
400 }
401
402 if ( !empty($id) ) {
403 $command = "wp stateless migrate $id --yes --progress=$progress";
404
405 WP_CLI::line('...');
406 WP_CLI::line("Launching external command '{$command}'");
407 WP_CLI::line('Waiting...');
408
409 @ob_flush();
410 flush();
411
412 $r = SM_CLI::launch($command, false, true);
413
414 if ($r->return_code) {
415 WP_CLI::error("Something went wrong. External command process failed.");
416 } else {
417 echo $r->stdout;
418 }
419
420 continue;
421 }
422
423 break;
424
425 } while(true);
426
427 WP_CLI::success('No pending migrations left.');
428 }
429
430 /**
431 * Run the specific migration
432 *
433 * @param string $id
434 * @param array $assoc_args
435 */
436 private function _run_migration($id, $assoc_args) {
437 $migrations = $this->_get_migrations();
438
439 if ( !isset($migrations[$id]) ) {
440 WP_CLI::error("Invalid migration ID: $id");
441 }
442
443 $migration = $migrations[$id];
444
445 // Check if we can run migration
446 if ( !$migration['can_start'] && !isset($assoc_args['force']) ) {
447 WP_CLI::error( 'Migration ' . $migration['description'] . ' is not ready for starting. ' . PHP_EOL .
448 'Migration status: ' . $migration['status_text'] . ', ' . strip_tags($migration['message']) . PHP_EOL .
449 'Please use --force to run it anyway.'
450 );
451 }
452
453 $email = $assoc_args['email'] ?? ud_get_stateless_media()->get_notification_email();
454
455 WP_CLI::line( 'Please make a backup copy of your database and try not to upload, change or delete your media while the process continues.' . PHP_EOL .
456 "After the process finishes an email will be sent to: $email" . PHP_EOL
457 );
458
459 WP_CLI::confirm( "Are you sure you want to run the migration $id?", $assoc_args );
460
461 // Run migration
462 Migrator::instance()->start_migration([], [
463 'id' => $id,
464 'is_migration' => true,
465 'force' => true,
466 ]);
467
468 WP_CLI::success( "Started migration $id" );
469 }
470
471 /**
472 * Get migrations state
473 *
474 * @return array
475 */
476 private function _get_migrations() {
477 $migrations = apply_filters('wp_stateless_batch_state', [], ['force_migrations' => true]);
478 return $migrations['migrations'] ?? [];
479 }
480
481 /**
482 * List migrations
483 */
484 private function _list_migrations() {
485 $migrations = $this->_get_migrations();
486
487 if ( empty($migrations) ) {
488 WP_CLI::success('No migrations found');
489 }
490
491 $data = [];
492
493 foreach ($migrations as $id => $migration) {
494 $data[$id] = [
495 'id' => $id,
496 'description' => $migration['description'],
497 'status' => $migration['status_text'],
498 'message' => strip_tags($migration['message']),
499 ];
500 }
501
502 WP_CLI\Utils\format_items('table', $data, ['id', 'description', 'status', 'message']);
503 }
504
505 /**
506 * Check progress
507 */
508 private function _check_progress($progress) {
509 global $wpdb;
510
511 $sleep = max($progress, 1);
512 $key = BatchTaskManager::instance()->get_state_key();
513
514 $sql = "SELECT option_value FROM $wpdb->options WHERE option_name = '%s' LIMIT 1";
515 $sql = $wpdb->prepare($sql, $key);
516
517 $description = '';
518
519 do {
520 // We need to omit the cache and get the data directly from the db
521 $state = $wpdb->get_var($sql);
522 $state = maybe_unserialize($state);
523
524 if ( empty($state) || !isset($state['is_migration']) || !$state['is_migration'] ) {
525 $message = empty($description) ? 'Migration finished' : "Migration '$description' finished";
526 WP_CLI::success($message);
527
528 return;
529 }
530
531 $description = $state['description'] ?? '';
532 $completed = $state['completed'] ?? 0;
533 $total = $state['total'] ?? 0;
534
535 $percent = $total > 0 ? round($completed / $total * 100, 2) : 0;
536
537 $message = sprintf("Migration '%s' compeleted %.2f%%: %d of %d items processed", $description, $percent, $completed, $total);
538 WP_CLI::line($message);
539
540 sleep($sleep);
541 } while (true);
542 }
543
544 /**
545 * Runs batches
546 */
547 private function _run_batches($method, $type, $assoc_args) {
548 $batches = isset($assoc_args['batches']) ? $assoc_args['batches'] : 10;
549 if (!is_numeric($batches) || $batches <= 0) {
550 WP_CLI::error('Parameter --batches must have numeric value.');
551 }
552 $limit = isset($assoc_args['limit']) ? $assoc_args['limit'] : 100;
553 if (!is_numeric($limit) || $limit <= 0) {
554 WP_CLI::error('Parameter --limit must have numeric value.');
555 }
556 $force = isset($assoc_args['force']) ? '--force' : '';
557
558 for ($i = 1; $i <= $batches; $i++) {
559
560 if (!empty($this->url)) {
561 $command = "wp stateless {$method} {$type} {$force} --batch={$i} --batches={$batches} --limit={$limit} --url={$this->url}";
562 } else {
563 $command = "wp stateless {$method} {$type} {$force} --batch={$i} --batches={$batches} --limit={$limit}";
564 }
565
566 WP_CLI::line('...');
567 WP_CLI::line("Launching external command '{$command}'");
568 WP_CLI::line('Waiting...');
569
570 @ob_flush();
571 flush();
572
573 $r = SM_CLI::launch($command, false, true);
574
575 if ($r->return_code) {
576 WP_CLI::error("Something went wrong. External command process failed.");
577 } else {
578 echo $r->stdout;
579 }
580 }
581 }
582
583 /**
584 * Optimization process
585 * Runs before command's process
586 */
587 private function _before_command_run() {
588 WP_CLI::line("Starting Database optimization process. Waiting...");
589 @ob_flush();
590 flush();
591 $command = !empty($this->url) ? "wp db optimize --url={$this->url}" : "wp db optimize";
592 $r = SM_CLI::launch($command, false, true);
593 if ($r->return_code) {
594 WP_CLI::error("Something went wrong. Database optimization process failed.");
595 } else {
596 WP_CLI::success("Database is optimized");
597 }
598 }
599
600 /**
601 * Optimization process
602 * Runs after command's process
603 */
604 private function _after_command_run() {
605 //** Run transient flushing */
606 WP_CLI::line("Starting remove transient. Waiting...");
607 @ob_flush();
608 flush();
609 $command = !empty($this->url) ? "wp transient delete-all --url={$this->url}" : "wp transient delete-all";
610 $r = SM_CLI::launch($command, false, true);
611 if ($r->return_code) {
612 WP_CLI::error("Something went wrong. Transient process failed.");
613 } else {
614 WP_CLI::success("Transient is removed");
615 }
616 //** Run MySQL optimization */
617 WP_CLI::line("Starting Database optimization process. Waiting...");
618 @ob_flush();
619 flush();
620 $command = !empty($this->url) ? "wp db optimize --url={$this->url}" : "wp db optimize";
621 $r = SM_CLI::launch($command, false, true);
622 if ($r->return_code) {
623 WP_CLI::error("Something went wrong. Database optimization process failed.");
624 } else {
625 WP_CLI::success("Database is optimized");
626 }
627 }
628 }
629
630 /** Add the commands from above */
631 WP_CLI::add_command('stateless', 'SM_CLI_Command');
632 }
633