PluginProbe
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.12.0
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.12.0
4.12.0 4.11.2 4.11.1 4.11.0 4.10.0 4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.1 trunk 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 All 67 releases
wp-staging / Backup / Task / FileRestoreTask.php

FileRestoreTask.php in WP STAGING – WordPress Backups, Restore, Migration & Clone 4.12.0, at Backup/Task/FileRestoreTask.php

383 lines 8.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPStaging\Backup\Task;
4
5 use WPStaging\Framework\Adapter\Directory;
6 use WPStaging\Framework\Filesystem\Filesystem;
7 use WPStaging\Framework\Filesystem\MissingFileException;
8 use WPStaging\Framework\Filesystem\PathIdentifier;
9 use WPStaging\Framework\Queue\FinishedQueueException;
10 use WPStaging\Framework\Queue\SeekableQueueInterface;
11 use WPStaging\Framework\Utils\Cache\Cache;
12 use WPStaging\Framework\Traits\EndOfLinePlaceholderTrait;
13 use WPStaging\Framework\Traits\RestoreFileExclusionTrait;
14 use WPStaging\Framework\Job\Dto\StepsDto;
15 use WPStaging\Framework\Job\Dto\TaskResponseDto;
16 use WPStaging\Backup\Entity\BackupMetadata;
17 use WPStaging\Framework\Job\Interfaces\FileTaskInterface;
18 use WPStaging\Framework\Job\Task\FileHandler\FileProcessor;
19 use WPStaging\Framework\SiteInfo;
20 use WPStaging\Vendor\Psr\Log\LoggerInterface;
21 use WPStaging\Framework\Facades\Hooks;
22
23
24
25
26
27
28
29
30
31
32
33
34 abstract class FileRestoreTask extends RestoreTask implements FileTaskInterface
35 {
36 use EndOfLinePlaceholderTrait;
37 use RestoreFileExclusionTrait;
38
39
40
41
42 const FILTER_EXCLUDE_FILES_DURING_RESTORE = 'wpstg.backup.restore.exclude_paths';
43
44
45
46
47
48 const FILTER_EXCLUDE_ENQUEUE_DELETE = 'wpstg.backup.restore.exclude_enqueue_delete';
49
50
51
52
53 protected $filesystem;
54
55
56
57
58 protected $directory;
59
60
61
62
63 private $restoreFileProcessor;
64
65
66
67
68 protected $processedNow;
69
70
71
72
73 protected $pathIdentifier;
74
75
76
77
78 protected $isSiteHostedOnWordPressCom = false;
79
80
81
82
83 private $movedAsideDestinations = [];
84
85 public function __construct(
86 LoggerInterface $logger,
87 Cache $cache,
88 StepsDto $stepsDto,
89 SeekableQueueInterface $taskQueue,
90 Filesystem $filesystem,
91 Directory $directory,
92 FileProcessor $restoreFileProcessor,
93 PathIdentifier $pathIdentifier,
94 SiteInfo $siteInfo
95 ) {
96 parent::__construct($logger, $cache, $stepsDto, $taskQueue);
97 $this->filesystem = $filesystem;
98 $this->directory = $directory;
99 $this->restoreFileProcessor = $restoreFileProcessor;
100 $this->pathIdentifier = $pathIdentifier;
101 $this->isSiteHostedOnWordPressCom = $siteInfo->isHostedOnWordPressCom();
102 }
103
104
105
106
107 public function prepareFileRestore()
108 {
109 if ($this->stepsDto->getTotal() === 0) {
110 $this->buildQueue();
111 $this->taskQueue->seek(0);
112
113
114 $this->stepsDto->setTotal(100);
115 }
116 }
117
118
119
120
121 public function execute(): TaskResponseDto
122 {
123 if ($this->isSkipped()) {
124 $this->stepsDto->finish();
125 $this->logger->warning(sprintf(esc_html__('%s skipped by filter!', 'wp-staging'), static::getTaskTitle()));
126 return $this->generateResponse(false);
127 }
128
129 try {
130 $this->checkMissingParts();
131 } catch (MissingFileException $ex) {
132 $this->stepsDto->finish();
133 $this->logger->warning(sprintf(esc_html__('%s skipped due to missing part!', 'wp-staging'), static::getTaskTitle()));
134 return $this->generateResponse(false);
135 }
136
137 $this->prepareFileRestore();
138
139 try {
140 while (!$this->isThreshold()) {
141 $this->processNextItemInQueue();
142 $this->processedNow++;
143 }
144 } catch (FinishedQueueException $e) {
145 $this->stepsDto->finish();
146 }
147
148 $this->logger->info(sprintf(esc_html__('%s (processed %d items)', 'wp-staging'), static::getTaskTitle(), (int)$this->processedNow));
149
150 return $this->generateResponse(false);
151 }
152
153 protected function getOriginalSuffix(): string
154 {
155 return '_wpstg_tmp';
156 }
157
158
159
160
161
162
163
164
165
166 abstract protected function buildQueue();
167
168
169
170
171
172
173 abstract protected function getParts(): array;
174
175
176
177
178
179
180 abstract protected function isSkipped(): bool;
181
182
183
184
185
186
187
188 protected function checkMissingParts()
189 {
190 if (!$this->jobDataDto->getBackupMetadata()->getIsMultipartBackup()) {
191 return;
192 }
193
194 $parts = $this->getParts();
195
196 $backupDir = $this->directory->getBackupDirectory();
197
198 foreach ($parts as $part) {
199 $filepath = $backupDir . $part;
200 if (!file_exists($filepath)) {
201 throw new MissingFileException();
202 }
203 }
204 }
205
206
207
208
209
210 protected function processNextItemInQueue()
211 {
212 $nextInQueueRaw = $this->taskQueue->dequeue();
213
214 if (is_null($nextInQueueRaw)) {
215 throw new FinishedQueueException();
216 }
217
218
219 if ($nextInQueueRaw === '') {
220 return;
221 }
222
223 $nextInQueue = json_decode($nextInQueueRaw, true);
224
225
226 if (!is_array($nextInQueue)) {
227 $this->logger->warning(sprintf(
228 __('%s: An internal error occurred that prevented this item from being restored. Skipping it... (Error Code: INVALID_QUEUE_ITEM)', 'wp-staging'),
229 static::getTaskTitle()
230 ));
231 $this->logger->debug($nextInQueueRaw);
232
233 return;
234 }
235
236
237 array_map(function ($requiredKey) use ($nextInQueue, $nextInQueueRaw) {
238 if (!array_key_exists($requiredKey, $nextInQueue)) {
239 $this->logger->warning(sprintf(
240 __('%s: An internal error occurred that prevented this item from being restored. Skipping it... (Error Code: INVALID_QUEUE_ITEM)', 'wp-staging'),
241 static::getTaskTitle()
242 ));
243 $this->logger->debug($nextInQueueRaw);
244
245 return;
246 }
247 }, ['action', 'source', 'destination']);
248
249 $source = $nextInQueue['source'];
250
251
252
253 $destination = $nextInQueue['destination'];
254 $destination = $this->replacePlaceholdersWithEOLs($destination);
255 $destination = wp_normalize_path($destination);
256
257
258 $this->restoreFileProcessor->handle($nextInQueue['action'], $source, $destination, $this, $this->logger);
259 }
260
261
262
263
264
265
266 public function enqueueMove(string $source, string $destination)
267 {
268 $destination = wp_normalize_path($destination);
269
270 if ($this->hasOriginalSuffix($destination)) {
271 $this->movedAsideDestinations[] = untrailingslashit($destination);
272 }
273
274 $this->enqueue([
275 'action' => 'move',
276 'source' => wp_normalize_path($source),
277 'destination' => $destination,
278 ]);
279 }
280
281
282
283
284
285 public function enqueueDelete(string $path)
286 {
287 if ($this->isExcludeEnqueueDelete($path)) {
288 return;
289 }
290
291 $path = wp_normalize_path($path);
292
293 $this->announceQueuedDeletion($path);
294
295 $this->enqueue([
296 'action' => 'delete',
297 'source' => '',
298 'destination' => $path,
299 ]);
300 }
301
302
303
304
305
306
307
308
309 protected function announceQueuedDeletion(string $path)
310 {
311 if (in_array(untrailingslashit($path), $this->movedAsideDestinations, true)) {
312 return;
313 }
314
315 $this->logger->info(sprintf(
316 esc_html__('%s: Deleting %s', 'wp-staging'),
317 static::getTaskTitle(),
318 $this->filesystem->getPathRelativeToAbspath($path)
319 ));
320 }
321
322
323
324
325
326 private function hasOriginalSuffix(string $path): bool
327 {
328 $suffix = $this->getOriginalSuffix();
329
330 return substr(untrailingslashit($path), -strlen($suffix)) === $suffix;
331 }
332
333
334
335
336
337
338 public function retryLastActionInNextRequest()
339 {
340 $this->taskQueue->retry($dequeue = false);
341 }
342
343
344
345
346 protected function isRestoreOnSubsite(): bool
347 {
348 if (!is_multisite()) {
349 return false;
350 }
351
352 return $this->jobDataDto->getBackupMetadata()->getBackupType() !== BackupMetadata::BACKUP_TYPE_MULTISITE;
353 }
354
355
356
357
358
359 private function enqueue(array $action)
360 {
361 $this->taskQueue->enqueue(json_encode($action));
362 }
363
364 private function isExcludeEnqueueDelete($filePath)
365 {
366 $normalizedFilePath = rtrim(wp_normalize_path($filePath), '/');
367 $excludedFiles = Hooks::applyFilters(self::FILTER_EXCLUDE_ENQUEUE_DELETE, []);
368
369 if (empty($excludedFiles)) {
370 return false;
371 }
372
373 foreach ($excludedFiles as $excludedFile) {
374 $normalizedExcludedFile = rtrim(wp_normalize_path($excludedFile), '/');
375 if (strpos($normalizedFilePath, $normalizedExcludedFile) === 0) {
376 return true;
377 }
378 }
379
380 return false;
381 }
382 }
383