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