PluginProbe ʕ •ᴥ•ʔ
JetBackup – Backup, Restore & Migrate / trunk
JetBackup – Backup, Restore & Migrate vtrunk
3.1.22.3 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.8.1 1.4.9 1.5.0 1.5.1 1.5.1.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 1.6.10 1.6.11 1.6.12 1.6.13 1.6.15 1.6.5.1 1.6.8.8 1.6.9 1.6.9.1 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7.5 2.0.8.7 2.0.9.11 2.0.9.14 2.0.9.15 2.0.9.6 2.0.9.7 2.0.9.9 3.1.10.7 3.1.11.1 3.1.12.3 3.1.13.4 3.1.14.17 3.1.15.4 3.1.16.1 3.1.17.5 3.1.18.10 3.1.18.8 3.1.18.9 3.1.19.8 3.1.20.3 3.1.21.3 3.1.7.9 3.1.9.2 trunk 1.1.90 1.1.91 1.2.0 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2
backup / src / JetBackup / Cron / Task / PreRestore.php
backup / src / JetBackup / Cron / Task Last commit date
.htaccess 1 year ago Backup.php 1 year ago Download.php 1 year ago DownloadBackupLog.php 1 year ago Export.php 1 year ago Extract.php 1 year ago PreRestore.php 5 months ago Reindex.php 1 month ago Restore.php 5 months ago RetentionCleanup.php 4 months ago System.php 5 months ago Task.php 4 months ago index.html 1 year ago web.config 1 year ago
PreRestore.php
384 lines
1 <?php
2
3 namespace JetBackup\Cron\Task;
4
5 use Exception;
6 use JetBackup\Archive\Archive;
7 use JetBackup\BackupJob\BackupJob;
8 use JetBackup\Data\Engine;
9 use JetBackup\Destination\Destination;
10 use JetBackup\Encryption\Crypt;
11 use JetBackup\Entities\Util;
12 use JetBackup\Exception\DBException;
13 use JetBackup\Exception\DownloaderException;
14 use JetBackup\Exception\ExecutionTimeException;
15 use JetBackup\Exception\RestoreException;
16 use JetBackup\Exception\SGBExtractorException;
17 use JetBackup\Exception\SnapshotMetaException;
18 use JetBackup\Exception\TaskException;
19 use JetBackup\Factory;
20 use JetBackup\JetBackup;
21 use JetBackup\License\License;
22 use JetBackup\Queue\Queue;
23 use JetBackup\Queue\QueueItem;
24 use JetBackup\Queue\QueueItemRestore;
25 use JetBackup\ResumableTask\ResumableTask;
26 use JetBackup\SGB\Extractor;
27 use JetBackup\Snapshot\Snapshot;
28 use JetBackup\Snapshot\SnapshotDownload;
29 use JetBackup\Wordpress\Helper;
30 use JetBackup\Wordpress\Wordpress;
31 use SleekDB\Exceptions\InvalidArgumentException;
32 use SleekDB\Exceptions\IOException;
33
34 if (!defined( '__JETBACKUP__')) die('Direct access is not allowed');
35
36 class PreRestore extends Task {
37
38 const LOG_FILENAME = 'pre_restore';
39
40 const RESTORE_FILE_NAME = 'jetbackup.restore';
41
42 private QueueItemRestore $_queue_item_restore;
43 public ?Snapshot $_snapshot=null;
44
45 public function __construct() {
46 parent::__construct(self::LOG_FILENAME);
47 }
48
49 /**
50 * @return void
51 * @throws DBException
52 * @throws IOException
53 * @throws InvalidArgumentException
54 * @throws TaskException
55 */
56 public function execute():void {
57 parent::execute();
58
59 if($this->getQueueItem()->getStatus() >= Queue::STATUS_RESTORE_WAITING_FOR_RESTORE) {
60
61 // If external restore hasn't initiated within 24 hours we need to abort the restore process
62 if($this->getQueueItem()->getStatusTime() < (time() - (60 * 60 * 24))) {
63 $this->getLogController()->logError("External restore hasn't initiated within the last 24 hours, Aborting the restore");
64 $this->getQueueItem()->updateStatus(Queue::STATUS_ABORTED);
65 $this->getQueueItem()->updateProgress('Restore Aborted!', QueueItem::PROGRESS_LAST_STEP);
66 } else {
67 $this->getLogController()->logMessage('Waiting for external restore');
68 }
69
70 return;
71 }
72
73 $this->_queue_item_restore = $this->getQueueItem()->getItemData();
74
75 if($this->_queue_item_restore->getSnapshotId()) {
76
77 $snapshot = new Snapshot($this->_queue_item_restore->getSnapshotId());
78 $destination = new Destination($snapshot->getDestinationId());
79
80 if(!License::isValid() &&
81 !in_array($destination->getType(), Destination::LICENSE_EXCLUDED) &&
82 $snapshot->getEngine() != Engine::ENGINE_JB) {
83
84 $this->getLogController()->logError("You can't restore from {$destination->getType()} destination without a license");
85 $this->getQueueItem()->updateStatus(Queue::STATUS_ABORTED);
86 $this->getQueueItem()->updateProgress('Restore Aborted!', QueueItem::PROGRESS_LAST_STEP);
87 return;
88 }
89 }
90
91 if($this->getQueueItem()->getStatus() == Queue::STATUS_PENDING) {
92 $this->getLogController()->logMessage("Starting restore");
93
94 $this->getQueueItem()->getProgress()->setTotalItems( count(Queue::STATUS_PRE_RESTORE_NAMES) + 3);
95 $this->getQueueItem()->save();
96
97 $this->getQueueItem()->updateProgress('Starting restore');
98 } else if($this->getQueueItem()->getStatus() > Queue::STATUS_PENDING) {
99 $this->getLogController()->logMessage('Resumed Restore');
100 }
101
102 try {
103
104 if(!$this->_queue_item_restore->getSnapshotId() && !$this->_queue_item_restore->getSnapshotPath())
105 throw new RestoreException("No snapshot id or path provided");
106 $this->getLogController()->logDebug('Item data: ' . print_r($this->_queue_item_restore, 1));
107 $this->_snapshot = $this->func([$this, '_download']);
108 $this->func([$this, '_extract']);
109 $this->func([$this, '_build_url']);
110
111 if($this->getQueueItem()->getStatus() < Queue::STATUS_DONE) $this->getQueueItem()->updateStatus(Queue::STATUS_RESTORE_WAITING_FOR_RESTORE);
112 $this->getLogController()->logMessage('Completed!');
113 } catch(Exception $e) {
114 $this->getQueueItem()->updateStatus(Queue::STATUS_FAILED);
115 $this->getLogController()->logError($e->getMessage());
116 $this->getLogController()->logMessage('Failed!');
117 }
118
119 $this->getQueueItem()->updateProgress($this->getQueueItem()->getStatus() == Queue::STATUS_RESTORE_WAITING_FOR_RESTORE ? 'Pre Restore Completed!' : 'Pre Restore Failed!', QueueItem::PROGRESS_LAST_STEP);
120 $this->getLogController()->logMessage('Total time: ' . $this->getExecutionTimeElapsed());
121 }
122
123 public static function findPublicRestoreFiles() : array {
124 $basePath = Factory::getWPHelper()->getRestoreFileLocation() . JetBackup::SEP . self::RESTORE_FILE_NAME . '.*';
125 if (defined('GLOB_BRACE')) return glob($basePath . '.{php,php.lock}', \GLOB_BRACE);
126 $phpFiles = glob($basePath . '.php');
127 $phpLockFiles = glob($basePath . '.php.lock');
128 return array_merge($phpFiles ?: [], $phpLockFiles ?: []);
129 }
130
131 /**
132 * @return void
133 * @throws IOException
134 * @throws InvalidArgumentException
135 * @throws RestoreException
136 */
137 public function _build_url() {
138
139 $this->getLogController()->logMessage('[ _build_url ]');
140 $this->getLogController()->logMessage('Execution time: ' . $this->getExecutionTimeElapsed());
141 $this->getLogController()->logMessage('TTL time: ' . $this->getExecutionTimeLimit());
142
143 $this->getQueueItem()->updateStatus(Queue::STATUS_RESTORE_BUILD_URL);
144 $this->getQueueItem()->updateProgress('Building restore URL');
145
146 $template_file = JetBackup::SRC_PATH . JetBackup::SEP . 'Restore' . JetBackup::SEP . 'restore.template.php';
147 if(!file_exists($template_file)) throw new RestoreException("Restore template file not found");
148
149 $public_dir = rtrim(Factory::getWPHelper()->getWordPressHomedir(), JetBackup::SEP);
150 foreach (self::findPublicRestoreFiles() as $file) @unlink($file);
151
152 // Capture runtime DB credentials for cloud environments where wp-config.php
153 // doesn't contain literal credentials (e.g., WordPress.com, WP Cloud, Porkbun)
154 $runtime_credentials = json_encode([
155 'db_name' => defined('DB_NAME') ? DB_NAME : '',
156 'db_user' => defined('DB_USER') ? DB_USER : '',
157 'db_password' => defined('DB_PASSWORD') ? DB_PASSWORD : '',
158 'db_host' => defined('DB_HOST') ? DB_HOST : '',
159 'table_prefix' => $GLOBALS['table_prefix'] ?? 'wp_',
160 ]);
161 $encrypted_creds = Crypt::encrypt($runtime_credentials, $this->getQueueItem()->getUniqueId());
162
163 $content = "<?php define('__JETBACKUP_RESTORE__', true); ?>\n";
164 $content .= "<?php define('WP_ROOT', '$public_dir'); ?>\n";
165 $content .= "<?php define('PUBLIC_PATH', '" . (Factory::getSettingsRestore()->isRestoreAlternatePathEnabled() ? str_repeat('../', substr_count(JetBackup::CRON_PUBLIC_URL, '/')) : '') . "'); ?>\n";
166 $content .= "<?php define('JB_RUNTIME_CREDENTIALS', '$encrypted_creds'); ?>\n";
167 $content .= file_get_contents($template_file);
168
169 $restore_file_name = self::RESTORE_FILE_NAME . '.' . Util::generateRandomString(24) . '.php';
170 $restore_file = Factory::getWPHelper()->getRestoreFileLocation() . JetBackup::SEP . $restore_file_name;
171
172 if(!file_put_contents($restore_file, $content)) throw new RestoreException("Failed creating restore file $restore_file");
173
174 // Dev Remark
175 //symlink('wp-content/plugins/backup/src/JetBackup/Restore/restore.template.php', $restore_file);
176
177 $alternate_path = Factory::getSettingsRestore()->isRestoreAlternatePathEnabled() ? JetBackup::CRON_PUBLIC_URL : '';
178
179 $url = Wordpress::getSiteURL() . $alternate_path . '/' . $restore_file_name . '?id=' . $this->getQueueItem()->getUniqueId();
180
181 $this->getLogController()->logMessage('Restore URL: ' . $url);
182
183 $this->_queue_item_restore->setRestoreURL($url);
184 $this->getQueueItem()->save();
185 }
186
187 /**
188 * @return void
189 * @throws DBException
190 * @throws DownloaderException
191 * @throws ExecutionTimeException
192 * @throws IOException
193 * @throws InvalidArgumentException
194 */
195 public function _extract() {
196
197 if($this->_snapshot->getEngine() == Engine::ENGINE_JB) return;
198
199 $this->getLogController()->logMessage('[ _extract ]');
200 $this->getLogController()->logMessage('Execution time: ' . $this->getExecutionTimeElapsed());
201 $this->getLogController()->logMessage('TTL time: ' . $this->getExecutionTimeLimit());
202
203 $this->getQueueItem()->updateStatus(Queue::STATUS_RESTORE_EXTRACT);
204 $this->getQueueItem()->updateProgress('Extracting backup items');
205
206 if($this->_snapshot->getEngine() == Engine::ENGINE_SGB) {
207
208 // SGB snapshot has only 1 item
209 $item = $this->_snapshot->getItems()[0];
210
211 $path = $this->getQueueItem()->getWorkspace() . JetBackup::SEP . $item->getPath();
212
213 try {
214 $extractor = new Extractor($path, $this->getQueueItem()->getWorkspace());
215 $extractor->setLogController($this->getLogController());
216 $extractor->extract(function() {
217 $this->checkExecutionTime();
218 });
219 } catch(SGBExtractorException $e) {
220 throw new DownloaderException($e->getMessage());
221 }
222
223 unlink($path);
224
225 } else {
226
227 $this->func(function () {
228 $this->_snapshot->extract($this->getQueueItem()->getWorkspace(), $this->getLogController(), function(string $type, string $action, int $total, int $read) {
229
230 $progress = $this->getQueueItem()->getProgress();
231 $progress->setMessage($type); // gzip / archive
232 $progress->setSubMessage($action); // decompress / extract
233 $progress->setTotalSubItems($total);
234 $progress->setCurrentSubItem($read);
235 $this->getQueueItem()->save();
236
237 // Call checkExecutionTime, passing the desired variables
238 $this->checkExecutionTime(function () use ($type, $action, $total, $read) {
239 $progress = $this->getQueueItem()->getProgress();
240 $progress->setMessage($type);
241 $progress->setSubMessage('Waiting for next cron');
242 $progress->setTotalSubItems($total);
243 $progress->setCurrentSubItem($read);
244 $this->getQueueItem()->save();
245 });
246
247 }, $this->_queue_item_restore->getExcludes(), $this->_queue_item_restore->getIncludes());
248 }, [], 'snapshot_extract');
249
250 // done extract, reset sub process bar
251 $this->getQueueItem()->getProgress()->resetSub();
252 $this->getQueueItem()->save();
253
254 }
255 }
256
257 /**
258 * @return void
259 * @throws RestoreException
260 * @throws DBException
261 * @throws IOException
262 * @throws InvalidArgumentException
263 * @throws Exception
264 */
265 public function _download():Snapshot {
266
267 $this->getLogController()->logMessage('[ _download ]');
268 $this->getLogController()->logMessage('Execution time: ' . $this->getExecutionTimeElapsed());
269 $this->getLogController()->logMessage('TTL time: ' . $this->getExecutionTimeLimit());
270
271 $this->getQueueItem()->updateStatus(Queue::STATUS_RESTORE_DOWNLOAD);
272 if($this->_queue_item_restore->getSnapshotId()) {
273 $this->getLogController()->logMessage('[ _download ] Downloading backup');
274 $this->getQueueItem()->updateProgress('Downloading backup');
275
276 $snapshot = new Snapshot($this->_queue_item_restore->getSnapshotId());
277
278 $this->getLogController()->logDebug('[ _download ] getOptions bitwise value: ' . $this->_queue_item_restore->getOptions());
279 $this->getLogController()->logDebug('[ _download ] getOptions decimal value: ' . decbin($this->_queue_item_restore->getOptions()));
280
281 if($snapshot->getEngine() == Engine::ENGINE_JB) return $snapshot;
282
283 $items_excluded = [];
284
285 if (($this->_queue_item_restore->getOptions() & QueueItemRestore::OPTION_RESTORE_FILES_SKIP)) {
286 $this->getLogController()->logDebug('[ _download ] Skipping Homedir');
287 $items_excluded[] = BackupJob::BACKUP_ACCOUNT_CONTAINS_HOMEDIR;
288 }
289
290 if (($this->_queue_item_restore->getOptions() & QueueItemRestore::OPTION_RESTORE_DATABASE_SKIP)) {
291 $this->getLogController()->logDebug('[ _download ] Skipping Database');
292 $items_excluded[] = BackupJob::BACKUP_ACCOUNT_CONTAINS_DATABASE;
293 }
294
295 $this->getLogController()->logDebug('[ _download ] Excluded items: ' . print_r($items_excluded, true));
296
297 $download = new SnapshotDownload($snapshot, $this->getQueueItem()->getWorkspace());
298 $download->setLogController($this->getLogController());
299 $download->setQueueItem($this->getQueueItem());
300 $download->setTask($this);
301 $download->setExcludedItems($items_excluded);
302 $download->setExcludedDatabases($this->_queue_item_restore->getExcludedDatabases());
303 $download->setIncludedDatabases($this->_queue_item_restore->getIncludedDatabases());
304 $download->downloadAll();
305
306 // done downloading, reset sub process bar
307 $this->getQueueItem()->getProgress()->resetSub();
308 $this->getQueueItem()->save();
309
310 } elseif($this->_queue_item_restore->getSnapshotPath()) {
311 $this->getLogController()->logMessage('[ _download ] Extracting backup from path');
312 $this->getQueueItem()->updateProgress('Extracting backup from path');
313 $snapshot = $this->_extractBackupFile();
314 }
315
316 return $snapshot;
317 }
318
319 /**
320 * @throws RestoreException
321 * @throws Exception
322 */
323 private function _extractBackupFile():Snapshot {
324 $path = $this->_queue_item_restore->getSnapshotPath();
325 if(!file_exists($path)) throw new RestoreException("The provided backup path doesn't exists");
326
327 if(Archive::isGzCompressed($path)) {
328 $this->getLogController()->logMessage('[ _extractBackupFile ] Decompressing GZIP');
329 $this->func(['\JetBackup\Archive\Gzip', 'decompress'], [$path, ResumableTask::PARAMS_EXECUTION_TIME]);
330 $path = substr($path, 0, -3); // remove .gz from name
331 }
332
333 if(!Archive::isTar($path)) throw new RestoreException("Invalid backup file provided, Should be tar.gz/tar.gz file");
334
335 $this->func(function($path) {
336 $archive = new Archive($path);
337 $archive->setExtractFileCallback(function($type,$action,$total,$read) {
338 $progress = $this->getQueueItem()->getProgress();
339 $progress->setMessage($type);
340 $progress->setSubMessage($action);
341 $progress->setTotalSubItems($total);
342 $progress->setCurrentSubItem($read);
343 $this->getQueueItem()->save();
344
345 $this->checkExecutionTime(function () use ($type, $action, $total, $read) {
346 $progress = $this->getQueueItem()->getProgress();
347 $progress->setMessage($type);
348 $progress->setSubMessage('Waiting for next cron');
349 $progress->setTotalSubItems($total);
350 $progress->setCurrentSubItem($read);
351 $this->getQueueItem()->save();
352 });
353
354 });
355 $archive->setExcludeCallback(function($path, $is_dir) {
356 $excludes = $this->_queue_item_restore->getExcludes();
357 foreach($excludes as $exclude) if(fnmatch($exclude, $path) || ($is_dir && str_ends_with($exclude, '/') && fnmatch(substr($exclude, 0, -1), $path))) return true;
358 return false;
359 });
360 $archive->setLogController($this->getLogController());
361 $archive->extract($this->getQueueItem()->getWorkspace());
362 unlink($path);
363 }, [$path], 'extract');
364
365 // done extract, reset sub process bar
366 $this->getQueueItem()->getProgress()->resetSub();
367 $this->getQueueItem()->save();
368
369 $meta_file = sprintf(Snapshot::META_FILEPATH, $this->getQueueItem()->getWorkspace());
370 if(!file_exists($meta_file)) throw new RestoreException("The provided backup doesn't contains meta file");
371
372 $this->getLogController()->logDebug("[ _extractBackupFile ] Importing meta file $meta_file");
373 $snapshot = new Snapshot();
374
375
376 try {
377 $snapshot->importMeta($meta_file, true);
378 } catch(SnapshotMetaException $e) {
379 throw new RestoreException("Can't use the provided backup file. Error: " . $e->getMessage());
380 }
381
382 return $snapshot;
383 }
384 }