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 / Service / FileBackupService.php
wp-staging / Backup / Service Last commit date
Compression 1 day ago Database 1 day ago DirectoryExplorer 1 day ago AbstractBackgroundBackupRequest.php 1 day ago AbstractBackupsFinder.php 1 day ago AbstractExtractor.php 1 day ago AbstractServiceProvider.php 1 day ago Archiver.php 1 day ago BackupAssets.php 1 day ago BackupContent.php 1 day ago BackupMetadataEditor.php 1 day ago BackupMetadataReader.php 1 day ago BackupSigner.php 1 day ago BackupsDirectoryResolver.php 1 day ago BackupsFinder.php 1 day ago BeforeUpdateBackupRequest.php 1 day ago BeforeUpdateBackupsService.php 1 day ago Extractor.php 1 day ago FileBackupService.php 1 day ago FileBackupServiceProvider.php 1 day ago ServiceInterface.php 1 day ago TmpBackupCleaner.php 1 day ago UpdateProtectionHealth.php 1 day ago UpdateProtectionSettings.php 1 day ago ZlibCompressor.php 1 day ago
FileBackupService.php
376 lines
1 <?php
2
3
4
5
6
7
8
9
10 namespace WPStaging\Backup\Service;
11
12 use WPStaging\Backup\Dto\Job\JobBackupDataDto;
13 use WPStaging\Backup\Dto\Service\ArchiverDto;
14 use WPStaging\Framework\Job\Dto\StepsDto;
15 use WPStaging\Backup\Exceptions\BackupSkipItemException;
16 use WPStaging\Backup\Task\FileBackupTask;
17 use WPStaging\Framework\Adapter\Directory;
18 use WPStaging\Framework\Job\Exception\DiskNotWritableException;
19 use WPStaging\Framework\Filesystem\Filesystem;
20 use WPStaging\Framework\Filesystem\FilesystemScanner;
21 use WPStaging\Framework\Job\Exception\ThresholdException;
22 use WPStaging\Framework\Queue\FinishedQueueException;
23 use WPStaging\Framework\Queue\SeekableQueueInterface;
24 use WPStaging\Framework\SiteInfo;
25 use WPStaging\Framework\Traits\EndOfLinePlaceholderTrait;
26 use WPStaging\Framework\Traits\ResourceTrait;
27 use WPStaging\Vendor\Psr\Log\LoggerInterface;
28
29 use function WPStaging\functions\debug_log;
30
31 class FileBackupService implements ServiceInterface
32 {
33 use ResourceTrait;
34 use EndOfLinePlaceholderTrait;
35
36
37 protected $archiver;
38
39
40 protected $directory;
41
42
43 protected $filesystem;
44
45
46 protected $taskQueue;
47
48
49 protected $logger;
50
51
52 protected $jobDataDto;
53
54
55 protected $stepsDto;
56
57
58 protected $bigFileBeingProcessed;
59
60
61 protected $fileBackupTask;
62
63
64 protected $isWpContentOutsideAbspath = false;
65
66
67 protected $isOtherWpRootFilesTask = false;
68
69
70 protected $isGracefulShutdown = true;
71
72
73 protected $start;
74
75
76 protected $fileIdentifier;
77
78 public function __construct(Archiver $archiver, Directory $directory, Filesystem $filesystem, SiteInfo $siteInfo)
79 {
80 $this->archiver = $archiver;
81 $this->directory = $directory;
82 $this->filesystem = $filesystem;
83
84 $this->isWpContentOutsideAbspath = $siteInfo->isWpContentOutsideAbspath();
85 }
86
87
88
89
90
91
92
93
94
95 public function inject(FileBackupTask $fileBackupTask, SeekableQueueInterface $taskQueue, LoggerInterface $logger, JobBackupDataDto $jobDataDto, StepsDto $stepsDto)
96 {
97 $this->fileBackupTask = $fileBackupTask;
98 $this->taskQueue = $taskQueue;
99 $this->logger = $logger;
100 $this->jobDataDto = $jobDataDto;
101 $this->stepsDto = $stepsDto;
102 }
103
104
105
106
107
108 public function setIsGracefulShutdown(bool $isGracefulShutdown)
109 {
110 $this->isGracefulShutdown = $isGracefulShutdown;
111 }
112
113
114
115
116
117
118 public function setupArchiver(string $fileIdentifier, bool $isOtherWpRootFilesTask = false)
119 {
120 $this->fileIdentifier = $fileIdentifier;
121 $this->isOtherWpRootFilesTask = $isOtherWpRootFilesTask;
122 $this->archiver->createArchiveFile(Archiver::CREATE_BINARY_HEADER);
123 }
124
125
126
127
128 public function execute()
129 {
130 $this->archiver->setFileAppendTimeLimit($this->jobDataDto->getFileAppendTimeLimit());
131 $this->start = microtime(true);
132
133 while (!$this->isThreshold() && !$this->stepsDto->isFinished()) {
134 try {
135 $this->backup();
136 } catch (ThresholdException $exception) {
137 break;
138 } catch (FinishedQueueException $exception) {
139 $this->stepsDto->finish();
140 $this->logger->info(sprintf('Added %d/%d %s files to backup (%s)', $this->stepsDto->getCurrent(), $this->stepsDto->getTotal(), $this->getTranslatedFileIdentifier(), $this->getBackupSpeed()));
141 $this->updateMultipartInfo();
142
143 return;
144 } catch (DiskNotWritableException $exception) {
145
146 throw new \Exception('Disk is probably full. Error message: ' . $exception->getMessage());
147 } catch (\Throwable $th) {
148 throw new \Exception('Fail to create backup. Error message: ' . $th->getMessage());
149 }
150 }
151
152 $this->logExecution();
153
154 $this->updateMultipartInfo();
155 }
156
157
158
159
160
161
162 public function backup()
163 {
164 $archiverDto = $this->archiver->getDto();
165 $archiverDto->setWrittenBytesTotal($this->jobDataDto->getFileBeingBackupWrittenBytes());
166 $archiverDto->setFileHeaderSizeInBytes($this->jobDataDto->getCurrentWrittenFileHeaderBytes());
167 $archiverDto->setStartOffset($this->jobDataDto->getCurrentFileStartOffset());
168
169 if ($archiverDto->getWrittenBytesTotal() !== 0) {
170 $archiverDto->setIndexPositionCreated(true);
171 $this->logger->debug('Resuming backup of a large file from previous request.');
172 }
173
174 $path = $this->taskQueue->dequeue();
175 $path = $this->replacePlaceholdersWithEOLs($path);
176
177 if (is_null($path)) {
178 debug_log("Backup error: no task to dequeue");
179 throw new FinishedQueueException();
180 }
181
182 if (empty($path)) {
183 return;
184 }
185
186 $indexPath = '';
187 if (strpos($path, FilesystemScanner::PATH_SEPARATOR) !== false) {
188 list($path, $indexPath) = explode(FilesystemScanner::PATH_SEPARATOR, $path);
189 }
190
191 if ($this->shouldPrependAbsPath()) {
192 $path = trailingslashit(ABSPATH) . $path;
193 }
194
195 try {
196 $isFileWrittenCompletely = $this->appendCurrentFileToBackup($path, $indexPath);
197 } catch (BackupSkipItemException $e) {
198 $isFileWrittenCompletely = true;
199 } catch (ThresholdException $e) {
200 $isFileWrittenCompletely = null;
201 } catch (\RuntimeException $e) {
202
203
204
205
206
207 $isFileWrittenCompletely = true;
208 $skippedFile = $indexPath !== '' ? $indexPath : $path;
209 $skippedFile = $skippedFile === '' ? '(empty path)' : $skippedFile;
210 $reason = str_replace(["\r", "\n"], ' ', $e->getMessage());
211 $this->logger->warning(sprintf('Skipped a file that could not be added to the backup: %s — %s', $skippedFile, $reason));
212 debug_log(sprintf('Backup: skipped file "%s" — %s', $skippedFile, $reason));
213 } catch (\Throwable $th) {
214 throw $th;
215 }
216
217 $this->jobDataDto->setCurrentWrittenFileHeaderBytes(0);
218
219 if ($isFileWrittenCompletely === true) {
220 $this->jobDataDto->setFileBeingBackupWrittenBytes(0);
221 $this->stepsDto->incrementCurrentStep();
222 $this->jobDataDto->setQueueOffset($this->taskQueue->getOffset());
223
224 if (!$this->jobDataDto->getIsMultipartBackup()) {
225 $this->jobDataDto->incrementFilesInPart($this->fileIdentifier);
226 }
227
228 $this->persistJobDataDto();
229 return;
230 }
231
232
233 $archiverDto = $this->archiver->getDto();
234 $this->jobDataDto->setFileBeingBackupWrittenBytes($archiverDto->getWrittenBytesTotal());
235 $this->jobDataDto->setCurrentFileStartOffset($archiverDto->getStartOffset());
236 $this->taskQueue->retry(false);
237
238 if ($archiverDto->getFileHeaderSizeInBytes() > 0) {
239 $this->jobDataDto->setCurrentWrittenFileHeaderBytes($archiverDto->getFileHeaderSizeInBytes());
240 }
241
242 if ($archiverDto->getWrittenBytesTotal() < $archiverDto->getFileSize() && $archiverDto->getFileSize() > 10 * MB_IN_BYTES) {
243 $this->bigFileBeingProcessed = $archiverDto;
244 }
245
246 $this->persistJobDataDto();
247 if ($isFileWrittenCompletely === null) {
248 throw new ThresholdException();
249 }
250 }
251
252 protected function getBackupSpeed(): string
253 {
254 $elapsed = microtime(true) - $this->start;
255
256 $elapsed = empty($elapsed) ? 1 : $elapsed;
257
258 $bytesPerSecond = min(10 * GB_IN_BYTES, absint($this->archiver->getBytesWrittenInThisRequest() / $elapsed));
259
260 if ($bytesPerSecond === 10 * GB_IN_BYTES) {
261 return '10GB/s+';
262 }
263
264
265 if ($bytesPerSecond >= MB_IN_BYTES) {
266 if ($bytesPerSecond >= GB_IN_BYTES) {
267 return number_format($bytesPerSecond / GB_IN_BYTES, 2) . 'GB/s';
268 }
269
270 return number_format($bytesPerSecond / MB_IN_BYTES, 2) . 'MB/s';
271 }
272
273 return size_format($bytesPerSecond) . '/s';
274 }
275
276 protected function shouldPrependAbsPath(): bool
277 {
278 return $this->isOtherWpRootFilesTask || ($this->isWpContentOutsideAbspath === false);
279 }
280
281
282
283
284
285 protected function logExecution()
286 {
287 if ($this->bigFileBeingProcessed instanceof ArchiverDto) {
288 $relativePathForLogging = str_replace($this->filesystem->normalizePath(ABSPATH, true), '', $this->filesystem->normalizePath($this->bigFileBeingProcessed->getFilePath()));
289 $percentProcessed = ceil(($this->bigFileBeingProcessed->getWrittenBytesTotal() / $this->bigFileBeingProcessed->getFileSize()) * 100);
290 $this->logger->info(sprintf(
291 'Adding big %s file: %s - %s/%s (%s%%) (%s)',
292 $this->getTranslatedFileIdentifier(),
293 $relativePathForLogging,
294 size_format($this->bigFileBeingProcessed->getWrittenBytesTotal(), 2),
295 size_format($this->bigFileBeingProcessed->getFileSize(), 2),
296 $percentProcessed,
297 $this->getBackupSpeed()
298 ));
299 return;
300 }
301
302 $this->logger->info(sprintf(
303 'Added %d/%d %s files to backup (%s)',
304 $this->stepsDto->getCurrent(),
305 $this->stepsDto->getTotal(),
306 $this->getTranslatedFileIdentifier(),
307 $this->getBackupSpeed()
308 ));
309 }
310
311
312
313
314 protected function updateMultipartInfo()
315 {
316
317 }
318
319
320
321
322 protected function maybeIncrementPartNo(string $path)
323 {
324
325 }
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343 protected function appendCurrentFileToBackup(string $path, string $indexPath)
344 {
345 $this->maybeIncrementPartNo($path);
346 return $this->archiver->appendFileToBackup($path, $indexPath);
347 }
348
349 protected function getTranslatedFileIdentifier(): string
350 {
351 switch ($this->fileIdentifier) {
352 case 'muplugins':
353 return __('mu-plugin', 'wp-staging');
354 case 'plugins':
355 return __('plugin', 'wp-staging');
356 case 'themes':
357 return __('theme', 'wp-staging');
358 case 'otherfiles':
359 return __('other', 'wp-staging');
360 case 'rootfiles':
361 return __('root', 'wp-staging');
362 default:
363 return $this->fileIdentifier;
364 }
365 }
366
367 protected function persistJobDataDto()
368 {
369 if ($this->jobDataDto->getIsFastPerformanceMode()) {
370 return;
371 }
372
373 $this->fileBackupTask->persistJobDataDto();
374 }
375 }
376