PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backup, Restore, Migration & Clone / 3.9.4
WP STAGING – WordPress Backup, Restore, Migration & Clone v3.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 year ago BackupTask.php 1 year ago FileBackupTask.php 1 year ago FileRestoreTask.php 1 year ago RestoreTask.php 1 year ago
FileRestoreTask.php
307 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
22 /**
23 * Class FileRestoreTask
24 *
25 * This is an abstract class for the filesystem-based restore actions of restoring a site,
26 * such as plugins, themes, mu-plugins and uploads files.
27 *
28 * It's main philosophy is to control the individual queue of what needs to be processed
29 * from each of the concrete restores. It delegates actual processing of the queue to a separate class.
30 *
31 * @package WPStaging\Backup\Abstracts\Task
32 */
33 abstract class FileRestoreTask extends RestoreTask implements FileTaskInterface
34 {
35 use EndOfLinePlaceholderTrait;
36 use RestoreFileExclusionTrait;
37
38 /**
39 * @var string
40 */
41 const FILTER_EXCLUDE_FILES_DURING_RESTORE = 'wpstg.backup.restore.exclude_paths';
42
43 /**
44 * @var Filesystem
45 */
46 protected $filesystem;
47
48 /**
49 * @var Directory
50 */
51 protected $directory;
52
53 /**
54 * @var FileProcessor
55 */
56 private $restoreFileProcessor;
57
58 /**
59 * @var int
60 */
61 protected $processedNow;
62
63 /**
64 * @var PathIdentifier
65 */
66 protected $pathIdentifier;
67
68 /**
69 * @var bool
70 */
71 protected $isSiteHostedOnWordPressCom = false;
72
73 public function __construct(
74 LoggerInterface $logger,
75 Cache $cache,
76 StepsDto $stepsDto,
77 SeekableQueueInterface $taskQueue,
78 Filesystem $filesystem,
79 Directory $directory,
80 FileProcessor $restoreFileProcessor,
81 PathIdentifier $pathIdentifier,
82 SiteInfo $siteInfo
83 ) {
84 parent::__construct($logger, $cache, $stepsDto, $taskQueue);
85 $this->filesystem = $filesystem;
86 $this->directory = $directory;
87 $this->restoreFileProcessor = $restoreFileProcessor;
88 $this->pathIdentifier = $pathIdentifier;
89 $this->isSiteHostedOnWordPressCom = $siteInfo->isHostedOnWordPressCom();
90 }
91
92 /**
93 * @return void
94 */
95 public function prepareFileRestore()
96 {
97 if ($this->stepsDto->getTotal() === 0) {
98 $this->buildQueue();
99 $this->taskQueue->seek(0);
100
101 // Just an arbitrary number, when there are no more items in the Queue we call stepsDto->finish()
102 $this->stepsDto->setTotal(100);
103 }
104 }
105
106 /**
107 * @return TaskResponseDto
108 */
109 public function execute(): TaskResponseDto
110 {
111 if ($this->isSkipped()) {
112 $this->stepsDto->finish();
113 $this->logger->warning(sprintf(esc_html__('%s skipped by filter!', 'wp-staging'), static::getTaskTitle()));
114 return $this->generateResponse(false);
115 }
116
117 try {
118 $this->checkMissingParts();
119 } catch (MissingFileException $ex) {
120 $this->stepsDto->finish();
121 $this->logger->warning(sprintf(esc_html__('%s skipped due to missing part!', 'wp-staging'), static::getTaskTitle()));
122 return $this->generateResponse(false);
123 }
124
125 $this->prepareFileRestore();
126
127 try {
128 while (!$this->isThreshold()) {
129 $this->processNextItemInQueue();
130 $this->processedNow++;
131 }
132 } catch (FinishedQueueException $e) {
133 $this->stepsDto->finish();
134 }
135
136 $this->logger->info(sprintf(esc_html__('%s (processed %d items)', 'wp-staging'), static::getTaskTitle(), (int)$this->processedNow));
137
138 return $this->generateResponse(false);
139 }
140
141 protected function getOriginalSuffix(): string
142 {
143 return '_wpstg_tmp';
144 }
145
146 /**
147 * Concrete classes of the FileRestoreTask must build
148 * the queue once, enqueuing everything that needs
149 * to be moved or deleted, using $this->enqueueMove
150 * or $this->enqueueDelete.
151 *
152 * @return void
153 */
154 abstract protected function buildQueue();
155
156 /**
157 * Skip the task if part is missing for this task
158 *
159 * @return array
160 */
161 abstract protected function getParts(): array;
162
163 /**
164 * Skip the task if set by filter
165 *
166 * @return bool
167 */
168 abstract protected function isSkipped(): bool;
169
170 /**
171 * Skip the task if part is missing for this task
172 *
173 * @throws MissingFileException
174 * @return void
175 */
176 protected function checkMissingParts()
177 {
178 if (!$this->jobDataDto->getBackupMetadata()->getIsMultipartBackup()) {
179 return;
180 }
181
182 $parts = $this->getParts();
183
184 $backupDir = $this->directory->getBackupDirectory();
185
186 foreach ($parts as $part) {
187 $filepath = $backupDir . $part;
188 if (!file_exists($filepath)) {
189 throw new MissingFileException();
190 }
191 }
192 }
193
194 /**
195 * Executes the next item in the queue.
196 * @return void
197 */
198 protected function processNextItemInQueue()
199 {
200 $nextInQueueRaw = $this->taskQueue->dequeue();
201
202 if (is_null($nextInQueueRaw)) {
203 throw new FinishedQueueException();
204 }
205
206 // Skip blank lines
207 if ($nextInQueueRaw === '') {
208 return;
209 }
210
211 $nextInQueue = json_decode($nextInQueueRaw, true);
212
213 // Make sure we read expected data from the queue
214 if (!is_array($nextInQueue)) {
215 $this->logger->warning(sprintf(
216 __('%s: An internal error occurred that prevented this item from being restored. Skipping it... (Error Code: INVALID_QUEUE_ITEM)', 'wp-staging'),
217 static::getTaskTitle()
218 ));
219 $this->logger->debug($nextInQueueRaw);
220
221 return;
222 }
223
224 // Make sure data is in the expected format
225 array_map(function ($requiredKey) use ($nextInQueue, $nextInQueueRaw) {
226 if (!array_key_exists($requiredKey, $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 }, ['action', 'source', 'destination']);
236
237 $source = $nextInQueue['source'];
238
239 // Make sure destination is within WordPress
240 // @todo Test backup in Windows and restoring in Linux and vice-versa
241 $destination = $nextInQueue['destination'];
242 $destination = $this->replacePlaceholdersWithEOLs($destination);
243 $destination = wp_normalize_path($destination);
244
245 // Executes the action
246 $this->restoreFileProcessor->handle($nextInQueue['action'], $source, $destination, $this, $this->logger);
247 }
248
249 /**
250 * @param string $source Source path to move.
251 * @param string $destination Where to move source to.
252 * @return void
253 */
254 public function enqueueMove(string $source, string $destination)
255 {
256 $this->enqueue([
257 'action' => 'move',
258 'source' => wp_normalize_path($source),
259 'destination' => wp_normalize_path($destination),
260 ]);
261 }
262
263 /**
264 * @param string $path The path to delete. Can be a folder, which will be deleted recursively.
265 * @return void
266 */
267 public function enqueueDelete(string $path)
268 {
269 $this->enqueue([
270 'action' => 'delete',
271 'source' => '',
272 'destination' => wp_normalize_path($path),
273 ]);
274 }
275
276 /**
277 * Use to retry last action in next request,
278 * if it wasn't completed in current request.
279 * @return void
280 */
281 public function retryLastActionInNextRequest()
282 {
283 $this->taskQueue->retry($dequeue = false);
284 }
285
286 /**
287 * @return bool
288 */
289 protected function isRestoreOnSubsite(): bool
290 {
291 if (!is_multisite()) {
292 return false;
293 }
294
295 return $this->jobDataDto->getBackupMetadata()->getBackupType() !== BackupMetadata::BACKUP_TYPE_MULTISITE;
296 }
297
298 /**
299 * @param array $action An array of actions to perform.
300 * @return void
301 */
302 private function enqueue(array $action)
303 {
304 $this->taskQueue->enqueue(json_encode($action));
305 }
306 }
307