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
BeforeUpdateBackupsService.php
265 lines
| 1 | <?php |
| 2 | |
| 3 | namespace WPStaging\Backup\Service; |
| 4 | |
| 5 | use SplFileInfo; |
| 6 | use Throwable; |
| 7 | use WPStaging\Backup\Entity\BackupMetadata; |
| 8 | use WPStaging\Backup\Utils\BackupPathResolver; |
| 9 | use WPStaging\Framework\Facades\Hooks; |
| 10 | |
| 11 | use function WPStaging\functions\debug_log; |
| 12 | |
| 13 | |
| 14 | |
| 15 | |
| 16 | |
| 17 | |
| 18 | |
| 19 | |
| 20 | class BeforeUpdateBackupsService |
| 21 | { |
| 22 | |
| 23 | |
| 24 | |
| 25 | |
| 26 | const FILTER_KEEP_COUNT = 'wpstg.backup.beforeUpdate.keepCount'; |
| 27 | |
| 28 | |
| 29 | |
| 30 | |
| 31 | |
| 32 | const FILTER_REUSE_WINDOW = 'wpstg.backup.beforeUpdate.reuseWindowSeconds'; |
| 33 | |
| 34 | |
| 35 | const DEFAULT_KEEP_COUNT = 3; |
| 36 | |
| 37 | |
| 38 | const DEFAULT_REUSE_WINDOW = 900; |
| 39 | |
| 40 | |
| 41 | |
| 42 | |
| 43 | |
| 44 | const SCOPE_KEYS = [ |
| 45 | 'isExportingPlugins', |
| 46 | 'isExportingMuPlugins', |
| 47 | 'isExportingThemes', |
| 48 | 'isExportingUploads', |
| 49 | 'isExportingOtherWpContentFiles', |
| 50 | 'isExportingOtherWpRootFiles', |
| 51 | 'isExportingDatabase', |
| 52 | ]; |
| 53 | |
| 54 | |
| 55 | private $backupsFinder; |
| 56 | |
| 57 | |
| 58 | private $backupPathResolver; |
| 59 | |
| 60 | |
| 61 | private $backups = null; |
| 62 | |
| 63 | |
| 64 | |
| 65 | |
| 66 | |
| 67 | public function __construct(BackupsFinder $backupsFinder, BackupPathResolver $backupPathResolver) |
| 68 | { |
| 69 | $this->backupsFinder = $backupsFinder; |
| 70 | $this->backupPathResolver = $backupPathResolver; |
| 71 | } |
| 72 | |
| 73 | |
| 74 | |
| 75 | |
| 76 | |
| 77 | |
| 78 | |
| 79 | |
| 80 | public function findReusableBackup(array $requiredScope = []): array |
| 81 | { |
| 82 | $now = time(); |
| 83 | $oldest = $now - $this->getReuseWindow(); |
| 84 | |
| 85 | foreach ($this->findBackups() as $backup) { |
| 86 | if ($backup['dateCreated'] < $oldest) { |
| 87 | return []; |
| 88 | } |
| 89 | |
| 90 | if ($backup['dateCreated'] > $now || !$this->covers($backup['scope'], $requiredScope)) { |
| 91 | continue; |
| 92 | } |
| 93 | |
| 94 | return [ |
| 95 | 'name' => $backup['name'], |
| 96 | 'ageMinutes' => (int)floor(($now - $backup['dateCreated']) / MINUTE_IN_SECONDS), |
| 97 | ]; |
| 98 | } |
| 99 | |
| 100 | return []; |
| 101 | } |
| 102 | |
| 103 | |
| 104 | |
| 105 | |
| 106 | |
| 107 | |
| 108 | private function covers(array $scope, array $requiredScope): bool |
| 109 | { |
| 110 | foreach (self::SCOPE_KEYS as $key) { |
| 111 | if (empty($requiredScope[$key])) { |
| 112 | continue; |
| 113 | } |
| 114 | |
| 115 | if (empty($scope[$key])) { |
| 116 | return false; |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | return true; |
| 121 | } |
| 122 | |
| 123 | |
| 124 | |
| 125 | |
| 126 | |
| 127 | |
| 128 | |
| 129 | public function prune(int $roomFor = 0): int |
| 130 | { |
| 131 | $keep = max(0, $this->getKeepCount() - $roomFor); |
| 132 | |
| 133 | $backups = $this->findBackups(); |
| 134 | if (count($backups) <= $keep) { |
| 135 | return 0; |
| 136 | } |
| 137 | |
| 138 | $deleted = 0; |
| 139 | foreach (array_slice($backups, $keep) as $backup) { |
| 140 | if ($this->delete($backup['file'], $backup['parts'])) { |
| 141 | $deleted++; |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | $this->backups = null; |
| 146 | |
| 147 | return $deleted; |
| 148 | } |
| 149 | |
| 150 | |
| 151 | |
| 152 | |
| 153 | private function findBackups(): array |
| 154 | { |
| 155 | if ($this->backups !== null) { |
| 156 | return $this->backups; |
| 157 | } |
| 158 | |
| 159 | $backups = []; |
| 160 | foreach ($this->backupsFinder->findBackups() as $splFileInfo) { |
| 161 | $metadata = $this->readMetadata($splFileInfo); |
| 162 | if ($metadata === null || !$metadata->getIsBeforeUpdateBackup()) { |
| 163 | continue; |
| 164 | } |
| 165 | |
| 166 | $backups[] = [ |
| 167 | 'file' => $splFileInfo, |
| 168 | 'name' => $metadata->getName(), |
| 169 | 'dateCreated' => (int)$metadata->getDateCreated(), |
| 170 | 'parts' => $this->getParts($metadata), |
| 171 | 'scope' => [ |
| 172 | 'isExportingPlugins' => $metadata->getIsExportingPlugins(), |
| 173 | 'isExportingMuPlugins' => $metadata->getIsExportingMuPlugins(), |
| 174 | 'isExportingThemes' => $metadata->getIsExportingThemes(), |
| 175 | 'isExportingUploads' => $metadata->getIsExportingUploads(), |
| 176 | 'isExportingOtherWpContentFiles' => $metadata->getIsExportingOtherWpContentFiles(), |
| 177 | 'isExportingOtherWpRootFiles' => $metadata->getIsExportingOtherWpRootFiles(), |
| 178 | 'isExportingDatabase' => $metadata->getIsExportingDatabase(), |
| 179 | ], |
| 180 | ]; |
| 181 | } |
| 182 | |
| 183 | usort($backups, function ($left, $right) { |
| 184 | return $right['dateCreated'] - $left['dateCreated']; |
| 185 | }); |
| 186 | |
| 187 | $this->backups = $backups; |
| 188 | |
| 189 | return $backups; |
| 190 | } |
| 191 | |
| 192 | |
| 193 | |
| 194 | |
| 195 | |
| 196 | private function readMetadata(SplFileInfo $splFileInfo) |
| 197 | { |
| 198 | try { |
| 199 | return (new BackupMetadata())->hydrateByFilePath($splFileInfo->getPathname()); |
| 200 | } catch (Throwable $e) { |
| 201 | debug_log('WP STAGING: Could not read metadata while pruning backup-before-update backups - File: ' . $splFileInfo->getPathname() . ' - ' . $e->getMessage()); |
| 202 | |
| 203 | return null; |
| 204 | } |
| 205 | } |
| 206 | |
| 207 | |
| 208 | |
| 209 | |
| 210 | |
| 211 | private function getParts(BackupMetadata $metadata): array |
| 212 | { |
| 213 | if (!$metadata->getIsMultipartBackup()) { |
| 214 | return []; |
| 215 | } |
| 216 | |
| 217 | return $metadata->getMultipartMetadata()->getBackupParts(); |
| 218 | } |
| 219 | |
| 220 | |
| 221 | |
| 222 | |
| 223 | |
| 224 | |
| 225 | private function delete(SplFileInfo $backup, array $parts): bool |
| 226 | { |
| 227 | foreach ($parts as $part) { |
| 228 | $partPath = $this->backupPathResolver->resolveBackupPartPath($part, $backup->getFilename()); |
| 229 | if ($partPath === '' || !file_exists($partPath)) { |
| 230 | continue; |
| 231 | } |
| 232 | |
| 233 | if (!unlink($partPath)) { |
| 234 | debug_log('WP STAGING: Could not delete backup part while pruning backup-before-update backups: ' . $partPath); |
| 235 | |
| 236 | return false; |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | if (!unlink($backup->getPathname())) { |
| 241 | debug_log('WP STAGING: Could not delete backup while pruning backup-before-update backups: ' . $backup->getPathname()); |
| 242 | |
| 243 | return false; |
| 244 | } |
| 245 | |
| 246 | return true; |
| 247 | } |
| 248 | |
| 249 | |
| 250 | |
| 251 | |
| 252 | private function getKeepCount(): int |
| 253 | { |
| 254 | return (int)Hooks::applyFilters(self::FILTER_KEEP_COUNT, self::DEFAULT_KEEP_COUNT); |
| 255 | } |
| 256 | |
| 257 | |
| 258 | |
| 259 | |
| 260 | private function getReuseWindow(): int |
| 261 | { |
| 262 | return (int)Hooks::applyFilters(self::FILTER_REUSE_WINDOW, self::DEFAULT_REUSE_WINDOW); |
| 263 | } |
| 264 | } |
| 265 |