PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.7.0
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.7.0
4.11.2 4.11.1 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 / BackupsFinder.php
wp-staging / Backup / Service Last commit date
Compression 6 months ago Database 6 months ago AbstractBackupsFinder.php 1 year ago AbstractExtractor.php 5 months ago AbstractServiceProvider.php 2 years ago Archiver.php 7 months ago BackupAssets.php 2 years ago BackupContent.php 1 year ago BackupMetadataEditor.php 1 year ago BackupMetadataReader.php 7 months ago BackupSigner.php 8 months ago BackupsFinder.php 6 months ago Extractor.php 5 months ago FileBackupService.php 8 months ago FileBackupServiceProvider.php 2 years ago ServiceInterface.php 2 years ago ZlibCompressor.php 1 year ago
BackupsFinder.php
209 lines
1 <?php
2
3 namespace WPStaging\Backup\Service;
4
5 use RuntimeException;
6 use SplFileInfo;
7 use Throwable;
8 use WPStaging\Core\WPStaging;
9 use WPStaging\Framework\Adapter\Directory;
10 use WPStaging\Framework\Filesystem\DirectoryListing;
11 use WPStaging\Framework\Filesystem\FileObject;
12 use WPStaging\Framework\Filesystem\Filesystem;
13 use WPStaging\Backup\BackupValidator;
14 use WPStaging\Backup\Entity\BackupMetadata;
15 use WPStaging\Backup\Exceptions\BackupRuntimeException;
16 use WPStaging\Framework\Facades\Hooks;
17 use WPStaging\Framework\Network\RemoteDownloader;
18
19 use function WPStaging\functions\debug_log;
20
21 /**
22 * Class BackupsFinder
23 *
24 * Finds the .wpstg backups in the filesystem.
25 *
26 * @package WPStaging\Backup
27 */
28 class BackupsFinder extends AbstractBackupsFinder
29 {
30 /** @var string */
31 const FILTER_BACKUP_DIRECTORY = 'wpstg.backup.directory';
32
33 /** @var Directory */
34 private $directory;
35
36 /** @var Filesystem */
37 private $filesystem;
38
39 /** @var DirectoryListing */
40 private $directoryListing;
41
42 public function __construct(Directory $directory, Filesystem $filesystem, DirectoryListing $directoryListing)
43 {
44 $this->directory = $directory;
45 $this->filesystem = $filesystem;
46 $this->directoryListing = $directoryListing;
47 }
48
49 /**
50 * @param bool $refresh
51 * @return string
52 * @throws BackupRuntimeException
53 */
54 public function getBackupsDirectory(bool $refresh = false): string
55 {
56 if ($refresh || $this->backupsDirectory === null) {
57 $defaultBackupUploadsDirectory = $this->directory->getPluginUploadsDirectory($refresh = true) . Archiver::BACKUP_DIR_NAME;
58
59 /**
60 * Allows filtering the path to the directory Backups will be written to and read from.
61 *
62 * Note: changing this directory while there are backups in the previous location will, in
63 * fact, hide those Backups from the plugin. The task of moving the Backups left in the previous
64 * location(s) is left to the user.
65 *
66 * By default it uses the folder ABSPATH/wp-content/uploads/wp-staging/backups
67 * You can overwrite the path with the filter wpstg.backup.directory.
68 * The filtered provided path needs to be an absolute path that is inside the WordPress root (ABSPATH)
69 * E.g. If ABSPATH: '/var/www/example.com' then filtered path can be '/var/www/example.com/backups'. It can not be '/var/www/backups'
70
71 *
72 * @param string $defaultBackupUploadsDirectory The default path to the directory Backups will be read from and
73 * written to.
74 */
75 $directory = Hooks::applyFilters(self::FILTER_BACKUP_DIRECTORY, $defaultBackupUploadsDirectory);
76
77 $directory = trailingslashit(wp_normalize_path($directory));
78
79 if (!$this->filesystem->mkdir($directory, true)) {
80 throw BackupRuntimeException::cannotCreateBackupsDirectory($directory);
81 }
82
83 if (!is_readable($directory)) {
84 throw BackupRuntimeException::backupsDirectoryNotReadable($directory);
85 }
86
87 if (!is_writeable($directory)) {
88 throw BackupRuntimeException::backupsDirectoryNotWriteable($directory);
89 }
90
91 $this->directoryListing->maybeUpdateOldHtaccessWebConfig($directory);
92
93 $this->backupsDirectory = $directory;
94 }
95
96 return $this->backupsDirectory;
97 }
98
99 /**
100 * Delete temp backup files associated with a job ID.
101 * This includes the temp backup file, any in-progress upload file,
102 * and the renamed .wpstg file from remote sync downloads.
103 *
104 * @param string $jobId
105 * @return void
106 */
107 public function deleteTempBackupByJobId(string $jobId)
108 {
109 $backupsDir = $this->getBackupsDirectory();
110
111 // Delete the temp backup file (e.g., {jobId}.wpstgtmp)
112 $tempBackupPath = $backupsDir . $jobId . '.' . Archiver::TMP_BACKUP_EXTENSION;
113 if (file_exists($tempBackupPath)) {
114 $this->filesystem->delete($tempBackupPath);
115 }
116
117 // Delete the in-progress upload file (e.g., {jobId}.wpstgtmp.uploading)
118 $uploadingPath = $tempBackupPath . '.' . RemoteDownloader::UPLOADING_EXTENSION;
119 if (file_exists($uploadingPath)) {
120 $this->filesystem->delete($uploadingPath);
121 }
122
123 // Delete the renamed backup file from remote sync (e.g., {jobId}.wpstg)
124 // This happens when a download "completes" but may be corrupt/incomplete
125 $renamedBackupPath = $backupsDir . $jobId . '.' . Archiver::BACKUP_EXTENSION;
126 if (file_exists($renamedBackupPath)) {
127 $this->filesystem->delete($renamedBackupPath);
128 }
129 }
130
131 /**
132 * @param string $scheduleId
133 *
134 * @return SplFileInfo[]
135 */
136 public function findBackupByScheduleId(string $scheduleId): array
137 {
138 $backups = array_filter($this->findBackups(), function ($splFileInfo) use ($scheduleId) {
139 $backupFile = $splFileInfo->getPathname();
140 try {
141 $metadata = (new BackupMetadata())->hydrateByFilePath($backupFile);
142
143 return $metadata->getScheduleId() == $scheduleId;
144 } catch (Throwable $ex) {
145 debug_log("WP STAGING: Could not find backup by schedule ID {$scheduleId} - File: {$backupFile} - " . $ex->getMessage());
146
147 return false;
148 }
149 });
150
151 if (empty($backups)) {
152 return [];
153 }
154
155 return $backups;
156 }
157
158 /**
159 * @return bool
160 * @throws \WPStaging\Framework\Job\Exception\DiskNotWritableException
161 */
162 public function hasInvalidFileIndex(): bool
163 {
164 $backupFiles = $this->findBackups();
165 $hasInvalidFilesIndexBackup = false;
166
167 /** @var BackupValidator */
168 $backupValidator = WPStaging::make(BackupValidator::class);
169
170 /** @var SplFileInfo $file */
171 foreach ($backupFiles as $file) {
172 if ($file->getExtension() !== 'wpstg') {
173 continue;
174 }
175
176 $isValidFileIndex = false;
177 try {
178 $isValidFileIndex = $this->validateBackupFileIndex($backupValidator, $file->getRealPath());
179 } catch (RuntimeException $ex) {
180 debug_log('Backup Validation: ' . $ex->getMessage());
181 continue;
182 }
183
184 if (!$isValidFileIndex) {
185 $hasInvalidFilesIndexBackup = true;
186 break;
187 }
188 }
189
190 return $hasInvalidFilesIndexBackup;
191 }
192
193 /**
194 * @param BackupValidator $backupValidator
195 * @param string $backupPath
196 * @return bool
197 *
198 * @throws RuntimeException
199 */
200 protected function validateBackupFileIndex(BackupValidator $backupValidator, string $backupPath): bool
201 {
202 $backupMetadata = new BackupMetadata();
203 $backupMetadata = $backupMetadata->hydrateByFilePath($backupPath);
204 $fileObject = new FileObject($backupPath);
205
206 return $backupValidator->validateFileIndex($fileObject, $backupMetadata);
207 }
208 }
209