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