| 1 |
<?php |
| 2 |
// phpcs:disable WordPress.WP.AlternativeFunctions -- WP-CLI command: reads STDIN or one local file, no WP_Filesystem. |
| 3 |
|
| 4 |
namespace FlyWP\StaticSite\Engine\Cli; |
| 5 |
|
| 6 |
use FlyWP\StaticSite\Engine\Incremental\ChangeStore; |
| 7 |
use FlyWP\StaticSite\Engine\Incremental\WpChangeStore; |
| 8 |
|
| 9 |
/** |
| 10 |
* WP-CLI command `wp flywp static changes clear` (contract N2). The FlyWP deploy script runs it after |
| 11 |
* a "success" or "partial" build, full or incremental. |
| 12 |
* |
| 13 |
* The change file of the engine: `{"max_change_id": 42, "retry_urls": ["https://..."]}`. |
| 14 |
* |
| 15 |
* - Adds a "url" change row (reason "build_error") for each retry URL. The engine puts only the provided URLs |
| 16 |
* that failed for a temporary cause (no HTTP answer, 429 or 5xx) in retry_urls. |
| 17 |
* - Then deletes the change rows up to max_change_id. A full build covers each change, so it also clears |
| 18 |
* the "global" rows. The retry rows get IDs above max_change_id, so the clear does not remove them. |
| 19 |
* |
| 20 |
* @since 1.8.0 |
| 21 |
*/ |
| 22 |
class ChangesCommand extends JsonCommand { |
| 23 |
|
| 24 |
/** |
| 25 |
* @var ChangeStore |
| 26 |
*/ |
| 27 |
private $store; |
| 28 |
|
| 29 |
/** |
| 30 |
* @var string The file that `--file=-` reads. |
| 31 |
*/ |
| 32 |
private $stdin; |
| 33 |
|
| 34 |
/** |
| 35 |
* @param ChangeStore|null $store Change rows. The database table when null. |
| 36 |
* @param callable|null $output Line writer. STDOUT when null. |
| 37 |
* @param string $stdin The file that `--file=-` reads. |
| 38 |
*/ |
| 39 |
public function __construct( ChangeStore $store = null, $output = null, $stdin = 'php://stdin' ) { |
| 40 |
parent::__construct( $output ); |
| 41 |
|
| 42 |
$this->store = $store ? $store : new WpChangeStore(); |
| 43 |
$this->stdin = $stdin; |
| 44 |
} |
| 45 |
|
| 46 |
/** |
| 47 |
* Clear the change rows that a static build used, and add rows for the URLs to try again. |
| 48 |
* |
| 49 |
* ## OPTIONS |
| 50 |
* |
| 51 |
* [--file=<file>] |
| 52 |
* : The change file that the engine wrote, or "-" for STDIN. Necessary. |
| 53 |
* |
| 54 |
* ## EXAMPLES |
| 55 |
* |
| 56 |
* wp flywp static changes clear --file=- < changes.json |
| 57 |
* |
| 58 |
* @param array $args Positional arguments. |
| 59 |
* @param array $assoc_args Named arguments. |
| 60 |
* |
| 61 |
* @return void |
| 62 |
*/ |
| 63 |
public function __invoke( $args, $assoc_args ) { |
| 64 |
$this->execute( $assoc_args ); |
| 65 |
} |
| 66 |
|
| 67 |
/** |
| 68 |
* {@inheritdoc} |
| 69 |
*/ |
| 70 |
public function run( array $assoc_args ) { |
| 71 |
$file = isset( $assoc_args['file'] ) ? (string) $assoc_args['file'] : ''; |
| 72 |
$changes = json_decode( $this->read( $file ), true ); |
| 73 |
|
| 74 |
if ( ! is_array( $changes ) || ! isset( $changes['max_change_id'] ) || ! is_int( $changes['max_change_id'] ) ) { |
| 75 |
return $this->result( sprintf( 'Unable to read the change file "%s".', $file ) ); |
| 76 |
} |
| 77 |
|
| 78 |
$errors = []; |
| 79 |
|
| 80 |
foreach ( isset( $changes['retry_urls'] ) ? (array) $changes['retry_urls'] : [] as $url ) { |
| 81 |
if ( ! $this->store->insert( 'url', null, null, (string) $url, 'build_error' ) ) { |
| 82 |
$errors[] = 'Some failed URLs were not added to the change log. The next incremental build does not try them again.'; |
| 83 |
break; |
| 84 |
} |
| 85 |
} |
| 86 |
|
| 87 |
if ( $changes['max_change_id'] > 0 && ! $this->store->clear_up_to( $changes['max_change_id'] ) ) { |
| 88 |
$errors[] = sprintf( 'The change log was not cleared up to row %d. The next incremental build does these changes again.', $changes['max_change_id'] ); |
| 89 |
} |
| 90 |
|
| 91 |
return $this->result( $errors ? implode( ' ', $errors ) : null ); |
| 92 |
} |
| 93 |
|
| 94 |
/** |
| 95 |
* Read the change file. The FlyWP deploy script sends it through STDIN: the build folder is on the host, |
| 96 |
* and the site container has no mount of it. |
| 97 |
* |
| 98 |
* @param string $file A path, or "-" for STDIN. |
| 99 |
* |
| 100 |
* @return string The file content, or an empty string when the file cannot be read. |
| 101 |
*/ |
| 102 |
private function read( $file ) { |
| 103 |
if ( $file === '-' ) { |
| 104 |
return (string) file_get_contents( $this->stdin ); |
| 105 |
} |
| 106 |
|
| 107 |
return $file !== '' && is_readable( $file ) ? (string) file_get_contents( $file ) : ''; |
| 108 |
} |
| 109 |
} |
| 110 |
|