PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.11.0
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.11.0
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 3.10.0 3.2.0 3.3.1 3.3.2 3.3.3 3.4.1 3.4.3 3.5.0 3.6.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 4.0.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.8.0
wp-staging / Backup / Task / FileRestoreTask.php
wp-staging / Backup / Task Last commit date
Tasks 1 day ago BackupTask.php 1 day ago FileBackupTask.php 1 day ago FileRestoreTask.php 1 day ago RestoreTask.php 1 day ago
FileRestoreTask.php
337 lines
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 public function __construct(
81 LoggerInterface $logger,
82 Cache $cache,
83 StepsDto $stepsDto,
84 SeekableQueueInterface $taskQueue,
85 Filesystem $filesystem,
86 Directory $directory,
87 FileProcessor $restoreFileProcessor,
88 PathIdentifier $pathIdentifier,
89 SiteInfo $siteInfo
90 ) {
91 parent::__construct($logger, $cache, $stepsDto, $taskQueue);
92 $this->filesystem = $filesystem;
93 $this->directory = $directory;
94 $this->restoreFileProcessor = $restoreFileProcessor;
95 $this->pathIdentifier = $pathIdentifier;
96 $this->isSiteHostedOnWordPressCom = $siteInfo->isHostedOnWordPressCom();
97 }
98
99
100
101
102 public function prepareFileRestore()
103 {
104 if ($this->stepsDto->getTotal() === 0) {
105 $this->buildQueue();
106 $this->taskQueue->seek(0);
107
108
109 $this->stepsDto->setTotal(100);
110 }
111 }
112
113
114
115
116 public function execute(): TaskResponseDto
117 {
118 if ($this->isSkipped()) {
119 $this->stepsDto->finish();
120 $this->logger->warning(sprintf(esc_html__('%s skipped by filter!', 'wp-staging'), static::getTaskTitle()));
121 return $this->generateResponse(false);
122 }
123
124 try {
125 $this->checkMissingParts();
126 } catch (MissingFileException $ex) {
127 $this->stepsDto->finish();
128 $this->logger->warning(sprintf(esc_html__('%s skipped due to missing part!', 'wp-staging'), static::getTaskTitle()));
129 return $this->generateResponse(false);
130 }
131
132 $this->prepareFileRestore();
133
134 try {
135 while (!$this->isThreshold()) {
136 $this->processNextItemInQueue();
137 $this->processedNow++;
138 }
139 } catch (FinishedQueueException $e) {
140 $this->stepsDto->finish();
141 }
142
143 $this->logger->info(sprintf(esc_html__('%s (processed %d items)', 'wp-staging'), static::getTaskTitle(), (int)$this->processedNow));
144
145 return $this->generateResponse(false);
146 }
147
148 protected function getOriginalSuffix(): string
149 {
150 return '_wpstg_tmp';
151 }
152
153
154
155
156
157
158
159
160
161 abstract protected function buildQueue();
162
163
164
165
166
167
168 abstract protected function getParts(): array;
169
170
171
172
173
174
175 abstract protected function isSkipped(): bool;
176
177
178
179
180
181
182
183 protected function checkMissingParts()
184 {
185 if (!$this->jobDataDto->getBackupMetadata()->getIsMultipartBackup()) {
186 return;
187 }
188
189 $parts = $this->getParts();
190
191 $backupDir = $this->directory->getBackupDirectory();
192
193 foreach ($parts as $part) {
194 $filepath = $backupDir . $part;
195 if (!file_exists($filepath)) {
196 throw new MissingFileException();
197 }
198 }
199 }
200
201
202
203
204
205 protected function processNextItemInQueue()
206 {
207 $nextInQueueRaw = $this->taskQueue->dequeue();
208
209 if (is_null($nextInQueueRaw)) {
210 throw new FinishedQueueException();
211 }
212
213
214 if ($nextInQueueRaw === '') {
215 return;
216 }
217
218 $nextInQueue = json_decode($nextInQueueRaw, true);
219
220
221 if (!is_array($nextInQueue)) {
222 $this->logger->warning(sprintf(
223 __('%s: An internal error occurred that prevented this item from being restored. Skipping it... (Error Code: INVALID_QUEUE_ITEM)', 'wp-staging'),
224 static::getTaskTitle()
225 ));
226 $this->logger->debug($nextInQueueRaw);
227
228 return;
229 }
230
231
232 array_map(function ($requiredKey) use ($nextInQueue, $nextInQueueRaw) {
233 if (!array_key_exists($requiredKey, $nextInQueue)) {
234 $this->logger->warning(sprintf(
235 __('%s: An internal error occurred that prevented this item from being restored. Skipping it... (Error Code: INVALID_QUEUE_ITEM)', 'wp-staging'),
236 static::getTaskTitle()
237 ));
238 $this->logger->debug($nextInQueueRaw);
239
240 return;
241 }
242 }, ['action', 'source', 'destination']);
243
244 $source = $nextInQueue['source'];
245
246
247
248 $destination = $nextInQueue['destination'];
249 $destination = $this->replacePlaceholdersWithEOLs($destination);
250 $destination = wp_normalize_path($destination);
251
252
253 $this->restoreFileProcessor->handle($nextInQueue['action'], $source, $destination, $this, $this->logger);
254 }
255
256
257
258
259
260
261 public function enqueueMove(string $source, string $destination)
262 {
263 $this->enqueue([
264 'action' => 'move',
265 'source' => wp_normalize_path($source),
266 'destination' => wp_normalize_path($destination),
267 ]);
268 }
269
270
271
272
273
274 public function enqueueDelete(string $path)
275 {
276 if ($this->isExcludeEnqueueDelete($path)) {
277 return;
278 }
279
280 $this->enqueue([
281 'action' => 'delete',
282 'source' => '',
283 'destination' => wp_normalize_path($path),
284 ]);
285 }
286
287
288
289
290
291
292 public function retryLastActionInNextRequest()
293 {
294 $this->taskQueue->retry($dequeue = false);
295 }
296
297
298
299
300 protected function isRestoreOnSubsite(): bool
301 {
302 if (!is_multisite()) {
303 return false;
304 }
305
306 return $this->jobDataDto->getBackupMetadata()->getBackupType() !== BackupMetadata::BACKUP_TYPE_MULTISITE;
307 }
308
309
310
311
312
313 private function enqueue(array $action)
314 {
315 $this->taskQueue->enqueue(json_encode($action));
316 }
317
318 private function isExcludeEnqueueDelete($filePath)
319 {
320 $normalizedFilePath = rtrim(wp_normalize_path($filePath), '/');
321 $excludedFiles = Hooks::applyFilters(self::FILTER_EXCLUDE_ENQUEUE_DELETE, []);
322
323 if (empty($excludedFiles)) {
324 return false;
325 }
326
327 foreach ($excludedFiles as $excludedFile) {
328 $normalizedExcludedFile = rtrim(wp_normalize_path($excludedFile), '/');
329 if (strpos($normalizedFilePath, $normalizedExcludedFile) === 0) {
330 return true;
331 }
332 }
333
334 return false;
335 }
336 }
337