PluginProbe ʕ •ᴥ•ʔ
JetBackup – Backup, Restore & Migrate / trunk
JetBackup – Backup, Restore & Migrate vtrunk
3.1.22.3 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.8.1 1.4.9 1.5.0 1.5.1 1.5.1.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 1.6.10 1.6.11 1.6.12 1.6.13 1.6.15 1.6.5.1 1.6.8.8 1.6.9 1.6.9.1 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7.5 2.0.8.7 2.0.9.11 2.0.9.14 2.0.9.15 2.0.9.6 2.0.9.7 2.0.9.9 3.1.10.7 3.1.11.1 3.1.12.3 3.1.13.4 3.1.14.17 3.1.15.4 3.1.16.1 3.1.17.5 3.1.18.10 3.1.18.8 3.1.18.9 3.1.19.8 3.1.20.3 3.1.21.3 3.1.7.9 3.1.9.2 trunk 1.1.90 1.1.91 1.2.0 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2
backup / src / JetBackup / Cron / Task / System.php
backup / src / JetBackup / Cron / Task Last commit date
.htaccess 1 year ago Backup.php 1 year ago Download.php 1 year ago DownloadBackupLog.php 1 year ago Export.php 1 year ago Extract.php 1 year ago PreRestore.php 5 months ago Reindex.php 1 month ago Restore.php 5 months ago RetentionCleanup.php 4 months ago System.php 5 months ago Task.php 4 months ago index.html 1 year ago web.config 1 year ago
System.php
545 lines
1 <?php
2
3 namespace JetBackup\Cron\Task;
4
5 use Exception;
6 use JetBackup\Alert\Alert;
7 use JetBackup\BackupJob\BackupJob;
8 use JetBackup\Download\Download;
9 use JetBackup\Entities\Util;
10 use JetBackup\Exception\DBException;
11 use JetBackup\Exception\HttpRequestException;
12 use JetBackup\Exception\IOException;
13 use JetBackup\Exception\JetBackupLinuxException;
14 use JetBackup\Exception\LicenseException;
15 use JetBackup\Exception\NotificationException;
16 use JetBackup\Exception\QueueException;
17 use JetBackup\Factory;
18 use JetBackup\JetBackup;
19 use JetBackup\JetBackupLinux\JetBackupLinux;
20 use JetBackup\License\License;
21 use JetBackup\Notification\Notification;
22 use JetBackup\Queue\Queue;
23 use JetBackup\Queue\QueueItem;
24 use JetBackup\Queue\QueueItemSystem;
25 use JetBackup\Upload\Upload;
26 use JetBackup\Web\JetHttp;
27 use JetBackup\Wordpress\Helper;
28 use JetBackup\Wordpress\Wordpress;
29 use SleekDB\Exceptions\InvalidArgumentException;
30
31 if (!defined( '__JETBACKUP__')) die('Direct access is not allowed');
32
33 class System extends Task {
34
35 const LOG_FILENAME = 'system';
36
37 const CHECKSUM_URL = "https://api.wordpress.org/core/checksums/1.0/?version=%s&locale=%s";
38
39 const TYPE_HOURLY = 1;
40 const TYPE_DAILY = 2;
41
42 const TYPE_NAMES = [
43 self::TYPE_HOURLY => 'Hourly',
44 self::TYPE_DAILY => 'Daily',
45 ];
46
47 const HOURLY_INTERVAL = 14400; // 4 hours
48 const DAILY_INTERVAL = 86400; // 24 hours
49
50 const TEMP_FILES_TTL = 86400;
51 const VALIDATE_CHECKSUMS_EXCLUDES = [ 'wp-content/themes/*', 'wp-content/plugins/*' ];
52 private QueueItemSystem $_queue_item_system;
53
54 public function __construct() {
55 parent::__construct(self::LOG_FILENAME);
56 }
57
58 public function execute():void {
59 parent::execute();
60
61 $this->_queue_item_system = $this->getQueueItem()->getItemData();
62
63 if($this->getQueueItem()->getStatus() == Queue::STATUS_PENDING) {
64 $this->getLogController()->logMessage('Starting ' . self::TYPE_NAMES[$this->_queue_item_system->getType()] . ' System Tasks');
65
66 $this->getQueueItem()->getProgress()->setTotalItems($this->_queue_item_system->getType() == self::TYPE_DAILY ? 9 : 6);
67 $this->getQueueItem()->save();
68
69 $this->getQueueItem()->updateProgress('Starting ' . self::TYPE_NAMES[$this->_queue_item_system->getType()] . ' System Tasks');
70 } else if($this->getQueueItem()->getStatus() > Queue::STATUS_PENDING) {
71 $this->getLogController()->logMessage('Resumed ' . self::TYPE_NAMES[$this->_queue_item_system->getType()] . ' System Tasks');
72 }
73
74 try {
75 switch($this->_queue_item_system->getType()) {
76 case self::TYPE_DAILY:
77 $this->func([$this, '_checkLicense']);
78 $this->func([$this, '_backupJobMonitor']);
79 $this->func([$this, '_databaseCleanup']);
80 $this->func([$this, '_retentionCleanup']);
81 $this->func([$this, '_uploadCleanup']);
82 $this->func([$this, '_systemCleanup']);
83 $this->func([$this, '_logsCleanup']);
84 $this->func([$this, '_validateChecksums']);
85 $this->func([$this, '_processDailyAlerts']);
86 $this->func([$this, '_indexJBBackups']);
87 break;
88
89 case self::TYPE_HOURLY:
90 $this->func([$this, '_checkLicense']);
91 $this->func([$this, '_databaseCleanup']);
92 $this->func([$this, '_retentionCleanup']);
93 $this->func([$this, '_downloadsCleanup']);
94 $this->func([$this, '_indexJBBackups']);
95 break;
96 }
97
98 if($this->getQueueItem()->getStatus() < Queue::STATUS_DONE && !$this->getQueueItem()->getErrors()) $this->getQueueItem()->updateStatus(Queue::STATUS_DONE);
99 else $this->getQueueItem()->updateStatus(Queue::STATUS_PARTIALLY);
100 $this->getLogController()->logMessage('Completed!');
101 } catch(\Exception $e) {
102 $this->getQueueItem()->updateStatus(Queue::STATUS_FAILED);
103 $this->getLogController()->logError($e->getMessage());
104 $this->getLogController()->logMessage('Failed!');
105 }
106
107 $this->getQueueItem()->updateProgress(
108 $this->getQueueItem()->getStatus() == Queue::STATUS_DONE
109 ? 'System Tasks Completed!'
110 : ($this->getQueueItem()->getStatus() == Queue::STATUS_PARTIALLY
111 ? 'Completed with errors (see logs)'
112 : 'System Tasks Failed!'),
113 QueueItem::PROGRESS_LAST_STEP
114 );
115
116 $this->getLogController()->logMessage('Total time: ' . $this->getExecutionTimeElapsed());
117 }
118
119 /**
120 * @return void
121 * @throws IOException
122 * @throws InvalidArgumentException
123 * @throws QueueException
124 * @throws \SleekDB\Exceptions\IOException
125 */
126 public static function addToQueue() {
127
128 $system = new QueueItemSystem();
129
130 $queue_item = QueueItem::prepare();
131 $queue_item->setType(Queue::QUEUE_TYPE_SYSTEM);
132 $daily_last_run = Factory::getConfig()->getSystemCronDailyLastRun();
133 $hourly_last_run = Factory::getConfig()->getSystemCronHourlyLastRun();
134
135 // Check if we need to run daily tasks
136 if(!$daily_last_run || $daily_last_run < (time() - self::DAILY_INTERVAL)) {
137 $system->setType(self::TYPE_DAILY);
138 Factory::getConfig()->setSystemCronDailyLastRun();
139 }
140 // Only if we don't need to run daily tasks, check if we need to run hourly tasks
141 elseif (!$hourly_last_run || $hourly_last_run < (time() - self::HOURLY_INTERVAL)) {
142 $system->setType(self::TYPE_HOURLY);
143 } else return;
144 Factory::getConfig()->setSystemCronHourlyLastRun();
145
146 $queue_item->setItemData($system);
147 Queue::addToQueue($queue_item);
148
149 Factory::getConfig()->save();
150 }
151
152 public function _uploadCleanup() {
153 $this->getLogController()->logMessage("\t [_retentionCleanup]");
154 $this->getLogController()->logMessage('Execution time: ' . $this->getExecutionTimeElapsed());
155 $this->getLogController()->logMessage('TTL time: ' . $this->getExecutionTimeLimit());
156
157 $this->getQueueItem()->updateStatus(Queue::STATUS_SYSTEM_UPLOAD_CLEANUP);
158 $this->getQueueItem()->updateProgress('Uploads Cleanup');
159
160 $list = Upload::query()
161 ->where([Upload::CREATED, '<', time() - (60 * 60 * 24)])
162 ->getQuery()
163 ->fetch();
164
165 foreach($list as $upload_details) {
166 $upload = new Upload($upload_details[JetBackup::ID_FIELD]);
167 if(!$upload->getId()) continue;
168
169 $location = dirname($upload->getFileLocation());
170
171 if($location && is_dir($location)) Util::rm($location);
172 $upload->delete();
173 }
174 }
175
176 /**
177 * @throws \SleekDB\Exceptions\IOException
178 * @throws InvalidArgumentException
179 */
180 public function _databaseCleanup() {
181
182 $this->getLogController()->logMessage("\t [_databaseCleanup]");
183 $this->getLogController()->logMessage('Execution time: ' . $this->getExecutionTimeElapsed());
184 $this->getLogController()->logMessage('TTL time: ' . $this->getExecutionTimeLimit());
185 $this->getQueueItem()->updateStatus(Queue::STATUS_SYSTEM_DB_CLEANUP);
186 $this->getQueueItem()->updateProgress('Database Cleanup');
187
188 if($ttl = Factory::getSettingsMaintenance()->getQueueItemsTTL()) {
189 QueueItem::query()
190 ->where([QueueItem::STATUS, '>=', Queue::STATUS_DONE])
191 ->where([QueueItem::CREATED, '<', (time() - ($ttl * 3600))])
192 ->getQuery()
193 ->delete();
194 }
195
196 if($ttl = Factory::getSettingsMaintenance()->getAlertsTTL()) {
197 Alert::query()
198 ->where([Alert::CREATED, '<', (time() - ($ttl * 3600))])
199 ->getQuery()
200 ->delete();
201 }
202 }
203
204 /**
205 * @throws IOException
206 * @throws \Exception
207 */
208 public function _systemCleanup() {
209 if($this->_queue_item_system->getType() != self::TYPE_DAILY) return;
210 $this->getLogController()->logMessage("\t [_systemCleanup]");
211 $this->getLogController()->logMessage('Execution time: ' . $this->getExecutionTimeElapsed());
212 $this->getLogController()->logMessage('TTL time: ' . $this->getExecutionTimeLimit());
213
214 $this->getQueueItem()->updateStatus(Queue::STATUS_SYSTEM_SYSTEM_CLEANUP);
215 $this->getQueueItem()->updateProgress('System Cleanup');
216
217 $tmp_dir = Factory::getLocations()->getTempDir();
218 $dir = dir($tmp_dir);
219 $clearedFolders = 0;
220
221 while(($file = $dir->read()) !== false) {
222 $filepath = $tmp_dir . JetBackup::SEP . $file;
223
224 $this->getLogController()->logDebug("\t File: {$filepath}, mtime: " . filemtime($filepath) . ", TTL Threshold: " . (time() - self::TEMP_FILES_TTL));
225
226 if($file == '.' ||
227 $file == '..' ||
228 !is_dir($filepath) ||
229 (filemtime($filepath) > (time() - self::TEMP_FILES_TTL))
230 ) {
231 $this->getLogController()->logDebug("\t Skipping: {$filepath}");
232 continue;
233 }
234 $this->getLogController()->logDebug("\t Removing: {$filepath}");
235 Util::rm($filepath);
236 $clearedFolders++;
237 }
238
239 $dir->close();
240 if($clearedFolders > 0) Alert::add('System Cleanup', "Removed $clearedFolders temporary folders, refer to system logs for more details", Alert::LEVEL_INFORMATION);
241
242 $this->getLogController()->logMessage("Searching for old public restore files...");
243 // Check if there are public restore leftovers file is older than 24 hours
244 foreach (PreRestore::findPublicRestoreFiles() as $file) {
245 if (filemtime($file) < (time() - self::TEMP_FILES_TTL)) { //24 hours
246 $this->getLogController()->logMessage("Removing $file");
247 @unlink($file);
248 }
249 }
250
251
252
253 $this->getLogController()->logMessage("Clearing generated support users if exists");
254 Helper::clearSupportUser();
255
256 }
257
258 /**
259 * @return void
260 */
261 public function _processDailyAlerts() : void {
262
263 if($this->_queue_item_system->getType() != self::TYPE_DAILY) return;
264 $this->getLogController()->logMessage("\t [_processDailyAlerts]");
265 $this->getLogController()->logMessage("\t Execution time: {$this->getExecutionTimeElapsed()}");
266 $this->getLogController()->logMessage("\t TTL time: {$this->getExecutionTimeLimit()}");
267
268 $this->getQueueItem()->updateStatus(Queue::STATUS_SYSTEM_DAILY_ALERTS);
269 $this->getQueueItem()->updateProgress(Queue::STATUS_SYSTEM_NAMES[Queue::STATUS_SYSTEM_DAILY_ALERTS]);
270
271 try {
272 Alert::processDailyAlerts();
273 } catch (Exception $e) {
274 $this->getLogController()->logMessage('[_processDailyAlerts] Error : ' . $e->getMessage());
275 // just logging without breaking
276 }
277
278 }
279
280 /**
281 * @return void
282 * @throws HttpRequestException
283 * @throws NotificationException
284 */
285 public function _validateChecksums() {
286
287 if(
288 $this->_queue_item_system->getType() != self::TYPE_DAILY ||
289 !Factory::getSettingsSecurity()->isValidateChecksumsEnabled()
290 ) return;
291
292 $this->getLogController()->logMessage("\t [_validateChecksums]");
293 $this->getLogController()->logMessage('Execution time: ' . $this->getExecutionTimeElapsed());
294 $this->getLogController()->logMessage('TTL time: ' . $this->getExecutionTimeLimit());
295
296 $this->getQueueItem()->updateStatus(Queue::STATUS_SYSTEM_VALIDATE_CHECKSUMS);
297 $this->getQueueItem()->updateProgress('Validate System Checksums');
298
299 $response = JetHttp::request()
300 ->setReturnTransfer()
301 ->setTimeout(5)
302 ->setFollowLocation()
303 ->exec(sprintf(self::CHECKSUM_URL, Wordpress::getVersion(), Wordpress::getLocale()));
304
305 if($response->getHeaders()->getCode() != 200 || !($body = $response->getBody())) return;
306
307 $files = [];
308 $homedir = Factory::getWPHelper()->getWordPressHomedir();
309 $body = json_decode($body);
310
311 foreach ($body->checksums as $file => $checksum) {
312
313 foreach (self::VALIDATE_CHECKSUMS_EXCLUDES as $pattern) {
314 if (fnmatch($pattern, $file)) continue 2;
315 }
316
317 $local_file = $homedir . $file;
318 if(!file_exists($local_file)) continue;
319
320 $local_checksum = md5_file($local_file);
321
322 if ($local_checksum !== $checksum) {
323 $files[] = [
324 'file' => $local_file,
325 'api_checksum' => $checksum,
326 'local_checksum' => $local_checksum,
327 ];
328 }
329 }
330
331
332 if(!sizeof($files)) return;
333 $this->getLogController()->logDebug("\t [_validateChecksums] Found files: " . print_r($files, true));
334 Notification::message()
335 ->addParam('backup_domain', Wordpress::getSiteDomain())
336 ->addParam('checksums', $files)
337 ->send('Checksum Verification Failed', 'checksum_alert');
338 }
339
340 /**
341 * @return void
342 * @throws InvalidArgumentException
343 * @throws \SleekDB\Exceptions\IOException
344 */
345 public function _retentionCleanup(): void
346 {
347 $this->getLogController()->logMessage("\t [_retentionCleanup]");
348 $this->getLogController()->logMessage('Execution time: ' . $this->getExecutionTimeElapsed());
349 $this->getLogController()->logMessage('TTL time: ' . $this->getExecutionTimeLimit());
350
351 $this->getQueueItem()->updateProgress('Adding retention cleanup to queue');
352
353 $itemId = 0;
354
355 // Singleton queue item: if already queued/running, do nothing (normal).
356 if (Queue::inQueue(Queue::QUEUE_TYPE_RETENTION_CLEANUP, $itemId)) {
357 $this->getLogController()->logDebug('Retention cleanup is already in the queue');
358 return;
359 }
360
361 try {
362 $queue_item = QueueItem::prepare();
363 $queue_item->setType(Queue::QUEUE_TYPE_RETENTION_CLEANUP);
364 $queue_item->setItemId($itemId);
365
366 Queue::addToQueue($queue_item);
367
368 } catch (QueueException $e) {
369 // Race-safe: two processes can pass the pre-check simultaneously.
370 if (stripos($e->getMessage(), 'already in queue') !== false) {
371 $this->getLogController()->logDebug('Retention cleanup is already in the queue');
372 return;
373 }
374
375 $this->getLogController()->logMessage('[System] Adding retention cleanup failed: ' . $e->getMessage());
376 // just logging without breaking
377 }
378 }
379
380
381 /**
382 * @return void
383 * @throws DBException
384 */
385 public function _downloadsCleanup() {
386 try {
387 $this->getLogController()->logMessage("\t [_downloadsCleanup]");
388 $this->getLogController()->logMessage('Execution time: ' . $this->getExecutionTimeElapsed());
389 $this->getLogController()->logMessage('TTL time: ' . $this->getExecutionTimeLimit());
390 $this->getQueueItem()->updateProgress('Cleaning download folder');
391
392 if (($ttl = Factory::getSettingsMaintenance()->getDownloadItemsTTL() * 3600) === 0) return;
393 Download::deleteByTTL($ttl);
394
395 } catch ( \SleekDB\Exceptions\IOException | InvalidArgumentException $e) {
396 $this->getLogController()->logMessage('[System] Cleaning download failed: ' . $e->getMessage());
397 // just logging without breaking
398 }
399 }
400
401 public function _logsCleanup() {
402 if($this->_queue_item_system->getType() != self::TYPE_DAILY) return;
403 $this->getLogController()->logMessage("\t [_logsCleanup]");
404 $this->getLogController()->logMessage('Execution time: ' . $this->getExecutionTimeElapsed());
405 $this->getLogController()->logMessage('TTL time: ' . $this->getExecutionTimeLimit());
406
407 $this->getQueueItem()->updateStatus(Queue::STATUS_SYSTEM_LOGS_CLEANUP);
408 $this->getQueueItem()->updateProgress('Logs Cleanup');
409
410 $log_dir = Factory::getLocations()->getLogsDir();
411 $dir = dir($log_dir);
412
413 $all_logs = [];
414
415 while(($file = $dir->read()) !== false) {
416 if($file == '.' || $file == '..' || !preg_match("/_([^_]+)\.log$/", $file, $matches)) continue;
417 $filepath = $log_dir . JetBackup::SEP . $file;
418 $all_logs[$matches[1]][filemtime($filepath)] = $filepath;
419 }
420
421 $dir->close();
422
423 $keep_logs = Factory::getSettingsLogging()->getLogRotate();
424
425 foreach ($all_logs as $logs) {
426 if(sizeof($logs) <= $keep_logs) continue;
427 krsort($logs);
428
429 $skip = $keep_logs;
430
431 foreach ($logs as $log) {
432 if($skip) {
433 $skip--;
434 continue;
435 }
436
437 unlink($log);
438 }
439 }
440 }
441
442 /**
443 * @return void
444 * @throws InvalidArgumentException
445 * @throws NotificationException
446 * @throws DBException
447 * @throws \SleekDB\Exceptions\IOException
448 */
449 public function _backupJobMonitor() {
450 if($this->_queue_item_system->getType() != self::TYPE_DAILY) return;
451 $this->getLogController()->logMessage("\t [_backupJobMonitor]");
452 $this->getLogController()->logMessage('Execution time: ' . $this->getExecutionTimeElapsed());
453 $this->getLogController()->logMessage('TTL time: ' . $this->getExecutionTimeLimit());
454
455 $this->getQueueItem()->updateStatus(Queue::STATUS_SYSTEM_JOB_MONITOR);
456 $this->getQueueItem()->updateProgress('Backup Jobs Monitor');
457
458
459 $backups = BackupJob::query()
460 ->getQuery()
461 ->fetch();
462
463 foreach($backups as $backup_details) {
464 $backup = new BackupJob( $backup_details[ JetBackup::ID_FIELD]);
465
466 if(
467 // skip if job never executed
468 !$backup->getLastRun() ||
469 // skip if monitor isn't set for this job
470 !$backup->getMonitor() ||
471 // skip if job was executed properly
472 ($backup->getLastRun() >= (time() - ($backup->getMonitor() * 86400)))
473 ) continue;
474
475 // send notification
476 Notification::message()
477 ->addParam('backup_domain', Wordpress::getSiteDomain())
478 ->addParam('job_name', $backup->getName())
479 ->addParam('job_monitor', $backup->getMonitor())
480 ->addParam('backup_date', Util::date('Y-m-d H:i:s', $backup->getLastRun()))
481 ->send('JetBackup Status Update', 'job_monitor');
482
483 }
484 }
485
486 /**
487 * @return void
488 * @throws IOException
489 */
490 public function _checkLicense() {
491 $this->getLogController()->logMessage("\t [_checkLicense]");
492 $this->getLogController()->logMessage('Execution time: ' . $this->getExecutionTimeElapsed());
493 $this->getLogController()->logMessage('TTL time: ' . $this->getExecutionTimeLimit());
494 $this->getQueueItem()->updateProgress('Checking license');
495 $settings = Factory::getConfig();
496 if(time() < $settings->getLicenseNextCheck()) return;
497 $settings->setLicenseNextCheck(time() + License::LOCALKEY_FAIL_INTERVAL);
498 $settings->save();
499
500 try {
501 License::retrieveLocalKey();
502 $this->getLogController()->logMessage("\t [_checkLicense] License Key Valid");
503 } catch(LicenseException $e) {
504 $error = "Failed retrieving license Local Key. Error: " . $e->getMessage();
505 $this->getLogController()->logError($error);
506 Alert::add("License check failed", $error, Alert::LEVEL_CRITICAL);
507 }
508 }
509
510 /**
511 * @return void
512 * @throws InvalidArgumentException
513 * @throws \SleekDB\Exceptions\IOException
514 * @throws DBException
515 */
516 public function _indexJBBackups() {
517
518 $settings = Factory::getSettingsGeneral();
519
520 if(!$settings->isJBIntegrationEnabled() || !JetBackupLinux::isInstalled()) {
521 JetBackupLinux::deleteSnapshots();
522 return;
523 }
524
525 try {
526 $this->getLogController()->logMessage("\t [_indexJBBackups]");
527 $this->getLogController()->logMessage('Execution time: ' . $this->getExecutionTimeElapsed());
528 $this->getLogController()->logMessage('TTL time: ' . $this->getExecutionTimeLimit());
529 $this->getQueueItem()->updateProgress('Indexing JB Linux');
530 JetBackupLinux::checkRequirements();
531 } catch (JetBackupLinuxException $e) {
532 $settings->setJBIntegrationEnabled(false);
533 $settings->save();
534 Alert::add("JetBackup Linux integration has been disabled", "JetBackup linux integration has been disabled due to the following error: " . $e->getMessage() . ". After fixing this issue you will need to manually re-enabled it from the general settings page.", Alert::LEVEL_WARNING);
535 JetBackupLinux::deleteSnapshots();
536 return;
537 }
538
539 try {
540 JetBackupLinux::addToQueue();
541 } catch (Exception $e) {
542 return;
543 }
544 }
545 }