PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.9.5
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.9.5
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 / Backend / Modules / Jobs / Job.php
wp-staging / Backend / Modules / Jobs Last commit date
Cleaners 4 months ago Exceptions 5 years ago Cancel.php 8 months ago CancelUpdate.php 8 months ago Cloning.php 6 days ago CloningProcess.php 6 days ago Data.php 8 months ago Database.php 6 days ago Delete.php 6 days ago Directories.php 5 months ago Files.php 1 month ago Finish.php 6 days ago Job.php 6 days ago JobExecutable.php 8 months ago Logs.php 3 years ago PreserveDataFirstStep.php 2 months ago PreserveDataSecondStep.php 2 months ago ProcessLock.php 1 year ago Scan.php 3 weeks ago SearchReplace.php 6 months ago TotalStepsAreNumberOfTables.php 5 years ago Updating.php 6 days ago
Job.php
652 lines
1 <?php
2
3 namespace WPStaging\Backend\Modules\Jobs;
4
5 use DateInterval;
6 use DateTime;
7 use Exception;
8 use stdClass;
9 use WPStaging\Core\DTO\Settings;
10 use WPStaging\Core\Utils\Logger;
11 use WPStaging\Core\WPStaging;
12 use WPStaging\Framework\Database\ExcludedTables;
13 use WPStaging\Framework\Database\ExternalDatabaseConfiguration;
14 use WPStaging\Framework\Interfaces\ShutdownableInterface;
15 use WPStaging\Framework\Traits\ResourceTrait;
16 use WPStaging\Framework\Utils\Math;
17 use WPStaging\Backend\Modules\SystemInfo;
18 use WPStaging\Framework\Database\WpDbInfo;
19 use WPStaging\Framework\Security\UniqueIdentifier;
20 use WPStaging\Framework\Utils\Cache\Cache;
21 use WPStaging\Staging\Sites;
22 use WPStaging\Staging\Service\StagingEngine;
23
24 /**
25 * Class Job
26 * @package WPStaging\Backend\Modules\Jobs
27 */
28 abstract class Job implements ShutdownableInterface
29 {
30 use ResourceTrait;
31
32 /**
33 * @var string
34 */
35 const PUSH = 'push';
36
37 /**
38 * @var string
39 */
40 const STAGING = 'cloning';
41
42 /**
43 * @var string
44 */
45 const RESET = 'resetting';
46
47 /**
48 * @var string
49 */
50 const UPDATE = 'updating';
51
52 /**
53 * Temp file base name for files index for cloning and push
54 * @var string
55 */
56 const FILES_INDEX_KEY = 'clone_files_index';
57
58 /**
59 * Temp file base name that contain clone related data for cloning and push
60 * @var string
61 */
62 const CLONE_OPTIONS_KEY = 'clone_options';
63
64 /**
65 * @var Cache
66 */
67 protected $cloneOptionCache;
68
69 /**
70 * @var Cache
71 */
72 protected $filesIndexCache;
73
74 /**
75 * @var Cache
76 */
77 protected $cache;
78
79 /**
80 * @var Logger
81 */
82 protected $logger;
83
84 /**
85 * @var stdClass|null
86 */
87 protected $options;
88
89 /**
90 * @var object
91 */
92 protected $settings;
93
94 /**
95 * Multisite home domain without scheme
96 * @var string
97 */
98 protected $baseUrl;
99
100 /** @var ExcludedTables */
101 protected $excludedTableService;
102
103 /** @var UniqueIdentifier */
104 protected $identifier;
105
106 /** @var Math */
107 protected $utilsMath;
108
109 /** @var SystemInfo */
110 protected $systemInfo;
111
112 /** @var ExternalDatabaseConfiguration */
113 protected $externalDatabaseConfiguration;
114
115 /**
116 * Job constructor.
117 * @throws Exception
118 */
119 public function __construct()
120 {
121 $this->utilsMath = new Math();
122
123 $this->excludedTableService = new ExcludedTables();
124 $this->externalDatabaseConfiguration = new ExternalDatabaseConfiguration();
125
126 // Services
127 //$this->logger = WPStaging::make(Logger::class);
128 $this->logger = WPStaging::getInstance()->get("logger");
129 $this->systemInfo = WPStaging::make(SystemInfo::class);
130 $this->identifier = WPStaging::make(UniqueIdentifier::class);
131
132 $this->setupCacheFiles();
133
134 // Settings and Options
135 $this->options = $this->cloneOptionCache->get();
136 // Convert into object
137 $this->options = json_decode(json_encode($this->options));
138 $this->settings = (object)((new Settings())->setDefault());
139
140 if (!$this->options) {
141 $this->options = new stdClass();
142 }
143
144 if (isset($this->options->existingClones) && is_object($this->options->existingClones)) {
145 $this->options->existingClones = json_decode(json_encode($this->options->existingClones), true);
146 }
147
148 $this->initialize();
149 }
150
151 /**
152 * To be override by child classes
153 * @return void
154 */
155 public function initialize()
156 {
157 // do nothing
158 }
159
160 /**
161 * @todo can be removed?
162 * @return void
163 */
164 public function onWpShutdown()
165 {
166 // do nothing
167 }
168
169 protected function setupCacheFiles()
170 {
171 // For clone options
172 $this->cloneOptionCache = WPStaging::make(Cache::class);
173 $this->cloneOptionCache->setLifetime(-1); // Non-expireable file
174 $this->cloneOptionCache->setPath(WPStaging::getContentDir());
175 $this->cloneOptionCache->setFileName(self::CLONE_OPTIONS_KEY);
176
177 // For files index to copy files
178 $this->filesIndexCache = WPStaging::make(Cache::class);
179 $this->filesIndexCache->setLifetime(-1); // Non-expireable file
180 $this->filesIndexCache->setPath(WPStaging::getContentDir());
181 $this->filesIndexCache->setFileName(self::FILES_INDEX_KEY);
182
183 // For other purposes
184 $this->cache = WPStaging::make(Cache::class);
185 $this->cache->setLifetime(-1); // Non-expireable file
186 $this->cache->setPath(WPStaging::getContentDir());
187 }
188
189 /**
190 * @param null|array|object $options
191 * @return bool
192 * @throws Exception
193 */
194 public function saveOptions($options = null)
195 {
196 // Get default options
197 if ($options === null) {
198 $options = $this->options;
199 }
200
201 if (!is_object($options)) {
202 return false;
203 }
204
205 $now = new DateTime();
206 $options->expiresAt = $now->add(new DateInterval('P1D'))->format('Y-m-d H:i:s');
207
208 if (!property_exists($options, 'jobIdentifier')) {
209 $options->jobIdentifier = rand(0, 2147483647); // 32 bits int max
210 }
211
212 // Ensure that it is an object
213 $options = json_decode(json_encode($options));
214 $result = $this->cloneOptionCache->save($options);
215
216 return $result !== false;
217 }
218
219 /**
220 * @return object
221 */
222 public function getOptions()
223 {
224 return $this->options;
225 }
226
227 /**
228 * @return void
229 */
230 protected function markLegacyStagingEngine()
231 {
232 $this->options->stagingEngine = StagingEngine::ENGINE_LEGACY;
233 WPStaging::make(StagingEngine::class)->saveEngine(StagingEngine::ENGINE_LEGACY);
234 }
235
236 /**
237 * Loads the staging site list that the legacy scan step used to cache.
238 *
239 * Direct legacy starts from the refactored setup UI skip the scan request, so
240 * update/reset need this before they can resolve the selected staging site.
241 *
242 * @return void
243 */
244 protected function loadLegacyExistingClones()
245 {
246 $existingClones = get_option(Sites::STAGING_SITES_OPTION, []);
247 $this->options->existingClones = is_array($existingClones)
248 ? array_change_key_case($existingClones, CASE_LOWER)
249 : [];
250 }
251
252 /**
253 * Initializes the legacy job state that used to be prepared by the scan step.
254 *
255 * The refactored setup UI can start legacy jobs directly, so create/update/reset
256 * need these defaults before the first wpstg_processing request runs.
257 *
258 * @param string $mainJob
259 * @return void
260 */
261 protected function initializeLegacyStagingRun($mainJob)
262 {
263 $this->options->mainJob = $mainJob;
264 $this->options->currentJob = 'PreserveDataFirstStep';
265 $this->options->currentStep = 0;
266 $this->options->totalSteps = 0;
267 $this->options->job = new stdClass();
268
269 $this->options->clonedTables = [];
270
271 if (!property_exists($this->options, 'excludedTables') || !is_array($this->options->excludedTables)) {
272 $this->options->excludedTables = [];
273 }
274
275 if (!property_exists($this->options, 'totalFiles')) {
276 $this->options->totalFiles = 0;
277 }
278
279 if (!property_exists($this->options, 'totalFileSize')) {
280 $this->options->totalFileSize = 0;
281 }
282
283 if (!property_exists($this->options, 'copiedFiles')) {
284 $this->options->copiedFiles = 0;
285 }
286
287 if (!property_exists($this->options, 'includedDirectories')) {
288 $this->options->includedDirectories = [];
289 }
290
291 if (!property_exists($this->options, 'includedExtraDirectories')) {
292 $this->options->includedExtraDirectories = [];
293 }
294
295 if (!property_exists($this->options, 'excludedDirectories')) {
296 $this->options->excludedDirectories = [];
297 }
298
299 if (!property_exists($this->options, 'extraDirectories')) {
300 $this->options->extraDirectories = [];
301 }
302
303 if (!property_exists($this->options, 'scannedDirectories')) {
304 $this->options->scannedDirectories = [];
305 }
306
307 if (!property_exists($this->options, 'root')) {
308 $this->options->root = str_replace(["\\", '/'], DIRECTORY_SEPARATOR, ABSPATH);
309 }
310
311 $this->markLegacyStagingEngine();
312 }
313
314 /**
315 * Get current time in seconds
316 * @return float
317 */
318 protected function time()
319 {
320 $time = microtime();
321 $time = explode(' ', $time);
322 $time = (float)$time[1] + (float)$time[0];
323 return $time;
324 }
325
326 /**
327 * @return bool
328 */
329 public function isOverThreshold()
330 {
331 // Check if the memory is over threshold
332 $usedMemory = $this->getMemoryPeakUsage();
333 $maxMemoryLimit = $this->getMaxMemoryLimit();
334 $scriptMemoryLimit = $this->getScriptMemoryLimit();
335
336 $this->debugLog(
337 sprintf(
338 "Used Memory: %s Max Memory Limit: %s Max Script Memory Limit: %s",
339 size_format($usedMemory),
340 size_format($maxMemoryLimit),
341 size_format($scriptMemoryLimit)
342 ),
343 Logger::TYPE_DEBUG
344 );
345
346 if ($this->isMemoryLimit()) {
347 $this->log(
348 sprintf(
349 "Used Memory: %s Memory Limit: %s Max Script memory limit: %s",
350 size_format($usedMemory),
351 size_format($maxMemoryLimit),
352 size_format($scriptMemoryLimit)
353 ),
354 Logger::TYPE_ERROR
355 );
356
357 return true;
358 }
359
360 // Check if execution time is over threshold
361 if ($this->isTimeLimit()) {
362 $this->debugLog(
363 sprintf(
364 "RESET TIME: current time: %s, Start Time: %d, exec time limit: %s",
365 $this->getRunningTime(),
366 WPStaging::$startTime,
367 $this->findExecutionTimeLimit()
368 )
369 );
370 return true;
371 }
372
373 return false;
374 }
375
376 /**
377 * @param string $msg
378 * @param string $type
379 */
380 public function log($msg, $type = Logger::TYPE_INFO)
381 {
382 if ($this->logger === null) {
383 return;
384 }
385
386 $this->logger->setFileName($this->getLogFilename());
387
388 $this->logger->add($msg, $type);
389 }
390
391 /**
392 * @return string
393 */
394 protected function getFilesIndexCacheFilePath(): string
395 {
396 return trailingslashit($this->cache->getPath()) . self::FILES_INDEX_KEY . '.' . Cache::FILE_EXTENSION;
397 }
398
399 /**
400 * @return string
401 */
402 private function getLogFilename()
403 {
404 $uniqueId = $this->identifier->getIdentifier();
405 // If job is not cloning i.e. updating, resetting, pushing
406 if (!empty($this->options->mainJob) && $this->options->mainJob !== Job::STAGING) {
407 return $this->options->mainJob . '_' . $uniqueId . '_' . date('Y-m-d', time());
408 }
409
410 // If job is cloning
411 if (!empty($this->options->clone) && !empty($this->options->mainJob)) {
412 return $this->options->mainJob . '_' . $uniqueId . '_' . $this->options->clone . '_' . date('Y-m-d', time());
413 }
414
415 if (empty($this->options->clone) && !empty($this->options->mainJob)) {
416 return $this->options->mainJob . '_' . $uniqueId . '_unknown_clone_' . date('Y-m-d', time());
417 }
418
419 if (!empty($this->options->clone) && empty($this->options->mainJob)) {
420 return 'unknown_job_' . $uniqueId . '_' . $this->options->clone . '_' . date('Y-m-d', time());
421 }
422
423 return 'unknown_job_' . $uniqueId . '_' . date('Y-m-d', time());
424 }
425
426 /**
427 * @param string $msg
428 * @param string $type
429 */
430 public function debugLog($msg, $type = Logger::TYPE_INFO)
431 {
432 $this->logger->setFileName($this->getLogFilename());
433
434 if (isset($this->settings->debugMode)) {
435 $this->logger->add($msg, $type);
436 }
437 }
438
439 /**
440 * Throw an error message via json and stop further execution
441 * @param string $message
442 */
443 public function returnException($message = '')
444 {
445 wp_die(
446 json_encode(
447 [
448 'job' => isset($this->options->currentJob) ? $this->options->currentJob : '',
449 'status' => false,
450 'message' => esc_html($message),
451 'error' => true,
452 ]
453 )
454 );
455 }
456
457 /**
458 * Is job running
459 * @return bool
460 */
461 protected function isRunning()
462 {
463 if (!isset($this->options) || !isset($this->options->isRunning) || !isset($this->options->expiresAt)) {
464 return false;
465 }
466
467 try {
468 $now = new DateTime();
469 $expiresAt = new DateTime($this->options->expiresAt);
470 return $this->options->isRunning === true && $now < $expiresAt;
471 } catch (Exception $e) {
472 }
473
474 return false;
475 }
476
477 protected function isPro()
478 {
479 return defined('WPSTGPRO_VERSION');
480 }
481
482 /**
483 * @return bool
484 */
485 protected function isMultisiteAndPro()
486 {
487 return $this->isPro() && is_multisite();
488 }
489
490 /**
491 * @return bool
492 */
493 public function isNetworkClone()
494 {
495 if (!isset($this->options->networkClone)) {
496 return false;
497 }
498
499 return $this->isMultisiteAndPro() && $this->options->networkClone;
500 }
501
502 /**
503 * Should exclude wp-config file during clone update
504 *
505 * @return bool
506 */
507 public function excludeWpConfigDuringUpdate()
508 {
509 return $this->options->mainJob === self::UPDATE;
510 }
511
512 /**
513 * Check if external database is used
514 * @return bool
515 */
516 protected function isExternalDatabase()
517 {
518 return $this->externalDatabaseConfiguration->isEnabled($this->options);
519 }
520
521 /**
522 * @return bool
523 */
524 protected function isStagingDatabaseSameAsProductionDatabase()
525 {
526 if (!$this->isExternalDatabase()) {
527 return true;
528 }
529
530 if (!$this->externalDatabaseConfiguration->hasConnectionTarget($this->options)) {
531 return false;
532 }
533
534 if ($this->options->databaseServer === DB_HOST && $this->options->databaseDatabase === DB_NAME) {
535 return true;
536 }
537
538 $productionDb = WPStaging::make('wpdb');
539 $productionDbInfo = new WpDbInfo($productionDb);
540 $productionServer = $productionDbInfo->getServer();
541
542 $stagingDb = new \wpdb($this->options->databaseUser, str_replace("\\\\", "\\", $this->options->databasePassword), $this->options->databaseDatabase, $this->options->databaseServer);
543 $stagingDbInfo = new WpDbInfo($stagingDb);
544 $stagingServer = $stagingDbInfo->getServer();
545
546 if ($productionServer === $stagingServer && $this->options->databaseDatabase === DB_NAME) {
547 return true;
548 }
549
550 return false;
551 }
552
553 /**
554 * Is the current main job UPDATE or RESET
555 *
556 * @return bool
557 */
558 public function isUpdateOrResetJob(): bool
559 {
560 return isset($this->options->mainJob) && ($this->options->mainJob === self::RESET || $this->options->mainJob === self::UPDATE);
561 }
562
563 /**
564 * @param string $jobName
565 * @return void
566 */
567 protected function addJobSettingsToLogs(string $jobName = 'WP Staging Job')
568 {
569 $this->logger->add(sprintf('%s Settings', esc_html($jobName)), Logger::TYPE_INFO);
570 $this->logger->writeSelectedTablesToLogs($this->options->tables);
571 $this->logger->add('Excluded Directories', Logger::TYPE_INFO);
572 foreach ($this->options->excludedDirectories as $directory) {
573 $this->logger->add(sprintf('- %s', esc_html($directory)), Logger::TYPE_INFO_SUB);
574 }
575
576 if (!empty($this->options->excludeGlobRules)) {
577 $this->logger->add('Exclude Global Rule', Logger::TYPE_INFO);
578
579 foreach ($this->options->excludeGlobRules as $rule) {
580 $excludeRule = explode(':', $rule);
581 $ruleName = ucwords($excludeRule[0] ?? '');
582 $ruleDescription = ucwords(str_replace('_', ' ', !empty($excludeRule[1]) ? $excludeRule[1] : ''));
583 $this->logger->add(sprintf('- Exclude %s : %s', esc_html($ruleName), esc_html($ruleDescription)), Logger::TYPE_INFO_SUB);
584 }
585 }
586
587 if (!empty($this->options->excludeSizeRules)) {
588 $this->logger->add('Exclude Size Rule', Logger::TYPE_INFO);
589 foreach ($this->options->excludeSizeRules as $rule) {
590 $ruleDescription = ucwords(str_replace('_', ' ', !empty($rule) ? $rule : ''));
591 $this->logger->add(sprintf('- Exclude Size : %s', esc_html($ruleDescription)), Logger::TYPE_INFO_SUB);
592 }
593 }
594
595
596 $this->writeAdvancedSettingsToLogs();
597 $this->logger->writeGlobalSettingsToLogs();
598 }
599
600 /**
601 * @return void
602 */
603 private function writeAdvancedSettingsToLogs()
604 {
605 $this->logger->add('Advanced Settings', Logger::TYPE_INFO);
606
607 if (isset($this->options->useNewAdminAccount)) {
608 $this->logger->add(sprintf('- New Admin Account : %s', ($this->options->useNewAdminAccount ? 'True' : 'False')), Logger::TYPE_INFO_SUB);
609 $this->logger->add(sprintf('- Email : %s', (!empty($this->options->adminEmail) ? $this->options->adminEmail : 'Not Set')), Logger::TYPE_INFO_SUB);
610 $this->logger->add(sprintf('- Password : %s', (!empty($this->options->adminPassword) ? '**************' : 'Not Set')), Logger::TYPE_INFO_SUB);
611 }
612
613 $this->logger->add(sprintf('- Database Server : %s', (!empty($this->options->databaseServer) ? $this->options->databaseServer : 'Not Set')), Logger::TYPE_INFO_SUB);
614 $this->logger->add(sprintf('- Database User : %s', (!empty($this->options->databaseUser) ? $this->options->databaseUser : 'Not Set')), Logger::TYPE_INFO_SUB);
615 $this->logger->add(sprintf('- Database Password : %s', (!empty($this->options->databasePassword) ? '*****************' : 'Not Set')), Logger::TYPE_INFO_SUB);
616 $this->logger->add(sprintf('- Database : %s', (!empty($this->options->databasePassword) ? $this->options->databaseDatabase : 'Not Set')), Logger::TYPE_INFO_SUB);
617 $this->logger->add(sprintf('- Database Prefix: %s', (!empty($this->options->databasePrefix) ? $this->options->databasePrefix : 'Not Set')), Logger::TYPE_INFO_SUB);
618 $this->logger->add(sprintf('- Database SSL: %s', ($this->options->databasePrefix ? 'True' : 'False')), Logger::TYPE_INFO_SUB);
619 $this->logger->add(sprintf('- Clone Directory : %s', (!empty($this->options->cloneDir) ? $this->options->cloneDir : 'Not Set')), Logger::TYPE_INFO_SUB);
620 $this->logger->add(sprintf('- Clone Host : %s', (!empty($this->options->cloneHostname) ? $this->options->cloneHostname : 'Not Set')), Logger::TYPE_INFO_SUB);
621 $this->logger->add(sprintf('- Symlink Uploads Folder : %s', ($this->options->uploadsSymlinked ? 'True' : 'False')), Logger::TYPE_INFO_SUB);
622
623 if (isset($this->options->isAutoUpdatePlugins)) {
624 $this->logger->add(sprintf('- Auto Update Plugins : %s', ($this->options->isAutoUpdatePlugins ? 'True' : 'False')), Logger::TYPE_INFO_SUB);
625 }
626
627 if (isset($this->options->isCronEnabled)) {
628 $this->logger->add(sprintf('- Enable WP_CRON : %s', ($this->options->isCronEnabled ? 'True' : 'False')), Logger::TYPE_INFO_SUB);
629 }
630
631 if (isset($this->options->isWooSchedulerEnabled)) {
632 $this->logger->add(sprintf('- Enable WooCommerce Scheduler : %s', ($this->options->isWooSchedulerEnabled ? 'True' : 'False')), Logger::TYPE_INFO_SUB);
633 }
634
635 if (isset($this->options->isEmailsAllowed)) {
636 $this->logger->add(sprintf('- Allow Emails Sending : %s', ($this->options->isEmailsAllowed ? 'True' : 'False')), Logger::TYPE_INFO_SUB);
637 }
638
639 if (isset($this->options->deletePluginsAndThemes)) {
640 $this->logger->add(sprintf('- Clean Plugins/Themes : %s', ($this->options->deletePluginsAndThemes ? 'True' : 'False')), Logger::TYPE_INFO_SUB);
641 }
642
643 if (isset($this->options->deleteUploadsFolder)) {
644 $this->logger->add(sprintf('- Clean Uploads : %s', ($this->options->deleteUploadsFolder ? 'True' : 'False')), Logger::TYPE_INFO_SUB);
645 }
646
647 if (isset($this->options->createBackupBeforePushing)) {
648 $this->logger->add(sprintf('- Create database backup : %s', ($this->options->createBackupBeforePushing ? 'True' : 'False')), Logger::TYPE_INFO_SUB);
649 }
650 }
651 }
652