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