PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backup, Restore, Migration & Clone / 4.7.0
WP STAGING – WordPress Backup, Restore, Migration & Clone v4.7.0
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 / Cloning.php
wp-staging / Backend / Modules / Jobs Last commit date
Cleaners 4 months ago Exceptions 5 years ago Cancel.php 7 months ago CancelUpdate.php 7 months ago Cloning.php 4 months ago CloningProcess.php 1 year ago Data.php 7 months ago Database.php 4 months ago Delete.php 5 months ago Directories.php 5 months ago Files.php 6 months ago Finish.php 5 months ago Job.php 5 months ago JobExecutable.php 7 months ago Logs.php 3 years ago PreserveDataFirstStep.php 5 months ago PreserveDataSecondStep.php 5 months ago ProcessLock.php 1 year ago Scan.php 5 months ago SearchReplace.php 6 months ago TotalStepsAreNumberOfTables.php 5 years ago Updating.php 5 months ago
Cloning.php
739 lines
1 <?php
2
3 namespace WPStaging\Backend\Modules\Jobs;
4
5 use Countable;
6 use Exception;
7 use WPStaging\Backend\Modules\Jobs\Exceptions\JobNotFoundException;
8 use WPStaging\Backup\Service\Database\DatabaseImporter;
9 use WPStaging\Core\WPStaging;
10 use WPStaging\Framework\Analytics\Actions\AnalyticsStagingCreate;
11 use WPStaging\Framework\Traits\TablePrefixValidator;
12 use WPStaging\Framework\Database\SelectedTables;
13 use WPStaging\Framework\Exceptions\WPStagingException;
14 use WPStaging\Framework\Filesystem\PathIdentifier;
15 use WPStaging\Framework\Filesystem\Scanning\ScanConst;
16 use WPStaging\Framework\Security\AccessToken;
17 use WPStaging\Framework\Utils\Urls;
18 use WPStaging\Framework\Utils\Sanitize;
19 use WPStaging\Framework\Adapter\Directory;
20 use WPStaging\Framework\Facades\Hooks;
21 use WPStaging\Framework\Utils\Strings;
22 use WPStaging\Framework\Utils\WpDefaultDirectories;
23 use WPStaging\Staging\Sites;
24
25 use function WPStaging\functions\debug_log;
26
27 /**
28 * Class Cloning
29 * @package WPStaging\Backend\Modules\Jobs
30 */
31 class Cloning extends Job
32 {
33 use TablePrefixValidator;
34
35 /**
36 * @var string
37 */
38 const WPSTG_REQUEST = 'wpstg_cloning';
39
40 /** @var string */
41 const FILTER_CLONE_EXCLUDED_FILES_FULL_PATH = 'wpstg.clone.excluded_files_full_path';
42
43 /** @var string */
44 const FILTER_CLONE_EXCLUDED_FILES = 'wpstg_clone_excluded_files';
45
46 /**
47 * @var object
48 */
49 private $db;
50
51 /**
52 * @var WpDefaultDirectories
53 */
54 private $dirUtils;
55
56 /**
57 * @var Sites
58 */
59 private $sitesHelper;
60
61 /**
62 * @var string
63 */
64 private $errorMessage;
65
66 /**
67 * @var Sanitize
68 */
69 protected $sanitize;
70
71 /**
72 * @var Urls
73 */
74 private $urls;
75
76 /** @var Directory */
77 private $dirAdapter;
78
79 /** @var PathIdentifier */
80 private $pathIdentifier;
81
82 /** @var Strings */
83 protected $strUtil;
84
85 /**
86 * Initialize is called in \Job
87 */
88 public function initialize()
89 {
90 $this->db = WPStaging::getInstance()->get("wpdb");
91 $this->dirUtils = new WpDefaultDirectories();
92 $this->sitesHelper = new Sites();
93 $this->sanitize = WPStaging::make(Sanitize::class);
94 $this->urls = WPStaging::make(Urls::class);
95 $this->dirAdapter = WPStaging::make(Directory::class);
96 $this->strUtil = WPStaging::make(Strings::class);
97 $this->pathIdentifier = WPStaging::make(PathIdentifier::class);
98 }
99
100 public function getErrorMessage(): string
101 {
102 return $this->errorMessage;
103 }
104
105 /**
106 * Save Chosen Cloning Settings
107 * @return bool
108 * @throws \Exception
109 */
110 public function save(): bool
111 {
112 if (!isset($_POST) || !isset($_POST["cloneID"])) {
113 $this->errorMessage = __("clone ID missing", 'wp-staging');
114 return false;
115 }
116
117 // Delete files index cache file
118 $this->filesIndexCache->delete();
119
120 // Generate Options
121 // Clone ID -> timestamp (time at which this clone creation initiated)
122 $this->options->clone = preg_replace("#\W+#", '-', strtolower($this->sanitize->sanitizeString($_POST["cloneID"])));
123
124 // Clone Name -> Site name that user input
125 if (isset($_POST["cloneName"])) {
126 $this->options->cloneName = sanitize_text_field($_POST["cloneName"]);
127 }
128
129 // If it's empty or it's a clone Id, try setting it to a random human-friendly name
130 if (empty($this->options->cloneName) || $this->options->cloneName === $this->options->clone) {
131 $this->options->cloneName = $this->maybeGenerateFriendlyName();
132 }
133
134 // The slugified version of Clone Name (to use in directory creation)
135 $this->options->cloneDirectoryName = $this->sitesHelper->sanitizeDirectoryName($this->options->cloneName);
136 $result = $this->sitesHelper->isCloneExists($this->options->cloneDirectoryName);
137 if ($result !== false) {
138 $this->errorMessage = $result;
139 return false;
140 }
141
142 $this->options->cloneNumber = 1;
143 $this->options->prefix = $this->setStagingPrefix();
144 $this->options->includedDirectories = [];
145 $this->options->excludedDirectories = [];
146 $this->options->extraDirectories = [];
147 $this->options->excludedFiles = Hooks::applyFilters(self::FILTER_CLONE_EXCLUDED_FILES, [
148 '.DS_Store',
149 '*.git',
150 '*.svn',
151 '*.tmp',
152 'desktop.ini',
153 '.gitignore',
154 '*.log',
155 'web.config', // Important: Windows IIS configuration file. Must not be in the staging site!
156 '.wp-staging', // Determines if a site is a staging site
157 '.wp-staging-cloneable', // File that makes the staging site cloneable.
158 ]);
159
160 $excludedFilesFullPath = [
161 '.htaccess',
162 PathIdentifier::IDENTIFIER_WP_CONTENT . 'db.php',
163 PathIdentifier::IDENTIFIER_WP_CONTENT . 'object-cache.php',
164 PathIdentifier::IDENTIFIER_WP_CONTENT . 'advanced-cache.php',
165 ];
166
167 $this->options->tmpExcludedGoDaddyFiles = [];
168 $muPluginsDir = trailingslashit($this->dirAdapter->getMuPluginsDirectory());
169 if (file_exists($muPluginsDir . 'gd-system-plugin.php')) {
170 $excludedFilesFullPath[] = PathIdentifier::IDENTIFIER_MUPLUGINS . 'gd-system-plugin.php';
171 $this->options->tmpExcludedGoDaddyFiles[] = $muPluginsDir . 'gd-system-plugin.php';
172 }
173
174 $this->options->excludedFilesFullPath = Hooks::applyFilters(self::FILTER_CLONE_EXCLUDED_FILES_FULL_PATH, $excludedFilesFullPath);
175
176 $this->options->currentStep = 0;
177
178 // Job
179 $this->options->job = new \stdClass();
180
181 // Check if clone data already exists and use that one
182 if (isset($this->options->existingClones[$this->options->clone])) {
183 $this->options->cloneNumber = $this->options->existingClones[$this->options->clone]->number;
184 $this->options->prefix = isset($this->options->existingClones[$this->options->clone]->prefix) ? $this->options->existingClones[$this->options->clone]->prefix : $this->setStagingPrefix();
185
186 // Clone does not exist but there are other clones in db
187 // Get data and increment it
188 } elseif (!empty($this->options->existingClones)) {
189 $this->options->cloneNumber = count($this->options->existingClones) + 1;
190 }
191
192 $this->options->networkClone = false;
193 if ($this->isMultisiteAndPro() && is_main_site()) {
194 $this->options->networkClone = isset($_POST['networkClone']) && $this->sanitize->sanitizeBool($_POST['networkClone']);
195 }
196
197 // Included Tables / Prefixed Table - Excluded Tables
198 $includedTables = isset($_POST['includedTables']) ? $this->sanitize->sanitizeString($_POST['includedTables']) : '';
199 $excludedTables = isset($_POST['excludedTables']) ? $this->sanitize->sanitizeString($_POST['excludedTables']) : '';
200 $selectedTablesWithoutPrefix = isset($_POST['selectedTablesWithoutPrefix']) ? $this->sanitize->sanitizeString($_POST['selectedTablesWithoutPrefix']) : '';
201 $selectedTables = new SelectedTables($includedTables, $excludedTables, $selectedTablesWithoutPrefix);
202 $selectedTables->setAllTablesExcluded(empty($_POST['allTablesExcluded']) ? false : $this->sanitize->sanitizeBool($_POST['allTablesExcluded']));
203 $this->options->tables = $selectedTables->getSelectedTables($this->options->networkClone);
204
205 // Exclude File Size Rules
206 $this->options->excludeGlobRules = [];
207 if (!empty($_POST["excludeGlobRules"])) {
208 $this->options->excludeGlobRules = $this->sanitize->sanitizeExcludeRules($_POST["excludeGlobRules"]);
209 }
210
211 // Exclude Glob Rules
212 $this->options->excludeSizeRules = [];
213 if (!empty($_POST["excludeSizeRules"])) {
214 $this->options->excludeSizeRules = $this->sanitize->sanitizeExcludeRules($_POST["excludeSizeRules"]);
215 }
216
217 $this->options->uploadsSymlinked = isset($_POST['uploadsSymlinked']) && $this->sanitize->sanitizeBool($_POST['uploadsSymlinked']);
218
219 $pluginWpContentDir = rtrim($this->dirAdapter->getPluginWpContentDirectory(), '/\\');
220
221 /**
222 * @see /WPStaging/Framework/CloningProcess/ExcludedPlugins.php to exclude plugins
223 * Only add other directories here
224 */
225 $excludedDirectories = [
226 PathIdentifier::IDENTIFIER_WP_CONTENT . 'cache',
227 $this->pathIdentifier->transformPathToIdentifiable($pluginWpContentDir), // wp-content/wp-staging
228 PathIdentifier::IDENTIFIER_WP_CONTENT . WPSTG_PLUGIN_DOMAIN, // Extra caution if pluginWpContentDir changed later
229 ];
230
231 // Go Daddy related exclusions
232 if (is_dir(trailingslashit($this->dirAdapter->getMuPluginsDirectory()) . 'gd-system-plugin')) {
233 $excludedDirectories[] = PathIdentifier::IDENTIFIER_MUPLUGINS . 'gd-system-plugin';
234 $excludedDirectories[] = PathIdentifier::IDENTIFIER_MUPLUGINS . 'vendor';
235
236 $this->options->tmpExcludedGoDaddyFiles[] = $muPluginsDir . 'gd-system-plugin';
237 $this->options->tmpExcludedGoDaddyFiles[] = $muPluginsDir . 'vendor';
238 }
239
240 // Add upload folder to list of excluded directories for push if symlink option is enabled
241 if ($this->options->uploadsSymlinked) {
242 $excludedDirectories[] = PathIdentifier::IDENTIFIER_UPLOADS;
243 }
244
245 $excludedDirectoriesRequest = isset($_POST["excludedDirectories"]) ? $this->sanitize->sanitizeString($_POST["excludedDirectories"]) : '';
246 $excludedDirectoriesRequest = $this->dirUtils->getExcludedDirectories($excludedDirectoriesRequest);
247
248 $this->options->excludedDirectories = array_merge($excludedDirectories, $excludedDirectoriesRequest);
249
250 // Extra Directories
251 if (isset($_POST["extraDirectories"])) {
252 $this->options->extraDirectories = explode(ScanConst::DIRECTORIES_SEPARATOR, $this->sanitize->sanitizeString($_POST["extraDirectories"]));
253 }
254
255 // New Admin Account
256 $this->options->useNewAdminAccount = false;
257 $this->options->adminEmail = '';
258 $this->options->adminPassword = '';
259
260 // External Database
261 $this->options->databaseServer = 'localhost';
262 $this->options->databaseUser = '';
263 $this->options->databasePassword = '';
264 $this->options->databaseDatabase = '';
265 // isExternalDatabase() depends upon databaseUser and databasePassword,
266 // Make sure they are set before calling this.
267 $this->options->databasePrefix = $this->isExternalDatabase() ? $this->db->prefix : '';
268 $this->options->databaseSsl = false;
269
270 // Custom Hosts
271 $this->options->cloneDir = '';
272 $this->options->cloneHostname = '';
273
274 // Default options for FREE version
275 $this->options->isEmailsAllowed = true;
276 $this->options->isCronEnabled = true;
277 $this->options->isWooSchedulerEnabled = true;
278 $this->options->isEmailsReminderEnabled = false;
279 $this->options->isAutoUpdatePlugins = false;
280 $this->setAdvancedCloningOptions();
281
282 $this->options->destinationDir = $this->getDestinationDir();
283 $this->options->destinationHostname = $this->getDestinationHostname();
284
285 $this->options->homeHostname = $this->urls->getHomeUrlWithoutScheme();
286
287 // Process lock state
288 $this->options->isRunning = true;
289
290 // id of the user creating the clone
291 $this->options->ownerId = get_current_user_id();
292 // Save Clone data
293 $this->saveClone();
294
295 WPStaging::make(AnalyticsStagingCreate::class)->enqueueStartEvent($this->options->jobIdentifier, $this->options);
296
297 $this->errorMessage = "";
298 return $this->saveOptions();
299 }
300
301 /**
302 * Save clone data initially
303 * @return void
304 */
305 private function saveClone()
306 {
307 // Save new clone data
308 $this->debugLog("Cloning: {$this->options->clone}'s clone job's data is not in database, generating data");
309
310 $this->options->existingClones[$this->options->clone] = [
311 "cloneName" => $this->options->cloneName,
312 "directoryName" => $this->options->cloneDirectoryName,
313 "path" => trailingslashit($this->options->destinationDir),
314 "url" => $this->getDestinationUrl(),
315 "number" => $this->options->cloneNumber,
316 "version" => WPStaging::getVersion(),
317 "status" => "unfinished or broken (?)",
318 "prefix" => $this->options->prefix,
319 "datetime" => time(),
320 "databaseUser" => $this->options->databaseUser,
321 "databasePassword" => $this->options->databasePassword,
322 "databaseDatabase" => $this->options->databaseDatabase,
323 "databaseServer" => $this->options->databaseServer,
324 "databasePrefix" => $this->options->databasePrefix,
325 "databaseSsl" => (bool)$this->options->databaseSsl,
326 "isCronEnabled" => (bool)$this->options->isCronEnabled,
327 "isEmailsAllowed" => (bool)$this->options->isEmailsAllowed,
328 "uploadsSymlinked" => (bool)$this->options->uploadsSymlinked,
329 "ownerId" => $this->options->ownerId,
330 "includedTables" => $this->options->tables,
331 "excludeSizeRules" => $this->options->excludeSizeRules,
332 "excludeGlobRules" => $this->options->excludeGlobRules,
333 "excludedDirectories" => $this->options->excludedDirectories,
334 "extraDirectories" => $this->options->extraDirectories,
335 "networkClone" => $this->isNetworkClone(),
336 'useNewAdminAccount' => $this->options->useNewAdminAccount,
337 'adminEmail' => $this->options->adminEmail,
338 'adminPassword' => $this->options->adminPassword,
339 'isWooSchedulerEnabled' => (bool)$this->options->isWooSchedulerEnabled,
340 "isEmailsReminderEnabled" => (bool)$this->options->isEmailsReminderEnabled,
341 'isAutoUpdatePlugins' => (bool)$this->options->isAutoUpdatePlugins,
342 ];
343
344 if ($this->sitesHelper->updateStagingSites($this->options->existingClones) === false) {
345 $this->log("Cloning: Failed to save {$this->options->clone}'s clone job data to database'");
346 }
347 }
348
349 /**
350 * Get destination Hostname depending on whether WP has been installed in sub dir or not
351 * @return string
352 */
353 private function getDestinationUrl(): string
354 {
355 if (!empty($this->options->cloneHostname)) {
356 return $this->options->cloneHostname;
357 }
358
359 return trailingslashit(get_site_url()) . $this->options->cloneDirectoryName;
360 }
361
362 /**
363 * Return target hostname
364 * @return string
365 */
366 private function getDestinationHostname(): string
367 {
368 if (empty($this->options->cloneHostname)) {
369 return $this->urls->getHomeUrlWithoutScheme();
370 }
371
372 return $this->getHostnameWithoutScheme($this->options->cloneHostname);
373 }
374
375 /**
376 * Return Hostname without scheme
377 * @param string $string
378 * @return string
379 */
380 private function getHostnameWithoutScheme(string $string): string
381 {
382 return preg_replace('#^https?://#', '', rtrim($string, '/'));
383 }
384
385 /**
386 * Get Destination Directory including staging subdirectory
387 * @return string
388 */
389 private function getDestinationDir(): string
390 {
391 // Throw fatal error
392 if (!empty($this->options->cloneDir) & (trailingslashit($this->options->cloneDir) === trailingslashit(WPStaging::getWPpath()))) {
393 $this->returnException('Error: Target path must be different from the root of the production website.');
394 }
395
396 // custom destination has been set
397 if (!empty($this->options->cloneDir)) {
398 return trailingslashit($this->options->cloneDir);
399 }
400
401 // No custom destination so default path will be in a subfolder of root or inside wp-content
402 $cloneDestinationPath = $this->dirAdapter->getAbsPath() . $this->options->cloneDirectoryName;
403
404 if (!is_writable($this->dirAdapter->getAbsPath())) {
405 $stagingSiteDirectory = $this->dirAdapter->getStagingSiteDirectoryInsideWpcontent();
406 if ($stagingSiteDirectory === false) {
407 debug_log(esc_html('Fail to get destination directory. The staging sites destination folder cannot be created.'));
408 $this->returnException('The staging sites directory is not writable. Please choose another path.');
409 }
410
411 $cloneDestinationPath = trailingslashit($stagingSiteDirectory) . $this->options->cloneDirectoryName;
412 if (empty($this->options->cloneHostname)) {
413 $this->options->cloneHostname = trailingslashit($this->dirAdapter->getStagingSiteUrl()) . $this->options->cloneDirectoryName;
414 }
415 }
416
417 $this->options->cloneDir = trailingslashit($cloneDestinationPath);
418 return $this->options->cloneDir;
419 }
420
421 /**
422 * Create a new staging prefix that does not exist in database
423 */
424 private function setStagingPrefix()
425 {
426 // Find a new prefix that does not already exist in database.
427 // Loop through up to 1000 different possible prefixes should be enough here;)
428 for ($i = 0; $i <= 10000; $i++) {
429 $this->options->prefix = !empty($this->options->existingClones) && $this->options->existingClones instanceof Countable
430 ? 'wpstg' . (count($this->options->existingClones) + $i) . '_'
431 : 'wpstg' . $i . '_';
432
433 $sql = "SHOW TABLE STATUS LIKE '{$this->options->prefix}%'";
434 $tables = $this->db->get_results($sql);
435
436 // Prefix does not exist. We can use it
437 if (!$tables) {
438 return $this->options->prefix;
439 }
440 }
441
442 $message = sprintf("Fatal Error: Can not create staging prefix. '%s' already exists! Stopping for security reasons. Contact support@wp-staging.com", $this->options->prefix);
443 $this->returnException($message);
444 wp_die(esc_html($message));
445 }
446
447
448 /**
449 * Start the cloning job
450 * @throws JobNotFoundException
451 */
452 public function start()
453 {
454 if (!is_object($this->options)) {
455 return;
456 }
457
458 if (!property_exists($this->options, 'currentJob') || $this->options->currentJob === null) {
459 $this->log("Cloning job finished");
460 return true;
461 }
462
463 $methodName = "job" . ucwords($this->options->currentJob);
464
465 if (!method_exists($this, $methodName)) {
466 $this->log("Can't execute job; Job's method $methodName is not found");
467 throw new JobNotFoundException($methodName);
468 }
469
470 if ($this->options->databasePrefix === $this->db->prefix && $this->isStagingDatabaseSameAsProductionDatabase()) {
471 $this->returnException('Table prefix for staging site can not be identical to live database if staging site will be cloned into production database! Please start over and change the table prefix or destination database.');
472 }
473
474 if (defined('WPSTG_IS_DEV') && WPSTG_IS_DEV === true) {
475 return $this->{$methodName}();
476 }
477
478 $tmpPrefixes = [
479 DatabaseImporter::TMP_DATABASE_PREFIX,
480 DatabaseImporter::TMP_DATABASE_PREFIX_TO_DROP,
481 ];
482
483 if (in_array($this->options->databasePrefix, $tmpPrefixes)) {
484 $this->returnException('Prefix wpstgtmp_ and wpstgbak_ are preserved by WP Staging and cannot be used for CLONING purpose! Please start over and change the table prefix.');
485 }
486
487 if ($this->isWpStagingReservedPrefix($this->options->databasePrefix)) {
488 $this->returnException($this->getReservedPrefixErrorMessage($this->options->databasePrefix));
489 }
490
491 // Call the job
492 return $this->{$methodName}();
493 }
494
495 /**
496 * @param object $response
497 * @param string $nextJob
498 * @return object
499 * @throws \Exception
500 */
501 private function handleJobResponse($response, string $nextJob)
502 {
503 // Job is not done
504 if ($response->status !== true) {
505 return $response;
506 }
507
508 $this->options->job = new \stdClass();
509 $this->options->currentJob = $nextJob;
510 $this->options->currentStep = 0;
511 $this->options->totalSteps = 0;
512
513 // Save options
514 $this->saveOptions();
515
516 return $response;
517 }
518
519 /**
520 * Copy data from staging site to temporary column to use it later
521 * @return object
522 * @throws \Exception
523 */
524 public function jobPreserveDataFirstStep()
525 {
526 $this->writeJobSpecificLogStartHeader();
527
528 $preserve = new PreserveDataFirstStep();
529 return $this->handleJobResponse($preserve->start(), 'database');
530 }
531
532 /**
533 * Clone Database
534 * @return object
535 * @throws \Exception
536 */
537 public function jobDatabase()
538 {
539 $database = new Database();
540 return $this->handleJobResponse($database->start(), "SearchReplace");
541 }
542
543 /**
544 * Search & Replace
545 * @return object
546 * @throws \Exception
547 */
548 public function jobSearchReplace()
549 {
550 $searchReplace = new SearchReplace();
551 return $this->handleJobResponse($searchReplace->start(), "PreserveDataSecondStep");
552 }
553
554 /**
555 * Copy tmp data back to staging site
556 * @return object
557 * @throws \Exception
558 */
559 public function jobPreserveDataSecondStep()
560 {
561 $preserve = new PreserveDataSecondStep();
562 return $this->handleJobResponse($preserve->start(), 'directories');
563 }
564
565 /**
566 * Get All Files From Selected Directories Recursively Into a File
567 * @return object
568 * @throws \Exception
569 */
570 public function jobDirectories()
571 {
572 $directories = new Directories();
573 return $this->handleJobResponse($directories->start(), "files");
574 }
575
576 /**
577 * Copy Files
578 * @return object
579 * @throws \Exception
580 */
581 public function jobFiles()
582 {
583 $files = new Files();
584 return $this->handleJobResponse($files->start(), "data");
585 }
586
587 /**
588 * Replace Data
589 * @return object
590 * @throws \Exception
591 */
592 public function jobData()
593 {
594 $dataJob = $this->getDataJob();
595 return $this->handleJobResponse($dataJob->start(), "finish");
596 }
597
598 /**
599 * Save Clone Data
600 * @return object
601 * @throws \Exception
602 */
603 public function jobFinish()
604 {
605 // Re-generate the token when the Clone is complete.
606 // Todo: Consider adding a do_action() on jobFinish to hook here.
607 // Todo: Inject using DI
608 $accessToken = new AccessToken();
609 $accessToken->generateNewToken();
610
611 $finish = new Finish();
612 return $this->handleJobResponse($finish->start(), '');
613 }
614
615 /**
616 * @return Data
617 */
618 public function getDataJob(): Data
619 {
620 return new Data();
621 }
622
623 /**
624 * @return void
625 */
626 protected function setAdvancedCloningOptions()
627 {
628 // no-op
629 }
630
631 /**
632 * @return void
633 */
634 private function writeJobSpecificLogStartHeader()
635 {
636
637 $jobName = empty($this->options->mainJob) ? 'Unknown' : $this->options->mainJob;
638
639 switch ($jobName) {
640 case Job::UPDATE:
641 $jobName = 'Update';
642 break;
643 case Job::RESET:
644 $jobName = 'Reset';
645 break;
646 case Job::STAGING:
647 $jobName = 'Cloning';
648 break;
649 default:
650 $jobName = 'Unknown';
651 break;
652 }
653
654 $this->log('#################### Start ' . $jobName . ' Job ####################', 'INFO');
655 if ($jobName !== 'Cloning' && !empty($this->options->clone)) {
656 $this->logger->info(esc_html('Staging Site ID: ' . $this->options->clone));
657 $this->logger->info(esc_html('Staging Site: ' . $this->options->cloneName));
658 }
659
660 $this->logger->writeLogHeader();
661 $this->logger->writeInstalledPluginsAndThemes();
662 $this->addJobSettingsToLogs($jobName);
663 }
664
665 /**
666 * @return string The generated friendly name or clone Id by default
667 * @throws WPStagingException
668 */
669 private function maybeGenerateFriendlyName(): string
670 {
671 // List of predefined names to choose from
672 $nameList = [
673 "enterprise",
674 "voyager",
675 "defiant",
676 "discovery",
677 "excelsior",
678 "intrepid",
679 "constitution",
680 "reliant",
681 "grissom",
682 "yamato",
683 "excelsior",
684 "venture",
685 "cerritos",
686 "prometheus",
687 "bellerophon",
688 "sanpablo",
689 "sutherland",
690 "shenzhou",
691 "titan",
692 "reliant",
693 "stargazer",
694 "franklin",
695 "protostar",
696 ];
697
698 // Randomly shuffle the list of names
699 shuffle($nameList);
700
701 // Get the list of staging sites
702 $stagingSites = $this->sitesHelper->tryGettingStagingSites();
703 foreach ($nameList as $name) {
704 // Sanitize the name to ensure it is safe for use
705 $name = sanitize_text_field($name);
706 $dirPath = ABSPATH . $name;
707 // Check if the directory exists
708 if (file_exists($dirPath)) {
709 continue;
710 }
711
712 // If the directory is free, then check the database
713 if (!$this->isStagingSiteNameExists($name, $stagingSites)) {
714 return $name;
715 }
716 }
717
718 // If all predefined names are taken, return a clone Id
719 return (string)$this->options->clone;
720 }
721
722 /**
723 * Check if the name already exists in the staging sites $stagingSites
724 * @param string $name
725 * @param array $stagingSites
726 * @return bool
727 */
728 private function isStagingSiteNameExists(string $name, array $stagingSites): bool
729 {
730 foreach ($stagingSites as $site) {
731 if ($site['directoryName'] === $name) {
732 return true;
733 }
734 }
735
736 return false;
737 }
738 }
739