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 / Archiver.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
Archiver.php
870 lines
1 <?php
2
3
4
5 namespace WPStaging\Backup\Service;
6
7 use Exception;
8 use LogicException;
9 use RuntimeException;
10 use WPStaging\Backup\BackupFileIndex;
11 use WPStaging\Backup\BackupHeader;
12 use WPStaging\Backup\Dto\Job\JobBackupDataDto;
13 use WPStaging\Backup\Dto\Service\ArchiverDto;
14 use WPStaging\Backup\Entity\BackupMetadata;
15 use WPStaging\Backup\Exceptions\BackupSkipItemException;
16 use WPStaging\Backup\FileHeader;
17 use WPStaging\Core\WPStaging;
18 use WPStaging\Framework\Adapter\Directory;
19 use WPStaging\Framework\Adapter\PhpAdapter;
20 use WPStaging\Framework\Facades\Hooks;
21 use WPStaging\Framework\Filesystem\DiskWriteCheck;
22 use WPStaging\Framework\Filesystem\Filesystem;
23 use WPStaging\Framework\Filesystem\PartIdentifier;
24 use WPStaging\Framework\Filesystem\PathIdentifier;
25 use WPStaging\Framework\Job\Dto\JobDataDto;
26 use WPStaging\Framework\Job\Exception\DiskNotWritableException;
27 use WPStaging\Framework\Job\Exception\NotFinishedException;
28 use WPStaging\Framework\Job\Exception\ThresholdException;
29 use WPStaging\Framework\Traits\EndOfLinePlaceholderTrait;
30 use WPStaging\Framework\Utils\Cache\BufferedCache;
31 use WPStaging\Vendor\lucatume\DI52\NotFoundException;
32
33 use function WPStaging\functions\debug_log;
34
35
36
37
38 class Archiver
39 {
40 use EndOfLinePlaceholderTrait;
41
42
43
44
45 const BACKUP_EXTENSION = 'wpstg';
46
47
48
49
50
51 const TMP_BACKUP_EXTENSION = 'wpstgtmp';
52
53
54
55
56
57 const MAX_RETRIES_BEFORE_EXTENDING_TIME_LIMIT = 1;
58
59
60
61
62
63 const MAX_ALLOWED_PHP_TIME_LIMIT = 60;
64
65
66
67
68
69 const MIN_ALLOWED_PHP_TIME_LIMIT = 10;
70
71
72
73
74
75
76 const PHP_TIME_LIMIT_IN_FRACTION = 0.8;
77
78
79 const BACKUP_DIR_NAME = 'backups';
80
81
82 const CREATE_BINARY_HEADER = true;
83
84
85 protected $tempBackupIndex;
86
87
88 protected $tempBackup;
89
90
91 protected $archiverDto;
92
93
94 protected $pathIdentifier;
95
96
97 protected $archivedFileSize = 0;
98
99
100 protected $jobDataDto;
101
102
103 protected $phpAdapter;
104
105
106 protected $isLocalBackup = false;
107
108
109 protected $bytesWrittenInThisRequest = 0;
110
111
112 protected $fileHeader;
113
114
115 protected $backupHeader;
116
117
118 protected $backupFileIndex;
119
120
121 protected $filesystem;
122
123
124 protected $isTempBackup = false;
125
126 public function __construct(
127 BufferedCache $cacheIndex,
128 BufferedCache $tempBackup,
129 PathIdentifier $pathIdentifier,
130 JobDataDto $jobDataDto,
131 ArchiverDto $archiverDto,
132 PhpAdapter $phpAdapter,
133 BackupFileIndex $backupFileIndex,
134 FileHeader $fileHeader,
135 BackupHeader $backupHeader,
136 Filesystem $filesystem
137 ) {
138 $this->jobDataDto = $jobDataDto;
139 $this->archiverDto = $archiverDto;
140 $this->tempBackupIndex = $cacheIndex;
141 $this->tempBackup = $tempBackup;
142 $this->pathIdentifier = $pathIdentifier;
143 $this->phpAdapter = $phpAdapter;
144 $this->backupFileIndex = $backupFileIndex;
145 $this->fileHeader = $fileHeader;
146 $this->backupHeader = $backupHeader;
147 $this->filesystem = $filesystem;
148 }
149
150
151
152
153
154 public function setFileAppendTimeLimit(int $fileAppendTimeLimit)
155 {
156 $this->tempBackup->setFileAppendTimeLimit($fileAppendTimeLimit);
157 $this->tempBackupIndex->setFileAppendTimeLimit($fileAppendTimeLimit);
158 }
159
160
161
162
163
164 public function setIsTempBackup(bool $isTempBackup)
165 {
166 $this->isTempBackup = $isTempBackup;
167 }
168
169
170
171
172
173 public function createArchiveFile(bool $isCreateBinaryHeader = false)
174 {
175 $this->setupTmpBackupFile();
176
177 if ($isCreateBinaryHeader && !$this->tempBackup->isValid()) {
178
179 $this->tempBackup->save($this->isBackupFormatV1() ? $this->backupHeader->getV1FormatHeader() : $this->backupHeader->getHeader() . "\n");
180 }
181 }
182
183
184
185
186
187 public function setupTmpBackupFile()
188 {
189 $this->tempBackup->setFilename('temp_wpstg_backup_' . $this->jobDataDto->getId());
190 $this->tempBackup->setLifetime(DAY_IN_SECONDS);
191
192 $tempBackupIndexFilePrefix = 'temp_backup_index_';
193 $this->tempBackupIndex->setFilename($tempBackupIndexFilePrefix . $this->jobDataDto->getId());
194 $this->tempBackupIndex->setLifetime(DAY_IN_SECONDS);
195 }
196
197
198
199
200 public function setIsLocalBackup(bool $isLocalBackup)
201 {
202 $this->isLocalBackup = $isLocalBackup;
203 }
204
205
206
207
208 public function getDto(): ArchiverDto
209 {
210 return $this->archiverDto;
211 }
212
213
214
215
216 public function getBytesWrittenInThisRequest(): int
217 {
218 return $this->bytesWrittenInThisRequest;
219 }
220
221
222
223
224 public function getTempBackupIndex(): BufferedCache
225 {
226 return $this->tempBackupIndex;
227 }
228
229
230
231
232 public function getTempBackup(): BufferedCache
233 {
234 return $this->tempBackup;
235 }
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251 public function appendFileToBackup(string $fullFilePath, string $indexPath = ''): bool
252 {
253
254
255
256 $resource = @fopen($fullFilePath, 'rb');
257 if (!$resource) {
258 debug_log("appendFileToBackup(): Can't open file {$fullFilePath} for reading");
259 throw new BackupSkipItemException();
260 }
261
262 if (empty($indexPath)) {
263 $indexPath = $fullFilePath;
264 }
265
266 $indexPath = $this->replaceEOLsWithPlaceholders($indexPath);
267 $fileStats = fstat($resource);
268 $this->initiateDtoByFilePath($fullFilePath, $fileStats);
269 $this->archiverDto->setIndexPath($indexPath);
270 $fileHeaderSizeInBytes = 0;
271 if (!$this->isBackupFormatV1() && !$this->archiverDto->isFileHeaderWritten()) {
272 $fileHeaderSizeInBytes = $this->writeFileHeader($fullFilePath, $indexPath);
273 $this->archiverDto->setFileHeaderSizeInBytes($fileHeaderSizeInBytes);
274 } elseif (!$this->isBackupFormatV1()) {
275 $identifiablePath = $this->pathIdentifier->transformPathToIdentifiable($this->filesystem->maybeNormalizePath($indexPath));
276 $this->fileHeader->readFile($fullFilePath, $identifiablePath);
277 }
278
279 $writtenBytesBefore = $this->archiverDto->getWrittenBytesTotal();
280 try {
281 $writtenBytesTotal = $this->appendToArchiveFile($resource, $fullFilePath);
282 } catch (ThresholdException $ex) {
283
284 fclose($resource);
285 $resource = null;
286 $this->maybeIncrementFileAppendTimeLimit();
287
288 throw $ex;
289 }
290
291 $newBytesWritten = $writtenBytesTotal + $fileHeaderSizeInBytes - $writtenBytesBefore;
292 $writtenBytesIncludingFileHeader = $writtenBytesTotal + $this->archiverDto->getFileHeaderSizeInBytes();
293
294 if (!$this->isBackupFormatV1() && empty($this->fileHeader->getFilePath())) {
295 $identifiablePath = $this->pathIdentifier->transformPathToIdentifiable($this->filesystem->maybeNormalizePath($indexPath));
296 $this->fileHeader->readFile($fullFilePath, $identifiablePath);
297 }
298
299 $retries = 0;
300
301 if (!$this->isBackupFormatV1() && empty($this->fileHeader->getFilePath())) {
302 $identifiablePath = $this->pathIdentifier->transformPathToIdentifiable($this->filesystem->maybeNormalizePath($indexPath));
303 $this->fileHeader->readFile($fullFilePath, $identifiablePath);
304 }
305
306 do {
307 if ($retries > 0) {
308 usleep((int)$this->getDelayForRetry($retries));
309 }
310
311 $bytesAddedForIndex = $this->addIndex($writtenBytesIncludingFileHeader, $newBytesWritten);
312 $retries++;
313 } while ($bytesAddedForIndex === 0 && $retries < 3);
314
315 $this->archiverDto->setWrittenBytesTotal($writtenBytesTotal);
316
317 $this->bytesWrittenInThisRequest += $newBytesWritten;
318
319 $isFinished = $this->archiverDto->isFinished();
320 if ($isFinished) {
321 $this->resetFileAppendTimeLimitAndRetries();
322 }
323
324 $this->archiverDto->resetIfFinished();
325
326 return $isFinished;
327 }
328
329
330
331
332
333
334 public function initiateDtoByFilePath(string $filePath, array $fileStats = []): bool
335 {
336 if (empty($filePath) || ($filePath === $this->archiverDto->getFilePath() && $fileStats['size'] === $this->archiverDto->getFileSize())) {
337 return false;
338 }
339
340 $this->archiverDto->setFilePath($filePath);
341 $this->archiverDto->setFileSize($fileStats['size']);
342 return true;
343 }
344
345
346
347
348
349
350
351
352
353
354
355 public function generateBackupMetadata(int $backupSizeBeforeAddingIndex = 0, string $finalFileNameOnRename = ''): string
356 {
357 clearstatcache();
358 $backupSizeAfterAddingIndex = filesize($this->tempBackup->getFilePath());
359
360 $backupMetadata = $this->archiverDto->getBackupMetadata();
361 $backupMetadata->setHeaderStart($backupSizeBeforeAddingIndex);
362 $backupMetadata->setHeaderEnd($backupSizeAfterAddingIndex);
363
364 if ($this->jobDataDto instanceof JobBackupDataDto) {
365
366 $jobDataDto = $this->jobDataDto;
367 $this->setBackupMetadataCategoryInfo($backupMetadata, $jobDataDto);
368 }
369
370 $this->tempBackup->append(json_encode($backupMetadata));
371 if (!$this->isBackupFormatV1()) {
372 $this->backupHeader->readFromPath($this->tempBackup->getFilePath());
373 $this->backupHeader->setMetadataStartOffset($backupSizeAfterAddingIndex);
374 $this->backupHeader->setMetadataEndOffset($backupSizeAfterAddingIndex);
375 $this->backupHeader->updateHeader($this->tempBackup->getFilePath());
376 }
377
378 return $this->renameBackup($finalFileNameOnRename);
379 }
380
381
382 public function addFileIndex(): int
383 {
384 clearstatcache();
385 $indexResource = fopen($this->tempBackupIndex->getFilePath(), 'rb');
386
387 if (!$indexResource) {
388 debug_log('[Add File Index] Nothing to backup, no index resource! File Index: ' . $this->tempBackupIndex->getFilePath());
389 throw new NotFoundException('Nothing to backup, no index resource found!');
390 }
391
392 static $isFirstInsert = false;
393 $insertSeparator = '';
394 if ($isFirstInsert === false) {
395 $lastLine = $this->tempBackup->readLastLine();
396 if (!empty($lastLine) && preg_match('@^INSERT\sINTO\s@', $lastLine)) {
397 $isFirstInsert = true;
398 $insertSeparator = "\n--\n-- SQL DATA END\n--\n";
399 $this->tempBackup->append($insertSeparator);
400 $this->tempBackup->deleteBottomBytes(strlen(PHP_EOL));
401 }
402 }
403
404 $indexStats = fstat($indexResource);
405 $this->initiateDtoByFilePath($this->tempBackupIndex->getFilePath(), $indexStats);
406
407 $lastLine = $this->tempBackup->readLastLine();
408 $writtenBytes = $this->archiverDto->getWrittenBytesTotal();
409 if ($lastLine !== PHP_EOL && $writtenBytes === 0) {
410 $this->tempBackup->append('');
411 }
412
413 clearstatcache();
414 $backupSizeBeforeAddingIndex = filesize($this->tempBackup->getFilePath());
415 $backupIndexFileSize = filesize($this->tempBackupIndex->getFilePath());
416
417
418
419 $writtenBytes = $this->appendToArchiveFile($indexResource, $this->tempBackupIndex->getFilePath());
420 $this->archiverDto->setWrittenBytesTotal($writtenBytes);
421
422 if ($writtenBytes === 0) {
423 $this->jobDataDto->setRetries($this->jobDataDto->getRetries() + 1);
424 } else {
425 $this->jobDataDto->setRetries(0);
426 }
427
428
429 fclose($indexResource);
430
431 if ($this->jobDataDto->getRetries() > 3) {
432 $indexSize = $backupIndexFileSize === false ? 0 : size_format($backupIndexFileSize, 3);
433 debug_log(sprintf('[Add File Index] Failed to write files-index to backup file! Tmp Size: %s. Index Size: %s', size_format($backupSizeBeforeAddingIndex, 3), $indexSize));
434 throw new Exception(sprintf('Failed to write files-index to backup file! Tmp Size: %s. Index Size: %s', size_format($backupSizeBeforeAddingIndex, 3), $indexSize));
435 } elseif ($writtenBytes === 0) {
436 debug_log('[Add File Index] Failed to write any byte to files-index! Retrying...');
437 }
438
439 if (!$this->archiverDto->isFinished()) {
440 throw new NotFinishedException('File backup is not finished yet!');
441 }
442
443 $this->tempBackupIndex->delete();
444 $this->archiverDto->reset();
445
446 $backupSizeAfterAddingIndex = filesize($this->tempBackup->getFilePath());
447 if (!$this->isBackupFormatV1()) {
448 $this->backupHeader->setFilesIndexStartOffset($backupSizeBeforeAddingIndex);
449 $this->backupHeader->setFilesIndexEndOffset($backupSizeAfterAddingIndex);
450 $this->backupHeader->updateHeader($this->tempBackup->getFilePath());
451 }
452
453 $this->tempBackup->append(PHP_EOL);
454
455 return $backupSizeBeforeAddingIndex;
456 }
457
458
459
460
461 public function getDestinationPath(): string
462 {
463 return sprintf(
464 '%s_%s_%s.%s',
465 parse_url(get_home_url())['host'],
466 current_time('Ymd-His'),
467 $this->jobDataDto->getId(),
468 self::BACKUP_EXTENSION
469 );
470 }
471
472
473
474
475
476
477 public function getFinalPath(string $renameFileTo = '', bool $isLocalBackup = true): string
478 {
479 $backupsDirectory = $this->getFinalBackupParentDirectory($isLocalBackup);
480 if ($renameFileTo === '') {
481 $renameFileTo = $this->getDestinationPath();
482 }
483
484 return $backupsDirectory . $renameFileTo;
485 }
486
487 public function getFinalBackupParentDirectory(bool $isLocalBackup = true): string
488 {
489 if ($isLocalBackup) {
490 return WPStaging::make(BackupsFinder::class)->getBackupsDirectory();
491 }
492
493 return WPStaging::make(Directory::class)->getCacheDirectory();
494 }
495
496
497
498
499
500
501 protected function writeFileHeader(string $filePath, string $indexPath): int
502 {
503 $identifiablePath = $this->pathIdentifier->transformPathToIdentifiable($this->filesystem->maybeNormalizePath($indexPath));
504 $this->fileHeader->readFile($filePath, $identifiablePath);
505
506 return $this->tempBackup->append($this->fileHeader->getFileHeader());
507 }
508
509
510
511
512
513
514
515 protected function getDelayForRetry(int $retry): float
516 {
517 $delay = 0.1;
518 for ($i = 0; $i < $retry; $i++) {
519 $delay *= 2;
520 }
521
522 return $delay * 1000;
523 }
524
525
526
527
528
529
530 protected function setBackupMetadataCategoryInfo(BackupMetadata $backupMetadata, JobBackupDataDto $jobBackupDataDto)
531 {
532 $backupMetadata->setIndexPartSize($jobBackupDataDto->getCategorySizes());
533 }
534
535
536
537
538
539 protected function incrementFilesCount(JobBackupDataDto $jobBackupDataDto)
540 {
541 $jobBackupDataDto->setTotalFiles($jobBackupDataDto->getTotalFiles() + 1);
542 }
543
544
545
546
547 protected function setIndexPositionCreated()
548 {
549 $this->archiverDto->setIndexPositionCreated(true);
550 }
551
552
553
554
555 protected function isIndexPositionCreated(): bool
556 {
557 return $this->archiverDto->isIndexPositionCreated();
558 }
559
560
561
562
563
564 protected function maybeIncrementFileAppendTimeLimit()
565 {
566 $this->jobDataDto->incrementNumberOfRetries();
567 if ($this->jobDataDto->getNumberOfRetries() > self::MAX_RETRIES_BEFORE_EXTENDING_TIME_LIMIT) {
568 return;
569 }
570
571
572 $jobDataDto = $this->jobDataDto;
573 $jobDataDto->incrementFileAppendTimeLimit();
574 if ($jobDataDto->getFileAppendTimeLimit() > $this->getMaxPhpTimeLimitAllowed()) {
575 throw new RuntimeException('Maximum file append time limit exceeded. Please increase your PHP max execution time to proceed.');
576 }
577 }
578
579 protected function getMaxPhpTimeLimitAllowed(): int
580 {
581 $maxAllowedPhpTimeLimit = (int)ini_get('max_execution_time');
582 if ($maxAllowedPhpTimeLimit === 0 || $maxAllowedPhpTimeLimit === -1) {
583 $maxAllowedPhpTimeLimit = self::MAX_ALLOWED_PHP_TIME_LIMIT * self::PHP_TIME_LIMIT_IN_FRACTION;
584 return (int)Hooks::applyFilters(JobDataDto::FILTER_RESOURCES_EXECUTION_TIME_LIMIT, $maxAllowedPhpTimeLimit);
585 }
586
587 $maxAllowedPhpTimeLimit = max(self::MIN_ALLOWED_PHP_TIME_LIMIT, $maxAllowedPhpTimeLimit);
588 $maxAllowedPhpTimeLimit = min(self::MAX_ALLOWED_PHP_TIME_LIMIT, $maxAllowedPhpTimeLimit);
589 $maxAllowedPhpTimeLimit = $maxAllowedPhpTimeLimit * self::PHP_TIME_LIMIT_IN_FRACTION;
590
591 return (int)Hooks::applyFilters(JobDataDto::FILTER_RESOURCES_EXECUTION_TIME_LIMIT, $maxAllowedPhpTimeLimit);
592 }
593
594
595
596
597 protected function resetFileAppendTimeLimitAndRetries()
598 {
599
600 $jobDataDto = $this->jobDataDto;
601
602 $jobDataDto->resetFileAppendTimeLimit();
603 $jobDataDto->resetNumberOfRetries();
604 }
605
606 protected function addNewFileHeaderToIndex(int $writtenBytes, int $startOffset): int
607 {
608 if ($this->isIndexPositionCreated()) {
609 return 0;
610 }
611
612 $this->fileHeader->setStartOffset($startOffset);
613 return $this->tempBackupIndex->append($this->fileHeader->getIndexHeader());
614 }
615
616
617
618
619
620
621
622
623
624
625 protected function appendToArchiveFile($resource, string $filePath): int
626 {
627 try {
628 return $this->tempBackup->appendFile(
629 $resource,
630 $this->archiverDto->getWrittenBytesTotal()
631 );
632 } catch (DiskNotWritableException $e) {
633 debug_log('Failed to write to file: ' . $filePath);
634 throw $this->reclassifyDiskFailure($e);
635 }
636 }
637
638
639
640
641
642
643
644
645 protected function reclassifyDiskFailure(DiskNotWritableException $original): DiskNotWritableException
646 {
647 try {
648 $remainingBytes = max(
649 0,
650 $this->archiverDto->getFileSize() - $this->archiverDto->getWrittenBytesTotal()
651 );
652 WPStaging::make(DiskWriteCheck::class)->checkPathCanStoreEnoughBytes(
653 dirname($this->tempBackup->getFilePath()),
654 $remainingBytes
655 );
656 } catch (DiskNotWritableException $diskFull) {
657 return $diskFull;
658 } catch (RuntimeException $cannotDetermine) {
659
660 }
661
662 return $original;
663 }
664
665
666
667
668
669
670
671 private function renameBackup(string $renameFileTo = ''): string
672 {
673 if ($renameFileTo === '') {
674 $renameFileTo = $this->getDestinationPath();
675 }
676
677 $destination = trailingslashit(dirname($this->tempBackup->getFilePath())) . $renameFileTo;
678 if ($this->isLocalBackup) {
679 $destination = $this->getFinalPath($renameFileTo);
680 }
681
682 if (!rename($this->tempBackup->getFilePath(), $destination)) {
683 throw new RuntimeException('Failed to generate destination');
684 }
685
686 return $destination;
687 }
688
689
690
691
692
693
694
695
696
697 private function addIndex(int $writtenBytesTotal, int $newBytesAdded = 0): int
698 {
699 clearstatcache();
700 if (file_exists($this->tempBackup->getFilePath())) {
701 $this->archivedFileSize = filesize($this->tempBackup->getFilePath());
702 }
703
704 $start = max($this->archivedFileSize - $writtenBytesTotal, 0);
705
706 $identifiablePath = $this->pathIdentifier->transformPathToIdentifiable($this->archiverDto->getIndexPath());
707
708
709 if ($this->isBackupFormatV1() && $this->isIndexPositionCreated()) {
710 return $this->updateIndexInformationForAlreadyAddedIndex($writtenBytesTotal);
711 }
712
713 if ($this->isBackupFormatV1()) {
714 $identifiablePath = $this->pathIdentifier->transformPathToIdentifiable($this->archiverDto->getIndexPath());
715 $backupFileIndex = $this->backupFileIndex->createIndex($identifiablePath, $start, $writtenBytesTotal, false);
716 $bytesWritten = $this->tempBackupIndex->append($backupFileIndex->getIndex());
717 }
718
719 if (!$this->isBackupFormatV1()) {
720 $bytesWritten = $this->addNewFileHeaderToIndex($newBytesAdded, $start);
721 if ($this->isIndexPositionCreated()) {
722 $this->addIndexPartSize($identifiablePath, $newBytesAdded);
723 return $newBytesAdded;
724 }
725 }
726
727 $this->archiverDto->setIndexPositionCreated(true);
728
729 $this->addIndexPartSize($identifiablePath, $writtenBytesTotal);
730
731
732
733
734
735
736 if (!$this->phpAdapter->isCallable([$this->jobDataDto, 'setTotalFiles']) || !$this->phpAdapter->isCallable([$this->jobDataDto, 'getTotalFiles'])) {
737 debug_log('This method can only be called from the context of Backup');
738 throw new LogicException('This method can only be called from the context of Backup');
739 }
740
741
742 $jobBackupDataDto = $this->jobDataDto;
743 if ($this->archiverDto->getFileSize() >= 2 * GB_IN_BYTES) {
744 $jobBackupDataDto->setIsContaining2GBFile(true);
745 }
746
747 $this->incrementFilesCount($jobBackupDataDto);
748
749 return $bytesWritten;
750 }
751
752
753
754
755
756
757
758 protected function addIndexPartSize(string $identifiablePath, int $newBytesWritten)
759 {
760
761 if (!$this->jobDataDto instanceof JobBackupDataDto) {
762 return;
763 }
764
765
766 $jobDataDto = $this->jobDataDto;
767
768 $collectPartSize = $jobDataDto->getCategorySizes();
769
770 $partName = '';
771 switch ($identifiablePath) {
772 case ($this->pathIdentifier::IDENTIFIER_WP_CONTENT === substr($identifiablePath, 0, strlen($this->pathIdentifier::IDENTIFIER_WP_CONTENT))):
773 $partName = PartIdentifier::WP_CONTENT_PART_SIZE_IDENTIFIER;
774
775 if ($this->pathIdentifier->hasDropinsFile($identifiablePath)) {
776 $dropinsPartName = PartIdentifier::DROPIN_PART_SIZE_IDENTIFIER;
777 if (!isset($collectPartSize[$dropinsPartName])) {
778 $collectPartSize[$dropinsPartName] = 0;
779 }
780
781 $collectPartSize[$dropinsPartName] += $newBytesWritten;
782 }
783
784 break;
785 case ($this->pathIdentifier::IDENTIFIER_PLUGINS === substr($identifiablePath, 0, strlen($this->pathIdentifier::IDENTIFIER_PLUGINS))):
786 $partName = PartIdentifier::PLUGIN_PART_SIZE_IDENTIFIER;
787 break;
788 case ($this->pathIdentifier::IDENTIFIER_THEMES === substr($identifiablePath, 0, strlen($this->pathIdentifier::IDENTIFIER_THEMES))):
789 $partName = PartIdentifier::THEME_PART_SIZE_IDENTIFIER;
790 break;
791 case ($this->pathIdentifier::IDENTIFIER_MUPLUGINS === substr($identifiablePath, 0, strlen($this->pathIdentifier::IDENTIFIER_MUPLUGINS))):
792 $partName = PartIdentifier::MU_PLUGIN_PART_SIZE_IDENTIFIER;
793 break;
794 case ($this->pathIdentifier::IDENTIFIER_UPLOADS === substr($identifiablePath, 0, strlen($this->pathIdentifier::IDENTIFIER_UPLOADS))):
795 $partName = PartIdentifier::UPLOAD_PART_SIZE_IDENTIFIER;
796 if (substr($identifiablePath, -4) === '.sql') {
797 $partName = PartIdentifier::DATABASE_PART_SIZE_IDENTIFIER;
798 }
799
800 break;
801 case ($this->pathIdentifier::IDENTIFIER_LANG === substr($identifiablePath, 0, strlen($this->pathIdentifier::IDENTIFIER_LANG))):
802 $partName = PartIdentifier::LANGUAGE_PART_SIZE_IDENTIFIER;
803 break;
804 case ($this->pathIdentifier::IDENTIFIER_ABSPATH === substr($identifiablePath, 0, strlen($this->pathIdentifier::IDENTIFIER_ABSPATH))):
805 $partName = PartIdentifier::WP_ROOT_PART_SIZE_IDENTIFIER;
806 break;
807 }
808
809 if (empty($partName)) {
810 return;
811 }
812
813
814 if (!isset($collectPartSize[$partName])) {
815 $collectPartSize[$partName] = 0;
816 }
817
818 $collectPartSize[$partName] += $newBytesWritten;
819 $jobDataDto->setCategorySizes($collectPartSize);
820 }
821
822
823
824
825
826
827
828
829 private function updateIndexInformationForAlreadyAddedIndex(int $writtenBytesTotal): int
830 {
831 $lastLine = $this->tempBackupIndex->readLines(1, null, BufferedCache::POSITION_BOTTOM);
832 if (!is_array($lastLine)) {
833 debug_log('Failed to read backup metadata file index information. Error: The last line is no array. Last line: ' . $lastLine);
834 throw new RuntimeException('Failed to read backup metadata file index information. Error: The last line is no array.');
835 }
836
837 $lastLine = array_filter($lastLine, [$this->backupFileIndex, 'isIndexLine']);
838
839 if (count($lastLine) !== 1) {
840 debug_log('Failed to read backup metadata file index information. Error: The last line is not an array or element with countable interface. Last line: ' . print_r($lastLine, true));
841 throw new RuntimeException('Failed to read backup metadata file index information. Error: The last line is not an array or element with countable interface.');
842 }
843
844 $lastLine = array_shift($lastLine);
845
846 $backupFileIndex = $this->backupFileIndex->readIndex($lastLine);
847 $writtenPreviously = $backupFileIndex->bytesEnd;
848
849 $this->tempBackupIndex->deleteBottomBytes(strlen($lastLine));
850
851 $identifiablePath = $this->pathIdentifier->transformPathToIdentifiable($this->archiverDto->getIndexPath());
852 $backupFileIndex = $this->backupFileIndex->createIndex($identifiablePath, $backupFileIndex->bytesStart, $writtenBytesTotal, false);
853 $bytesWritten = $this->tempBackupIndex->append($backupFileIndex->getIndex());
854
855 $this->setIndexPositionCreated();
856
857
858 $this->addIndexPartSize($identifiablePath, $writtenBytesTotal - (int)$writtenPreviously);
859
860 return $bytesWritten;
861 }
862
863 private function isBackupFormatV1(): bool
864 {
865
866 $jobDataDto = $this->jobDataDto;
867 return $jobDataDto->getIsBackupFormatV1();
868 }
869 }
870