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 / AbstractBackgroundBackupRequest.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
AbstractBackgroundBackupRequest.php
373 lines
1 <?php
2
3 namespace WPStaging\Backup\Service;
4
5 use WPStaging\Backup\BackgroundProcessing\Backup\PrepareBackup;
6 use WPStaging\Core\WPStaging;
7 use WPStaging\Framework\Analytics\Actions\AnalyticsGenericEvent;
8 use WPStaging\Framework\BackgroundProcessing\QueueProcessor;
9 use WPStaging\Framework\Job\JobTransientCache;
10
11 use function WPStaging\functions\debug_log;
12
13
14
15
16
17
18
19
20
21
22
23 abstract class AbstractBackgroundBackupRequest
24 {
25
26 const STATUS_QUEUED = 'queued';
27
28
29 const STATUS_RUNNING = 'running';
30
31 const STATUS_COMPLETED = 'completed';
32
33
34 const STATUS_FAILED = 'failed';
35
36
37
38
39
40
41 const MAX_QUEUED_AGE_IN_SECONDS = 6 * HOUR_IN_SECONDS;
42
43
44
45
46
47 const MAX_RUNNING_AGE_IN_SECONDS = DAY_IN_SECONDS;
48
49
50
51
52
53 const MAX_TERMINAL_AGE_IN_SECONDS = HOUR_IN_SECONDS;
54
55
56 const NUDGE_LOCK_IN_SECONDS = 10;
57
58
59 private $state = null;
60
61
62
63
64 abstract protected function getOptionName(): string;
65
66
67
68
69 abstract protected function getNudgeTransientName(): string;
70
71
72
73
74 abstract protected function getEventNames(): array;
75
76
77
78
79 abstract protected function getAnalyticsGroup(): string;
80
81
82
83
84 abstract protected function getLogContext(): string;
85
86
87
88
89
90 public function getStatus(): string
91 {
92 $state = $this->read();
93
94 return isset($state['status']) ? $state['status'] : '';
95 }
96
97
98
99
100 public function isPending(): bool
101 {
102 return in_array($this->getStatus(), [self::STATUS_QUEUED, self::STATUS_RUNNING], true);
103 }
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121 public function runWaitingWork()
122 {
123
124
125 if (!$this->isPending() || get_transient($this->getNudgeTransientName()) !== false) {
126 return;
127 }
128
129 set_transient($this->getNudgeTransientName(), time(), self::NUDGE_LOCK_IN_SECONDS);
130
131 try {
132 WPStaging::make(QueueProcessor::class)->process();
133 } catch (\Throwable $e) {
134 debug_log($this->getLogContext() . ': could not run the queued backup. ' . $e->getMessage(), 'debug', false);
135 }
136 }
137
138
139
140
141 public function markCompleted()
142 {
143 if ($this->getStatus() !== self::STATUS_RUNNING || !$this->isOwnBackupJob()) {
144 return;
145 }
146
147 $this->transition(self::STATUS_COMPLETED, 'completed_at', $this->getEventName('completed'));
148 }
149
150
151
152
153
154
155
156
157
158 public function onBackgroundJobFailure($failure = [])
159 {
160 $reporter = is_array($failure) && isset($failure['jobTransientCache']) ? $failure['jobTransientCache'] : null;
161
162 if ($this->getStatus() !== self::STATUS_RUNNING || !$this->isOwnBackupJob($reporter)) {
163 return;
164 }
165
166 $this->markFailed(is_array($failure) && isset($failure['errorMessage']) ? (string)$failure['errorMessage'] : '');
167 }
168
169
170
171
172
173 public function markFailed(string $reason = '')
174 {
175 if (!$this->isPending()) {
176 return;
177 }
178
179 if ($reason !== '') {
180 debug_log($this->getLogContext() . ': queued backup failed. ' . $reason, 'debug', false);
181 }
182
183 $this->transition(self::STATUS_FAILED, 'failed_at', $this->getEventName('failed'), [], $this->getFailureReport($reason));
184 $this->afterFailed($reason);
185 }
186
187
188
189
190 public function clear()
191 {
192 $this->state = [];
193 delete_option($this->getOptionName());
194 }
195
196
197
198
199
200
201
202 protected function start(array $backupData): bool
203 {
204 $jobId = WPStaging::make(PrepareBackup::class)->prepare($backupData);
205
206 if (is_wp_error($jobId)) {
207 $this->markFailed($jobId->get_error_message());
208
209 return false;
210 }
211
212 $this->transition(self::STATUS_RUNNING, 'started_at', $this->getEventName('started'), [
213 'backup_job_id' => (string)$jobId,
214 ]);
215
216 $this->afterStarted();
217
218 return true;
219 }
220
221
222
223
224 protected function afterStarted()
225 {
226 }
227
228
229
230
231
232 protected function afterFailed(string $reason = '')
233 {
234 }
235
236
237
238
239
240
241
242
243
244
245 protected function getFailureReport(string $reason): array
246 {
247 return [];
248 }
249
250
251
252
253
254
255
256
257
258
259 protected function read(): array
260 {
261 if ($this->state !== null) {
262 return $this->state;
263 }
264
265 $state = get_option($this->getOptionName(), []);
266 $this->state = (is_array($state) && !empty($state['status'])) ? $state : [];
267
268 if ($this->state !== [] && $this->isStale($this->state)) {
269 $this->clear();
270 }
271
272 return $this->state;
273 }
274
275
276
277
278
279 protected function write(array $state)
280 {
281 $this->state = $state;
282 update_option($this->getOptionName(), $state, false);
283 }
284
285
286
287
288
289
290
291
292
293 protected function transition(string $status, string $timeKey, string $event, array $extra = [], array $eventData = [])
294 {
295 $this->write(array_merge($this->read(), $extra, [
296 'status' => $status,
297 $timeKey => time(),
298 ]));
299
300 AnalyticsGenericEvent::logEvent($event, $this->getAnalyticsGroup(), $eventData);
301 }
302
303
304
305
306
307 private function getEventName(string $transition): string
308 {
309 $events = $this->getEventNames();
310
311 return isset($events[$transition]) ? $events[$transition] : '';
312 }
313
314
315
316
317
318 private function isStale(array $state): bool
319 {
320 if ($state['status'] === self::STATUS_QUEUED) {
321 return $this->isOlderThan($state, ['queued_at'], self::MAX_QUEUED_AGE_IN_SECONDS);
322 }
323
324 if ($state['status'] === self::STATUS_RUNNING) {
325 return $this->isOlderThan($state, ['started_at', 'queued_at'], self::MAX_RUNNING_AGE_IN_SECONDS);
326 }
327
328 return $this->isOlderThan($state, ['completed_at', 'failed_at', 'started_at', 'queued_at'], self::MAX_TERMINAL_AGE_IN_SECONDS);
329 }
330
331
332
333
334
335
336
337 private function isOlderThan(array $state, array $timeKeys, int $maxAgeInSeconds): bool
338 {
339 foreach ($timeKeys as $timeKey) {
340 if (!empty($state[$timeKey])) {
341 return (int)$state[$timeKey] < time() - $maxAgeInSeconds;
342 }
343 }
344
345 return true;
346 }
347
348
349
350
351
352
353
354
355
356
357
358
359
360 private function isOwnBackupJob($reporter = null): bool
361 {
362 $state = $this->read();
363 if (empty($state['backup_job_id'])) {
364 return false;
365 }
366
367 $cache = $reporter instanceof JobTransientCache ? $reporter : WPStaging::make(JobTransientCache::class);
368 $job = $cache->getJob();
369
370 return is_array($job) && isset($job['queueId']) && $job['queueId'] === $state['backup_job_id'];
371 }
372 }
373