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