PluginProbe ʕ •ᴥ•ʔ
Atarim – AI Agency for WordPress: Edit Pages, Fix Code, Update Plugins, SEO & Client Feedback / trunk
Atarim – AI Agency for WordPress: Edit Pages, Fix Code, Update Plugins, SEO & Client Feedback vtrunk
5.1.3 5.1.2 5.1.1 5.1 5.0 trunk 3.10 3.11 3.12 3.13 3.14 3.15 3.16 3.17 3.18 3.19 3.2.0 3.2.1 3.22 3.22.1 3.22.2 3.22.3 3.22.4 3.22.5 3.22.6 3.3.0 3.3.1 3.3.2 3.3.2.1 3.3.2.2 3.3.3 3.30 3.31 3.32 3.4 3.4.1 3.4.3 3.4.4 3.5 3.5.1 3.6 3.6.1 3.7 3.8 3.9 3.9.1 3.9.2 3.9.3 3.9.4 3.9.6 3.9.6.1 4.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.0.9 4.1.0 4.1.1 4.1.2 4.1.3 4.2 4.2.1 4.2.2 4.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.4
atarim-visual-collaboration / third-party / backup / jetbackup / class-avcf-abilities-jetbackup-restore.php
atarim-visual-collaboration / third-party / backup / jetbackup Last commit date
class-avcf-abilities-jetbackup-backups.php 2 days ago class-avcf-abilities-jetbackup-destinations.php 2 weeks ago class-avcf-abilities-jetbackup-jobs.php 2 weeks ago class-avcf-abilities-jetbackup-queue.php 2 days ago class-avcf-abilities-jetbackup-restore-point.php 2 days ago class-avcf-abilities-jetbackup-restore.php 2 days ago class-avcf-abilities-jetbackup-schedules.php 2 weeks ago class-avcf-abilities-jetbackup-settings.php 2 weeks ago class-avcf-abilities-jetbackup-system.php 2 weeks ago class-avcf-jetbackup-detector.php 2 weeks ago class-avcf-jetbackup-helpers.php 2 days ago
class-avcf-abilities-jetbackup-restore.php
263 lines
1 <?php
2 /**
3 * JetBackup — Restore ability cluster.
4 *
5 * The highest-stakes JetBackup surface:
6 * - jetbackup-restore-backup initiate a restore of an existing snapshot
7 * - jetbackup-get-queue-item read restore progress AND the standalone
8 * restore_url (the "recover even if the site is
9 * broken" link) once it is generated
10 * - jetbackup-get-restore-settings / jetbackup-manage-restore-settings
11 *
12 * Restore is by snapshot id only. JetBackup's file-upload / import-by-path
13 * branches need a multipart upload, which isn't expressible as an MCP ability,
14 * so they're intentionally not exposed here.
15 *
16 * The raw restoreOptions bitmask is hidden behind friendly enums
17 * (entire/include/exclude/skip for files and database) that we compile into the
18 * bitmask server-side — safer for an agent than hand-building a bitmask.
19 *
20 * Gated by manage_options; restore requires confirm:true. Built on JetBackup
21 * 3.1.23.x; not runtime-tested here.
22 *
23 * @package atarim-visual-collaboration
24 */
25
26 if ( ! defined( 'ABSPATH' ) ) {
27 exit;
28 }
29
30 class AVCF_Abilities_JetBackup_Restore extends AVCF_Abilities_Base {
31
32 /** @var AVCF_JetBackup_Detector */
33 private $detector;
34
35 public function __construct() {
36 $this->detector = new AVCF_JetBackup_Detector();
37 }
38
39 public function register() {
40 if ( ! $this->detector->avcf_jetbackup_is_available() ) {
41 return;
42 }
43 $this->register_restore_backup();
44 $this->register_get_queue_item();
45 $this->register_get_restore_settings();
46 $this->register_manage_restore_settings();
47 }
48
49 /* ------------------------------ shared ----------------------------- */
50
51 private function ro_meta() {
52 return [ 'mcp' => [ 'public' => true, 'type' => 'tool' ], 'annotations' => [ 'readonly' => true, 'destructive' => false, 'idempotent' => true ] ];
53 }
54 private function write_meta( $d = false ) {
55 return [ 'mcp' => [ 'public' => true, 'type' => 'tool' ], 'annotations' => [ 'readonly' => false, 'destructive' => (bool) $d, 'idempotent' => false ] ];
56 }
57 public function can() {
58 return AVCF_JetBackup_Helpers::can_manage();
59 }
60 private function out_schema() {
61 return [
62 'type' => 'object',
63 'properties' => [
64 'success' => [ 'type' => 'boolean' ],
65 'message' => [ 'type' => 'string' ],
66 'data' => [ 'type' => 'object' ],
67 ],
68 'required' => [ 'success', 'message' ],
69 ];
70 }
71
72 /**
73 * Compile the friendly files/database enums into JetBackup's restoreOptions
74 * bitmask. Defaults to a full restore (entire files + entire database).
75 */
76 private function compute_restore_options( $files, $db ) {
77 $map_files = [
78 'entire' => AVCF_JetBackup_Helpers::R_FILES_ENTIRE,
79 'include' => AVCF_JetBackup_Helpers::R_FILES_INCLUDE,
80 'exclude' => AVCF_JetBackup_Helpers::R_FILES_EXCLUDE,
81 'skip' => AVCF_JetBackup_Helpers::R_FILES_SKIP,
82 ];
83 $map_db = [
84 'entire' => AVCF_JetBackup_Helpers::R_DB_ENTIRE,
85 'include' => AVCF_JetBackup_Helpers::R_DB_INCLUDE,
86 'exclude' => AVCF_JetBackup_Helpers::R_DB_EXCLUDE,
87 'skip' => AVCF_JetBackup_Helpers::R_DB_SKIP,
88 ];
89 $f = isset( $map_files[ $files ] ) ? $map_files[ $files ] : AVCF_JetBackup_Helpers::R_FILES_ENTIRE;
90 $d = isset( $map_db[ $db ] ) ? $map_db[ $db ] : AVCF_JetBackup_Helpers::R_DB_ENTIRE;
91 return $f | $d;
92 }
93
94 /* ---------------------------- restore-backup ------------------------ */
95
96 private function register_restore_backup() {
97 $self = $this;
98 $output_schema = $this->out_schema();
99 $output_schema['properties']['queue_item_id'] = [
100 'type' => 'integer',
101 'description' => 'The queue item id of the restore just started — poll jetbackup-get-queue-item with this to track progress and read restore_url once available.',
102 ];
103
104 wp_register_ability( 'atarim/jetbackup-restore-backup', [
105 'label' => 'Restore JetBackup Backup',
106 'category' => 'atarim',
107 'description' => 'Initiate a restore of an existing backup snapshot (by id, or by snapshot_name from a completed backup). DESTRUCTIVE: this overwrites the live site with the backup\'s contents; requires confirm:true. Only "account" backups are restorable. restore_files / restore_database choose entire|include|exclude|skip (default entire = full restore); pass folders / tables when using include or exclude. After queuing, poll jetbackup-get-queue-item to read progress and the standalone restore_url (usable to complete the restore even if the site later breaks).',
108 'input_schema' => [
109 'type' => 'object',
110 'properties' => [
111 'id' => [ 'type' => 'integer', 'minimum' => 1, 'description' => 'Backup snapshot id to restore. Provide this OR snapshot_name.' ],
112 'snapshot_name' => [ 'type' => 'string', 'description' => 'Snapshot name to restore (alternative to id) — e.g. the snapshot_name returned by a completed run-backup queue item. Resolved to the snapshot id automatically.' ],
113 'restore_files' => [ 'type' => 'string', 'enum' => [ 'entire', 'include', 'exclude', 'skip' ], 'default' => 'entire', 'description' => 'How to restore files.' ],
114 'restore_database' => [ 'type' => 'string', 'enum' => [ 'entire', 'include', 'exclude', 'skip' ], 'default' => 'entire', 'description' => 'How to restore the database.' ],
115 'folders' => [ 'type' => 'array', 'items' => [ 'type' => 'string' ], 'description' => 'Home-dir folders/files to include or exclude (when restore_files is include/exclude).' ],
116 'tables' => [ 'type' => 'array', 'items' => [ 'type' => 'string' ], 'description' => 'Database tables to include or exclude (when restore_database is include/exclude).' ],
117 'restore_options' => [ 'type' => 'integer', 'description' => 'Advanced: raw JetBackup restoreOptions bitmask. Overrides restore_files/restore_database if provided.' ],
118 'mixed_sites' => [ 'type' => 'boolean', 'description' => 'Multisite: allow mixed-site restore.' ],
119 'confirm' => [ 'type' => 'boolean', 'description' => 'Must be true to proceed with the restore.' ],
120 ],
121 // Deliberately no 'required': this ability takes id OR snapshot_name,
122 // as its description says and its callback implements. Naming id here
123 // made the framework refuse a snapshot_name-only call before the
124 // callback could resolve it, which is how every caller that follows
125 // the documentation was rejected. The callback already answers the
126 // neither-supplied case, and more usefully than the schema does.
127 'additionalProperties' => false,
128 ],
129 'output_schema' => $output_schema,
130 'execute_callback' => function( $input = [] ) use ( $self ) {
131 $input = (array) $input;
132 $snapshot_id = ! empty( $input['id'] ) ? (int) $input['id'] : 0;
133 if ( ! $snapshot_id && ! empty( $input['snapshot_name'] ) ) {
134 $snapshot_id = (int) AVCF_JetBackup_Helpers::resolve_snapshot_id_by_name( $input['snapshot_name'] );
135 if ( ! $snapshot_id ) {
136 return [ 'success' => false, 'message' => sprintf( 'No backup snapshot found with name "%s". Use jetbackup-list-backups to find the snapshot id.', (string) $input['snapshot_name'] ), 'data' => [] ];
137 }
138 }
139 if ( ! $snapshot_id ) {
140 return [ 'success' => false, 'message' => 'Provide either id (snapshot id) or snapshot_name (e.g. from a completed run-backup, or jetbackup-list-backups).', 'data' => [] ];
141 }
142 if ( ! AVCF_JetBackup_Helpers::confirmed( $input ) ) {
143 return AVCF_JetBackup_Helpers::confirm_required_response( 'restore backup — overwrites the live site' );
144 }
145
146 $files = isset( $input['restore_files'] ) ? strtolower( (string) $input['restore_files'] ) : 'entire';
147 $db = isset( $input['restore_database'] ) ? strtolower( (string) $input['restore_database'] ) : 'entire';
148 $opts = isset( $input['restore_options'] ) ? (int) $input['restore_options'] : $self->compute_restore_options( $files, $db );
149
150 $payload = [
151 AVCF_JetBackup_Helpers::ID_FIELD => $snapshot_id,
152 AVCF_JetBackup_Helpers::TYPE_FIELD => AVCF_JetBackup_Helpers::QUEUE_RESTORE,
153 'restoreOptions' => $opts,
154 ];
155
156 if ( in_array( $files, [ 'include', 'exclude' ], true ) && ! empty( $input['folders'] ) ) {
157 $payload['folderList'] = array_values( array_map( 'strval', (array) $input['folders'] ) );
158 }
159 if ( in_array( $db, [ 'include', 'exclude' ], true ) && ! empty( $input['tables'] ) ) {
160 $payload['selectedTables'] = array_values( array_map( 'strval', (array) $input['tables'] ) );
161 }
162 if ( isset( $input['mixed_sites'] ) ) {
163 $payload['mixedSites'] = filter_var( $input['mixed_sites'], FILTER_VALIDATE_BOOLEAN );
164 }
165
166 return AVCF_JetBackup_Helpers::surface_queue_id(
167 AVCF_JetBackup_Helpers::invoke( 'AddToQueue', $payload ),
168 AVCF_JetBackup_Helpers::QUEUE_RESTORE
169 );
170 },
171 'permission_callback' => function() use ( $self ) { return $self->can(); },
172 'meta' => $this->write_meta( true ),
173 ] );
174 }
175
176 /* --------------------------- get-queue-item ------------------------- */
177
178 private function register_get_queue_item() {
179 $self = $this;
180 wp_register_ability( 'atarim/jetbackup-get-queue-item', [
181 'label' => 'Get JetBackup Queue Item',
182 'category' => 'atarim',
183 'description' => 'Get a single JetBackup queue item by id, including status and progress. For a completed BACKUP item, data includes snapshot_name (the snapshot it produced) — pass that to jetbackup-restore-backup. For a RESTORE item, data includes restore_url — the standalone recovery link — once the item reaches the waiting-for-restore stage.',
184 'input_schema' => [
185 'type' => 'object',
186 'properties' => [ 'id' => [ 'type' => 'integer', 'minimum' => 1, 'description' => 'Queue item id.' ] ],
187 'required' => [ 'id' ],
188 'additionalProperties' => false,
189 ],
190 'output_schema' => $this->out_schema(),
191 'execute_callback' => function( $input = [] ) {
192 $input = (array) $input;
193 if ( empty( $input['id'] ) ) {
194 return [ 'success' => false, 'message' => 'Missing id (queue item id).', 'data' => [] ];
195 }
196 return AVCF_JetBackup_Helpers::invoke( 'GetQueueItem', AVCF_JetBackup_Helpers::id_payload( $input ) );
197 },
198 'permission_callback' => function() use ( $self ) { return $self->can(); },
199 'meta' => $this->ro_meta(),
200 ] );
201 }
202
203 /* ----------------------- get-restore-settings ----------------------- */
204
205 private function register_get_restore_settings() {
206 $self = $this;
207 wp_register_ability( 'atarim/jetbackup-get-restore-settings', [
208 'label' => 'Get JetBackup Restore Settings',
209 'category' => 'atarim',
210 'description' => 'Get JetBackup restore settings (compatibility check, allow cross-domain, alternate path, wp-content only).',
211 'input_schema' => [ 'type' => 'object', 'properties' => new \stdClass(), 'additionalProperties' => false ],
212 'output_schema' => $this->out_schema(),
213 'execute_callback' => function( $input = [] ) {
214 return AVCF_JetBackup_Helpers::invoke( 'GetSettingsRestore', [] );
215 },
216 'permission_callback' => function() use ( $self ) { return $self->can(); },
217 'meta' => $this->ro_meta(),
218 ] );
219 }
220
221 /* --------------------- manage-restore-settings ---------------------- */
222
223 private function register_manage_restore_settings() {
224 $self = $this;
225 wp_register_ability( 'atarim/jetbackup-manage-restore-settings', [
226 'label' => 'Manage JetBackup Restore Settings',
227 'category' => 'atarim',
228 'description' => 'Update JetBackup restore settings. Only the fields you pass are changed. All are booleans: compatibility_check, allow_cross_domain, alternate_path, wp_content_only.',
229 'input_schema' => [
230 'type' => 'object',
231 'properties' => [
232 'compatibility_check' => [ 'type' => 'boolean', 'description' => 'Run compatibility checks before restore.' ],
233 'allow_cross_domain' => [ 'type' => 'boolean', 'description' => 'Allow restoring a backup taken on a different domain.' ],
234 'alternate_path' => [ 'type' => 'boolean', 'description' => 'Use an alternate wp-config.php path during restore.' ],
235 'wp_content_only' => [ 'type' => 'boolean', 'description' => 'Restore wp-content only.' ],
236 ],
237 'additionalProperties' => false,
238 ],
239 'output_schema' => $this->out_schema(),
240 'execute_callback' => function( $input = [] ) {
241 $input = (array) $input;
242 $map = [
243 'compatibility_check' => 'RESTORE_COMPATIBILITY_CHECK',
244 'allow_cross_domain' => 'RESTORE_ALLOW_CROSS_DOMAIN',
245 'alternate_path' => 'RESTORE_ALTERNATE_PATH',
246 'wp_content_only' => 'RESTORE_WP_CONTENT_ONLY',
247 ];
248 $payload = [];
249 foreach ( $map as $friendly => $key ) {
250 if ( array_key_exists( $friendly, $input ) ) {
251 $payload[ $key ] = filter_var( $input[ $friendly ], FILTER_VALIDATE_BOOLEAN );
252 }
253 }
254 if ( empty( $payload ) ) {
255 return [ 'success' => false, 'message' => 'No restore settings provided to change.', 'data' => [] ];
256 }
257 return AVCF_JetBackup_Helpers::invoke( 'ManageSettingsRestore', $payload );
258 },
259 'permission_callback' => function() use ( $self ) { return $self->can(); },
260 'meta' => $this->write_meta( false ),
261 ] );
262 }
263 }