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