WP Staging – DB & File Duplicator & Migration - Version 2.3.9

Version Description

  • New: Support for wp-config.php located in one level up of the root folder for multisites
  • New: Allow exclusion of custom options from wp_options from beeing pushed
  • New: Make sure Optimizer is installed and activated
  • New: Show date of site creation/update in list of staging sites
  • Tweak: Better looking UI elements
  • Fix: Requirements Check not working as intended
  • Fix: Filesize() failed if file to copy is not a regular file
  • Fix: remove ? parameter from staging site
Download this release

Release Info

Developer ReneHermi
Plugin Icon 128x128 WP Staging – DB & File Duplicator & Migration
Version 2.3.9
Comparing to
See all releases

Code changes from version 2.3.8 to 2.3.9

Files changed (33) hide show
  1. apps/Backend/Administrator.php +43 -5
  2. apps/Backend/Modules/Jobs/Cancel.php +16 -13
  3. apps/Backend/Modules/Jobs/Cloning.php +23 -0
  4. apps/Backend/Modules/Jobs/Data.php +22 -13
  5. apps/Backend/Modules/Jobs/Delete.php +442 -408
  6. apps/Backend/Modules/Jobs/Files.php +15 -16
  7. apps/Backend/Modules/Jobs/Finish.php +6 -1
  8. apps/Backend/Modules/Jobs/Job.php +1 -0
  9. apps/Backend/Modules/Jobs/Multisite/Data.php +966 -980
  10. apps/Backend/Modules/Jobs/Multisite/DataExternal.php +1082 -0
  11. apps/Backend/Modules/Jobs/Multisite/Database.php +315 -315
  12. apps/Backend/Modules/Jobs/Multisite/DatabaseExternal.php +350 -0
  13. apps/Backend/Modules/Jobs/Multisite/Directories.php +655 -655
  14. apps/Backend/Modules/Jobs/Multisite/Files.php +383 -384
  15. apps/Backend/Modules/Jobs/Multisite/Finish.php +120 -115
  16. apps/Backend/Modules/Jobs/Multisite/SearchReplace.php +775 -776
  17. apps/Backend/Modules/Jobs/Multisite/SearchReplaceExternal.php +727 -0
  18. apps/Backend/Modules/Jobs/Scan.php +14 -2
  19. apps/Backend/Modules/Jobs/SearchReplace.php +55 -60
  20. apps/Backend/Modules/SystemInfo.php +1 -0
  21. apps/Backend/public/css/wpstg-admin.css +40 -1
  22. apps/Backend/public/js/wpstg-admin.js +21 -14
  23. apps/Backend/views/_includes/messages/rating.php +1 -1
  24. apps/Backend/views/clone/ajax/delete-confirmation.php +17 -6
  25. apps/Backend/views/clone/ajax/external-database.php +24 -0
  26. apps/Backend/views/clone/ajax/scan.php +7 -16
  27. apps/Backend/views/clone/ajax/single-overview.php +9 -4
  28. apps/Backend/views/clone/includes/footer.php +3 -2
  29. apps/Core/Utils/requirements-check.php +205 -64
  30. apps/Core/WPStaging.php +1 -1
  31. readme.txt +12 -14
  32. uninstall.php +1 -0
  33. wp-staging.php +11 -9
apps/Backend/Administrator.php CHANGED
@@ -17,7 +17,7 @@ use WPStaging\Backend\Modules\Jobs\Delete;
17
  use WPStaging\Backend\Modules\Jobs\Files;
18
  use WPStaging\Backend\Modules\Jobs\Scan;
19
  use WPStaging\Backend\Modules\Jobs\Logs;
20
- use WPStaging\Backend\Modules\Optimizer;
21
  use WPStaging\Backend\Modules\SystemInfo;
22
  use WPStaging\Backend\Modules\Views\Tabs\Tabs;
23
  use WPStaging\Backend\Notices\Notices;
@@ -93,7 +93,6 @@ class Administrator extends InjectionAware {
93
  $loader->addAction( "wp_ajax_wpstg_overview", $this, "ajaxOverview" );
94
  $loader->addAction( "wp_ajax_wpstg_scanning", $this, "ajaxScan" );
95
  $loader->addAction( "wp_ajax_wpstg_check_clone", $this, "ajaxcheckCloneName" );
96
- //$loader->addAction( "wp_ajax_wpstg_update_struc", $this, "ajaxStartUpdate" );
97
  $loader->addAction( "wp_ajax_wpstg_update", $this, "ajaxUpdateProcess" );
98
  $loader->addAction( "wp_ajax_wpstg_cloning", $this, "ajaxStartClone" );
99
  $loader->addAction( "wp_ajax_wpstg_clone_database", $this, "ajaxCloneDatabase" );
@@ -397,6 +396,7 @@ class Administrator extends InjectionAware {
397
  public function ajaxScan() {
398
  check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
399
 
 
400
 
401
  // Scan
402
  $scan = new Scan();
@@ -418,7 +418,7 @@ class Administrator extends InjectionAware {
418
  $cloneNameLength = strlen( $cloneName );
419
  $clones = get_option( "wpstg_existing_clones_beta", array() );
420
 
421
- $clonePath = trailingslashit(get_home_path()) . $cloneName;
422
 
423
  // Check clone name length
424
  if( $cloneNameLength < 1 || $cloneNameLength > 16 ) {
@@ -456,8 +456,8 @@ class Administrator extends InjectionAware {
456
  require_once "{$this->path}views/clone/ajax/update.php";
457
 
458
  wp_die();
459
-
460
  }
 
461
  /**
462
  * Ajax Start Clone (Basically just layout and saving data)
463
  */
@@ -545,6 +545,8 @@ class Administrator extends InjectionAware {
545
 
546
  $clone = $delete->getClone();
547
 
 
 
548
  require_once "{$this->path}views/clone/ajax/delete-confirmation.php";
549
 
550
  wp_die();
@@ -557,6 +559,7 @@ class Administrator extends InjectionAware {
557
  check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
558
 
559
  $delete = new Delete();
 
560
  wp_send_json( $delete->start() );
561
  }
562
 
@@ -567,6 +570,7 @@ class Administrator extends InjectionAware {
567
  check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
568
 
569
  $cancel = new Cancel();
 
570
  wp_send_json( $cancel->start() );
571
  }
572
 
@@ -720,11 +724,45 @@ class Administrator extends InjectionAware {
720
  $terms = ( bool ) $args['wpstg_terms'];
721
  }
722
 
723
- $report = new Report($this->di);
724
  $errors = $report->send( $email, $message, $terms, $syslog );
725
 
726
  echo json_encode( array('errors' => $errors) );
727
  exit;
728
  }
729
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
730
  }
17
  use WPStaging\Backend\Modules\Jobs\Files;
18
  use WPStaging\Backend\Modules\Jobs\Scan;
19
  use WPStaging\Backend\Modules\Jobs\Logs;
20
+ #use WPStaging\Backend\Modules\Optimizer;
21
  use WPStaging\Backend\Modules\SystemInfo;
22
  use WPStaging\Backend\Modules\Views\Tabs\Tabs;
23
  use WPStaging\Backend\Notices\Notices;
93
  $loader->addAction( "wp_ajax_wpstg_overview", $this, "ajaxOverview" );
94
  $loader->addAction( "wp_ajax_wpstg_scanning", $this, "ajaxScan" );
95
  $loader->addAction( "wp_ajax_wpstg_check_clone", $this, "ajaxcheckCloneName" );
 
96
  $loader->addAction( "wp_ajax_wpstg_update", $this, "ajaxUpdateProcess" );
97
  $loader->addAction( "wp_ajax_wpstg_cloning", $this, "ajaxStartClone" );
98
  $loader->addAction( "wp_ajax_wpstg_clone_database", $this, "ajaxCloneDatabase" );
396
  public function ajaxScan() {
397
  check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
398
 
399
+ $db = WPStaging::getInstance()->get( 'wpdb' );
400
 
401
  // Scan
402
  $scan = new Scan();
418
  $cloneNameLength = strlen( $cloneName );
419
  $clones = get_option( "wpstg_existing_clones_beta", array() );
420
 
421
+ $clonePath = trailingslashit( get_home_path() ) . $cloneName;
422
 
423
  // Check clone name length
424
  if( $cloneNameLength < 1 || $cloneNameLength > 16 ) {
456
  require_once "{$this->path}views/clone/ajax/update.php";
457
 
458
  wp_die();
 
459
  }
460
+
461
  /**
462
  * Ajax Start Clone (Basically just layout and saving data)
463
  */
545
 
546
  $clone = $delete->getClone();
547
 
548
+ $dbname = $delete->getDbName();
549
+
550
  require_once "{$this->path}views/clone/ajax/delete-confirmation.php";
551
 
552
  wp_die();
559
  check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
560
 
561
  $delete = new Delete();
562
+
563
  wp_send_json( $delete->start() );
564
  }
565
 
570
  check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
571
 
572
  $cancel = new Cancel();
573
+
574
  wp_send_json( $cancel->start() );
575
  }
576
 
724
  $terms = ( bool ) $args['wpstg_terms'];
725
  }
726
 
727
+ $report = new Report( $this->di );
728
  $errors = $report->send( $email, $message, $terms, $syslog );
729
 
730
  echo json_encode( array('errors' => $errors) );
731
  exit;
732
  }
733
 
734
+ /**
735
+ * Connect to external database for testing correct credentials
736
+ */
737
+ public function ajaxDatabaseConnect() {
738
+ // Set params
739
+ if( empty( $args ) ) {
740
+ $args = stripslashes_deep( $_POST );
741
+ }
742
+
743
+ $user = !empty( $args['databaseUser'] ) ? $args['databaseUser'] : '';
744
+ $password = !empty( $args['databasePassword'] ) ? $args['databasePassword'] : '';
745
+ $database = !empty( $args['databaseDatabase'] ) ? $args['databaseDatabase'] : '';
746
+ $server = !empty( $args['databaseServer'] ) ? $args['databaseServer'] : '';
747
+
748
+ $db = new \wpdb( $user, $password, $database, $server );
749
+
750
+ // Can not connect to mysql
751
+ if( !empty( $db->error->errors['db_connect_fail']['0'] ) ) {
752
+ echo json_encode( array('errors' => $db->error->errors['db_connect_fail']['0']) );
753
+ exit;
754
+ }
755
+
756
+
757
+ // Can not connect to database
758
+ $sql = "SHOW DATABASES LIKE '{$database}';";
759
+ $results = $db->query( $sql );
760
+ if( empty( $results ) ) {
761
+ echo json_encode( array('errors' => " Database {$database} does not exist. You need to create it first. ") );
762
+ exit;
763
+ }
764
+ echo json_encode( array('success' => 'true') );
765
+ exit;
766
+ }
767
+
768
  }
apps/Backend/Modules/Jobs/Cancel.php CHANGED
@@ -15,14 +15,17 @@ class Cancel extends Job {
15
  public function start() {
16
  $cloneData = $this->createCloneData();
17
 
18
- if (empty($cloneData)) {
19
  return true;
20
  }
21
-
22
-
23
  $delete = new Delete();
24
- return $delete->start($cloneData);
 
25
  }
 
 
26
 
27
  /**
28
  * @return array
@@ -30,7 +33,7 @@ class Cancel extends Job {
30
  protected function createCloneData() {
31
  $clone = array();
32
 
33
- if (!$this->check()) {
34
  return $clone;
35
  }
36
 
@@ -47,11 +50,11 @@ class Cancel extends Job {
47
  */
48
  public function check() {
49
  return (
50
- isset($this->options) &&
51
- isset($this->options->clone) &&
52
- isset($this->options->cloneNumber) &&
53
- isset($this->options->cloneDirectoryName) &&
54
- isset($_POST["clone"]) &&
55
  $_POST["clone"] === $this->options->clone
56
  );
57
  }
@@ -60,15 +63,15 @@ class Cancel extends Job {
60
  * Get json response
61
  * return json
62
  */
63
- private function returnFinish($message = '') {
64
 
65
- wp_die(json_encode(array(
66
  'job' => 'delete',
67
  'status' => true,
68
  'message' => $message,
69
  'error' => false,
70
  'delete' => 'finished'
71
- )));
72
  }
73
 
74
  }
15
  public function start() {
16
  $cloneData = $this->createCloneData();
17
 
18
+ if( empty( $cloneData ) ) {
19
  return true;
20
  }
21
+ // Delete data in external database
22
+ if( empty( $this->options->databaseUser ) ) {
23
  $delete = new Delete();
24
+ } else {
25
+ $delete = new Delete( true );
26
  }
27
+ return $delete->start( $cloneData );
28
+ }
29
 
30
  /**
31
  * @return array
33
  protected function createCloneData() {
34
  $clone = array();
35
 
36
+ if( !$this->check() ) {
37
  return $clone;
38
  }
39
 
50
  */
51
  public function check() {
52
  return (
53
+ isset( $this->options ) &&
54
+ isset( $this->options->clone ) &&
55
+ isset( $this->options->cloneNumber ) &&
56
+ isset( $this->options->cloneDirectoryName ) &&
57
+ isset( $_POST["clone"] ) &&
58
  $_POST["clone"] === $this->options->clone
59
  );
60
  }
63
  * Get json response
64
  * return json
65
  */
66
+ private function returnFinish( $message = '' ) {
67
 
68
+ wp_die( json_encode( array(
69
  'job' => 'delete',
70
  'status' => true,
71
  'message' => $message,
72
  'error' => false,
73
  'delete' => 'finished'
74
+ ) ) );
75
  }
76
 
77
  }
apps/Backend/Modules/Jobs/Cloning.php CHANGED
@@ -118,6 +118,29 @@ class Cloning extends Job {
118
 
119
  array_unshift( $this->options->directoriesToCopy, \WPStaging\WPStaging::getWPpath() );
120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  // Delete files to copy listing
122
  $this->cache->delete( "files_to_copy" );
123
 
118
 
119
  array_unshift( $this->options->directoriesToCopy, \WPStaging\WPStaging::getWPpath() );
120
 
121
+ $this->options->databaseServer = 'localhost';
122
+ if( isset( $_POST["databaseServer"] ) && !empty( $_POST["databaseServer"] ) ) {
123
+ $this->options->databaseServer = $_POST["databaseServer"];
124
+ }
125
+ $this->options->databaseUser = '';
126
+ if( isset( $_POST["databaseUser"] ) && !empty( $_POST["databaseUser"] ) ) {
127
+ $this->options->databaseUser = $_POST["databaseUser"];
128
+ }
129
+ $this->options->databasePassword = '';
130
+ if( isset( $_POST["databasePassword"] ) && !empty( $_POST["databasePassword"] ) ) {
131
+ $this->options->databasePassword = $_POST["databasePassword"];
132
+ }
133
+ $this->options->databaseDatabase = '';
134
+ if( isset( $_POST["databaseDatabase"] ) && !empty( $_POST["databaseDatabase"] ) ) {
135
+ $this->options->databaseDatabase = $_POST["databaseDatabase"];
136
+ }
137
+ $this->options->databasePrefix = 'wp_';
138
+ if( isset( $_POST["databasePrefix"] ) && !empty( $_POST["databasePrefix"] ) ) {
139
+ $this->options->databasePrefix = $_POST["databasePrefix"];
140
+ }
141
+
142
+
143
+
144
  // Delete files to copy listing
145
  $this->cache->delete( "files_to_copy" );
146
 
apps/Backend/Modules/Jobs/Data.php CHANGED
@@ -278,7 +278,8 @@ class Data extends JobExecutable {
278
 
279
 
280
  // All good
281
- if( $result ) {
 
282
  return true;
283
  }
284
 
@@ -296,6 +297,7 @@ class Data extends JobExecutable {
296
 
297
  // Skip - Table does not exist
298
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
 
299
  return true;
300
  }
301
  // Skip - Table is not selected or updated
@@ -304,23 +306,25 @@ class Data extends JobExecutable {
304
  return true;
305
  }
306
 
307
- $result = $this->db->query(
308
  $this->db->prepare(
309
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_is_staging_site'", "true"
310
  )
311
  );
312
 
 
313
  // No errors but no option name such as wpstg_is_staging_site
314
- if( '' === $this->db->last_error && 0 == $result ) {
315
- $result = $this->db->query(
316
  $this->db->prepare(
317
  "INSERT INTO {$this->prefix}options (option_name,option_value) VALUES ('wpstg_is_staging_site',%s)", "true"
318
  )
319
  );
320
- }
321
 
322
  // All good
323
- if( $result ) {
 
324
  return true;
325
  }
326
 
@@ -607,7 +611,7 @@ class Data extends JobExecutable {
607
  $replace.= " // Changed by WP-Staging";
608
 
609
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
610
- $this->log( "Preparing Data: Failed to reset index.php for sub directory; replacement failed", Logger::TYPE_ERROR );
611
  return false;
612
  }
613
  } else {
@@ -615,10 +619,10 @@ class Data extends JobExecutable {
615
  }
616
 
617
  if( false === @file_put_contents( $path, $content ) ) {
618
- $this->log( "Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR );
619
  return false;
620
  }
621
- $this->Log( "Preparing Data: Finished Step 11 successfully" );
622
  return true;
623
  }
624
 
@@ -709,10 +713,10 @@ class Data extends JobExecutable {
709
 
710
  if( !$updateOptions ) {
711
  $this->log( "Preparing Data Step12: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_ERROR );
712
- $this->returnException( "Data Crunching Step 15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
713
  return false;
714
  }
715
-
716
  return true;
717
  }
718
 
@@ -832,7 +836,12 @@ class Data extends JobExecutable {
832
  * @return boolean
833
  */
834
  protected function isSubDir() {
835
- if( get_option( 'siteurl' ) !== get_option( 'home' ) ) {
 
 
 
 
 
836
  return true;
837
  }
838
  return false;
278
 
279
 
280
  // All good
281
+ if( false !== $result) {
282
+ $this->log( "Preparing Data Step1: Successfull", Logger::TYPE_INFO );
283
  return true;
284
  }
285
 
297
 
298
  // Skip - Table does not exist
299
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
300
+ $this->log( "Preparing Data Step2: Skipping" );
301
  return true;
302
  }
303
  // Skip - Table is not selected or updated
306
  return true;
307
  }
308
 
309
+ $delete = $this->db->query(
310
  $this->db->prepare(
311
+ "DELETE FROM `{$this->prefix}options` WHERE `option_name` = %s;", 'wpstg_is_staging_site'
312
  )
313
  );
314
 
315
+
316
  // No errors but no option name such as wpstg_is_staging_site
317
+ //if( '' === $this->db->last_error && 0 == $result ) {
318
+ $insert = $this->db->query(
319
  $this->db->prepare(
320
  "INSERT INTO {$this->prefix}options (option_name,option_value) VALUES ('wpstg_is_staging_site',%s)", "true"
321
  )
322
  );
323
+ //}
324
 
325
  // All good
326
+ if( $insert ) {
327
+ $this->log( "Preparing Data Step2: Successfull", Logger::TYPE_INFO );
328
  return true;
329
  }
330
 
611
  $replace.= " // Changed by WP-Staging";
612
 
613
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
614
+ $this->log( "Preparing Data: Failed to update WP_HOME", Logger::TYPE_ERROR );
615
  return false;
616
  }
617
  } else {
619
  }
620
 
621
  if( false === @file_put_contents( $path, $content ) ) {
622
+ $this->log( "Preparing Data Step10: Failed to update WP_HOME. Can't save contents", Logger::TYPE_ERROR );
623
  return false;
624
  }
625
+ $this->Log( "Preparing Data: Finished Step 10 successfully" );
626
  return true;
627
  }
628
 
713
 
714
  if( !$updateOptions ) {
715
  $this->log( "Preparing Data Step12: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_ERROR );
716
+ $this->returnException( " Preparing Data Step12: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
717
  return false;
718
  }
719
+ $this->Log( "Preparing Data: Finished Step 12 successfully" );
720
  return true;
721
  }
722
 
836
  * @return boolean
837
  */
838
  protected function isSubDir() {
839
+ // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
840
+ // This is happening much more often than you would expect
841
+ $siteurl = preg_replace( '#^https?://#', '', rtrim( get_option( 'siteurl' ), '/' ) );
842
+ $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
843
+
844
+ if( $home !== $siteurl ) {
845
  return true;
846
  }
847
  return false;
apps/Backend/Modules/Jobs/Delete.php CHANGED
@@ -13,417 +13,451 @@ use WPStaging\WPStaging;
13
  */
14
  class Delete extends Job {
15
 
16
- /**
17
- * @var false
18
- */
19
- private $clone = false;
20
-
21
- /**
22
- * @var null|object
23
- */
24
- private $tables = null;
25
-
26
- /**
27
- * @var object|null
28
- */
29
- private $job = null;
30
-
31
- /**
32
- * @var bool
33
- */
34
- private $forceDeleteDirectories = false;
35
-
36
- /**
37
- *
38
- * @var object
39
- */
40
- public $wpdb;
41
-
42
- public function __construct() {
43
- parent::__construct();
44
- $this->wpdb = WPStaging::getInstance()->get("wpdb");
45
- }
46
-
47
- /**
48
- * Sets Clone and Table Records
49
- * @param null|array $clone
50
- */
51
- public function setData($clone = null) {
52
- if (!is_array($clone)) {
53
- $this->getCloneRecords();
54
- } else {
55
- $this->clone = (object) $clone;
56
- $this->forceDeleteDirectories = true;
57
- }
58
-
59
- $this->getTableRecords();
60
- }
61
-
62
- /**
63
- * Get clone
64
- * @param null|string $name
65
- * @throws CloneNotFoundException
66
- */
67
- private function getCloneRecords($name = null) {
68
- if (null === $name && !isset($_POST["clone"])) {
69
- $this->log("Clone name is not set", Logger::TYPE_FATAL);
70
- throw new CloneNotFoundException();
71
- }
72
-
73
- if (null === $name) {
74
- $name = $_POST["clone"];
75
- }
76
-
77
- $clones = get_option("wpstg_existing_clones_beta", array());
78
-
79
- if (empty($clones) || !isset($clones[$name])) {
80
- $this->log("Couldn't find clone name {$name} or no existing clone", Logger::TYPE_FATAL);
81
- throw new CloneNotFoundException();
82
- }
83
-
84
- $this->clone = $clones[$name];
85
- $this->clone["name"] = $name;
86
-
87
- $this->clone = (object) $this->clone;
88
-
89
- unset($clones);
90
- }
91
-
92
- /**
93
- * Get Tables
94
- */
95
- private function getTableRecords() {
96
- //$wpdb = WPStaging::getInstance()->get("wpdb");
97
- $this->wpdb = WPStaging::getInstance()->get("wpdb");
98
-
99
- $stagingPrefix = $this->getStagingPrefix();
100
-
101
- $tables = $this->wpdb->get_results("SHOW TABLE STATUS LIKE '{$stagingPrefix}%'");
102
-
103
- $this->tables = array();
104
-
105
- foreach ($tables as $table) {
106
- $this->tables[] = array(
107
- "name" => $table->Name,
108
- "size" => $this->formatSize(($table->Data_length + $table->Index_length))
109
- );
110
- }
111
-
112
- $this->tables = json_decode(json_encode($this->tables));
113
- }
114
-
115
- /**
116
- * Check and return prefix of the staging site
117
- */
118
- public function getStagingPrefix() {
119
- // Prefix not defined! Happens if staging site has ben generated with older version of wpstg
120
- // Try to get staging prefix from wp-config.php of staging site
121
- if (empty($this->clone->prefix)) {
122
- // Throw error
123
- $path = ABSPATH . $this->clone->directoryName . "/wp-config.php";
124
- if (false === ($content = @file_get_contents($path))) {
125
- $this->log("Can not open {$path}. Can't read contents", Logger::TYPE_ERROR);
126
- // Create a random prefix which hopefully never exists.
127
- $this->clone->prefix = rand(7, 15) . '_';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  } else {
129
-
130
- // Get prefix from wp-config.php
131
- //preg_match_all("/table_prefix\s*=\s*'(\w*)';/", $content, $matches);
132
- preg_match("/table_prefix\s*=\s*'(\w*)';/", $content, $matches);
133
- //wp_die(var_dump($matches));
134
-
135
- if (!empty($matches[1])) {
136
- $this->clone->prefix = $matches[1];
137
- } else {
138
- $this->log("Fatal Error: Can not delete staging site. Can not find Prefix. '{$matches[1]}'. Stopping for security reasons. Creating a new staging site will likely resolve this the next time. Contact support@wp-staging.com");
139
- // Create a random prefix which hopefully never exists.
140
- return $this->clone->prefix = rand(7, 15) . '_';
141
- }
142
  }
143
- }
144
-
145
- // Check if staging prefix is the same as the live prefix
146
- if ($this->wpdb->prefix == $this->clone->prefix) {
147
- $this->log("Fatal Error: Can not delete staging site. Prefix. '{$this->clone->prefix}' is used for the live site. Creating a new staging site will likely resolve this the next time. Stopping for security reasons. Contact support@wp-staging.com");
148
- wp_die("Fatal Error: Can not delete staging site. Prefix. '{$this->clone->prefix}' is used for the live site. Creating a new staging site will likely resolve this the next time. Stopping for security reasons. Contact support@wp-staging.com");
149
- }
150
-
151
- // Else
152
- return $this->clone->prefix;
153
- }
154
-
155
- /**
156
- * Format bytes into human readable form
157
- * @param int $bytes
158
- * @param int $precision
159
- * @return string
160
- */
161
- public function formatSize($bytes, $precision = 2) {
162
- if ((int) $bytes < 1) {
163
- return '';
164
- }
165
-
166
- $units = array('B', "KB", "MB", "GB", "TB");
167
-
168
- $bytes = (int) $bytes;
169
- $base = log($bytes) / log(1000); // 1024 would be for MiB KiB etc
170
- $pow = pow(1000, $base - floor($base)); // Same rule for 1000
171
-
172
- return round($pow, $precision) . ' ' . $units[(int) floor($base)];
173
- }
174
-
175
- /**
176
- * @return false
177
- */
178
- public function getClone() {
179
- return $this->clone;
180
- }
181
-
182
- /**
183
- * @return null|object
184
- */
185
- public function getTables() {
186
- return $this->tables;
187
- }
188
-
189
- /**
190
- * Start Module
191
- * @param null|array $clone
192
- * @return bool
193
- */
194
- public function start($clone = null) {
195
- // Set data
196
- $this->setData($clone);
197
-
198
- // Get the job first
199
- $this->getJob();
200
-
201
- $method = "delete" . ucwords($this->job->current);
202
- return $this->{$method}();
203
- }
204
-
205
- /**
206
- * Get job data
207
- */
208
- private function getJob() {
209
- $this->job = $this->cache->get("delete_job_{$this->clone->name}");
210
-
211
-
212
- if (null !== $this->job) {
213
- return;
214
- }
215
-
216
- // Generate JOB
217
- $this->job = (object) array(
218
- "current" => "tables",
219
- "nextDirectoryToDelete" => $this->clone->path,
220
- "name" => $this->clone->name
221
- );
222
-
223
- $this->cache->save("delete_job_{$this->clone->name}", $this->job);
224
- }
225
-
226
- /**
227
- * @return bool
228
- */
229
- private function updateJob() {
230
- $this->job->nextDirectoryToDelete = trim($this->job->nextDirectoryToDelete);
231
- return $this->cache->save("delete_job_{$this->clone->name}", $this->job);
232
- }
233
-
234
- /**
235
- * @return array
236
- */
237
- private function getTablesToRemove() {
238
- $tables = $this->getTableNames();
239
-
240
- if (!isset($_POST["excludedTables"]) || !is_array($_POST["excludedTables"]) || empty($_POST["excludedTables"])) {
241
- return $tables;
242
- }
243
-
244
- return array_diff($tables, $_POST["excludedTables"]);
245
- }
246
-
247
- /**
248
- * @return array
249
- */
250
- private function getTableNames() {
251
- return (!is_array($this->tables)) ? array() : array_map(function($value) {
252
- return ($value->name);
253
- }, $this->tables);
254
- }
255
-
256
- /**
257
- * Delete Tables
258
- */
259
- public function deleteTables() {
260
- if ($this->isOverThreshold()) {
261
- return;
262
- }
263
-
264
- //$wpdb = WPStaging::getInstance()->get("wpdb");
265
-
266
- foreach ($this->getTablesToRemove() as $table) {
267
- // PROTECTION: Never delete any table that beginns with wp prefix of live site
268
- if ($this->startsWith($table, $this->wpdb->prefix)) {
269
- $this->log("Fatal Error: Trying to delete table {$table} of main WP installation!", Logger::TYPE_CRITICAL);
270
- return false;
271
- } else {
272
- $this->wpdb->query("DROP TABLE {$table}");
273
- }
274
- }
275
-
276
- // Move on to the next
277
- $this->job->current = "directory";
278
- $this->updateJob();
279
- }
280
-
281
- /**
282
- * Check if a strings start with a specific string
283
- * @param string $haystack
284
- * @param string $needle
285
- * @return bool
286
- */
287
- protected function startsWith($haystack, $needle) {
288
- $length = strlen($needle);
289
- return (substr($haystack, 0, $length) === $needle);
290
- }
291
-
292
- /**
293
- * Delete complete directory including all files and subfolders
294
- *
295
- * @throws InvalidArgumentException
296
- */
297
- public function deleteDirectory() {
298
- if ($this->isFatalError()) {
299
- $this->returnException('Can not delete directory: ' . $this->clone->path . '. This seems to be the root directory. Please contact support@wp-staging.com');
300
- throw new \Exception('Can not delete directory: ' . $this->clone->path . ' This seems to be the root directory. Please contact support@wp-staging.com');
301
- }
302
- // Finished or path does not exist
303
- if (
304
- empty($this->clone->path) ||
305
- $this->clone->path == get_home_path() ||
306
- !is_dir($this->clone->path)) {
307
-
308
- $this->job->current = "finish";
309
- $this->updateJob();
310
- return $this->returnFinish();
311
- }
312
-
313
- $this->log("Delete staging site: " . $this->clone->path, Logger::TYPE_INFO);
314
-
315
- // Just to make sure the root dir is never deleted!
316
- if ($this->clone->path == get_home_path()) {
317
- $this->log("Fatal Error 8: Trying to delete root of WP installation!", Logger::TYPE_CRITICAL);
318
- $this->returnException('Fatal Error 8: Trying to delete root of WP installation!');
319
- }
320
-
321
- // Check if threshold is reached
322
- if ($this->isOverThreshold()) {
 
 
 
 
 
 
 
 
 
 
 
 
 
323
  //$this->returnException('Maximum PHP execution time exceeded. Run again and repeat the deletion process until it is sucessfully finished.');
324
  return;
325
- }
326
-
327
- $di = new \RecursiveDirectoryIterator($this->clone->path, \FilesystemIterator::SKIP_DOTS);
328
- $ri = new \RecursiveIteratorIterator($di, \RecursiveIteratorIterator::CHILD_FIRST);
329
- foreach ($ri as $file) {
330
- //$file->isDir() ? @rmdir($file) : unlink($file);
331
- $this->deleteFile($file);
332
- if ($this->isOverThreshold()) {
333
- //$this->returnException('Maximum PHP execution time exceeded. Run again and repeat the deletion process until it is sucessfully finished.');
334
- return;
335
- }
336
- }
337
-
338
- if (@rmdir($this->clone->path)) {
339
- return $this->returnFinish();
340
- }
341
- return;
342
- }
343
-
344
- /**
345
- * Delete file
346
- * @param object iterator $file
347
- */
348
- private function deleteFile($file) {
349
- if ($file->isDir()) {
350
- if (!@rmdir($file)) {
351
- $this->returnException('Permission Error: Can not delete folder ' . $file);
352
- }
353
- } else {
354
- if (!unlink($file)) {
355
- $this->returnException('Permission Error: Can not delete file ' . $file);
356
- }
357
- }
358
- }
359
-
360
- /**
361
- * @return bool
362
- */
363
- public function isDirectoryDeletingFinished() {
364
- return (
365
- (false === $this->forceDeleteDirectories && (!isset($_POST["deleteDir"]) || '1' !== $_POST["deleteDir"])) ||
366
- !is_dir($this->clone->path) || ABSPATH === $this->job->nextDirectoryToDelete
367
- );
368
- }
369
-
370
-
371
-
372
- /**
373
- *
374
- * @return boolean
375
- */
376
- public function isFatalError(){
377
- $homePath = rtrim(get_home_path(), "/");
378
- if (rtrim($this->clone->path,"/") == $homePath){
379
- return true;
380
- }
381
- return false;
382
- }
383
-
384
- /**
385
- * Finish / Update Existing Clones
386
- */
387
- public function deleteFinish() {
388
- $existingClones = get_option("wpstg_existing_clones_beta", array());
389
-
390
- // Check if clones still exist
391
- $this->log("Verifying existing clones...");
392
- foreach ($existingClones as $name => $clone) {
393
- if (!is_dir($clone["path"])) {
394
- unset($existingClones[$name]);
395
- }
396
- }
397
- $this->log("Existing clones verified!");
398
-
399
- if (false === update_option("wpstg_existing_clones_beta", $existingClones)) {
400
- $this->log("Failed to save {$this->options->clone}'s clone job data to database'");
401
- }
402
-
403
- // Delete cached file
404
- $this->cache->delete("delete_job_{$this->clone->name}");
405
- $this->cache->delete("delete_directories_{$this->clone->name}");
406
-
407
- //return true;
408
- $response = array('delete' => 'finished');
409
- wp_die(json_encode($response));
410
- }
411
-
412
- /**
413
- * Get json response
414
- * return json
415
- */
416
- private function returnFinish($message = '') {
417
-
418
- $this->deleteFinish();
419
-
420
- wp_die(json_encode(array(
421
- 'job' => 'delete',
422
- 'status' => true,
423
- 'message' => $message,
424
- 'error' => false,
425
- 'delete' => 'finished'
426
- )));
427
- }
428
 
429
  }
13
  */
14
  class Delete extends Job {
15
 
16
+ /**
17
+ * @var false
18
+ */
19
+ private $clone = false;
20
+
21
+ /**
22
+ * @var null|object
23
+ */
24
+ private $tables = null;
25
+
26
+ /**
27
+ * @var object|null
28
+ */
29
+ private $job = null;
30
+
31
+ /**
32
+ * @var bool
33
+ */
34
+ private $forceDeleteDirectories = false;
35
+
36
+ /**
37
+ *
38
+ * @var object
39
+ */
40
+ public $wpdb;
41
+ private $isExternal;
42
+
43
+ public function __construct( $isExternal = false ) {
44
+ parent::__construct();
45
+ $this->isExternal = $isExternal;
46
+ //$this->wpdb = WPStaging::getInstance()->get("wpdb");
47
+ }
48
+
49
+ /**
50
+ * Sets Clone and Table Records
51
+ * @param null|array $clone
52
+ */
53
+ public function setData( $clone = null ) {
54
+ if( !is_array( $clone ) ) {
55
+ $this->getCloneRecords();
56
+ } else {
57
+ $this->clone = ( object ) $clone;
58
+ $this->forceDeleteDirectories = true;
59
+ }
60
+
61
+ if( $this->isExternalDatabase() ) {
62
+ $this->wpdb = $this->getStagingDb();
63
+ } else {
64
+ $this->wpdb = WPStaging::getInstance()->get( "wpdb" );
65
+ }
66
+
67
+ $this->getTableRecords();
68
+ }
69
+
70
+ /**
71
+ * Get database object to interact with
72
+ */
73
+ private function getStagingDb() {
74
+ return new \wpdb( $this->clone->databaseUser, $this->clone->databasePassword, $this->clone->databaseDatabase, $this->clone->databaseServer );
75
+ }
76
+
77
+ public function getDbName() {
78
+ return $this->wpdb->dbname;
79
+ }
80
+
81
+ /**
82
+ * Check if external database is used
83
+ * @return boolean
84
+ */
85
+ private function isExternalDatabase() {
86
+ if( $this->isExternal ) {
87
+ return true;
88
+ }
89
+
90
+ if( !empty( $this->clone->databaseUser ) ) {
91
+ return true;
92
+ }
93
+ return false;
94
+ }
95
+
96
+ /**
97
+ * Get clone
98
+ * @param null|string $name
99
+ * @throws CloneNotFoundException
100
+ */
101
+ private function getCloneRecords( $name = null ) {
102
+ if( null === $name && !isset( $_POST["clone"] ) ) {
103
+ $this->log( "Clone name is not set", Logger::TYPE_FATAL );
104
+ //throw new CloneNotFoundException();
105
+ $this->returnException( "Clone name is not set" );
106
+ }
107
+
108
+ if( null === $name ) {
109
+ $name = $_POST["clone"];
110
+ }
111
+
112
+ $clones = get_option( "wpstg_existing_clones_beta", array() );
113
+
114
+ if( empty( $clones ) || !isset( $clones[$name] ) ) {
115
+ $this->log( "Couldn't find clone name {$name} or no existing clone", Logger::TYPE_FATAL );
116
+ //throw new CloneNotFoundException();
117
+ $this->returnException( "Couldn't find clone name {$name} or no existing clone" );
118
+ }
119
+
120
+ $this->clone = $clones[$name];
121
+ $this->clone["name"] = $name;
122
+
123
+ $this->clone = ( object ) $this->clone;
124
+
125
+ unset( $clones );
126
+ }
127
+
128
+ /**
129
+ * Get Tables
130
+ */
131
+ private function getTableRecords() {
132
+
133
+ $stagingPrefix = $this->getStagingPrefix();
134
+
135
+ $tables = $this->wpdb->get_results( "SHOW TABLE STATUS LIKE '{$stagingPrefix}%'" );
136
+
137
+ $this->tables = array();
138
+
139
+ foreach ( $tables as $table ) {
140
+ $this->tables[] = array(
141
+ "name" => $table->Name,
142
+ "size" => $this->formatSize( ($table->Data_length + $table->Index_length ) )
143
+ );
144
+ }
145
+
146
+ $this->tables = json_decode( json_encode( $this->tables ) );
147
+ }
148
+
149
+ /**
150
+ * Check and return prefix of the staging site
151
+ */
152
+ public function getStagingPrefix() {
153
+
154
+ if( $this->isExternalDatabase() && !empty($this->clone->prefix) ) {
155
+ return $this->clone->prefix;
156
+ }
157
+
158
+ // Prefix not defined! Happens if staging site has been generated with older version of wpstg
159
+ // Try to get staging prefix from wp-config.php of staging site
160
+ if( empty( $this->clone->prefix ) ) {
161
+ // Throw error
162
+ $path = ABSPATH . $this->clone->directoryName . "/wp-config.php";
163
+ if( false === ($content = @file_get_contents( $path )) ) {
164
+ $this->log( "Can not open {$path}. Can't read contents", Logger::TYPE_ERROR );
165
+ } else {
166
+ // Try to get prefix from wp-config.php
167
+ preg_match( "/table_prefix\s*=\s*'(\w*)';/", $content, $matches );
168
+
169
+ if( !empty( $matches[1] ) ) {
170
+ $this->clone->prefix = $matches[1];
171
  } else {
172
+ $this->returnException( "Fatal Error: Can not delete staging site. Can not find Prefix. '{$matches[1]}'. Stopping for security reasons. Creating a new staging site will likely resolve this the next time. Contact support@wp-staging.com" );
 
 
 
 
 
 
 
 
 
 
 
 
173
  }
174
+ }
175
+ }
176
+
177
+ if( empty( $this->clone->prefix ) ) {
178
+ $this->returnException( "Fatal Error: Can not delete staging site. Can not find table prefix. Contact support@wp-staging.com" );
179
+ }
180
+
181
+ // Check if staging prefix is the same as the live prefix
182
+ if( empty( $this->options->databaseUser ) && $this->wpdb->prefix == $this->clone->prefix ) {
183
+ $this->log( "Fatal Error: Can not delete staging site. Prefix. '{$this->clone->prefix}' is used for the live site. Creating a new staging site will likely resolve this the next time. Stopping for security reasons. Contact support@wp-staging.com" );
184
+ $this->returnException( "Fatal Error: Can not delete staging site. Prefix. '{$this->clone->prefix}' is used for the live site. Creating a new staging site will likely resolve this the next time. Stopping for security reasons. Contact support@wp-staging.com" );
185
+ }
186
+
187
+ // Else
188
+ return $this->clone->prefix;
189
+ }
190
+
191
+ /**
192
+ * Format bytes into human readable form
193
+ * @param int $bytes
194
+ * @param int $precision
195
+ * @return string
196
+ */
197
+ public function formatSize( $bytes, $precision = 2 ) {
198
+ if( ( int ) $bytes < 1 ) {
199
+ return '';
200
+ }
201
+
202
+ $units = array('B', "KB", "MB", "GB", "TB");
203
+
204
+ $bytes = ( int ) $bytes;
205
+ $base = log( $bytes ) / log( 1000 ); // 1024 would be for MiB KiB etc
206
+ $pow = pow( 1000, $base - floor( $base ) ); // Same rule for 1000
207
+
208
+ return round( $pow, $precision ) . ' ' . $units[( int ) floor( $base )];
209
+ }
210
+
211
+ /**
212
+ * @return false
213
+ */
214
+ public function getClone() {
215
+ return $this->clone;
216
+ }
217
+
218
+ /**
219
+ * @return null|object
220
+ */
221
+ public function getTables() {
222
+ return $this->tables;
223
+ }
224
+
225
+ /**
226
+ * Start Module
227
+ * @param null|array $clone
228
+ * @return bool
229
+ */
230
+ public function start( $clone = null ) {
231
+ // Set data
232
+ $this->setData( $clone );
233
+
234
+ // Get the job first
235
+ $this->getJob();
236
+
237
+ $method = "delete" . ucwords( $this->job->current );
238
+ return $this->{$method}();
239
+ }
240
+
241
+ /**
242
+ * Get job data
243
+ */
244
+ private function getJob() {
245
+ $this->job = $this->cache->get( "delete_job_{$this->clone->name}" );
246
+
247
+
248
+ if( null !== $this->job ) {
249
+ return;
250
+ }
251
+
252
+ // Generate JOB
253
+ $this->job = ( object ) array(
254
+ "current" => "tables",
255
+ "nextDirectoryToDelete" => $this->clone->path,
256
+ "name" => $this->clone->name
257
+ );
258
+
259
+ $this->cache->save( "delete_job_{$this->clone->name}", $this->job );
260
+ }
261
+
262
+ /**
263
+ * @return bool
264
+ */
265
+ private function updateJob() {
266
+ $this->job->nextDirectoryToDelete = trim( $this->job->nextDirectoryToDelete );
267
+ return $this->cache->save( "delete_job_{$this->clone->name}", $this->job );
268
+ }
269
+
270
+ /**
271
+ * @return array
272
+ */
273
+ private function getTablesToRemove() {
274
+ $tables = $this->getTableNames();
275
+
276
+ if( !isset( $_POST["excludedTables"] ) || !is_array( $_POST["excludedTables"] ) || empty( $_POST["excludedTables"] ) ) {
277
+ return $tables;
278
+ }
279
+
280
+ return array_diff( $tables, $_POST["excludedTables"] );
281
+ }
282
+
283
+ /**
284
+ * @return array
285
+ */
286
+ private function getTableNames() {
287
+ return (!is_array( $this->tables )) ? array() : array_map( function($value) {
288
+ return ($value->name);
289
+ }, $this->tables );
290
+ }
291
+
292
+ /**
293
+ * Delete Tables
294
+ */
295
+ public function deleteTables() {
296
+ if( $this->isOverThreshold() ) {
297
+ return;
298
+ }
299
+
300
+ foreach ( $this->getTablesToRemove() as $table ) {
301
+ // PROTECTION: Never delete any table that beginns with wp prefix of live site
302
+ if( !$this->isExternalDatabase() && $this->startsWith( $table, $this->wpdb->prefix ) ) {
303
+ $this->log( "Fatal Error: Trying to delete table {$table} of main WP installation!", Logger::TYPE_CRITICAL );
304
+ return false;
305
+ } else {
306
+ $this->wpdb->query( "DROP TABLE {$table}" );
307
+ }
308
+ }
309
+
310
+ // Move on to the next
311
+ $this->job->current = "directory";
312
+ $this->updateJob();
313
+ }
314
+
315
+ /**
316
+ * Check if a strings start with a specific string
317
+ * @param string $haystack
318
+ * @param string $needle
319
+ * @return bool
320
+ */
321
+ protected function startsWith( $haystack, $needle ) {
322
+ $length = strlen( $needle );
323
+ return (substr( $haystack, 0, $length ) === $needle);
324
+ }
325
+
326
+ /**
327
+ * Delete complete directory including all files and subfolders
328
+ *
329
+ * @throws InvalidArgumentException
330
+ */
331
+ public function deleteDirectory() {
332
+ if( $this->isFatalError() ) {
333
+ $this->returnException( 'Can not delete directory: ' . $this->clone->path . '. This seems to be the root directory. Please contact support@wp-staging.com' );
334
+ throw new \Exception( 'Can not delete directory: ' . $this->clone->path . ' This seems to be the root directory. Please contact support@wp-staging.com' );
335
+ }
336
+ // Finished or path does not exist
337
+ if(
338
+ empty( $this->clone->path ) ||
339
+ $this->clone->path == get_home_path() ||
340
+ !is_dir( $this->clone->path ) ) {
341
+
342
+ $this->job->current = "finish";
343
+ $this->updateJob();
344
+ return $this->returnFinish();
345
+ }
346
+
347
+ $this->log( "Delete staging site: " . $this->clone->path, Logger::TYPE_INFO );
348
+
349
+ // Just to make sure the root dir is never deleted!
350
+ if( $this->clone->path == get_home_path() ) {
351
+ $this->log( "Fatal Error 8: Trying to delete root of WP installation!", Logger::TYPE_CRITICAL );
352
+ $this->returnException( 'Fatal Error 8: Trying to delete root of WP installation!' );
353
+ }
354
+
355
+ // Check if threshold is reached
356
+ if( $this->isOverThreshold() ) {
357
+ //$this->returnException('Maximum PHP execution time exceeded. Run again and repeat the deletion process until it is sucessfully finished.');
358
+ return;
359
+ }
360
+
361
+ $di = new \RecursiveDirectoryIterator( $this->clone->path, \FilesystemIterator::SKIP_DOTS );
362
+ $ri = new \RecursiveIteratorIterator( $di, \RecursiveIteratorIterator::CHILD_FIRST );
363
+ foreach ( $ri as $file ) {
364
+ //$file->isDir() ? @rmdir($file) : unlink($file);
365
+ $this->deleteFile( $file );
366
+ if( $this->isOverThreshold() ) {
367
  //$this->returnException('Maximum PHP execution time exceeded. Run again and repeat the deletion process until it is sucessfully finished.');
368
  return;
369
+ }
370
+ }
371
+
372
+ if( @rmdir( $this->clone->path ) ) {
373
+ return $this->returnFinish();
374
+ }
375
+ return;
376
+ }
377
+
378
+ /**
379
+ * Delete file
380
+ * @param object iterator $file
381
+ */
382
+ private function deleteFile( $file ) {
383
+ if( $file->isDir() ) {
384
+ if( !@rmdir( $file ) ) {
385
+ $this->returnException( 'Permission Error: Can not delete folder ' . $file );
386
+ }
387
+ } elseif( !is_file( $file ) ) {
388
+ return false;
389
+ } else {
390
+ if( !unlink( $file ) ) {
391
+ $this->returnException( 'Permission Error: Can not delete file ' . $file );
392
+ }
393
+ }
394
+ }
395
+
396
+ /**
397
+ * @return bool
398
+ */
399
+ public function isDirectoryDeletingFinished() {
400
+ return (
401
+ (false === $this->forceDeleteDirectories && (!isset( $_POST["deleteDir"] ) || '1' !== $_POST["deleteDir"])) ||
402
+ !is_dir( $this->clone->path ) || ABSPATH === $this->job->nextDirectoryToDelete
403
+ );
404
+ }
405
+
406
+ /**
407
+ *
408
+ * @return boolean
409
+ */
410
+ public function isFatalError() {
411
+ $homePath = rtrim( get_home_path(), "/" );
412
+ if( rtrim( $this->clone->path, "/" ) == $homePath ) {
413
+ return true;
414
+ }
415
+ return false;
416
+ }
417
+
418
+ /**
419
+ * Finish / Update Existing Clones
420
+ */
421
+ public function deleteFinish() {
422
+ $existingClones = get_option( "wpstg_existing_clones_beta", array() );
423
+
424
+ // Check if clones still exist
425
+ $this->log( "Verifying existing clones..." );
426
+ foreach ( $existingClones as $name => $clone ) {
427
+ if( !is_dir( $clone["path"] ) ) {
428
+ unset( $existingClones[$name] );
429
+ }
430
+ }
431
+ $this->log( "Existing clones verified!" );
432
+
433
+ if( false === update_option( "wpstg_existing_clones_beta", $existingClones ) ) {
434
+ $this->log( "Failed to save {$this->options->clone}'s clone job data to database'" );
435
+ }
436
+
437
+ // Delete cached file
438
+ $this->cache->delete( "delete_job_{$this->clone->name}" );
439
+ $this->cache->delete( "delete_directories_{$this->clone->name}" );
440
+
441
+ //return true;
442
+ $response = array('delete' => 'finished');
443
+ wp_die( json_encode( $response ) );
444
+ }
445
+
446
+ /**
447
+ * Get json response
448
+ * return json
449
+ */
450
+ private function returnFinish( $message = '' ) {
451
+
452
+ $this->deleteFinish();
453
+
454
+ wp_die( json_encode( array(
455
+ 'job' => 'delete',
456
+ 'status' => true,
457
+ 'message' => $message,
458
+ 'error' => false,
459
+ 'delete' => 'finished'
460
+ ) ) );
461
+ }
 
 
 
 
 
 
 
 
 
 
462
 
463
  }
apps/Backend/Modules/Jobs/Files.php CHANGED
@@ -156,9 +156,6 @@ class Files extends JobExecutable {
156
 
157
  $directory = dirname( $file );
158
 
159
- // Get file size
160
- $fileSize = filesize( $file );
161
-
162
  // Directory is excluded
163
  if( $this->isDirectoryExcluded( $directory ) ) {
164
  $this->debugLog( "Directory Excluded: {$file}", Logger::TYPE_INFO );
@@ -170,24 +167,27 @@ class Files extends JobExecutable {
170
  $this->debugLog( "File excluded: {$file}", Logger::TYPE_INFO );
171
  return false;
172
  }
173
-
174
  // Path + File is excluded
175
  if( $this->isFileExcludedFullPath( $file ) ) {
176
  $this->debugLog( "File Excluded Full Path: {$file}", Logger::TYPE_INFO );
177
  return false;
178
  }
179
-
180
- // File is over maximum allowed file size (8MB)
181
- if( $fileSize >= $this->settings->maxFileSize * 1000000 ) {
182
- $this->log( "Skipping big file: {$file}", Logger::TYPE_INFO );
183
- return false;
184
- }
185
 
186
  // Invalid file, skipping it as if succeeded
187
  if( !is_file( $file ) ) {
188
  $this->debugLog( "Not a file {$file}" );
189
  return true;
190
  }
 
 
 
 
 
 
 
 
191
  // Invalid file, skipping it as if succeeded
192
  if( !is_readable( $file ) ) {
193
  $this->log( "Can't read file {$file}" );
@@ -286,15 +286,14 @@ class Files extends JobExecutable {
286
  // Do not copy wp-config.php if the clone gets updated. This is for security purposes,
287
  // because if the updating process fails, the staging site would not be accessable any longer
288
  if( isset( $this->options->mainJob ) && $this->options->mainJob == "updating" && stripos( strrev( $file ), strrev( "wp-config.php" ) ) === 0 ) {
289
- return true;
290
-
291
  }
292
 
293
 
294
  return false;
295
  }
296
-
297
- /**
298
  * Check if certain file is excluded from copying process
299
  *
300
  * @param string $file filename including ending + (part) path e.g wp-content/db.php
@@ -302,8 +301,8 @@ class Files extends JobExecutable {
302
  */
303
  private function isFileExcludedFullPath( $file ) {
304
  // If path + file exists
305
- foreach ($this->options->excludedFilesFullPath as $excludedFile){
306
- if( false !== strpos( $file, $excludedFile )){
307
  return true;
308
  }
309
  }
156
 
157
  $directory = dirname( $file );
158
 
 
 
 
159
  // Directory is excluded
160
  if( $this->isDirectoryExcluded( $directory ) ) {
161
  $this->debugLog( "Directory Excluded: {$file}", Logger::TYPE_INFO );
167
  $this->debugLog( "File excluded: {$file}", Logger::TYPE_INFO );
168
  return false;
169
  }
170
+
171
  // Path + File is excluded
172
  if( $this->isFileExcludedFullPath( $file ) ) {
173
  $this->debugLog( "File Excluded Full Path: {$file}", Logger::TYPE_INFO );
174
  return false;
175
  }
176
+
 
 
 
 
 
177
 
178
  // Invalid file, skipping it as if succeeded
179
  if( !is_file( $file ) ) {
180
  $this->debugLog( "Not a file {$file}" );
181
  return true;
182
  }
183
+
184
+ // Get file size
185
+ $fileSize = filesize( $file );
186
+ // File is over maximum allowed file size (8MB)
187
+ if( $fileSize >= $this->settings->maxFileSize * 1000000 ) {
188
+ $this->log( "Skipping big file: {$file}", Logger::TYPE_INFO );
189
+ return false;
190
+ }
191
  // Invalid file, skipping it as if succeeded
192
  if( !is_readable( $file ) ) {
193
  $this->log( "Can't read file {$file}" );
286
  // Do not copy wp-config.php if the clone gets updated. This is for security purposes,
287
  // because if the updating process fails, the staging site would not be accessable any longer
288
  if( isset( $this->options->mainJob ) && $this->options->mainJob == "updating" && stripos( strrev( $file ), strrev( "wp-config.php" ) ) === 0 ) {
289
+ return true;
 
290
  }
291
 
292
 
293
  return false;
294
  }
295
+
296
+ /**
297
  * Check if certain file is excluded from copying process
298
  *
299
  * @param string $file filename including ending + (part) path e.g wp-content/db.php
301
  */
302
  private function isFileExcludedFullPath( $file ) {
303
  // If path + file exists
304
+ foreach ( $this->options->excludedFilesFullPath as $excludedFile ) {
305
+ if( false !== strpos( $file, $excludedFile ) ) {
306
  return true;
307
  }
308
  }
apps/Backend/Modules/Jobs/Finish.php CHANGED
@@ -98,7 +98,12 @@ class Finish extends Job
98
  "version" => \WPStaging\WPStaging::VERSION,
99
  "status" => false,
100
  "prefix" => $this->options->prefix,
101
- "datetime" => time()
 
 
 
 
 
102
  );
103
 
104
  if (false === update_option("wpstg_existing_clones_beta", $this->options->existingClones))
98
  "version" => \WPStaging\WPStaging::VERSION,
99
  "status" => false,
100
  "prefix" => $this->options->prefix,
101
+ "datetime" => time(),
102
+ "databaseUser" => $this->options->databaseUser,
103
+ "databasePassword" => $this->options->databasePassword,
104
+ "databaseDatabase" => $this->options->databaseDatabase,
105
+ "databaseServer" => $this->options->databaseServer,
106
+ "databasePrefix" => $this->options->databasePrefix,
107
  );
108
 
109
  if (false === update_option("wpstg_existing_clones_beta", $this->options->existingClones))
apps/Backend/Modules/Jobs/Job.php CHANGED
@@ -201,6 +201,7 @@ abstract class Job implements JobInterface {
201
  $this->settings->batchSize = "2";
202
  $this->settings->cpuLoad = 'low';
203
  $this->settings->maxFileSize = 8;
 
204
  update_option( 'wpstg_settings', $this->settings );
205
  }
206
 
201
  $this->settings->batchSize = "2";
202
  $this->settings->cpuLoad = 'low';
203
  $this->settings->maxFileSize = 8;
204
+ $this->settings->optimizer = "1";
205
  update_option( 'wpstg_settings', $this->settings );
206
  }
207
 
apps/Backend/Modules/Jobs/Multisite/Data.php CHANGED
@@ -1,980 +1,966 @@
1
- <?php
2
-
3
- namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
-
5
- // No Direct Access
6
- if( !defined( "WPINC" ) ) {
7
- die;
8
- }
9
-
10
- use WPStaging\Utils\Logger;
11
- use WPStaging\WPStaging;
12
- use WPStaging\Utils\Helper;
13
- use WPStaging\Utils\Multisite;
14
- use WPStaging\Utils\Strings;
15
- use WPStaging\Backend\Modules\Jobs\JobExecutable;
16
-
17
- /**
18
- * Class Data
19
- * @package WPStaging\Backend\Modules\Jobs
20
- */
21
- class Data extends JobExecutable {
22
-
23
- /**
24
- * @var \wpdb
25
- */
26
- private $db;
27
-
28
- /**
29
- * @var string
30
- */
31
- private $prefix;
32
-
33
- /**
34
- * Tables e.g wpstg3_options
35
- * @var array
36
- */
37
- private $tables;
38
-
39
- /**
40
- * Initialize
41
- */
42
- public function initialize() {
43
- $this->db = WPStaging::getInstance()->get( "wpdb" );
44
-
45
- $this->prefix = $this->options->prefix;
46
-
47
- $this->getTables();
48
-
49
- // Fix current step
50
- if( 0 == $this->options->currentStep ) {
51
- $this->options->currentStep = 1;
52
- }
53
- }
54
-
55
- /**
56
- * Get a list of tables to copy
57
- */
58
- private function getTables() {
59
- $strings = new Strings();
60
- $this->tables = array();
61
- foreach ( $this->options->tables as $table ) {
62
- $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->db->prefix, null, $table );
63
- }
64
- // Add extra global tables from main multisite (wpstgx_users and wpstgx_usermeta)
65
- $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->db->prefix, null, 'users' );
66
- $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->db->prefix, null, 'usermeta' );
67
- }
68
-
69
- /**
70
- * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
71
- * @return void
72
- */
73
- protected function calculateTotalSteps() {
74
- $this->options->totalSteps = 17;
75
- }
76
-
77
- /**
78
- * Start Module
79
- * @return object
80
- */
81
- public function start() {
82
- // Execute steps
83
- $this->run();
84
-
85
- // Save option, progress
86
- $this->saveOptions();
87
-
88
- return ( object ) $this->response;
89
- }
90
-
91
- /**
92
- * Execute the Current Step
93
- * Returns false when over threshold limits are hit or when the job is done, true otherwise
94
- * @return bool
95
- */
96
- protected function execute() {
97
- // Fatal error. Let this happen never and break here immediately
98
- if( $this->isRoot() ) {
99
- return false;
100
- }
101
-
102
- // Over limits threshold
103
- if( $this->isOverThreshold() ) {
104
- // Prepare response and save current progress
105
- $this->prepareResponse( false, false );
106
- $this->saveOptions();
107
- return false;
108
- }
109
-
110
- // No more steps, finished
111
- if( $this->isFinished() ) {
112
- $this->prepareResponse( true, false );
113
- return false;
114
- }
115
-
116
- // Execute step
117
- $stepMethodName = "step" . $this->options->currentStep;
118
- if( !$this->{$stepMethodName}() ) {
119
- $this->prepareResponse( false, false );
120
- return false;
121
- }
122
-
123
- // Prepare Response
124
- $this->prepareResponse();
125
-
126
- // Not finished
127
- return true;
128
- }
129
-
130
- /**
131
- * Checks Whether There is Any Job to Execute or Not
132
- * @return bool
133
- */
134
- protected function isFinished() {
135
- return (
136
- $this->options->currentStep > $this->options->totalSteps ||
137
- !method_exists( $this, "step" . $this->options->currentStep )
138
- );
139
- }
140
-
141
- /**
142
- * Check if current operation is done on the root folder or on the live DB
143
- * @return boolean
144
- */
145
- protected function isRoot() {
146
-
147
- // Prefix is the same as the one of live site
148
- $wpdb = WPStaging::getInstance()->get( "wpdb" );
149
- if( $wpdb->prefix === $this->prefix ) {
150
- return true;
151
- }
152
-
153
- // CloneName is empty
154
- $name = ( array ) $this->options->cloneDirectoryName;
155
- if( empty( $name ) ) {
156
- return true;
157
- }
158
-
159
- // Live Path === Staging path
160
- if( $this->multisiteHomeDomain . $this->options->cloneDirectoryName === $this->multisiteHomeDomain ) {
161
- return true;
162
- }
163
-
164
- return false;
165
- }
166
-
167
- /**
168
- * Check if table exists
169
- * @param string $table
170
- * @return boolean
171
- */
172
- protected function isTable( $table ) {
173
- if( $this->db->get_var( "SHOW TABLES LIKE '{$table}'" ) != $table ) {
174
- $this->log( "Table {$table} does not exists", Logger::TYPE_ERROR );
175
- return false;
176
- }
177
- return true;
178
- }
179
-
180
- /**
181
- * Get the install sub directory if WP is installed in sub directory
182
- * @return string
183
- */
184
- protected function getSubDir() {
185
- return '/';
186
-
187
- // $home = get_option( 'home' );
188
- // $siteurl = get_option( 'siteurl' );
189
- //
190
- // if( empty( $home ) || empty( $siteurl ) ) {
191
- // return '/';
192
- // }
193
- //
194
- // $dir = str_replace( $home, '', $siteurl );
195
- // return '/' . str_replace( '/', '', $dir ) . '/';
196
- }
197
-
198
- /**
199
- * Copy wp-config.php if it is located outside of root one level up
200
- * @todo Needs some more testing before it will be released
201
- * @return boolean
202
- */
203
- // protected function step0(){
204
- // $this->log( "Preparing Data Step0: Check if wp-config.php is located in root path", Logger::TYPE_INFO );
205
- //
206
- // $dir = trailingslashit( dirname( ABSPATH ) );
207
- //
208
- // $source = $dir . 'wp-config.php';
209
- //
210
- // $destination = trailingslashit(ABSPATH) . $this->clone->cloneDirectoryName . DIRECTORY_SEPARATOR . 'wp-config.php';
211
- //
212
- //
213
- // // Do not do anything
214
- // if( (!is_file( $source ) && !is_link($source)) || is_file($destination) ) {
215
- // $this->log( "Preparing Data Step0: Skip it", Logger::TYPE_INFO );
216
- // return true;
217
- // }
218
- //
219
- // // Copy target of a symbolic link
220
- // if (is_link($source)){
221
- // $this->log( "Preparing Data Step0: Symbolic link found...", Logger::TYPE_INFO );
222
- // if (!@copy(readlink($source), $destination)) {
223
- // $this->log("Preparing Data Step0: Failed to copy wp-config.php {$source} -> {$destination}", Logger::TYPE_ERROR);
224
- // return true;
225
- // }
226
- // }
227
- //
228
- // // regular copy of wp-config.php
229
- // if (!@copy($source, $destination)) {
230
- // $this->log("Preparing Data Step0: Failed to copy wp-config.php {$source} -> {$destination}", Logger::TYPE_ERROR);
231
- // return true;
232
- // }
233
- // $this->log( "Preparing Data Step0: Successfull", Logger::TYPE_INFO );
234
- // return true;
235
- //
236
- // }
237
-
238
- /**
239
- * Replace "siteurl" and "home"
240
- * @return bool
241
- */
242
- protected function step1() {
243
- $this->log( "Preparing Data Step1: Updating siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_INFO );
244
-
245
- // Skip - Table does not exist
246
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
247
- return true;
248
- }
249
- // Skip - Table is not selected or updated
250
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
251
- $this->log( "Preparing Data Step1: Skipping" );
252
- return true;
253
- }
254
-
255
- // Installed in sub-directory
256
- if( $this->isSubDir() ) {
257
- $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . rtrim( $this->multisiteHomeDomain, "/" ) . $this->getSubDir() . $this->options->cloneDirectoryName );
258
- // Replace URLs
259
- $result = $this->db->query(
260
- $this->db->prepare(
261
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", rtrim( $this->multisiteHomeDomain, "/" ) . $this->getSubDir() . $this->options->cloneDirectoryName
262
- )
263
- );
264
- } else {
265
- $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . rtrim( $this->multisiteHomeDomain, "/" ) . '/' . $this->options->cloneDirectoryName );
266
- // Replace URLs
267
- $result = $this->db->query(
268
- $this->db->prepare(
269
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", $this->multisiteHomeDomain . '/' . $this->options->cloneDirectoryName
270
- )
271
- );
272
- }
273
-
274
-
275
- // All good
276
- if( $result ) {
277
- return true;
278
- }
279
-
280
- $this->log( "Preparing Data Step1: Failed to update siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
281
- return false;
282
- }
283
-
284
- /**
285
- * Update "wpstg_is_staging_site"
286
- * @return bool
287
- */
288
- protected function step2() {
289
-
290
- $this->log( "Preparing Data Step2: Updating row wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}" );
291
-
292
- // Skip - Table does not exist
293
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
294
- return true;
295
- }
296
- // Skip - Table is not selected or updated
297
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
298
- $this->log( "Preparing Data Step2: Skipping" );
299
- return true;
300
- }
301
-
302
- $result = $this->db->query(
303
- $this->db->prepare(
304
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_is_staging_site'", "true"
305
- )
306
- );
307
-
308
- // No errors but no option name such as wpstg_is_staging_site
309
- if( '' === $this->db->last_error && 0 == $result ) {
310
- $result = $this->db->query(
311
- $this->db->prepare(
312
- "INSERT INTO {$this->prefix}options (option_name,option_value) VALUES ('wpstg_is_staging_site',%s)", "true"
313
- )
314
- );
315
- }
316
-
317
- // All good
318
- if( $result ) {
319
- return true;
320
- }
321
-
322
- $this->log( "Preparing Data Step2: Failed to update wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
323
- return false;
324
- }
325
-
326
- /**
327
- * Update rewrite_rules
328
- * @return bool
329
- */
330
- protected function step3() {
331
-
332
- $this->log( "Preparing Data Step3: Updating rewrite_rules in {$this->prefix}options {$this->db->last_error}" );
333
-
334
- // Skip - Table does not exist
335
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
336
- return true;
337
- }
338
-
339
- // Skip - Table is not selected or updated
340
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
341
- $this->log( "Preparing Data Step3: Skipping" );
342
- return true;
343
- }
344
-
345
- $result = $this->db->query(
346
- $this->db->prepare(
347
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'rewrite_rules'", ' '
348
- )
349
- );
350
-
351
- // All good
352
- if( $result ) {
353
- return true;
354
- }
355
-
356
- $this->log( "Preparing Data Step3: Failed to update rewrite_rules in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
357
- return true;
358
- }
359
-
360
- /**
361
- * Update Table Prefix in wp_usermeta and wp_options
362
- * @return bool
363
- */
364
- protected function step4() {
365
- $this->log( "Preparing Data Step4: Updating db prefix in {$this->prefix}usermeta. {$this->db->last_error}" );
366
-
367
- // Skip - Table does not exist
368
- if( false === $this->isTable( $this->prefix . 'usermeta' ) ) {
369
- return true;
370
- }
371
-
372
- // Skip - Table is not selected or updated
373
- if( !in_array( $this->prefix . 'usermeta', $this->tables ) ) {
374
- $this->log( "Preparing Data Step4: Skipping" );
375
- return true;
376
- }
377
-
378
- $update = $this->db->query(
379
- $this->db->prepare(
380
- "UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, %s, %s) WHERE meta_key LIKE %s", $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
381
- )
382
- );
383
-
384
- if( !$update ) {
385
- $this->log( "Preparing Data Step4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}", Logger::TYPE_ERROR );
386
- $this->returnException( "Data Crunching Step 4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}" );
387
- return false;
388
- }
389
- return true;
390
- }
391
-
392
- /**
393
- * Update $table_prefix in wp-config.php
394
- * @return bool
395
- */
396
- protected function step5() {
397
- $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
398
-
399
- $this->log( "Preparing Data Step5: Updating table_prefix in {$path} to " . $this->prefix );
400
- if( false === ($content = file_get_contents( $path )) ) {
401
- $this->log( "Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR );
402
- return false;
403
- }
404
-
405
- // Replace table prefix
406
- $content = str_replace( '$table_prefix', '$table_prefix = \'' . $this->prefix . '\';//', $content );
407
-
408
- // Replace URLs
409
- $content = str_replace( $this->multisiteHomeDomain, $this->multisiteHomeDomain . '/' . $this->options->cloneDirectoryName, $content );
410
-
411
- if( false === @file_put_contents( $path, $content ) ) {
412
- $this->log( "Preparing Data Step5: Failed to update $table_prefix in {$path} to " . $this->prefix . ". Can't save contents", Logger::TYPE_ERROR );
413
- return false;
414
- }
415
-
416
- return true;
417
- }
418
-
419
- /**
420
- * Reset index.php to original file
421
- * This is needed if live site is located in subfolder
422
- * Check first if main wordpress is used in subfolder and index.php in parent directory
423
- * @see: https://codex.wordpress.org/Giving_WordPress_Its_Own_Directory
424
- * @return bool
425
- */
426
- protected function step6() {
427
-
428
- if( !$this->isSubDir() ) {
429
- $this->debugLog( "Preparing Data Step6: WP installation is not in a subdirectory! All good, skipping this step" );
430
- return true;
431
- }
432
-
433
- $path = ABSPATH . $this->options->cloneDirectoryName . "/index.php";
434
-
435
- if( false === ($content = file_get_contents( $path )) ) {
436
- $this->log( "Preparing Data Step6: Failed to reset {$path} for sub directory; can't read contents", Logger::TYPE_ERROR );
437
- return false;
438
- }
439
-
440
-
441
- if( !preg_match( "/(require(.*)wp-blog-header.php' \);)/", $content, $matches ) ) {
442
- $this->log(
443
- "Preparing Data Step6: Failed to reset index.php for sub directory; wp-blog-header.php is missing", Logger::TYPE_ERROR
444
- );
445
- return false;
446
- }
447
- $this->log( "Preparing Data: WP installation is in a subdirectory. Progressing..." );
448
-
449
- $pattern = "/require(.*) dirname(.*) __FILE__ (.*) \. '(.*)wp-blog-header.php'(.*);/";
450
-
451
- $replace = "require( dirname( __FILE__ ) . '/wp-blog-header.php' ); // " . $matches[0];
452
- $replace.= " // Changed by WP-Staging";
453
-
454
-
455
-
456
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
457
- $this->log( "Preparing Data: Failed to reset index.php for sub directory; replacement failed", Logger::TYPE_ERROR );
458
- return false;
459
- }
460
-
461
- if( false === @file_put_contents( $path, $content ) ) {
462
- $this->log( "Preparing Data: Failed to reset index.php for sub directory; can't save contents", Logger::TYPE_ERROR );
463
- return false;
464
- }
465
- $this->Log( "Preparing Data: Finished Step 6 successfully" );
466
- return true;
467
- }
468
-
469
- /**
470
- * Update wpstg_rmpermalinks_executed
471
- * @return bool
472
- */
473
- protected function step7() {
474
-
475
- $this->log( "Preparing Data Step7: Updating wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}" );
476
-
477
- // Skip - Table does not exist
478
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
479
- return true;
480
- }
481
-
482
- // Skip - Table is not selected or updated
483
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
484
- $this->log( "Preparing Data Step7: Skipping" );
485
- return true;
486
- }
487
-
488
- $result = $this->db->query(
489
- $this->db->prepare(
490
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_rmpermalinks_executed'", ' '
491
- )
492
- );
493
-
494
- // All good
495
- if( $result ) {
496
- $this->Log( "Preparing Data Step7: Finished Step 7 successfully" );
497
- return true;
498
- }
499
-
500
- $this->log( "Failed to update wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_WARNING );
501
- return true;
502
- }
503
-
504
- /**
505
- * Update permalink_structure
506
- * @return bool
507
- */
508
- protected function step8() {
509
-
510
- $this->log( "Preparing Data Step8: Updating permalink_structure in {$this->prefix}options {$this->db->last_error}" );
511
-
512
- // Skip - Table does not exist
513
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
514
- return true;
515
- }
516
-
517
- // Skip - Table is not selected or updated
518
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
519
- $this->log( "Preparing Data Step8: Skipping" );
520
- return true;
521
- }
522
-
523
- $result = $this->db->query(
524
- $this->db->prepare(
525
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'permalink_structure'", ' '
526
- )
527
- );
528
-
529
- // All good
530
- if( $result ) {
531
- $this->Log( "Preparing Data Step8: Finished Step 8 successfully" );
532
- return true;
533
- }
534
-
535
- $this->log( "Failed to update permalink_structure in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
536
- return true;
537
- }
538
-
539
- /**
540
- * Update blog_public option to not allow staging site to be indexed by search engines
541
- * @return bool
542
- */
543
- protected function step9() {
544
-
545
- $this->log( "Preparing Data Step9: Set staging site to noindex" );
546
-
547
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
548
- return true;
549
- }
550
-
551
- // Skip - Table is not selected or updated
552
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
553
- $this->log( "Preparing Data Step9: Skipping" );
554
- return true;
555
- }
556
-
557
- $result = $this->db->query(
558
- $this->db->prepare(
559
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'blog_public'", '0'
560
- )
561
- );
562
-
563
- // All good
564
- if( $result ) {
565
- $this->Log( "Preparing Data Step9: Finished Step 9 successfully" );
566
- return true;
567
- }
568
-
569
- $this->log( "Can not update staging site to noindex. Possible already done!", Logger::TYPE_WARNING );
570
- return true;
571
- }
572
-
573
- /**
574
- * Update WP_HOME in wp-config.php
575
- * @return bool
576
- */
577
- protected function step10() {
578
- $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
579
-
580
- $this->log( "Preparing Data Step10: Updating WP_HOME in wp-config.php to " . $this->getStagingSiteUrl() );
581
-
582
- if( false === ($content = file_get_contents( $path )) ) {
583
- $this->log( "Preparing Data Step10: Failed to update WP_HOME in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
584
- return false;
585
- }
586
-
587
-
588
- // Get WP_HOME from wp-config.php
589
- preg_match( "/define\s*\(\s*'WP_HOME'\s*,\s*(.*)\s*\);/", $content, $matches );
590
-
591
- if( !empty( $matches[1] ) ) {
592
- $matches[1];
593
-
594
- $pattern = "/define\s*\(\s*'WP_HOME'\s*,\s*(.*)\s*\);/";
595
-
596
- $replace = "define('WP_HOME','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
597
- $replace.= " // Changed by WP-Staging";
598
-
599
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
600
- $this->log( "Preparing Data: Failed to reset index.php for sub directory; replacement failed", Logger::TYPE_ERROR );
601
- return false;
602
- }
603
- } else {
604
- $this->log( "Preparing Data Step10: WP_HOME not defined in wp-config.php. Skipping this step." );
605
- }
606
-
607
- if( false === @file_put_contents( $path, $content ) ) {
608
- $this->log( "Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR );
609
- return false;
610
- }
611
- $this->Log( "Preparing Data: Finished Step 11 successfully" );
612
- return true;
613
- }
614
-
615
- /**
616
- * Update WP_SITEURL in wp-config.php
617
- * @return bool
618
- */
619
- protected function step11() {
620
- $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
621
-
622
- $this->log( "Preparing Data Step11: Updating WP_SITEURL in wp-config.php to " . $this->getStagingSiteUrl() );
623
-
624
- if( false === ($content = file_get_contents( $path )) ) {
625
- $this->log( "Preparing Data Step11: Failed to update WP_SITEURL in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
626
- return false;
627
- }
628
-
629
-
630
- // Get WP_SITEURL from wp-config.php
631
- preg_match( "/define\s*\(\s*'WP_SITEURL'\s*,\s*(.*)\s*\);/", $content, $matches );
632
-
633
- if( !empty( $matches[1] ) ) {
634
- $matches[1];
635
-
636
- $pattern = "/define\s*\(\s*'WP_SITEURL'\s*,\s*(.*)\s*\);/";
637
-
638
- $replace = "define('WP_SITEURL','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
639
- $replace.= " // Changed by WP-Staging";
640
-
641
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
642
- $this->log( "Preparing Data Step11: Failed to update WP_SITEURL", Logger::TYPE_ERROR );
643
- return false;
644
- }
645
- } else {
646
- $this->log( "Preparing Data Step11: WP_SITEURL not defined in wp-config.php. Skipping this step." );
647
- }
648
-
649
-
650
- if( false === @file_put_contents( $path, $content ) ) {
651
- $this->log( "Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR );
652
- return false;
653
- }
654
- $this->Log( "Preparing Data: Finished Step 11 successfully" );
655
- return true;
656
- }
657
-
658
- /**
659
- * Update WP_ALLOW_MULTISITE constant in wp-config.php
660
- * @return bool
661
- */
662
- protected function step12() {
663
- $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
664
-
665
- $this->log( "Preparing Data Step12: Updating WP_ALLOW_MULTISITE in wp-config.php to false" );
666
-
667
- if( false === ($content = file_get_contents( $path )) ) {
668
- $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
669
- return false;
670
- }
671
-
672
-
673
- // Get WP_SITEURL from wp-config.php
674
- preg_match( "/define\s*\(\s*'WP_ALLOW_MULTISITE'\s*,\s*(.*)\s*\);/", $content, $matches );
675
-
676
- if( !empty( $matches[1] ) ) {
677
- $matches[1];
678
-
679
- $pattern = "/define\s*\(\s*'WP_ALLOW_MULTISITE'\s*,\s*(.*)\s*\);/";
680
-
681
- $replace = "define('WP_ALLOW_MULTISITE',false); // " . $matches[1];
682
- $replace.= " // Changed by WP-Staging";
683
-
684
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
685
- $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE", Logger::TYPE_ERROR );
686
- return false;
687
- }
688
- } else {
689
- $this->log( "Preparing Data Step12: WP_ALLOW_MULTISITE not defined in wp-config.php. Skipping this step." );
690
- }
691
-
692
-
693
- if( false === @file_put_contents( $path, $content ) ) {
694
- $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE. Can't save contents", Logger::TYPE_ERROR );
695
- return false;
696
- }
697
- $this->Log( "Preparing Data: Finished Step 12 successfully" );
698
- return true;
699
- }
700
-
701
- /**
702
- * Update MULTISITE constant in wp-config.php
703
- * @return bool
704
- */
705
- protected function step13() {
706
- $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
707
-
708
- $this->log( "Preparing Data Step13: Updating MULTISITE in wp-config.php to false" );
709
-
710
- if( false === ($content = file_get_contents( $path )) ) {
711
- $this->log( "Preparing Data Step13: Failed to update MULTISITE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
712
- return false;
713
- }
714
-
715
-
716
- // Get WP_SITEURL from wp-config.php
717
- preg_match( "/define\s*\(\s*'MULTISITE'\s*,\s*(.*)\s*\);/", $content, $matches );
718
-
719
- if( !empty( $matches[1] ) ) {
720
- $matches[1];
721
-
722
- $pattern = "/define\s*\(\s*'MULTISITE'\s*,\s*(.*)\s*\);/";
723
-
724
- $replace = "define('MULTISITE',false); // " . $matches[1];
725
- $replace.= " // Changed by WP-Staging";
726
-
727
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
728
- $this->log( "Preparing Data Step13: Failed to update MULTISITE", Logger::TYPE_ERROR );
729
- return false;
730
- }
731
- } else {
732
- $this->log( "Preparing Data Step13: MULTISITE not defined in wp-config.php. Skipping this step." );
733
- }
734
-
735
-
736
- if( false === @file_put_contents( $path, $content ) ) {
737
- $this->log( "Preparing Data Step13: Failed to update MULTISITE. Can't save contents", Logger::TYPE_ERROR );
738
- return false;
739
- }
740
- $this->Log( "Preparing Data: Finished Step 13 successfully" );
741
- return true;
742
- }
743
-
744
- /**
745
- * Get active_sitewide_plugins from wp_sitemeta and active_plugins from subsite
746
- * Merge both arrays and copy them to the staging site into active_plugins
747
- */
748
- protected function step14() {
749
-
750
-
751
- $this->log( "Data Crunching Step 14: Updating active_plugins" );
752
-
753
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
754
- $this->log( 'Data Crunching Step 14: Fatal Error ' . $this->prefix . 'options does not exist' );
755
- $this->returnException( 'Data Crunching Step 8: Fatal Error ' . $this->prefix . 'options does not exist' );
756
- return false;
757
- }
758
-
759
- // Skip - Table is not selected or updated
760
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
761
- $this->log( "Preparing Data Step14: Skipping" );
762
- return true;
763
- }
764
-
765
- // Get active_plugins value from sub site options table
766
- $active_plugins = $this->db->get_var( "SELECT option_value FROM {$this->db->prefix}options WHERE option_name = 'active_plugins' " );
767
-
768
- if( !$active_plugins ) {
769
- $this->log( "Data Crunching Step 14: Option active_plugins are empty " );
770
- $active_plugins = array();
771
- }
772
- // Get active_sitewide_plugins value from main multisite wp_sitemeta table
773
- $active_sitewide_plugins = $this->db->get_var( "SELECT meta_value FROM {$this->db->base_prefix}sitemeta WHERE meta_key = 'active_sitewide_plugins' " );
774
-
775
- if( !$active_sitewide_plugins ) {
776
- $this->log( "Data Crunching Step 14: Options {$this->db->base_prefix}active_sitewide_plugins is empty " );
777
- $active_sitewide_plugins = array();
778
- }
779
-
780
- $active_sitewide_plugins = unserialize( $active_sitewide_plugins );
781
- $active_plugins = unserialize( $active_plugins );
782
-
783
- $all_plugins = array_merge( $active_plugins, array_keys( $active_sitewide_plugins ) );
784
-
785
- sort( $all_plugins );
786
-
787
-
788
- // Update active_plugins
789
- $update = $this->db->query(
790
- "UPDATE {$this->prefix}options SET option_value = '" . serialize( $all_plugins ) . "' WHERE option_name = 'active_plugins'"
791
- );
792
-
793
- if( false === $update ) {
794
- $this->log( "Data Crunching Step 14: Can not update option active_plugins in {$this->prefix}options", Logger::TYPE_WARNING );
795
- //$this->returnException( "Data Crunching Step 8: Can not update row wpstg_version in {$this->tmpPrefix}options - db error: " . $this->db->last_error );
796
- return false;
797
- }
798
-
799
- $this->log( "Data Crunching Step 14: Successfull!" );
800
- return true;
801
- }
802
-
803
- /**
804
- * Update Table Prefix in wp_options
805
- * @return bool
806
- */
807
- protected function step15() {
808
- $this->log( "Preparing Data Step15: Updating db prefix in {$this->prefix}options." );
809
-
810
- // Skip - Table does not exist
811
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
812
- return true;
813
- }
814
-
815
- // Skip - Table is not selected or updated
816
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
817
- $this->log( "Preparing Data Step4: Skipping" );
818
- return true;
819
- }
820
-
821
-
822
- $this->log( "Updating db option_names in {$this->prefix}options. " );
823
-
824
- // Filter the rows below. Do not update them!
825
- $filters = array(
826
- 'wp_mail_smtp',
827
- 'wp_mail_smtp_version',
828
- 'wp_mail_smtp_debug',
829
- );
830
-
831
- $filters = apply_filters( 'wpstg_data_excl_rows', $filters );
832
-
833
- $where = "";
834
- foreach ( $filters as $filter ) {
835
- $where .= " AND option_name <> '" . $filter . "'";
836
- }
837
-
838
- $updateOptions = $this->db->query(
839
- $this->db->prepare(
840
- "UPDATE IGNORE {$this->prefix}options SET option_name= replace(option_name, %s, %s) WHERE option_name LIKE %s" . $where, $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
841
- )
842
- );
843
-
844
- if( !$updateOptions ) {
845
- $this->log( "Preparing Data Step15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_ERROR );
846
- $this->returnException( "Data Crunching Step 15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
847
- return false;
848
- }
849
-
850
- return true;
851
- }
852
-
853
- /**
854
- * Change upload_path in wp_options (if it is defined)
855
- * @return bool
856
- */
857
- protected function step16() {
858
- $this->log( "Preparing Data Step16: Updating upload_path {$this->prefix}options." );
859
-
860
- // Skip - Table does not exist
861
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
862
- return true;
863
- }
864
-
865
- $newUploadPath = $this->getNewUploadPath();
866
-
867
- if( false === $newUploadPath ) {
868
- $this->log( "Preparing Data Step16: Skipping" );
869
- return true;
870
- }
871
-
872
- // Skip - Table is not selected or updated
873
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
874
- $this->log( "Preparing Data Step16: Skipping" );
875
- return true;
876
- }
877
-
878
- $error = isset( $this->db->last_error ) ? 'Last error: ' . $this->db->last_error : '';
879
-
880
- $this->log( "Updating upload_path in {$this->prefix}options. {$error}" );
881
-
882
- $updateOptions = $this->db->query(
883
- $this->db->prepare(
884
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'upload_path'", $newUploadPath
885
- )
886
- );
887
-
888
- if( !$updateOptions ) {
889
- $this->log( "Preparing Data Step16: Failed to update upload_path in {$this->prefix}options. {$error}", Logger::TYPE_ERROR );
890
- return true;
891
- }
892
- $this->Log( "Preparing Data: Finished Step 16 successfully" );
893
- return true;
894
- }
895
-
896
- /**
897
- * Update WP_CACHE in wp-config.php
898
- * @return bool
899
- */
900
- protected function step17() {
901
- $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
902
-
903
- $this->log( "Preparing Data Step17: Set WP_CACHE in wp-config.php to false");
904
-
905
- if( false === ($content = file_get_contents( $path )) ) {
906
- $this->log( "Preparing Data Step17: Failed to update WP_CACHE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
907
- return false;
908
- }
909
-
910
-
911
- // Get WP_CACHE from wp-config.php
912
- preg_match( "/define\s*\(\s*'WP_CACHE'\s*,\s*(.*)\s*\);/", $content, $matches );
913
-
914
- if( !empty( $matches[1] ) ) {
915
- $matches[1];
916
-
917
- $pattern = "/define\s*\(\s*'WP_CACHE'\s*,\s*(.*)\s*\);/";
918
-
919
- $replace = "define('WP_CACHE',false); // " . $matches[1];
920
- $replace.= " // Changed by WP-Staging";
921
-
922
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
923
- $this->log( "Preparing Data: Failed to change WP_CACHE", Logger::TYPE_ERROR );
924
- return false;
925
- }
926
- } else {
927
- $this->log( "Preparing Data Step17: WP_CACHE not defined in wp-config.php. Skipping this step." );
928
- }
929
-
930
- if( false === @file_put_contents( $path, $content ) ) {
931
- $this->log( "Preparing Data Step17: Failed to update WP_CACHE. Can't save contents", Logger::TYPE_ERROR );
932
- return false;
933
- }
934
- $this->Log( "Preparing Data: Finished Step 17 successfully" );
935
- return true;
936
- }
937
-
938
- protected function getNewUploadPath() {
939
- $uploadPath = get_option( 'upload_path' );
940
-
941
- if( !$uploadPath ) {
942
- return false;
943
- }
944
-
945
- $customSlug = str_replace( \WPStaging\WPStaging::getWPpath(), '', $uploadPath );
946
-
947
- $newUploadPath = \WPStaging\WPStaging::getWPpath() . $this->options->cloneDirectoryName . DIRECTORY_SEPARATOR . $customSlug;
948
-
949
- return $newUploadPath;
950
- }
951
-
952
- /**
953
- * Return URL to staging site
954
- * @return string
955
- */
956
- protected function getStagingSiteUrl() {
957
- if( $this->isSubDir() ) {
958
- return rtrim( $this->multisiteHomeDomain, "/" ) . $this->getSubDir() . $this->options->cloneDirectoryName;
959
- }
960
-
961
- return rtrim( $this->multisiteHomeDomain, "/" ) . '/' . $this->options->cloneDirectoryName;
962
- }
963
-
964
- /**
965
- * Check if WP is installed in subdir
966
- * @return boolean
967
- */
968
- protected function isSubDir() {
969
- // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
970
- // This is happening much more often than you would expect
971
- $siteurl = preg_replace( '#^https?://#', '', rtrim( get_option( 'siteurl' ), '/' ) );
972
- $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
973
-
974
- if( $home !== $siteurl ) {
975
- return true;
976
- }
977
- return false;
978
- }
979
-
980
- }
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
+
5
+ // No Direct Access
6
+ if( !defined( "WPINC" ) ) {
7
+ die;
8
+ }
9
+
10
+ use WPStaging\Utils\Logger;
11
+ use WPStaging\WPStaging;
12
+ use WPStaging\Utils\Helper;
13
+ use WPStaging\Utils\Multisite;
14
+ use WPStaging\Utils\Strings;
15
+ use WPStaging\Backend\Modules\Jobs\JobExecutable;
16
+
17
+ /**
18
+ * Class Data
19
+ * @package WPStaging\Backend\Modules\Jobs
20
+ */
21
+ class Data extends JobExecutable {
22
+
23
+ /**
24
+ * @var \wpdb
25
+ */
26
+ private $db;
27
+
28
+ /**
29
+ * @var string
30
+ */
31
+ private $prefix;
32
+
33
+ /**
34
+ * Tables e.g wpstg3_options
35
+ * @var array
36
+ */
37
+ private $tables;
38
+
39
+ /**
40
+ * Initialize
41
+ */
42
+ public function initialize() {
43
+ $this->db = WPStaging::getInstance()->get( "wpdb" );
44
+
45
+ $this->prefix = $this->options->prefix;
46
+
47
+ $this->getTables();
48
+
49
+ // Fix current step
50
+ if( 0 == $this->options->currentStep ) {
51
+ $this->options->currentStep = 0;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Get a list of tables to copy
57
+ */
58
+ private function getTables() {
59
+ $strings = new Strings();
60
+ $this->tables = array();
61
+ foreach ( $this->options->tables as $table ) {
62
+ $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->db->prefix, null, $table );
63
+ }
64
+ // Add extra global tables from main multisite (wpstgx_users and wpstgx_usermeta)
65
+ $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->db->prefix, null, 'users' );
66
+ $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->db->prefix, null, 'usermeta' );
67
+ }
68
+
69
+ /**
70
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
71
+ * @return void
72
+ */
73
+ protected function calculateTotalSteps() {
74
+ $this->options->totalSteps = 18;
75
+ }
76
+
77
+ /**
78
+ * Start Module
79
+ * @return object
80
+ */
81
+ public function start() {
82
+ // Execute steps
83
+ $this->run();
84
+
85
+ // Save option, progress
86
+ $this->saveOptions();
87
+
88
+ return ( object ) $this->response;
89
+ }
90
+
91
+ /**
92
+ * Execute the Current Step
93
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
94
+ * @return bool
95
+ */
96
+ protected function execute() {
97
+ // Fatal error. Let this happen never and break here immediately
98
+ if( $this->isRoot() ) {
99
+ return false;
100
+ }
101
+
102
+ // Over limits threshold
103
+ if( $this->isOverThreshold() ) {
104
+ // Prepare response and save current progress
105
+ $this->prepareResponse( false, false );
106
+ $this->saveOptions();
107
+ return false;
108
+ }
109
+
110
+ // No more steps, finished
111
+ if( $this->isFinished() ) {
112
+ $this->prepareResponse( true, false );
113
+ return false;
114
+ }
115
+
116
+ // Execute step
117
+ $stepMethodName = "step" . $this->options->currentStep;
118
+ if( !$this->{$stepMethodName}() ) {
119
+ $this->prepareResponse( false, false );
120
+ return false;
121
+ }
122
+
123
+ // Prepare Response
124
+ $this->prepareResponse();
125
+
126
+ // Not finished
127
+ return true;
128
+ }
129
+
130
+ /**
131
+ * Checks Whether There is Any Job to Execute or Not
132
+ * @return bool
133
+ */
134
+ protected function isFinished() {
135
+ return (
136
+ $this->options->currentStep > $this->options->totalSteps ||
137
+ !method_exists( $this, "step" . $this->options->currentStep )
138
+ );
139
+ }
140
+
141
+ /**
142
+ * Check if current operation is done on the root folder or on the live DB
143
+ * @return boolean
144
+ */
145
+ protected function isRoot() {
146
+
147
+ // Prefix is the same as the one of live site
148
+ $wpdb = WPStaging::getInstance()->get( "wpdb" );
149
+ if( $wpdb->prefix === $this->prefix ) {
150
+ return true;
151
+ }
152
+
153
+ // CloneName is empty
154
+ $name = ( array ) $this->options->cloneDirectoryName;
155
+ if( empty( $name ) ) {
156
+ return true;
157
+ }
158
+
159
+ // Live Path === Staging path
160
+ if( $this->multisiteHomeDomain . $this->options->cloneDirectoryName === $this->multisiteHomeDomain ) {
161
+ return true;
162
+ }
163
+
164
+ return false;
165
+ }
166
+
167
+ /**
168
+ * Check if table exists
169
+ * @param string $table
170
+ * @return boolean
171
+ */
172
+ protected function isTable( $table ) {
173
+ if( $this->db->get_var( "SHOW TABLES LIKE '{$table}'" ) != $table ) {
174
+ $this->log( "Table {$table} does not exists", Logger::TYPE_ERROR );
175
+ return false;
176
+ }
177
+ return true;
178
+ }
179
+
180
+ /**
181
+ * Copy wp-config.php if it is located outside of root one level up
182
+ * @todo Needs some more testing before it will be released
183
+ * @return boolean
184
+ */
185
+ protected function step0() {
186
+ $this->log( "Preparing Data Step0: Check if wp-config.php is located in root path", Logger::TYPE_INFO );
187
+
188
+ $dir = trailingslashit( dirname( ABSPATH ) );
189
+
190
+ $source = $dir . 'wp-config.php';
191
+
192
+ $destination = trailingslashit( ABSPATH ) . $this->options->cloneDirectoryName . DIRECTORY_SEPARATOR . 'wp-config.php';
193
+
194
+
195
+ // Do not do anything
196
+ if( (!is_file( $source ) && !is_link( $source )) || is_file( $destination ) ) {
197
+ $this->log( "Preparing Data Step0: Skip it", Logger::TYPE_INFO );
198
+ return true;
199
+ }
200
+
201
+ // Copy target of a symbolic link
202
+ if( is_link( $source ) ) {
203
+ $this->log( "Preparing Data Step0: Symbolic link found...", Logger::TYPE_INFO );
204
+ if( !@copy( readlink( $source ), $destination ) ) {
205
+ $errors = error_get_last();
206
+ $this->log( "Preparing Data Step0: Failed to copy wp-config.php! Error: {$errors['message']} {$source} -> {$destination}", Logger::TYPE_ERROR );
207
+ return true;
208
+ }
209
+ }
210
+
211
+ // Copy file wp-config.php
212
+ if( !@copy( $source, $destination ) ) {
213
+ $errors = error_get_last();
214
+ $this->log( "Preparing Data Step0: Failed to copy wp-config.php! Error: {$errors['message']} {$source} -> {$destination}", Logger::TYPE_ERROR );
215
+ return true;
216
+ }
217
+ $this->log( "Preparing Data Step0: Successfull", Logger::TYPE_INFO );
218
+ return true;
219
+ }
220
+
221
+ /**
222
+ * Replace "siteurl" and "home"
223
+ * @return bool
224
+ */
225
+ protected function step1() {
226
+ $this->log( "Preparing Data Step1: Updating siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_INFO );
227
+
228
+ // Skip - Table does not exist
229
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
230
+ return true;
231
+ }
232
+ // Skip - Table is not selected or updated
233
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
234
+ $this->log( "Preparing Data Step1: Skipping" );
235
+ return true;
236
+ }
237
+
238
+ // Installed in sub-directory
239
+ if( $this->isSubDir() ) {
240
+ $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . rtrim( $this->multisiteHomeDomain, "/" ) . '/' . $this->options->cloneDirectoryName );
241
+ // Replace URLs
242
+ $result = $this->db->query(
243
+ $this->db->prepare(
244
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", rtrim( $this->multisiteHomeDomain, "/" ) . '/' . $this->options->cloneDirectoryName
245
+ )
246
+ );
247
+ } else {
248
+ $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . rtrim( $this->multisiteHomeDomain, "/" ) . '/' . $this->options->cloneDirectoryName );
249
+ // Replace URLs
250
+ $result = $this->db->query(
251
+ $this->db->prepare(
252
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", $this->multisiteHomeDomain . '/' . $this->options->cloneDirectoryName
253
+ )
254
+ );
255
+ }
256
+
257
+
258
+ // All good
259
+ if( $result ) {
260
+ return true;
261
+ }
262
+
263
+ $this->log( "Preparing Data Step1: Failed to update siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
264
+ return false;
265
+ }
266
+
267
+ /**
268
+ * Update "wpstg_is_staging_site"
269
+ * @return bool
270
+ */
271
+ protected function step2() {
272
+
273
+ $this->log( "Preparing Data Step2: Updating row wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}" );
274
+
275
+ // Skip - Table does not exist
276
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
277
+ return true;
278
+ }
279
+ // Skip - Table is not selected or updated
280
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
281
+ $this->log( "Preparing Data Step2: Skipping" );
282
+ return true;
283
+ }
284
+
285
+ $result = $this->db->query(
286
+ $this->db->prepare(
287
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_is_staging_site'", "true"
288
+ )
289
+ );
290
+
291
+ // No errors but no option name such as wpstg_is_staging_site
292
+ if( '' === $this->db->last_error && 0 == $result ) {
293
+ $result = $this->db->query(
294
+ $this->db->prepare(
295
+ "INSERT INTO {$this->prefix}options (option_name,option_value) VALUES ('wpstg_is_staging_site',%s)", "true"
296
+ )
297
+ );
298
+ }
299
+
300
+ // All good
301
+ if( $result ) {
302
+ return true;
303
+ }
304
+
305
+ $this->log( "Preparing Data Step2: Failed to update wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
306
+ return false;
307
+ }
308
+
309
+ /**
310
+ * Update rewrite_rules
311
+ * @return bool
312
+ */
313
+ protected function step3() {
314
+
315
+ $this->log( "Preparing Data Step3: Updating rewrite_rules in {$this->prefix}options {$this->db->last_error}" );
316
+
317
+ // Skip - Table does not exist
318
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
319
+ return true;
320
+ }
321
+
322
+ // Skip - Table is not selected or updated
323
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
324
+ $this->log( "Preparing Data Step3: Skipping" );
325
+ return true;
326
+ }
327
+
328
+ $result = $this->db->query(
329
+ $this->db->prepare(
330
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'rewrite_rules'", ' '
331
+ )
332
+ );
333
+
334
+ // All good
335
+ if( $result ) {
336
+ return true;
337
+ }
338
+
339
+ $this->log( "Preparing Data Step3: Failed to update rewrite_rules in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
340
+ return true;
341
+ }
342
+
343
+ /**
344
+ * Update Table Prefix in wp_usermeta and wp_options
345
+ * @return bool
346
+ */
347
+ protected function step4() {
348
+ $this->log( "Preparing Data Step4: Updating db prefix in {$this->prefix}usermeta. {$this->db->last_error}" );
349
+
350
+ // Skip - Table does not exist
351
+ if( false === $this->isTable( $this->prefix . 'usermeta' ) ) {
352
+ return true;
353
+ }
354
+
355
+ // Skip - Table is not selected or updated
356
+ if( !in_array( $this->prefix . 'usermeta', $this->tables ) ) {
357
+ $this->log( "Preparing Data Step4: Skipping" );
358
+ return true;
359
+ }
360
+
361
+ $update = $this->db->query(
362
+ $this->db->prepare(
363
+ "UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, %s, %s) WHERE meta_key LIKE %s", $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
364
+ )
365
+ );
366
+
367
+ if( !$update ) {
368
+ $this->log( "Preparing Data Step4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}", Logger::TYPE_ERROR );
369
+ $this->returnException( "Data Crunching Step 4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}" );
370
+ return false;
371
+ }
372
+ return true;
373
+ }
374
+
375
+ /**
376
+ * Update $table_prefix in wp-config.php
377
+ * @return bool
378
+ */
379
+ protected function step5() {
380
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
381
+
382
+ $this->log( "Preparing Data Step5: Updating table_prefix in {$path} to " . $this->prefix );
383
+ if( false === ($content = file_get_contents( $path )) ) {
384
+ $this->log( "Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR );
385
+ return false;
386
+ }
387
+
388
+ // Replace table prefix
389
+ $content = str_replace( '$table_prefix', '$table_prefix = \'' . $this->prefix . '\';//', $content );
390
+
391
+ // Replace URLs
392
+ $content = str_replace( $this->multisiteHomeDomain, $this->multisiteHomeDomain . '/' . $this->options->cloneDirectoryName, $content );
393
+
394
+ if( false === @file_put_contents( $path, $content ) ) {
395
+ $this->log( "Preparing Data Step5: Failed to update $table_prefix in {$path} to " . $this->prefix . ". Can't save contents", Logger::TYPE_ERROR );
396
+ return false;
397
+ }
398
+
399
+ return true;
400
+ }
401
+
402
+ /**
403
+ * Reset index.php to original file
404
+ * This is needed if live site is located in subfolder
405
+ * Check first if main wordpress is used in subfolder and index.php in parent directory
406
+ * @see: https://codex.wordpress.org/Giving_WordPress_Its_Own_Directory
407
+ * @return bool
408
+ */
409
+ protected function step6() {
410
+
411
+ if( !$this->isSubDir() ) {
412
+ $this->debugLog( "Preparing Data Step6: WP installation is not in a subdirectory! All good, skipping this step" );
413
+ return true;
414
+ }
415
+
416
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/index.php";
417
+
418
+ if( false === ($content = file_get_contents( $path )) ) {
419
+ $this->log( "Preparing Data Step6: Failed to reset {$path} for sub directory; can't read contents", Logger::TYPE_ERROR );
420
+ return false;
421
+ }
422
+
423
+
424
+ if( !preg_match( "/(require(.*)wp-blog-header.php' \);)/", $content, $matches ) ) {
425
+ $this->log(
426
+ "Preparing Data Step6: Failed to reset index.php for sub directory; wp-blog-header.php is missing", Logger::TYPE_ERROR
427
+ );
428
+ return false;
429
+ }
430
+ $this->log( "Preparing Data: WP installation is in a subdirectory. Progressing..." );
431
+
432
+ $pattern = "/require(.*) dirname(.*) __FILE__ (.*) \. '(.*)wp-blog-header.php'(.*);/";
433
+
434
+ $replace = "require( dirname( __FILE__ ) . '/wp-blog-header.php' ); // " . $matches[0];
435
+ $replace.= " // Changed by WP-Staging";
436
+
437
+
438
+
439
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
440
+ $this->log( "Preparing Data: Failed to reset index.php for sub directory; replacement failed", Logger::TYPE_ERROR );
441
+ return false;
442
+ }
443
+
444
+ if( false === @file_put_contents( $path, $content ) ) {
445
+ $this->log( "Preparing Data: Failed to reset index.php for sub directory; can't save contents", Logger::TYPE_ERROR );
446
+ return false;
447
+ }
448
+ $this->Log( "Preparing Data: Finished Step 6 successfully" );
449
+ return true;
450
+ }
451
+
452
+ /**
453
+ * Update wpstg_rmpermalinks_executed
454
+ * @return bool
455
+ */
456
+ protected function step7() {
457
+
458
+ $this->log( "Preparing Data Step7: Updating wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}" );
459
+
460
+ // Skip - Table does not exist
461
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
462
+ return true;
463
+ }
464
+
465
+ // Skip - Table is not selected or updated
466
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
467
+ $this->log( "Preparing Data Step7: Skipping" );
468
+ return true;
469
+ }
470
+
471
+ $result = $this->db->query(
472
+ $this->db->prepare(
473
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_rmpermalinks_executed'", ' '
474
+ )
475
+ );
476
+
477
+ // All good
478
+ if( $result ) {
479
+ $this->Log( "Preparing Data Step7: Finished Step 7 successfully" );
480
+ return true;
481
+ }
482
+
483
+ $this->log( "Failed to update wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_WARNING );
484
+ return true;
485
+ }
486
+
487
+ /**
488
+ * Update permalink_structure
489
+ * @return bool
490
+ */
491
+ protected function step8() {
492
+
493
+ $this->log( "Preparing Data Step8: Updating permalink_structure in {$this->prefix}options {$this->db->last_error}" );
494
+
495
+ // Skip - Table does not exist
496
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
497
+ return true;
498
+ }
499
+
500
+ // Skip - Table is not selected or updated
501
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
502
+ $this->log( "Preparing Data Step8: Skipping" );
503
+ return true;
504
+ }
505
+
506
+ $result = $this->db->query(
507
+ $this->db->prepare(
508
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'permalink_structure'", ' '
509
+ )
510
+ );
511
+
512
+ // All good
513
+ if( $result ) {
514
+ $this->Log( "Preparing Data Step8: Finished Step 8 successfully" );
515
+ return true;
516
+ }
517
+
518
+ $this->log( "Failed to update permalink_structure in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
519
+ return true;
520
+ }
521
+
522
+ /**
523
+ * Update blog_public option to not allow staging site to be indexed by search engines
524
+ * @return bool
525
+ */
526
+ protected function step9() {
527
+
528
+ $this->log( "Preparing Data Step9: Set staging site to noindex" );
529
+
530
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
531
+ return true;
532
+ }
533
+
534
+ // Skip - Table is not selected or updated
535
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
536
+ $this->log( "Preparing Data Step9: Skipping" );
537
+ return true;
538
+ }
539
+
540
+ $result = $this->db->query(
541
+ $this->db->prepare(
542
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'blog_public'", '0'
543
+ )
544
+ );
545
+
546
+ // All good
547
+ if( $result ) {
548
+ $this->Log( "Preparing Data Step9: Finished Step 9 successfully" );
549
+ return true;
550
+ }
551
+
552
+ $this->log( "Can not update staging site to noindex. Possible already done!", Logger::TYPE_WARNING );
553
+ return true;
554
+ }
555
+
556
+ /**
557
+ * Update WP_HOME in wp-config.php
558
+ * @return bool
559
+ */
560
+ protected function step10() {
561
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
562
+
563
+ $this->log( "Preparing Data Step10: Updating WP_HOME in wp-config.php to " . $this->getStagingSiteUrl() );
564
+
565
+ if( false === ($content = file_get_contents( $path )) ) {
566
+ $this->log( "Preparing Data Step10: Failed to update WP_HOME in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
567
+ return false;
568
+ }
569
+
570
+
571
+ // Get WP_HOME from wp-config.php
572
+ preg_match( "/define\s*\(\s*'WP_HOME'\s*,\s*(.*)\s*\);/", $content, $matches );
573
+
574
+ if( !empty( $matches[1] ) ) {
575
+ $matches[1];
576
+
577
+ $pattern = "/define\s*\(\s*'WP_HOME'\s*,\s*(.*)\s*\);/";
578
+
579
+ $replace = "define('WP_HOME','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
580
+ $replace.= " // Changed by WP-Staging";
581
+
582
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
583
+ $this->log( "Preparing Data: Failed to reset index.php for sub directory; replacement failed", Logger::TYPE_ERROR );
584
+ return false;
585
+ }
586
+ } else {
587
+ $this->log( "Preparing Data Step10: WP_HOME not defined in wp-config.php. Skipping this step." );
588
+ }
589
+
590
+ if( false === @file_put_contents( $path, $content ) ) {
591
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR );
592
+ return false;
593
+ }
594
+ $this->Log( "Preparing Data: Finished Step 11 successfully" );
595
+ return true;
596
+ }
597
+
598
+ /**
599
+ * Update WP_SITEURL in wp-config.php
600
+ * @return bool
601
+ */
602
+ protected function step11() {
603
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
604
+
605
+ $this->log( "Preparing Data Step11: Updating WP_SITEURL in wp-config.php to " . $this->getStagingSiteUrl() );
606
+
607
+ if( false === ($content = file_get_contents( $path )) ) {
608
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
609
+ return false;
610
+ }
611
+
612
+
613
+ // Get WP_SITEURL from wp-config.php
614
+ preg_match( "/define\s*\(\s*'WP_SITEURL'\s*,\s*(.*)\s*\);/", $content, $matches );
615
+
616
+ if( !empty( $matches[1] ) ) {
617
+ $matches[1];
618
+
619
+ $pattern = "/define\s*\(\s*'WP_SITEURL'\s*,\s*(.*)\s*\);/";
620
+
621
+ $replace = "define('WP_SITEURL','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
622
+ $replace.= " // Changed by WP-Staging";
623
+
624
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
625
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL", Logger::TYPE_ERROR );
626
+ return false;
627
+ }
628
+ } else {
629
+ $this->log( "Preparing Data Step11: WP_SITEURL not defined in wp-config.php. Skipping this step." );
630
+ }
631
+
632
+
633
+ if( false === @file_put_contents( $path, $content ) ) {
634
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR );
635
+ return false;
636
+ }
637
+ $this->Log( "Preparing Data: Finished Step 11 successfully" );
638
+ return true;
639
+ }
640
+
641
+ /**
642
+ * Update WP_ALLOW_MULTISITE constant in wp-config.php
643
+ * @return bool
644
+ */
645
+ protected function step12() {
646
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
647
+
648
+ $this->log( "Preparing Data Step12: Updating WP_ALLOW_MULTISITE in wp-config.php to false" );
649
+
650
+ if( false === ($content = file_get_contents( $path )) ) {
651
+ $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
652
+ return false;
653
+ }
654
+
655
+
656
+ // Get WP_SITEURL from wp-config.php
657
+ preg_match( "/define\s*\(\s*'WP_ALLOW_MULTISITE'\s*,\s*(.*)\s*\);/", $content, $matches );
658
+
659
+ if( !empty( $matches[1] ) ) {
660
+ $matches[1];
661
+
662
+ $pattern = "/define\s*\(\s*'WP_ALLOW_MULTISITE'\s*,\s*(.*)\s*\);/";
663
+
664
+ $replace = "define('WP_ALLOW_MULTISITE',false); // " . $matches[1];
665
+ $replace.= " // Changed by WP-Staging";
666
+
667
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
668
+ $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE", Logger::TYPE_ERROR );
669
+ return false;
670
+ }
671
+ } else {
672
+ $this->log( "Preparing Data Step12: WP_ALLOW_MULTISITE not defined in wp-config.php. Skipping this step." );
673
+ }
674
+
675
+
676
+ if( false === @file_put_contents( $path, $content ) ) {
677
+ $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE. Can't save contents", Logger::TYPE_ERROR );
678
+ return false;
679
+ }
680
+ $this->Log( "Preparing Data: Finished Step 12 successfully" );
681
+ return true;
682
+ }
683
+
684
+ /**
685
+ * Update MULTISITE constant in wp-config.php
686
+ * @return bool
687
+ */
688
+ protected function step13() {
689
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
690
+
691
+ $this->log( "Preparing Data Step13: Updating MULTISITE in wp-config.php to false" );
692
+
693
+ if( false === ($content = file_get_contents( $path )) ) {
694
+ $this->log( "Preparing Data Step13: Failed to update MULTISITE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
695
+ return false;
696
+ }
697
+
698
+
699
+ // Get WP_SITEURL from wp-config.php
700
+ preg_match( "/define\s*\(\s*'MULTISITE'\s*,\s*(.*)\s*\);/", $content, $matches );
701
+
702
+ if( !empty( $matches[1] ) ) {
703
+ $matches[1];
704
+
705
+ $pattern = "/define\s*\(\s*'MULTISITE'\s*,\s*(.*)\s*\);/";
706
+
707
+ $replace = "define('MULTISITE',false); // " . $matches[1];
708
+ $replace.= " // Changed by WP-Staging";
709
+
710
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
711
+ $this->log( "Preparing Data Step13: Failed to update MULTISITE", Logger::TYPE_ERROR );
712
+ return false;
713
+ }
714
+ } else {
715
+ $this->log( "Preparing Data Step13: MULTISITE not defined in wp-config.php. Skipping this step." );
716
+ }
717
+
718
+
719
+ if( false === @file_put_contents( $path, $content ) ) {
720
+ $this->log( "Preparing Data Step13: Failed to update MULTISITE. Can't save contents", Logger::TYPE_ERROR );
721
+ return false;
722
+ }
723
+ $this->Log( "Preparing Data: Finished Step 13 successfully" );
724
+ return true;
725
+ }
726
+
727
+ /**
728
+ * Get active_sitewide_plugins from wp_sitemeta and active_plugins from subsite
729
+ * Merge both arrays and copy them to the staging site into active_plugins
730
+ */
731
+ protected function step14() {
732
+
733
+
734
+ $this->log( "Data Crunching Step 14: Updating active_plugins" );
735
+
736
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
737
+ $this->log( 'Data Crunching Step 14: Fatal Error ' . $this->prefix . 'options does not exist' );
738
+ $this->returnException( 'Data Crunching Step 14: Fatal Error ' . $this->prefix . 'options does not exist' );
739
+ return false;
740
+ }
741
+
742
+ // Skip - Table is not selected or updated
743
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
744
+ $this->log( "Preparing Data Step14: Skipping" );
745
+ return true;
746
+ }
747
+
748
+ // Get active_plugins value from sub site options table
749
+ $active_plugins = $this->db->get_var( "SELECT option_value FROM {$this->db->prefix}options WHERE option_name = 'active_plugins' " );
750
+
751
+ if( !$active_plugins ) {
752
+ $this->log( "Data Crunching Step 14: Option active_plugins are empty " );
753
+ $active_plugins = array();
754
+ }
755
+ // Get active_sitewide_plugins value from main multisite wp_sitemeta table
756
+ $active_sitewide_plugins = $this->db->get_var( "SELECT meta_value FROM {$this->db->base_prefix}sitemeta WHERE meta_key = 'active_sitewide_plugins' " );
757
+
758
+ if( !$active_sitewide_plugins ) {
759
+ $this->log( "Data Crunching Step 14: Options {$this->db->base_prefix}active_sitewide_plugins is empty " );
760
+ $active_sitewide_plugins = array();
761
+ }
762
+
763
+ $active_sitewide_plugins = unserialize( $active_sitewide_plugins );
764
+ $active_plugins = unserialize( $active_plugins );
765
+
766
+ $all_plugins = array_merge( $active_plugins, array_keys( $active_sitewide_plugins ) );
767
+
768
+ sort( $all_plugins );
769
+
770
+
771
+ // Update active_plugins
772
+ $update = $this->db->query(
773
+ "UPDATE {$this->prefix}options SET option_value = '" . serialize( $all_plugins ) . "' WHERE option_name = 'active_plugins'"
774
+ );
775
+
776
+ if( false === $update ) {
777
+ $this->log( "Data Crunching Step 14: Can not update option active_plugins in {$this->prefix}options", Logger::TYPE_WARNING );
778
+ return false;
779
+ }
780
+
781
+ $this->log( "Data Crunching Step 14: Successfull!" );
782
+ return true;
783
+ }
784
+
785
+ /**
786
+ * Update Table Prefix in wp_options
787
+ * @return bool
788
+ */
789
+ protected function step15() {
790
+ $this->log( "Preparing Data Step15: Updating db prefix in {$this->prefix}options." );
791
+
792
+ // Skip - Table does not exist
793
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
794
+ return true;
795
+ }
796
+
797
+ // Skip - Table is not selected or updated
798
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
799
+ $this->log( "Preparing Data Step4: Skipping" );
800
+ return true;
801
+ }
802
+
803
+
804
+ $this->log( "Updating db option_names in {$this->prefix}options. " );
805
+
806
+ // Filter the rows below. Do not update them!
807
+ $filters = array(
808
+ 'wp_mail_smtp',
809
+ 'wp_mail_smtp_version',
810
+ 'wp_mail_smtp_debug',
811
+ );
812
+
813
+ $filters = apply_filters( 'wpstg_data_excl_rows', $filters );
814
+
815
+ $where = "";
816
+ foreach ( $filters as $filter ) {
817
+ $where .= " AND option_name <> '" . $filter . "'";
818
+ }
819
+
820
+ $updateOptions = $this->db->query(
821
+ $this->db->prepare(
822
+ "UPDATE IGNORE {$this->prefix}options SET option_name= replace(option_name, %s, %s) WHERE option_name LIKE %s" . $where, $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
823
+ )
824
+ );
825
+
826
+ if( !$updateOptions ) {
827
+ $this->log( "Preparing Data Step15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_ERROR );
828
+ $this->returnException( "Data Crunching Step 15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
829
+ return false;
830
+ }
831
+
832
+ return true;
833
+ }
834
+
835
+ /**
836
+ * Change upload_path in wp_options (if it is defined)
837
+ * @return bool
838
+ */
839
+ protected function step16() {
840
+ $this->log( "Preparing Data Step16: Updating upload_path {$this->prefix}options." );
841
+
842
+ // Skip - Table does not exist
843
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
844
+ return true;
845
+ }
846
+
847
+ $newUploadPath = $this->getNewUploadPath();
848
+
849
+ if( false === $newUploadPath ) {
850
+ $this->log( "Preparing Data Step16: Skipping" );
851
+ return true;
852
+ }
853
+
854
+ // Skip - Table is not selected or updated
855
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
856
+ $this->log( "Preparing Data Step16: Skipping" );
857
+ return true;
858
+ }
859
+
860
+ $error = isset( $this->db->last_error ) ? 'Last error: ' . $this->db->last_error : '';
861
+
862
+ $this->log( "Updating upload_path in {$this->prefix}options. {$error}" );
863
+
864
+ $updateOptions = $this->db->query(
865
+ $this->db->prepare(
866
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'upload_path'", $newUploadPath
867
+ )
868
+ );
869
+
870
+ if( !$updateOptions ) {
871
+ $this->log( "Preparing Data Step16: Failed to update upload_path in {$this->prefix}options. {$error}", Logger::TYPE_ERROR );
872
+ return true;
873
+ }
874
+ $this->Log( "Preparing Data: Finished Step 16 successfully" );
875
+ return true;
876
+ }
877
+
878
+ /**
879
+ * Update WP_CACHE in wp-config.php
880
+ * @return bool
881
+ */
882
+ protected function step17() {
883
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
884
+
885
+ $this->log( "Preparing Data Step17: Set WP_CACHE in wp-config.php to false" );
886
+
887
+ if( false === ($content = file_get_contents( $path )) ) {
888
+ $this->log( "Preparing Data Step17: Failed to update WP_CACHE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
889
+ return false;
890
+ }
891
+
892
+
893
+ // Get WP_CACHE from wp-config.php
894
+ preg_match( "/define\s*\(\s*'WP_CACHE'\s*,\s*(.*)\s*\);/", $content, $matches );
895
+
896
+ if( !empty( $matches[1] ) ) {
897
+ $matches[1];
898
+
899
+ $pattern = "/define\s*\(\s*'WP_CACHE'\s*,\s*(.*)\s*\);/";
900
+
901
+ $replace = "define('WP_CACHE',false); // " . $matches[1];
902
+ $replace.= " // Changed by WP-Staging";
903
+
904
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
905
+ $this->log( "Preparing Data: Failed to change WP_CACHE", Logger::TYPE_ERROR );
906
+ return false;
907
+ }
908
+ } else {
909
+ $this->log( "Preparing Data Step17: WP_CACHE not defined in wp-config.php. Skipping this step." );
910
+ }
911
+
912
+ if( false === @file_put_contents( $path, $content ) ) {
913
+ $this->log( "Preparing Data Step17: Failed to update WP_CACHE. Can't save contents", Logger::TYPE_ERROR );
914
+ return false;
915
+ }
916
+ $this->Log( "Preparing Data: Finished Step 17 successfully" );
917
+ return true;
918
+ }
919
+
920
+ /**
921
+ * Get Upload Path to staging site
922
+ * @return boolean|string
923
+ */
924
+ protected function getNewUploadPath() {
925
+ $uploadPath = get_option( 'upload_path' );
926
+
927
+ if( !$uploadPath ) {
928
+ return false;
929
+ }
930
+
931
+ $customSlug = str_replace( \WPStaging\WPStaging::getWPpath(), '', $uploadPath );
932
+
933
+ $newUploadPath = \WPStaging\WPStaging::getWPpath() . $this->options->cloneDirectoryName . DIRECTORY_SEPARATOR . $customSlug;
934
+
935
+ return $newUploadPath;
936
+ }
937
+
938
+ /**
939
+ * Return URL to staging site
940
+ * @return string
941
+ */
942
+ protected function getStagingSiteUrl() {
943
+ if( $this->isSubDir() ) {
944
+ return rtrim( $this->multisiteHomeDomain, "/" ) . '/' . $this->options->cloneDirectoryName;
945
+ }
946
+
947
+ return rtrim( $this->multisiteHomeDomain, "/" ) . '/' . $this->options->cloneDirectoryName;
948
+ }
949
+
950
+ /**
951
+ * Check if WP is installed in subdir
952
+ * @return boolean
953
+ */
954
+ protected function isSubDir() {
955
+ // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
956
+ // This is happening much more often than you would expect
957
+ $siteurl = preg_replace( '#^https?://#', '', rtrim( get_option( 'siteurl' ), '/' ) );
958
+ $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
959
+
960
+ if( $home !== $siteurl ) {
961
+ return true;
962
+ }
963
+ return false;
964
+ }
965
+
966
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
apps/Backend/Modules/Jobs/Multisite/DataExternal.php ADDED
@@ -0,0 +1,1082 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
+
5
+ // No Direct Access
6
+ if( !defined( "WPINC" ) ) {
7
+ die;
8
+ }
9
+
10
+ use WPStaging\Utils\Logger;
11
+ use WPStaging\WPStaging;
12
+ use WPStaging\Utils\Helper;
13
+ use WPStaging\Utils\Multisite;
14
+ use WPStaging\Utils\Strings;
15
+ use WPStaging\Backend\Modules\Jobs\JobExecutable;
16
+
17
+ /**
18
+ * Class Data
19
+ * @package WPStaging\Backend\Modules\Jobs
20
+ */
21
+ class DataExternal extends JobExecutable {
22
+
23
+ /**
24
+ * @var \wpdb
25
+ */
26
+ private $db;
27
+
28
+ /**
29
+ * @var string
30
+ */
31
+ private $prefix;
32
+
33
+ /**
34
+ * Tables e.g wpstg3_options
35
+ * @var array
36
+ */
37
+ private $tables;
38
+
39
+ /**
40
+ * Initialize
41
+ */
42
+ public function initialize() {
43
+ //$this->db = WPStaging::getInstance()->get( "wpdb" );
44
+ $this->db = $this->getStagingDB();
45
+ $this->productionDb = WPStaging::getInstance()->get( "wpdb" );
46
+ $this->prefix = $this->options->prefix;
47
+ $this->db->prefix = $this->options->databasePrefix;
48
+
49
+ $this->getTables();
50
+
51
+ // Fix current step
52
+ if( 0 == $this->options->currentStep ) {
53
+ $this->options->currentStep = 0;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Get database object to interact with
59
+ */
60
+ private function getStagingDB() {
61
+ return new \wpdb( $this->options->databaseUser, $this->options->databasePassword, $this->options->databaseDatabase, $this->options->databaseServer );
62
+ }
63
+
64
+ /**
65
+ * Get a list of tables to copy
66
+ */
67
+ private function getTables() {
68
+ $strings = new Strings();
69
+ $this->tables = array();
70
+ foreach ( $this->options->tables as $table ) {
71
+ $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->db->prefix, null, $table );
72
+ }
73
+ // Add extra global tables from main multisite (wpstgx_users and wpstgx_usermeta)
74
+ $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->db->prefix, null, 'users' );
75
+ $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->db->prefix, null, 'usermeta' );
76
+ }
77
+
78
+ /**
79
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
80
+ * @return void
81
+ */
82
+ protected function calculateTotalSteps() {
83
+ $this->options->totalSteps = 19;
84
+ }
85
+
86
+ /**
87
+ * Start Module
88
+ * @return object
89
+ */
90
+ public function start() {
91
+ // Execute steps
92
+ $this->run();
93
+
94
+ // Save option, progress
95
+ $this->saveOptions();
96
+
97
+ return ( object ) $this->response;
98
+ }
99
+
100
+ /**
101
+ * Execute the Current Step
102
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
103
+ * @return bool
104
+ */
105
+ protected function execute() {
106
+ // Fatal error. Let this happen never and break here immediately
107
+ if( $this->isRoot() ) {
108
+ return false;
109
+ }
110
+
111
+ // Over limits threshold
112
+ if( $this->isOverThreshold() ) {
113
+ // Prepare response and save current progress
114
+ $this->prepareResponse( false, false );
115
+ $this->saveOptions();
116
+ return false;
117
+ }
118
+
119
+ // No more steps, finished
120
+ if( $this->isFinished() ) {
121
+ $this->prepareResponse( true, false );
122
+ return false;
123
+ }
124
+
125
+ // Execute step
126
+ $stepMethodName = "step" . $this->options->currentStep;
127
+ if( !$this->{$stepMethodName}() ) {
128
+ $this->prepareResponse( false, false );
129
+ return false;
130
+ }
131
+
132
+ // Prepare Response
133
+ $this->prepareResponse();
134
+
135
+ // Not finished
136
+ return true;
137
+ }
138
+
139
+ /**
140
+ * Checks Whether There is Any Job to Execute or Not
141
+ * @return bool
142
+ */
143
+ protected function isFinished() {
144
+ return (
145
+ $this->options->currentStep > $this->options->totalSteps ||
146
+ !method_exists( $this, "step" . $this->options->currentStep )
147
+ );
148
+ }
149
+
150
+ /**
151
+ * Check if current operation is done on the root folder or on the live DB
152
+ * @return boolean
153
+ */
154
+ protected function isRoot() {
155
+
156
+ // Prefix is the same as the one of live site
157
+ // $wpdb = WPStaging::getInstance()->get( "wpdb" );
158
+ // if( $wpdb->prefix === $this->prefix ) {
159
+ // return true;
160
+ // }
161
+ // CloneName is empty
162
+ $name = ( array ) $this->options->cloneDirectoryName;
163
+ if( empty( $name ) ) {
164
+ return true;
165
+ }
166
+
167
+ // Live Path === Staging path
168
+ if( $this->multisiteHomeDomain . $this->options->cloneDirectoryName === $this->multisiteHomeDomain ) {
169
+ return true;
170
+ }
171
+
172
+ return false;
173
+ }
174
+
175
+ /**
176
+ * Check if table exists
177
+ * @param string $table
178
+ * @return boolean
179
+ */
180
+ protected function isTable( $table ) {
181
+ if( $this->db->get_var( "SHOW TABLES LIKE '{$table}'" ) != $table ) {
182
+ $this->log( "Table {$table} does not exists", Logger::TYPE_ERROR );
183
+ return false;
184
+ }
185
+ return true;
186
+ }
187
+
188
+ /**
189
+ * Copy wp-config.php if it is located outside of root one level up
190
+ * @todo Needs some more testing before it will be released
191
+ * @return boolean
192
+ */
193
+ protected function step0() {
194
+ $this->log( "Preparing Data Step0: Check if wp-config.php is located in root path", Logger::TYPE_INFO );
195
+
196
+ $dir = trailingslashit( dirname( ABSPATH ) );
197
+
198
+ $source = $dir . 'wp-config.php';
199
+
200
+ $destination = trailingslashit( ABSPATH ) . $this->options->cloneDirectoryName . DIRECTORY_SEPARATOR . 'wp-config.php';
201
+
202
+
203
+ // Do not do anything
204
+ if( (!is_file( $source ) && !is_link( $source )) || is_file( $destination ) ) {
205
+ $this->log( "Preparing Data Step0: Skip it", Logger::TYPE_INFO );
206
+ return true;
207
+ }
208
+
209
+ // Copy target of a symbolic link
210
+ if( is_link( $source ) ) {
211
+ $this->log( "Preparing Data Step0: Symbolic link found...", Logger::TYPE_INFO );
212
+ if( !@copy( readlink( $source ), $destination ) ) {
213
+ $errors = error_get_last();
214
+ $this->log( "Preparing Data Step0: Failed to copy wp-config.php! Error: {$errors['message']} {$source} -> {$destination}", Logger::TYPE_ERROR );
215
+ return true;
216
+ }
217
+ }
218
+
219
+ // Copy file wp-config.php
220
+ if( !@copy( $source, $destination ) ) {
221
+ $errors = error_get_last();
222
+ $this->log( "Preparing Data Step0: Failed to copy wp-config.php! Error: {$errors['message']} {$source} -> {$destination}", Logger::TYPE_ERROR );
223
+ return true;
224
+ }
225
+ $this->log( "Preparing Data Step0: Successfull", Logger::TYPE_INFO );
226
+ return true;
227
+ }
228
+
229
+ /**
230
+ * Replace "siteurl" and "home"
231
+ * @return bool
232
+ */
233
+ protected function step1() {
234
+ $this->log( "Preparing Data Step1: Updating siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_INFO );
235
+
236
+ // Skip - Table does not exist
237
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
238
+ return true;
239
+ }
240
+ // Skip - Table is not selected or updated
241
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
242
+ $this->log( "Preparing Data Step1: Skipping" );
243
+ return true;
244
+ }
245
+
246
+ // Installed in sub-directory
247
+ if( $this->isSubDir() ) {
248
+ $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . rtrim( $this->multisiteHomeDomain, "/" ) . '/' . $this->options->cloneDirectoryName );
249
+ // Replace URLs
250
+ $result = $this->db->query(
251
+ $this->db->prepare(
252
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", rtrim( $this->multisiteHomeDomain, "/" ) . '/' . $this->options->cloneDirectoryName
253
+ )
254
+ );
255
+ } else {
256
+ $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . rtrim( $this->multisiteHomeDomain, "/" ) . '/' . $this->options->cloneDirectoryName );
257
+ // Replace URLs
258
+ $result = $this->db->query(
259
+ $this->db->prepare(
260
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", $this->multisiteHomeDomain . '/' . $this->options->cloneDirectoryName
261
+ )
262
+ );
263
+ }
264
+
265
+
266
+ // All good
267
+ if( $result ) {
268
+ return true;
269
+ }
270
+
271
+ $this->log( "Preparing Data Step1: Failed to update siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
272
+ return false;
273
+ }
274
+
275
+ /**
276
+ * Update "wpstg_is_staging_site"
277
+ * @return bool
278
+ */
279
+ protected function step2() {
280
+
281
+ $this->log( "Preparing Data Step2: Updating row wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}" );
282
+
283
+ // Skip - Table does not exist
284
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
285
+ return true;
286
+ }
287
+ // Skip - Table is not selected or updated
288
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
289
+ $this->log( "Preparing Data Step2: Skipping" );
290
+ return true;
291
+ }
292
+
293
+ $result = $this->db->query(
294
+ $this->db->prepare(
295
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_is_staging_site'", "true"
296
+ )
297
+ );
298
+
299
+ // No errors but no option name such as wpstg_is_staging_site
300
+ if( '' === $this->db->last_error && 0 == $result ) {
301
+ $result = $this->db->query(
302
+ $this->db->prepare(
303
+ "INSERT INTO {$this->prefix}options (option_name,option_value) VALUES ('wpstg_is_staging_site',%s)", "true"
304
+ )
305
+ );
306
+ }
307
+
308
+ // All good
309
+ if( $result ) {
310
+ return true;
311
+ }
312
+
313
+ $this->log( "Preparing Data Step2: Failed to update wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
314
+ return false;
315
+ }
316
+
317
+ /**
318
+ * Update rewrite_rules
319
+ * @return bool
320
+ */
321
+ protected function step3() {
322
+
323
+ $this->log( "Preparing Data Step3: Updating rewrite_rules in {$this->prefix}options {$this->db->last_error}" );
324
+
325
+ // Skip - Table does not exist
326
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
327
+ return true;
328
+ }
329
+
330
+ // Skip - Table is not selected or updated
331
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
332
+ $this->log( "Preparing Data Step3: Skipping" );
333
+ return true;
334
+ }
335
+
336
+ $result = $this->db->query(
337
+ $this->db->prepare(
338
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'rewrite_rules'", ' '
339
+ )
340
+ );
341
+
342
+ // All good
343
+ if( $result ) {
344
+ return true;
345
+ }
346
+
347
+ $this->log( "Preparing Data Step3: Failed to update rewrite_rules in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
348
+ return true;
349
+ }
350
+
351
+ /**
352
+ * Update Table Prefix in wp_usermeta and wp_options
353
+ * @return bool
354
+ */
355
+ protected function step4() {
356
+ $this->log( "Preparing Data Step4: Updating db prefix in {$this->prefix}usermeta. " );
357
+
358
+ // Skip - Table does not exist
359
+ if( false === $this->isTable( $this->prefix . 'usermeta' ) ) {
360
+ return true;
361
+ }
362
+
363
+ // Skip - Table is not selected or updated
364
+ if( !in_array( $this->prefix . 'usermeta', $this->tables ) ) {
365
+ $this->log( "Preparing Data Step4: Skipping" );
366
+ return true;
367
+ }
368
+
369
+ // Skip, prefixes are identical. No change needed
370
+ if( $this->db->prefix === $this->prefix ) {
371
+ $this->log( "Preparing Data Step4: Skipping" );
372
+ return true;
373
+ }
374
+
375
+ $update = $this->db->query(
376
+ $this->db->prepare(
377
+ "UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, %s, %s) WHERE meta_key LIKE %s", $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
378
+ )
379
+ );
380
+
381
+ if( !$update ) {
382
+ $this->log( "Preparing Data Step4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}", Logger::TYPE_ERROR );
383
+ $this->returnException( "Data Crunching Step 4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}" );
384
+ return false;
385
+ }
386
+ return true;
387
+ }
388
+
389
+ /**
390
+ * Update $table_prefix in wp-config.php
391
+ * @return bool
392
+ */
393
+ protected function step5() {
394
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
395
+
396
+ $this->log( "Preparing Data Step5: Updating table_prefix in {$path} to " . $this->prefix );
397
+ if( false === ($content = file_get_contents( $path )) ) {
398
+ $this->log( "Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR );
399
+ return false;
400
+ }
401
+
402
+ // Replace table prefix
403
+ $content = str_replace( '$table_prefix', '$table_prefix = \'' . $this->prefix . '\';//', $content );
404
+
405
+ // Replace URLs
406
+ $content = str_replace( $this->multisiteHomeDomain, $this->multisiteHomeDomain . '/' . $this->options->cloneDirectoryName, $content );
407
+
408
+ if( false === @file_put_contents( $path, $content ) ) {
409
+ $this->log( "Preparing Data Step5: Failed to update $table_prefix in {$path} to " . $this->prefix . ". Can't save contents", Logger::TYPE_ERROR );
410
+ return false;
411
+ }
412
+
413
+ return true;
414
+ }
415
+
416
+ /**
417
+ * Reset index.php to original file
418
+ * This is needed if live site is located in subfolder
419
+ * Check first if main wordpress is used in subfolder and index.php in parent directory
420
+ * @see: https://codex.wordpress.org/Giving_WordPress_Its_Own_Directory
421
+ * @return bool
422
+ */
423
+ protected function step6() {
424
+
425
+ if( !$this->isSubDir() ) {
426
+ $this->debugLog( "Preparing Data Step6: WP installation is not in a subdirectory! All good, skipping this step" );
427
+ return true;
428
+ }
429
+
430
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/index.php";
431
+
432
+ if( false === ($content = file_get_contents( $path )) ) {
433
+ $this->log( "Preparing Data Step6: Failed to reset {$path} for sub directory; can't read contents", Logger::TYPE_ERROR );
434
+ return false;
435
+ }
436
+
437
+
438
+ if( !preg_match( "/(require(.*)wp-blog-header.php' \);)/", $content, $matches ) ) {
439
+ $this->log(
440
+ "Preparing Data Step6: Failed to reset index.php for sub directory; wp-blog-header.php is missing", Logger::TYPE_ERROR
441
+ );
442
+ return false;
443
+ }
444
+ $this->log( "Preparing Data: WP installation is in a subdirectory. Progressing..." );
445
+
446
+ $pattern = "/require(.*) dirname(.*) __FILE__ (.*) \. '(.*)wp-blog-header.php'(.*);/";
447
+
448
+ $replace = "require( dirname( __FILE__ ) . '/wp-blog-header.php' ); // " . $matches[0];
449
+ $replace.= " // Changed by WP-Staging";
450
+
451
+
452
+
453
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
454
+ $this->log( "Preparing Data: Failed to reset index.php for sub directory; replacement failed", Logger::TYPE_ERROR );
455
+ return false;
456
+ }
457
+
458
+ if( false === @file_put_contents( $path, $content ) ) {
459
+ $this->log( "Preparing Data: Failed to reset index.php for sub directory; can't save contents", Logger::TYPE_ERROR );
460
+ return false;
461
+ }
462
+ $this->Log( "Preparing Data: Finished Step 6 successfully" );
463
+ return true;
464
+ }
465
+
466
+ /**
467
+ * Update wpstg_rmpermalinks_executed
468
+ * @return bool
469
+ */
470
+ protected function step7() {
471
+
472
+ $this->log( "Preparing Data Step7: Updating wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}" );
473
+
474
+ // Skip - Table does not exist
475
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
476
+ return true;
477
+ }
478
+
479
+ // Skip - Table is not selected or updated
480
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
481
+ $this->log( "Preparing Data Step7: Skipping" );
482
+ return true;
483
+ }
484
+
485
+ $result = $this->db->query(
486
+ $this->db->prepare(
487
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_rmpermalinks_executed'", ' '
488
+ )
489
+ );
490
+
491
+ // All good
492
+ if( $result ) {
493
+ $this->Log( "Preparing Data Step7: Finished Step 7 successfully" );
494
+ return true;
495
+ }
496
+
497
+ $this->log( "Failed to update wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_WARNING );
498
+ return true;
499
+ }
500
+
501
+ /**
502
+ * Update permalink_structure
503
+ * @return bool
504
+ */
505
+ protected function step8() {
506
+
507
+ $this->log( "Preparing Data Step8: Updating permalink_structure in {$this->prefix}options {$this->db->last_error}" );
508
+
509
+ // Skip - Table does not exist
510
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
511
+ return true;
512
+ }
513
+
514
+ // Skip - Table is not selected or updated
515
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
516
+ $this->log( "Preparing Data Step8: Skipping" );
517
+ return true;
518
+ }
519
+
520
+ $result = $this->db->query(
521
+ $this->db->prepare(
522
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'permalink_structure'", ' '
523
+ )
524
+ );
525
+
526
+ // All good
527
+ if( $result ) {
528
+ $this->Log( "Preparing Data Step8: Finished Step 8 successfully" );
529
+ return true;
530
+ }
531
+
532
+ $this->log( "Failed to update permalink_structure in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
533
+ return true;
534
+ }
535
+
536
+ /**
537
+ * Update blog_public option to not allow staging site to be indexed by search engines
538
+ * @return bool
539
+ */
540
+ protected function step9() {
541
+
542
+ $this->log( "Preparing Data Step9: Set staging site to noindex" );
543
+
544
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
545
+ return true;
546
+ }
547
+
548
+ // Skip - Table is not selected or updated
549
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
550
+ $this->log( "Preparing Data Step9: Skipping" );
551
+ return true;
552
+ }
553
+
554
+ $result = $this->db->query(
555
+ $this->db->prepare(
556
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'blog_public'", '0'
557
+ )
558
+ );
559
+
560
+ // All good
561
+ if( $result ) {
562
+ $this->Log( "Preparing Data Step9: Finished Step 9 successfully" );
563
+ return true;
564
+ }
565
+
566
+ $this->log( "Can not update staging site to noindex. Possible already done!", Logger::TYPE_WARNING );
567
+ return true;
568
+ }
569
+
570
+ /**
571
+ * Update WP_HOME in wp-config.php
572
+ * @return bool
573
+ */
574
+ protected function step10() {
575
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
576
+
577
+ $this->log( "Preparing Data Step10: Updating WP_HOME in wp-config.php to " . $this->getStagingSiteUrl() );
578
+
579
+ if( false === ($content = file_get_contents( $path )) ) {
580
+ $this->log( "Preparing Data Step10: Failed to update WP_HOME in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
581
+ return false;
582
+ }
583
+
584
+
585
+ // Get WP_HOME from wp-config.php
586
+ preg_match( "/define\s*\(\s*'WP_HOME'\s*,\s*(.*)\s*\);/", $content, $matches );
587
+
588
+ if( !empty( $matches[1] ) ) {
589
+ $matches[1];
590
+
591
+ $pattern = "/define\s*\(\s*'WP_HOME'\s*,\s*(.*)\s*\);/";
592
+
593
+ $replace = "define('WP_HOME','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
594
+ $replace.= " // Changed by WP-Staging";
595
+
596
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
597
+ $this->log( "Preparing Data: Failed to update WP_HOME", Logger::TYPE_ERROR );
598
+ return false;
599
+ }
600
+ } else {
601
+ $this->log( "Preparing Data Step10: WP_HOME not defined in wp-config.php. Skipping this step." );
602
+ }
603
+
604
+ if( false === @file_put_contents( $path, $content ) ) {
605
+ $this->log( "Preparing Data Step10: Failed to update WP_HOME. Can't save contents", Logger::TYPE_ERROR );
606
+ return false;
607
+ }
608
+ $this->Log( "Preparing Data: Finished Step 10 successfully" );
609
+ return true;
610
+ }
611
+
612
+ /**
613
+ * Update WP_SITEURL in wp-config.php
614
+ * @return bool
615
+ */
616
+ protected function step11() {
617
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
618
+
619
+ $this->log( "Preparing Data Step11: Updating WP_SITEURL in wp-config.php to " . $this->getStagingSiteUrl() );
620
+
621
+ if( false === ($content = file_get_contents( $path )) ) {
622
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
623
+ return false;
624
+ }
625
+
626
+
627
+ // Get WP_SITEURL from wp-config.php
628
+ preg_match( "/define\s*\(\s*'WP_SITEURL'\s*,\s*(.*)\s*\);/", $content, $matches );
629
+
630
+ if( !empty( $matches[1] ) ) {
631
+ $matches[1];
632
+
633
+ $pattern = "/define\s*\(\s*'WP_SITEURL'\s*,\s*(.*)\s*\);/";
634
+
635
+ $replace = "define('WP_SITEURL','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
636
+ $replace.= " // Changed by WP-Staging";
637
+
638
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
639
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL", Logger::TYPE_ERROR );
640
+ return false;
641
+ }
642
+ } else {
643
+ $this->log( "Preparing Data Step11: WP_SITEURL not defined in wp-config.php. Skipping this step." );
644
+ }
645
+
646
+
647
+ if( false === @file_put_contents( $path, $content ) ) {
648
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR );
649
+ return false;
650
+ }
651
+ $this->Log( "Preparing Data: Finished Step 11 successfully" );
652
+ return true;
653
+ }
654
+
655
+ /**
656
+ * Update WP_ALLOW_MULTISITE constant in wp-config.php
657
+ * @return bool
658
+ */
659
+ protected function step12() {
660
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
661
+
662
+ $this->log( "Preparing Data Step12: Updating WP_ALLOW_MULTISITE in wp-config.php to false" );
663
+
664
+ if( false === ($content = file_get_contents( $path )) ) {
665
+ $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
666
+ return false;
667
+ }
668
+
669
+
670
+ // Get WP_SITEURL from wp-config.php
671
+ preg_match( "/define\s*\(\s*'WP_ALLOW_MULTISITE'\s*,\s*(.*)\s*\);/", $content, $matches );
672
+
673
+ if( !empty( $matches[1] ) ) {
674
+ $matches[1];
675
+
676
+ $pattern = "/define\s*\(\s*'WP_ALLOW_MULTISITE'\s*,\s*(.*)\s*\);/";
677
+
678
+ $replace = "define('WP_ALLOW_MULTISITE',false); // " . $matches[1];
679
+ $replace.= " // Changed by WP-Staging";
680
+
681
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
682
+ $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE", Logger::TYPE_ERROR );
683
+ return false;
684
+ }
685
+ } else {
686
+ $this->log( "Preparing Data Step12: WP_ALLOW_MULTISITE not defined in wp-config.php. Skipping this step." );
687
+ }
688
+
689
+
690
+ if( false === @file_put_contents( $path, $content ) ) {
691
+ $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE. Can't save contents", Logger::TYPE_ERROR );
692
+ return false;
693
+ }
694
+ $this->Log( "Preparing Data: Finished Step 12 successfully" );
695
+ return true;
696
+ }
697
+
698
+ /**
699
+ * Update MULTISITE constant in wp-config.php
700
+ * @return bool
701
+ */
702
+ protected function step13() {
703
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
704
+
705
+ $this->log( "Preparing Data Step13: Updating MULTISITE in wp-config.php to false" );
706
+
707
+ if( false === ($content = file_get_contents( $path )) ) {
708
+ $this->log( "Preparing Data Step13: Failed to update MULTISITE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
709
+ return false;
710
+ }
711
+
712
+
713
+ // Get WP_SITEURL from wp-config.php
714
+ preg_match( "/define\s*\(\s*'MULTISITE'\s*,\s*(.*)\s*\);/", $content, $matches );
715
+
716
+ if( !empty( $matches[1] ) ) {
717
+ $matches[1];
718
+
719
+ $pattern = "/define\s*\(\s*'MULTISITE'\s*,\s*(.*)\s*\);/";
720
+
721
+ $replace = "define('MULTISITE',false); // " . $matches[1];
722
+ $replace.= " // Changed by WP-Staging";
723
+
724
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
725
+ $this->log( "Preparing Data Step13: Failed to update MULTISITE", Logger::TYPE_ERROR );
726
+ return false;
727
+ }
728
+ } else {
729
+ $this->log( "Preparing Data Step13: MULTISITE not defined in wp-config.php. Skipping this step." );
730
+ }
731
+
732
+
733
+ if( false === @file_put_contents( $path, $content ) ) {
734
+ $this->log( "Preparing Data Step13: Failed to update MULTISITE. Can't save contents", Logger::TYPE_ERROR );
735
+ return false;
736
+ }
737
+ $this->Log( "Preparing Data: Finished Step 13 successfully" );
738
+ return true;
739
+ }
740
+
741
+ /**
742
+ * Get active_sitewide_plugins from wp_sitemeta and active_plugins from subsite
743
+ * Merge both arrays and copy them to the staging site into active_plugins
744
+ */
745
+ protected function step14() {
746
+
747
+
748
+ $this->log( "Data Crunching Step 14: Updating active_plugins" );
749
+
750
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
751
+ $this->log( 'Data Crunching Step 14: Fatal Error ' . $this->prefix . 'options does not exist' );
752
+ $this->returnException( 'Data Crunching Step 14: Fatal Error ' . $this->prefix . 'options does not exist' );
753
+ return false;
754
+ }
755
+
756
+ // Skip - Table is not selected or updated
757
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
758
+ $this->log( "Preparing Data Step14: Skipping" );
759
+ return true;
760
+ }
761
+
762
+ // Get active_plugins value from sub site options table
763
+ $active_plugins = $this->db->get_var( "SELECT option_value FROM {$this->productionDb->prefix}options WHERE option_name = 'active_plugins' " );
764
+
765
+ if( !$active_plugins ) {
766
+ $this->log( "Data Crunching Step 14: Option active_plugins are empty " );
767
+ $active_plugins = array();
768
+ }
769
+ // Get active_sitewide_plugins value from main multisite wp_sitemeta table
770
+ $active_sitewide_plugins = $this->db->get_var( "SELECT meta_value FROM {$this->productionDb->base_prefix}sitemeta WHERE meta_key = 'active_sitewide_plugins' " );
771
+
772
+ if( !$active_sitewide_plugins ) {
773
+ $this->log( "Data Crunching Step 14: Options {$this->liveDb->base_prefix}active_sitewide_plugins is empty " );
774
+ $active_sitewide_plugins = array();
775
+ }
776
+
777
+ $active_sitewide_plugins = unserialize( $active_sitewide_plugins );
778
+ $active_plugins = unserialize( $active_plugins );
779
+
780
+ $all_plugins = array_merge( $active_plugins, array_keys( $active_sitewide_plugins ) );
781
+
782
+ sort( $all_plugins );
783
+
784
+
785
+ // Update active_plugins
786
+ $update = $this->db->query(
787
+ "UPDATE {$this->prefix}options SET option_value = '" . serialize( $all_plugins ) . "' WHERE option_name = 'active_plugins'"
788
+ );
789
+
790
+ if( false === $update ) {
791
+ $this->log( "Data Crunching Step 14: Can not update option active_plugins in {$this->prefix}options", Logger::TYPE_WARNING );
792
+ return false;
793
+ }
794
+
795
+ $this->log( "Data Crunching Step 14: Successfull!" );
796
+ return true;
797
+ }
798
+
799
+ /**
800
+ * Update Table Prefix in wp_options
801
+ * @return bool
802
+ */
803
+ protected function step15() {
804
+ $this->log( "Preparing Data Step15: Updating db prefix in {$this->prefix}options." );
805
+
806
+ // Skip - Table does not exist
807
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
808
+ return true;
809
+ }
810
+
811
+ // Skip - Table is not selected or updated
812
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
813
+ $this->log( "Preparing Data Step4: Skipping" );
814
+ return true;
815
+ }
816
+
817
+ // Skip, prefixes are identical. No change needed
818
+ if( $this->db->prefix === $this->prefix ) {
819
+ $this->log( "Preparing Data Step4: Skipping" );
820
+ return true;
821
+ }
822
+
823
+ $this->log( "Updating db option_names in {$this->prefix}options. " );
824
+
825
+ // Filter the rows below. Do not update them!
826
+ $filters = array(
827
+ 'wp_mail_smtp',
828
+ 'wp_mail_smtp_version',
829
+ 'wp_mail_smtp_debug',
830
+ );
831
+
832
+ $filters = apply_filters( 'wpstg_data_excl_rows', $filters );
833
+
834
+ $where = "";
835
+ foreach ( $filters as $filter ) {
836
+ $where .= " AND option_name <> '" . $filter . "'";
837
+ }
838
+
839
+ $updateOptions = $this->db->query(
840
+ $this->db->prepare(
841
+ "UPDATE IGNORE {$this->prefix}options SET option_name= replace(option_name, %s, %s) WHERE option_name LIKE %s" . $where, $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
842
+ )
843
+ );
844
+
845
+ if( !$updateOptions ) {
846
+ $this->log( "Preparing Data Step15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_ERROR );
847
+ $this->returnException( "Data Crunching Step 15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
848
+ return false;
849
+ }
850
+
851
+ return true;
852
+ }
853
+
854
+ /**
855
+ * Change upload_path in wp_options (if it is defined)
856
+ * @return bool
857
+ */
858
+ protected function step16() {
859
+ $this->log( "Preparing Data Step16: Updating upload_path {$this->prefix}options." );
860
+
861
+ // Skip - Table does not exist
862
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
863
+ return true;
864
+ }
865
+
866
+ $newUploadPath = $this->getNewUploadPath();
867
+
868
+ if( false === $newUploadPath ) {
869
+ $this->log( "Preparing Data Step16: Skipping" );
870
+ return true;
871
+ }
872
+
873
+ // Skip - Table is not selected or updated
874
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
875
+ $this->log( "Preparing Data Step16: Skipping" );
876
+ return true;
877
+ }
878
+
879
+ $error = isset( $this->db->last_error ) ? 'Last error: ' . $this->db->last_error : '';
880
+
881
+ $this->log( "Updating upload_path in {$this->prefix}options. {$error}" );
882
+
883
+ $updateOptions = $this->db->query(
884
+ $this->db->prepare(
885
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'upload_path'", $newUploadPath
886
+ )
887
+ );
888
+
889
+ if( !$updateOptions ) {
890
+ $this->log( "Preparing Data Step16: Failed to update upload_path in {$this->prefix}options. {$error}", Logger::TYPE_ERROR );
891
+ return true;
892
+ }
893
+ $this->Log( "Preparing Data: Finished Step 16 successfully" );
894
+ return true;
895
+ }
896
+
897
+ /**
898
+ * Update WP_CACHE in wp-config.php
899
+ * @return bool
900
+ */
901
+ protected function step17() {
902
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
903
+
904
+ $this->log( "Preparing Data Step17: Set WP_CACHE in wp-config.php to false" );
905
+
906
+ if( false === ($content = file_get_contents( $path )) ) {
907
+ $this->log( "Preparing Data Step17: Failed to update WP_CACHE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
908
+ return false;
909
+ }
910
+
911
+
912
+ // Get WP_CACHE from wp-config.php
913
+ preg_match( "/define\s*\(\s*'WP_CACHE'\s*,\s*(.*)\s*\);/", $content, $matches );
914
+
915
+ if( !empty( $matches[1] ) ) {
916
+ $matches[1];
917
+
918
+ $pattern = "/define\s*\(\s*'WP_CACHE'\s*,\s*(.*)\s*\);/";
919
+
920
+ $replace = "define('WP_CACHE',false); // " . $matches[1];
921
+ $replace.= " // Changed by WP-Staging";
922
+
923
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
924
+ $this->log( "Preparing Data: Failed to change WP_CACHE", Logger::TYPE_ERROR );
925
+ return false;
926
+ }
927
+ } else {
928
+ $this->log( "Preparing Data Step17: WP_CACHE not defined in wp-config.php. Skipping this step." );
929
+ }
930
+
931
+ if( false === @file_put_contents( $path, $content ) ) {
932
+ $this->log( "Preparing Data Step17: Failed to update WP_CACHE. Can't save contents", Logger::TYPE_ERROR );
933
+ return false;
934
+ }
935
+ $this->Log( "Preparing Data: Finished Step 17 successfully" );
936
+ return true;
937
+ }
938
+
939
+ /**
940
+ * Update database credentials in wp-config.php
941
+ * @return bool
942
+ */
943
+ protected function step18() {
944
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
945
+
946
+ $this->log( "Preparing Data Step18: Change database credentials in wp-config.php" );
947
+
948
+ if( false === ($content = file_get_contents( $path )) ) {
949
+ $this->log( "Preparing Data Step18: Failed to update database credentials in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
950
+ return false;
951
+ }
952
+
953
+
954
+ // Get DB_NAME from wp-config.php
955
+ preg_match( "/define\s*\(\s*'DB_NAME'\s*,\s*(.*)\s*\);/", $content, $matches );
956
+
957
+ if( !empty( $matches[1] ) ) {
958
+ $matches[1];
959
+
960
+ $pattern = "/define\s*\(\s*'DB_NAME'\s*,\s*(.*)\s*\);/";
961
+
962
+ $replace = "define('DB_NAME','{$this->options->databaseDatabase}'); // " . $matches[1];
963
+ $replace.= " // Changed by WP-Staging";
964
+
965
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
966
+ $this->log( "Preparing Data: Failed to change DB_NAME", Logger::TYPE_ERROR );
967
+ return false;
968
+ }
969
+ } else {
970
+ $this->log( "Preparing Data Step18: DB_NAME not defined in wp-config.php. Skipping this step." );
971
+ }
972
+ // Get DB_USER from wp-config.php
973
+ preg_match( "/define\s*\(\s*'DB_USER'\s*,\s*(.*)\s*\);/", $content, $matches );
974
+
975
+ if( !empty( $matches[1] ) ) {
976
+ $matches[1];
977
+
978
+ $pattern = "/define\s*\(\s*'DB_USER'\s*,\s*(.*)\s*\);/";
979
+
980
+ $replace = "define('DB_USER','{$this->options->databaseUser}'); // " . $matches[1];
981
+ $replace.= " // Changed by WP-Staging";
982
+
983
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
984
+ $this->log( "Preparing Data: Failed to change DB_USER", Logger::TYPE_ERROR );
985
+ return false;
986
+ }
987
+ } else {
988
+ $this->log( "Preparing Data Step18: DB_USER not defined in wp-config.php. Skipping this step." );
989
+ }
990
+ // Get DB_PASSWORD from wp-config.php
991
+ preg_match( "/define\s*\(\s*'DB_PASSWORD'\s*,\s*(.*)\s*\);/", $content, $matches );
992
+
993
+ if( !empty( $matches[1] ) ) {
994
+ $matches[1];
995
+
996
+ $pattern = "/define\s*\(\s*'DB_PASSWORD'\s*,\s*(.*)\s*\);/";
997
+
998
+ $replace = "define('DB_PASSWORD','{$this->options->databasePassword}'); // " . $matches[1];
999
+ $replace.= " // Changed by WP-Staging";
1000
+
1001
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1002
+ $this->log( "Preparing Data: Failed to change DB_PASSWORD", Logger::TYPE_ERROR );
1003
+ return false;
1004
+ }
1005
+ } else {
1006
+ $this->log( "Preparing Data Step18: DB_PASSWORD not defined in wp-config.php. Skipping this step." );
1007
+ }
1008
+ // Get DB_HOST from wp-config.php
1009
+ preg_match( "/define\s*\(\s*'DB_HOST'\s*,\s*(.*)\s*\);/", $content, $matches );
1010
+
1011
+ if( !empty( $matches[1] ) ) {
1012
+ $matches[1];
1013
+
1014
+ $pattern = "/define\s*\(\s*'DB_HOST'\s*,\s*(.*)\s*\);/";
1015
+
1016
+ $replace = "define('DB_HOST','{$this->options->databaseServer}'); // " . $matches[1];
1017
+ $replace.= " // Changed by WP-Staging";
1018
+
1019
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1020
+ $this->log( "Preparing Data: Failed to change DB_HOST", Logger::TYPE_ERROR );
1021
+ return false;
1022
+ }
1023
+ } else {
1024
+ $this->log( "Preparing Data Step18: DB_HOST not defined in wp-config.php. Skipping this step." );
1025
+ }
1026
+
1027
+
1028
+ if( false === @file_put_contents( $path, $content ) ) {
1029
+ $this->log( "Preparing Data Step18: Failed to update database credentials in wp-config.php. Can't save contents", Logger::TYPE_ERROR );
1030
+ return false;
1031
+ }
1032
+ $this->Log( "Preparing Data: Finished Step 18 successfully" );
1033
+ return true;
1034
+ }
1035
+
1036
+ /**
1037
+ * Get upload path
1038
+ * @return boolean|string
1039
+ */
1040
+ protected function getNewUploadPath() {
1041
+ $uploadPath = get_option( 'upload_path' );
1042
+
1043
+ if( !$uploadPath ) {
1044
+ return false;
1045
+ }
1046
+
1047
+ $customSlug = str_replace( \WPStaging\WPStaging::getWPpath(), '', $uploadPath );
1048
+
1049
+ $newUploadPath = \WPStaging\WPStaging::getWPpath() . $this->options->cloneDirectoryName . DIRECTORY_SEPARATOR . $customSlug;
1050
+
1051
+ return $newUploadPath;
1052
+ }
1053
+
1054
+ /**
1055
+ * Return URL to staging site
1056
+ * @return string
1057
+ */
1058
+ protected function getStagingSiteUrl() {
1059
+ if( $this->isSubDir() ) {
1060
+ return rtrim( $this->multisiteHomeDomain, "/" ) . '/' . $this->options->cloneDirectoryName;
1061
+ }
1062
+
1063
+ return rtrim( $this->multisiteHomeDomain, "/" ) . '/' . $this->options->cloneDirectoryName;
1064
+ }
1065
+
1066
+ /**
1067
+ * Check if WP is installed in subdir
1068
+ * @return boolean
1069
+ */
1070
+ protected function isSubDir() {
1071
+ // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
1072
+ // This is happening much more often than you would expect
1073
+ $siteurl = preg_replace( '#^https?://#', '', rtrim( get_option( 'siteurl' ), '/' ) );
1074
+ $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
1075
+
1076
+ if( $home !== $siteurl ) {
1077
+ return true;
1078
+ }
1079
+ return false;
1080
+ }
1081
+
1082
+ }
apps/Backend/Modules/Jobs/Multisite/Database.php CHANGED
@@ -1,315 +1,315 @@
1
- <?php
2
-
3
- namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
-
5
- // No Direct Access
6
- if( !defined( "WPINC" ) ) {
7
- die;
8
- }
9
-
10
- use WPStaging\WPStaging;
11
- use WPStaging\Utils\Strings;
12
- use WPStaging\Backend\Modules\Jobs\JobExecutable;
13
-
14
- /**
15
- * Class Database
16
- * @package WPStaging\Backend\Modules\Jobs
17
- */
18
- class Database extends JobExecutable {
19
-
20
- /**
21
- * @var int
22
- */
23
- private $total = 0;
24
-
25
- /**
26
- * @var \WPDB
27
- */
28
- private $db;
29
-
30
- /**
31
- * Initialize
32
- */
33
- public function initialize() {
34
- // Add 2 to total table count because we need to copy two more tables from the main multisite installation wp_users and wp_usermeta
35
- $this->total = count( $this->options->tables ) + 2;
36
- $this->db = WPStaging::getInstance()->get( "wpdb" );
37
- $this->isFatalError();
38
-
39
- }
40
-
41
-
42
- /**
43
- * Return fatal error and stops here if subfolder already exists
44
- * and mainJob is not updating the clone
45
- * @return boolean
46
- */
47
- private function isFatalError(){
48
- $path = trailingslashit(get_home_path()) . $this->options->cloneDirectoryName;
49
- if (isset($this->options->mainJob) && $this->options->mainJob !== 'updating' && is_dir($path)){
50
- $this->returnException( " Can not continue! Change the name of the clone or delete existing folder. Then try again. Folder already exists: " . $path );
51
- }
52
- return false;
53
- }
54
-
55
- /**
56
- * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
57
- * @return void
58
- */
59
- protected function calculateTotalSteps() {
60
- $this->options->totalSteps = $this->total === 0 ? 1 : $this->total;
61
- }
62
-
63
- /**
64
- * Execute the Current Step
65
- * Returns false when over threshold limits are hit or when the job is done, true otherwise
66
- * @return bool
67
- */
68
- protected function execute() {
69
-
70
-
71
- // Over limits threshold
72
- if( $this->isOverThreshold() ) {
73
- // Prepare response and save current progress
74
- $this->prepareResponse( false, false );
75
- $this->saveOptions();
76
- return false;
77
- }
78
-
79
- // No more steps, finished
80
- //if( $this->options->currentStep > $this->total || !isset( $this->options->tables[$this->options->currentStep] ) ) {
81
- if( $this->options->currentStep > $this->total ) {
82
- $this->prepareResponse( true, false );
83
- return false;
84
- }
85
-
86
- // Copy table
87
- //if (!$this->copyTable($this->options->tables[$this->options->currentStep]->name))
88
- if( isset($this->options->tables[$this->options->currentStep]) && !$this->copyTable( $this->options->tables[$this->options->currentStep] ) ) {
89
- // Prepare Response
90
- $this->prepareResponse( false, false );
91
-
92
- // Not finished
93
- return true;
94
- }
95
-
96
- $this->copyWpUsers();
97
-
98
- $this->copyWpUsermeta();
99
-
100
- // Prepare Response
101
- $this->prepareResponse();
102
-
103
- // Not finished
104
- return true;
105
- }
106
-
107
- /**
108
- * Get new prefix for the staging site
109
- * @return string
110
- */
111
- private function getStagingPrefix() {
112
- $stagingPrefix = $this->options->prefix;
113
- // Make sure prefix of staging site is NEVER identical to prefix of live site!
114
- if( $stagingPrefix == $this->db->prefix ) {
115
- wp_die( 'Fatal error 7: The new database table prefix ' . $stagingPrefix . ' would be identical to the table prefix of the live site. Please open a support ticket at support@wp-staging.com' );
116
- }
117
- return $stagingPrefix;
118
- }
119
-
120
- /**
121
- * No worries, SQL queries don't eat from PHP execution time!
122
- * @param string $tableName
123
- * @return bool
124
- */
125
- private function copyTable( $tableName ) {
126
-
127
- $strings = new Strings();
128
- $tableName = is_object( $tableName ) ? $tableName->name : $tableName;
129
- $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->prefix, null, $tableName );
130
-
131
- // Drop table if necessary
132
- $this->dropTable( $newTableName );
133
-
134
- // Save current job
135
- $this->setJob( $newTableName );
136
-
137
- // Beginning of the job
138
- if( !$this->startJob( $newTableName, $tableName ) ) {
139
- return true;
140
- }
141
-
142
- // Copy data
143
- $this->copyData( $newTableName, $tableName );
144
-
145
- // Finish the step
146
- return $this->finishStep();
147
- }
148
-
149
- /**
150
- * Copy multisite global user table wp_users to wpstgX_users
151
- * @return bool
152
- */
153
- private function copyWpUsers() {
154
- $strings = new Strings();
155
- $tableName = $this->db->base_prefix . 'users';
156
- $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->base_prefix, null, $tableName );
157
-
158
- // Drop table if necessary
159
- $this->dropTable( $newTableName );
160
-
161
- // Save current job
162
- $this->setJob( $newTableName );
163
-
164
- // Beginning of the job
165
- if( !$this->startJob( $newTableName, $tableName ) ) {
166
- return true;
167
- }
168
-
169
- // Copy data
170
- $this->copyData( $newTableName, $tableName );
171
-
172
- // Finish the step
173
- return $this->finishStep();
174
- }
175
-
176
- /**
177
- * Copy multisite global user table wp_usermeta to wpstgX_users
178
- * @return bool
179
- */
180
- private function copyWpUsermeta() {
181
- $strings = new Strings();
182
- $tableName = $this->db->base_prefix . 'usermeta';
183
- $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->base_prefix, null, $tableName );
184
-
185
- // Drop table if necessary
186
- $this->dropTable( $newTableName );
187
-
188
- // Save current job
189
- $this->setJob( $newTableName );
190
-
191
- // Beginning of the job
192
- if( !$this->startJob( $newTableName, $tableName ) ) {
193
- return true;
194
- }
195
- // Copy data
196
- $this->copyData( $newTableName, $tableName );
197
-
198
- // Finish the step
199
- return $this->finishStep();
200
- }
201
-
202
- /**
203
- * Copy data from old table to new table
204
- * @param string $new
205
- * @param string $old
206
- */
207
- private function copyData( $new, $old ) {
208
- $rows = $this->options->job->start + $this->settings->queryLimit;
209
- $this->log(
210
- "DB Copy: {$old} as {$new} from {$this->options->job->start} to {$rows} records"
211
- );
212
-
213
- $limitation = '';
214
-
215
- if( 0 < ( int ) $this->settings->queryLimit ) {
216
- $limitation = " LIMIT {$this->settings->queryLimit} OFFSET {$this->options->job->start}";
217
- }
218
-
219
- $this->db->query(
220
- "INSERT INTO {$new} SELECT * FROM {$old} {$limitation}"
221
- );
222
-
223
- // Set new offset
224
- $this->options->job->start += $this->settings->queryLimit;
225
- }
226
-
227
- /**
228
- * Set the job
229
- * @param string $table
230
- */
231
- private function setJob( $table ) {
232
- if( isset( $this->options->job->current ) ) {
233
- return;
234
- }
235
-
236
- $this->options->job->current = $table;
237
- $this->options->job->start = 0;
238
- }
239
-
240
- /**
241
- * Start Job
242
- * @param string $new
243
- * @param string $old
244
- * @return bool
245
- */
246
- private function startJob( $new, $old ) {
247
- if( 0 != $this->options->job->start ) {
248
- return true;
249
- }
250
-
251
- $this->log( "DB Copy: Creating table {$new}" );
252
-
253
- $this->db->query( "CREATE TABLE {$new} LIKE {$old}" );
254
-
255
- $this->options->job->total = ( int ) $this->db->get_var( "SELECT COUNT(1) FROM {$old}" );
256
-
257
- if( 0 == $this->options->job->total ) {
258
- $this->finishStep();
259
- return false;
260
- }
261
-
262
- return true;
263
- }
264
-
265
- /**
266
- * Finish the step
267
- */
268
- private function finishStep() {
269
- // This job is not finished yet
270
- if( $this->options->job->total > $this->options->job->start ) {
271
- return false;
272
- }
273
-
274
- // Add it to cloned tables listing
275
- $this->options->clonedTables[] = isset($this->options->tables[$this->options->currentStep]) ? $this->options->tables[$this->options->currentStep] : false;
276
-
277
- // Reset job
278
- $this->options->job = new \stdClass();
279
-
280
- return true;
281
- }
282
-
283
- /**
284
- * Drop table if necessary
285
- * @param string $new
286
- */
287
- private function dropTable( $new ) {
288
- $old = $this->db->get_var( $this->db->prepare( "SHOW TABLES LIKE %s", $new ) );
289
-
290
- if( !$this->shouldDropTable( $new, $old ) ) {
291
- return;
292
- }
293
-
294
- $this->log( "DB Copy: {$new} already exists, dropping it first" );
295
- $this->db->query( "DROP TABLE {$new}" );
296
- }
297
-
298
- /**
299
- * Check if table needs to be dropped
300
- * @param string $new
301
- * @param string $old
302
- * @return bool
303
- */
304
- private function shouldDropTable( $new, $old ) {
305
- return (
306
- $old === $new &&
307
- (
308
- !isset( $this->options->job->current ) ||
309
- !isset( $this->options->job->start ) ||
310
- 0 == $this->options->job->start
311
- )
312
- );
313
- }
314
-
315
- }
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
+
5
+ // No Direct Access
6
+ if( !defined( "WPINC" ) ) {
7
+ die;
8
+ }
9
+
10
+ use WPStaging\WPStaging;
11
+ use WPStaging\Utils\Strings;
12
+ use WPStaging\Backend\Modules\Jobs\JobExecutable;
13
+
14
+ /**
15
+ * Class Database
16
+ * @package WPStaging\Backend\Modules\Jobs
17
+ */
18
+ class Database extends JobExecutable {
19
+
20
+ /**
21
+ * @var int
22
+ */
23
+ private $total = 0;
24
+
25
+ /**
26
+ * @var \WPDB
27
+ */
28
+ private $db;
29
+
30
+ /**
31
+ * Initialize
32
+ */
33
+ public function initialize() {
34
+ // Add 2 to total table count because we need to copy two more tables from the main multisite installation wp_users and wp_usermeta
35
+ $this->total = count( $this->options->tables ) + 2;
36
+ $this->db = WPStaging::getInstance()->get( "wpdb" );
37
+ $this->isFatalError();
38
+
39
+ }
40
+
41
+
42
+ /**
43
+ * Return fatal error and stops here if subfolder already exists
44
+ * and mainJob is not updating the clone
45
+ * @return boolean
46
+ */
47
+ private function isFatalError(){
48
+ $path = trailingslashit(get_home_path()) . $this->options->cloneDirectoryName;
49
+ if (isset($this->options->mainJob) && $this->options->mainJob !== 'updating' && is_dir($path)){
50
+ $this->returnException( " Can not continue! Change the name of the clone or delete existing folder. Then try again. Folder already exists: " . $path );
51
+ }
52
+ return false;
53
+ }
54
+
55
+ /**
56
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
57
+ * @return void
58
+ */
59
+ protected function calculateTotalSteps() {
60
+ $this->options->totalSteps = $this->total === 0 ? 1 : $this->total;
61
+ }
62
+
63
+ /**
64
+ * Execute the Current Step
65
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
66
+ * @return bool
67
+ */
68
+ protected function execute() {
69
+
70
+
71
+ // Over limits threshold
72
+ if( $this->isOverThreshold() ) {
73
+ // Prepare response and save current progress
74
+ $this->prepareResponse( false, false );
75
+ $this->saveOptions();
76
+ return false;
77
+ }
78
+
79
+ // No more steps, finished
80
+ //if( $this->options->currentStep > $this->total || !isset( $this->options->tables[$this->options->currentStep] ) ) {
81
+ if( $this->options->currentStep > $this->total ) {
82
+ $this->prepareResponse( true, false );
83
+ return false;
84
+ }
85
+
86
+ // Copy table
87
+ //if (!$this->copyTable($this->options->tables[$this->options->currentStep]->name))
88
+ if( isset($this->options->tables[$this->options->currentStep]) && !$this->copyTable( $this->options->tables[$this->options->currentStep] ) ) {
89
+ // Prepare Response
90
+ $this->prepareResponse( false, false );
91
+
92
+ // Not finished
93
+ return true;
94
+ }
95
+
96
+ $this->copyWpUsers();
97
+
98
+ $this->copyWpUsermeta();
99
+
100
+ // Prepare Response
101
+ $this->prepareResponse();
102
+
103
+ // Not finished
104
+ return true;
105
+ }
106
+
107
+ /**
108
+ * Get new prefix for the staging site
109
+ * @return string
110
+ */
111
+ private function getStagingPrefix() {
112
+ $stagingPrefix = $this->options->prefix;
113
+ // Make sure prefix of staging site is NEVER identical to prefix of live site!
114
+ if( $stagingPrefix == $this->db->prefix ) {
115
+ wp_die( 'Fatal error 7: The new database table prefix ' . $stagingPrefix . ' would be identical to the table prefix of the live site. Please open a support ticket at support@wp-staging.com' );
116
+ }
117
+ return $stagingPrefix;
118
+ }
119
+
120
+ /**
121
+ * No worries, SQL queries don't eat from PHP execution time!
122
+ * @param string $tableName
123
+ * @return bool
124
+ */
125
+ private function copyTable( $tableName ) {
126
+
127
+ $strings = new Strings();
128
+ $tableName = is_object( $tableName ) ? $tableName->name : $tableName;
129
+ $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->prefix, null, $tableName );
130
+
131
+ // Drop table if necessary
132
+ $this->dropTable( $newTableName );
133
+
134
+ // Save current job
135
+ $this->setJob( $newTableName );
136
+
137
+ // Beginning of the job
138
+ if( !$this->startJob( $newTableName, $tableName ) ) {
139
+ return true;
140
+ }
141
+
142
+ // Copy data
143
+ $this->copyData( $newTableName, $tableName );
144
+
145
+ // Finish the step
146
+ return $this->finishStep();
147
+ }
148
+
149
+ /**
150
+ * Copy multisite global user table wp_users to wpstgX_users
151
+ * @return bool
152
+ */
153
+ private function copyWpUsers() {
154
+ $strings = new Strings();
155
+ $tableName = $this->db->base_prefix . 'users';
156
+ $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->base_prefix, null, $tableName );
157
+
158
+ // Drop table if necessary
159
+ $this->dropTable( $newTableName );
160
+
161
+ // Save current job
162
+ $this->setJob( $newTableName );
163
+
164
+ // Beginning of the job
165
+ if( !$this->startJob( $newTableName, $tableName ) ) {
166
+ return true;
167
+ }
168
+
169
+ // Copy data
170
+ $this->copyData( $newTableName, $tableName );
171
+
172
+ // Finish the step
173
+ return $this->finishStep();
174
+ }
175
+
176
+ /**
177
+ * Copy multisite global user table wp_usermeta to wpstgX_users
178
+ * @return bool
179
+ */
180
+ private function copyWpUsermeta() {
181
+ $strings = new Strings();
182
+ $tableName = $this->db->base_prefix . 'usermeta';
183
+ $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->base_prefix, null, $tableName );
184
+
185
+ // Drop table if necessary
186
+ $this->dropTable( $newTableName );
187
+
188
+ // Save current job
189
+ $this->setJob( $newTableName );
190
+
191
+ // Beginning of the job
192
+ if( !$this->startJob( $newTableName, $tableName ) ) {
193
+ return true;
194
+ }
195
+ // Copy data
196
+ $this->copyData( $newTableName, $tableName );
197
+
198
+ // Finish the step
199
+ return $this->finishStep();
200
+ }
201
+
202
+ /**
203
+ * Copy data from old table to new table
204
+ * @param string $new
205
+ * @param string $old
206
+ */
207
+ private function copyData( $new, $old ) {
208
+ $rows = $this->options->job->start + $this->settings->queryLimit;
209
+ $this->log(
210
+ "DB Copy: {$old} as {$new} from {$this->options->job->start} to {$rows} records"
211
+ );
212
+
213
+ $limitation = '';
214
+
215
+ if( 0 < ( int ) $this->settings->queryLimit ) {
216
+ $limitation = " LIMIT {$this->settings->queryLimit} OFFSET {$this->options->job->start}";
217
+ }
218
+
219
+ $this->db->query(
220
+ "INSERT INTO {$new} SELECT * FROM {$old} {$limitation}"
221
+ );
222
+
223
+ // Set new offset
224
+ $this->options->job->start += $this->settings->queryLimit;
225
+ }
226
+
227
+ /**
228
+ * Set the job
229
+ * @param string $table
230
+ */
231
+ private function setJob( $table ) {
232
+ if( isset( $this->options->job->current ) ) {
233
+ return;
234
+ }
235
+
236
+ $this->options->job->current = $table;
237
+ $this->options->job->start = 0;
238
+ }
239
+
240
+ /**
241
+ * Start Job
242
+ * @param string $new
243
+ * @param string $old
244
+ * @return bool
245
+ */
246
+ private function startJob( $new, $old ) {
247
+ if( 0 != $this->options->job->start ) {
248
+ return true;
249
+ }
250
+
251
+ $this->log( "DB Copy: Creating table {$new}" );
252
+
253
+ $this->db->query( "CREATE TABLE {$new} LIKE {$old}" );
254
+
255
+ $this->options->job->total = ( int ) $this->db->get_var( "SELECT COUNT(1) FROM {$old}" );
256
+
257
+ if( 0 == $this->options->job->total ) {
258
+ $this->finishStep();
259
+ return false;
260
+ }
261
+
262
+ return true;
263
+ }
264
+
265
+ /**
266
+ * Finish the step
267
+ */
268
+ private function finishStep() {
269
+ // This job is not finished yet
270
+ if( $this->options->job->total > $this->options->job->start ) {
271
+ return false;
272
+ }
273
+
274
+ // Add it to cloned tables listing
275
+ $this->options->clonedTables[] = isset($this->options->tables[$this->options->currentStep]) ? $this->options->tables[$this->options->currentStep] : false;
276
+
277
+ // Reset job
278
+ $this->options->job = new \stdClass();
279
+
280
+ return true;
281
+ }
282
+
283
+ /**
284
+ * Drop table if necessary
285
+ * @param string $new
286
+ */
287
+ private function dropTable( $new ) {
288
+ $old = $this->db->get_var( $this->db->prepare( "SHOW TABLES LIKE %s", $new ) );
289
+
290
+ if( !$this->shouldDropTable( $new, $old ) ) {
291
+ return;
292
+ }
293
+
294
+ $this->log( "DB Copy: {$new} already exists, dropping it first" );
295
+ $this->db->query( "DROP TABLE {$new}" );
296
+ }
297
+
298
+ /**
299
+ * Check if table needs to be dropped
300
+ * @param string $new
301
+ * @param string $old
302
+ * @return bool
303
+ */
304
+ private function shouldDropTable( $new, $old ) {
305
+ return (
306
+ $old === $new &&
307
+ (
308
+ !isset( $this->options->job->current ) ||
309
+ !isset( $this->options->job->start ) ||
310
+ 0 == $this->options->job->start
311
+ )
312
+ );
313
+ }
314
+
315
+ }
apps/Backend/Modules/Jobs/Multisite/DatabaseExternal.php ADDED
@@ -0,0 +1,350 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
+
5
+ // No Direct Access
6
+ if( !defined( "WPINC" ) ) {
7
+ die;
8
+ }
9
+
10
+ use WPStaging\WPStaging;
11
+ use WPStaging\Utils\Strings;
12
+ use WPStaging\Backend\Modules\Jobs\JobExecutable;
13
+
14
+ /**
15
+ * Class Database
16
+ * @package WPStaging\Backend\Modules\Jobs
17
+ */
18
+ class DatabaseExternal extends JobExecutable {
19
+
20
+ /**
21
+ * @var int
22
+ */
23
+ private $total = 0;
24
+
25
+ /**
26
+ * @var \WPDB
27
+ */
28
+ private $db;
29
+
30
+ /**
31
+ * Staging Database
32
+ * @var \WPDB
33
+ */
34
+ private $stagingDb;
35
+
36
+ /**
37
+ * Initialize
38
+ */
39
+ public function initialize() {
40
+ // Add 2 to total table count because we need to copy two more tables from the main multisite installation wp_users and wp_usermeta
41
+ $this->total = count( $this->options->tables ) + 2;
42
+ $this->db = WPStaging::getInstance()->get( "wpdb" );
43
+ $this->stagingDb = $this->getExternalDBConnection();
44
+ $this->isFatalError();
45
+ }
46
+
47
+ /**
48
+ * Get external db object
49
+ * @return mixed db | false
50
+ */
51
+ private function getExternalDBConnection() {
52
+
53
+ $db = new \wpdb( $this->options->databaseUser, $this->options->databasePassword, $this->options->databaseDatabase, $this->options->databaseServer );
54
+
55
+ // Can not connect to mysql
56
+ if( !empty( $db->error->errors['db_connect_fail']['0'] ) ) {
57
+ $this->returnException( "Can not connect to external database {$this->options->databaseDatabase}" );
58
+ return false;
59
+ }
60
+
61
+ // Can not connect to database
62
+ $sql = "SHOW DATABASES LIKE '{$this->options->databaseDatabase}';";
63
+ $results = $db->query( $sql );
64
+ if( empty( $results ) ) {
65
+ $this->returnException( "Can not connect to external database {$this->options->databaseDatabase}" );
66
+ return false;
67
+ }
68
+ return $db;
69
+ }
70
+
71
+
72
+ /**
73
+ * Return fatal error and stops here if subfolder already exists
74
+ * and mainJob is not updating the clone
75
+ * @return boolean
76
+ */
77
+ private function isFatalError() {
78
+ $path = trailingslashit( get_home_path() ) . $this->options->cloneDirectoryName;
79
+ if( isset( $this->options->mainJob ) && $this->options->mainJob !== 'updating' && is_dir( $path ) ) {
80
+ $this->returnException( " Can not continue! Change the name of the clone or delete existing folder. Then try again. Folder already exists: " . $path );
81
+ }
82
+ return false;
83
+ }
84
+
85
+ /**
86
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
87
+ * @return void
88
+ */
89
+ protected function calculateTotalSteps() {
90
+ $this->options->totalSteps = $this->total === 0 ? 1 : $this->total;
91
+ }
92
+
93
+ /**
94
+ * Execute the Current Step
95
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
96
+ * @return bool
97
+ */
98
+ protected function execute() {
99
+
100
+
101
+ // Over limits threshold
102
+ if( $this->isOverThreshold() ) {
103
+ // Prepare response and save current progress
104
+ $this->prepareResponse( false, false );
105
+ $this->saveOptions();
106
+ return false;
107
+ }
108
+
109
+ // No more steps, finished
110
+ //if( $this->options->currentStep > $this->total || !isset( $this->options->tables[$this->options->currentStep] ) ) {
111
+ if( $this->options->currentStep > $this->total ) {
112
+ $this->prepareResponse( true, false );
113
+ return false;
114
+ }
115
+
116
+ // Copy table
117
+ if( isset( $this->options->tables[$this->options->currentStep] ) && !$this->copyTable( $this->options->tables[$this->options->currentStep] ) ) {
118
+ // Prepare Response
119
+ $this->prepareResponse( false, false );
120
+
121
+ // Not finished
122
+ return true;
123
+ }
124
+
125
+ $this->copyWpUsers();
126
+
127
+ $this->copyWpUsermeta();
128
+
129
+ // Prepare Response
130
+ $this->prepareResponse();
131
+
132
+ // Not finished
133
+ return true;
134
+ }
135
+
136
+ /**
137
+ * Get new prefix for the staging site
138
+ * @return string
139
+ */
140
+ private function getStagingPrefix() {
141
+
142
+ $this->options->prefix = $this->options->databasePrefix;
143
+
144
+ return $this->options->prefix;
145
+ }
146
+
147
+ /**
148
+ * No worries, SQL queries don't eat from PHP execution time!
149
+ * @param string $tableName
150
+ * @return bool
151
+ */
152
+ private function copyTable( $tableName ) {
153
+
154
+ $strings = new Strings();
155
+ $tableName = is_object( $tableName ) ? $tableName->name : $tableName;
156
+ $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->prefix, null, $tableName );
157
+
158
+ // Drop table if necessary
159
+ $this->dropTable( $newTableName );
160
+
161
+ // Save current job
162
+ $this->setJob( $newTableName );
163
+
164
+ // Beginning of the job
165
+ if( !$this->startJob( $newTableName, $tableName ) ) {
166
+ return true;
167
+ }
168
+
169
+ // Copy data
170
+ $this->copyData( $newTableName, $tableName );
171
+
172
+ // Finish the step
173
+ return $this->finishStep();
174
+ }
175
+
176
+ /**
177
+ * Copy multisite global user table wp_users to wpstgX_users
178
+ * @return bool
179
+ */
180
+ private function copyWpUsers() {
181
+ $strings = new Strings();
182
+ $tableName = $this->db->base_prefix . 'users';
183
+ $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->base_prefix, null, $tableName );
184
+
185
+ // Drop table if necessary
186
+ $this->dropTable( $newTableName );
187
+
188
+ // Save current job
189
+ $this->setJob( $newTableName );
190
+
191
+ // Beginning of the job
192
+ if( !$this->startJob( $newTableName, $tableName ) ) {
193
+ return true;
194
+ }
195
+
196
+ // Copy data
197
+ $this->copyData( $newTableName, $tableName );
198
+
199
+ // Finish the step
200
+ return $this->finishStep();
201
+ }
202
+
203
+ /**
204
+ * Copy multisite global user table wp_usermeta to wpstgX_users
205
+ * @return bool
206
+ */
207
+ private function copyWpUsermeta() {
208
+ $strings = new Strings();
209
+ $tableName = $this->db->base_prefix . 'usermeta';
210
+ $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->base_prefix, null, $tableName );
211
+
212
+ // Drop table if necessary
213
+ $this->dropTable( $newTableName );
214
+
215
+ // Save current job
216
+ $this->setJob( $newTableName );
217
+
218
+ // Beginning of the job
219
+ if( !$this->startJob( $newTableName, $tableName ) ) {
220
+ return true;
221
+ }
222
+ // Copy data
223
+ $this->copyData( $newTableName, $tableName );
224
+
225
+ // Finish the step
226
+ return $this->finishStep();
227
+ }
228
+
229
+ /**
230
+ * Copy data from old table to new table
231
+ * @param string $new
232
+ * @param string $old
233
+ */
234
+ private function copyData( $new, $old ) {
235
+ $old = $this->db->dbname . '.' . $new;
236
+ $new = $this->options->databaseDatabase . '.' . $new;
237
+
238
+
239
+ $rows = $this->options->job->start + $this->settings->queryLimit;
240
+ $this->log(
241
+ "DB Copy: {$old} as {$new} from {$this->options->job->start} to {$rows} records"
242
+ );
243
+
244
+ $limitation = '';
245
+
246
+ if( 0 < ( int ) $this->settings->queryLimit ) {
247
+ $limitation = " LIMIT {$this->settings->queryLimit} OFFSET {$this->options->job->start}";
248
+ }
249
+
250
+ $this->stagingDb->query(
251
+ "INSERT INTO {$new} SELECT * FROM {$old} {$limitation}"
252
+ );
253
+
254
+ // Set new offset
255
+ $this->options->job->start += $this->settings->queryLimit;
256
+ }
257
+
258
+ /**
259
+ * Set the job
260
+ * @param string $table
261
+ */
262
+ private function setJob( $table ) {
263
+ if( isset( $this->options->job->current ) ) {
264
+ return;
265
+ }
266
+
267
+ $this->options->job->current = $table;
268
+ $this->options->job->start = 0;
269
+ }
270
+
271
+ /**
272
+ * Start Job
273
+ * @param string $new
274
+ * @param string $old
275
+ * @return bool
276
+ */
277
+ private function startJob( $new, $old ) {
278
+
279
+ $new = $this->options->databaseDatabase . '.' . $new;
280
+ $old = $this->db->dbname . '.' . $old;
281
+
282
+ if( 0 != $this->options->job->start ) {
283
+ return true;
284
+ }
285
+
286
+ $this->log( "DB Copy: Creating table {$new}" );
287
+
288
+ $this->stagingDb->query( "CREATE TABLE {$new} LIKE {$old}" );
289
+
290
+ $this->options->job->total = ( int ) $this->stagingDb->get_var( "SELECT COUNT(1) FROM {$old}" );
291
+
292
+ if( 0 == $this->options->job->total ) {
293
+ $this->finishStep();
294
+ return false;
295
+ }
296
+
297
+ return true;
298
+ }
299
+
300
+ /**
301
+ * Finish the step
302
+ */
303
+ private function finishStep() {
304
+ // This job is not finished yet
305
+ if( $this->options->job->total > $this->options->job->start ) {
306
+ return false;
307
+ }
308
+
309
+ // Add it to cloned tables listing
310
+ $this->options->clonedTables[] = isset( $this->options->tables[$this->options->currentStep] ) ? $this->options->tables[$this->options->currentStep] : false;
311
+
312
+ // Reset job
313
+ $this->options->job = new \stdClass();
314
+
315
+ return true;
316
+ }
317
+
318
+ /**
319
+ * Drop table if necessary
320
+ * @param string $new
321
+ */
322
+ private function dropTable( $new ) {
323
+ $old = $this->stagingDb->get_var( $this->stagingDb->prepare( "SHOW TABLES LIKE %s", $new ) );
324
+
325
+ if( !$this->shouldDropTable( $new, $old ) ) {
326
+ return;
327
+ }
328
+
329
+ $this->log( "DB Copy: {$new} already exists, dropping it first" );
330
+ $this->stagingDb->query( "DROP TABLE {$new}" );
331
+ }
332
+
333
+ /**
334
+ * Check if table needs to be dropped
335
+ * @param string $new
336
+ * @param string $old
337
+ * @return bool
338
+ */
339
+ private function shouldDropTable( $new, $old ) {
340
+ return (
341
+ $old === $new &&
342
+ (
343
+ !isset( $this->options->job->current ) ||
344
+ !isset( $this->options->job->start ) ||
345
+ 0 == $this->options->job->start
346
+ )
347
+ );
348
+ }
349
+
350
+ }
apps/Backend/Modules/Jobs/Multisite/Directories.php CHANGED
@@ -1,655 +1,655 @@
1
- <?php
2
-
3
- namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
-
5
- // No Direct Access
6
- if( !defined( "WPINC" ) ) {
7
- die;
8
- }
9
-
10
- use WPStaging\WPStaging;
11
- use WPStaging\Utils\Logger;
12
- use WPStaging\Utils\Strings;
13
- use WPStaging\Iterators\RecursiveDirectoryIterator;
14
- use WPStaging\Iterators\RecursiveFilterNewLine;
15
- use WPStaging\Iterators\RecursiveFilterExclude;
16
- use WPStaging\Backend\Modules\Jobs\JobExecutable;
17
-
18
- /**
19
- * Class Files
20
- * @package WPStaging\Backend\Modules\Directories
21
- */
22
- class Directories extends JobExecutable {
23
-
24
- /**
25
- * @var array
26
- */
27
- private $files = array();
28
-
29
- /**
30
- * Total steps to do
31
- * @var int
32
- */
33
- private $total = 5;
34
-
35
- /**
36
- * path to the cache file
37
- * @var string
38
- */
39
- private $filename;
40
-
41
- /**
42
- * Initialize
43
- */
44
- public function initialize() {
45
- $this->filename = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
46
- }
47
-
48
- /**
49
- * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
50
- * @return void
51
- */
52
- protected function calculateTotalSteps() {
53
-
54
- $this->options->totalSteps = $this->total + count( $this->options->extraDirectories );
55
- }
56
-
57
- /**
58
- * Start Module
59
- * @return object
60
- */
61
- public function start() {
62
-
63
- // Execute steps
64
- $this->run();
65
-
66
- // Save option, progress
67
- $this->saveProgress();
68
-
69
- return ( object ) $this->response;
70
- }
71
-
72
- /**
73
- * Step 0
74
- * Get WP Root files
75
- * Does not collect any sub folders
76
- */
77
- private function getWpRootFiles() {
78
-
79
- // open file handle
80
- $files = $this->open( $this->filename, 'a' );
81
-
82
-
83
- try {
84
-
85
- // Iterate over wp root directory
86
- $iterator = new \DirectoryIterator( \WPStaging\WPStaging::getWPpath() );
87
-
88
- $this->log( "Scanning / for files" );
89
-
90
- // Write path line
91
- foreach ( $iterator as $item ) {
92
- if( !$item->isDot() && $item->isFile() ) {
93
- if( $this->write( $files, $iterator->getFilename() . PHP_EOL ) ) {
94
- $this->options->totalFiles++;
95
-
96
- // Add current file size
97
- $this->options->totalFileSize += $iterator->getSize();
98
- }
99
- }
100
- }
101
- } catch ( \Exception $e ) {
102
- $this->returnException( 'Error: ' . $e->getMessage() );
103
- } catch ( \Exception $e ) {
104
- // Skip bad file permissions
105
- }
106
-
107
- $this->close( $files );
108
- return true;
109
- }
110
-
111
- /**
112
- * Step 2
113
- * Get WP Content Files without multisite folder wp-content/uploads/sites
114
- */
115
- private function getWpContentFiles() {
116
-
117
- // Skip it
118
- if( $this->isDirectoryExcluded( WP_CONTENT_DIR ) ) {
119
- $this->log( "Skip " . \WPStaging\WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR );
120
- return true;
121
- }
122
- // open file handle
123
- $files = $this->open( $this->filename, 'a' );
124
-
125
- /**
126
- * Excluded folders relative to the folder to iterate
127
- */
128
- $excludePaths = array(
129
- 'cache',
130
- 'plugins' . DIRECTORY_SEPARATOR . 'wps-hide-login',
131
- 'uploads' . DIRECTORY_SEPARATOR . 'sites'
132
- );
133
-
134
- /**
135
- * Get user excluded folders
136
- */
137
- $directory = array();
138
- foreach ( $this->options->excludedDirectories as $dir ) {
139
- if( strpos( $dir, WP_CONTENT_DIR ) !== false ) {
140
- $directory[] = ltrim( str_replace( WP_CONTENT_DIR, '', $dir ), '/' );
141
- }
142
- }
143
-
144
- $excludePaths = array_merge( $excludePaths, $directory );
145
-
146
- // $excludeFolders = array(
147
- // 'cache',
148
- // 'node_modules',
149
- // 'nbproject',
150
- // 'wps-hide-login'
151
- // );
152
-
153
- try {
154
-
155
- // Iterate over content directory
156
- $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( WP_CONTENT_DIR );
157
-
158
- // Exclude new line file names
159
- $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
160
-
161
- // Exclude sites, uploads, plugins or themes
162
- $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, apply_filters( 'wpstg_clone_mu_excl_folders', $excludePaths ) );
163
- // Recursively iterate over content directory
164
- $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
165
-
166
- $this->log( "Scanning /wp-content for its sub-directories and files" );
167
-
168
- // Write path line
169
- foreach ( $iterator as $item ) {
170
- if( $item->isFile() ) {
171
- $file = 'wp-content' . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL;
172
- if( $this->write( $files, $file ) ) {
173
- $this->options->totalFiles++;
174
-
175
- // Add current file size
176
- $this->options->totalFileSize += $iterator->getSize();
177
- }
178
- }
179
- }
180
- } catch ( \Exception $e ) {
181
- $this->returnException( 'Error: ' . $e->getMessage() );
182
- //throw new \Exception( 'Error: ' . $e->getMessage() );
183
- } catch ( \Exception $e ) {
184
- // Skip bad file permissions
185
- }
186
-
187
- // close the file handler
188
- $this->close( $files );
189
- return true;
190
- }
191
-
192
- /**
193
- * Step 2
194
- * @return boolean
195
- * @throws \Exception
196
- */
197
- private function getWpIncludesFiles() {
198
-
199
- // Skip it
200
- if( $this->isDirectoryExcluded( \WPStaging\WPStaging::getWPpath() . 'wp-includes' . DIRECTORY_SEPARATOR ) ) {
201
- $this->log( "Skip " . \WPStaging\WPStaging::getWPpath() . 'wp-includes' . DIRECTORY_SEPARATOR );
202
- return true;
203
- }
204
-
205
- // open file handle and attach data to end of file
206
- $files = $this->open( $this->filename, 'a' );
207
-
208
- try {
209
-
210
- // Iterate over wp-admin directory
211
- $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( \WPStaging\WPStaging::getWPpath() . 'wp-includes' . DIRECTORY_SEPARATOR );
212
-
213
- // Exclude new line file names
214
- $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
215
-
216
- // Recursively iterate over wp-includes directory
217
- $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
218
-
219
- $this->log( "Scanning /wp-includes for its sub-directories and files" );
220
-
221
- // Write files
222
- foreach ( $iterator as $item ) {
223
- if( $item->isFile() ) {
224
- if( $this->write( $files, 'wp-includes' . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
225
- $this->options->totalFiles++;
226
-
227
- // Add current file size
228
- $this->options->totalFileSize += $iterator->getSize();
229
- }
230
- }
231
- }
232
- } catch ( \Exception $e ) {
233
- //$this->returnException('Out of disk space.');
234
- throw new \Exception( 'Error: ' . $e->getMessage() );
235
- } catch ( \Exception $e ) {
236
- // Skip bad file permissions
237
- }
238
-
239
- // close the file handler
240
- $this->close( $files );
241
- return true;
242
- }
243
-
244
- /**
245
- * Step 3
246
- * @return boolean
247
- * @throws \Exception
248
- */
249
- private function getWpAdminFiles() {
250
-
251
- // Skip it
252
- if( $this->isDirectoryExcluded( \WPStaging\WPStaging::getWPpath() . 'wp-admin' . DIRECTORY_SEPARATOR ) ) {
253
- $this->log( "Skip " . \WPStaging\WPStaging::getWPpath() . 'wp-admin' . DIRECTORY_SEPARATOR );
254
- return true;
255
- }
256
-
257
- // open file handle and attach data to end of file
258
- $files = $this->open( $this->filename, 'a' );
259
-
260
- try {
261
-
262
- // Iterate over wp-admin directory
263
- $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( \WPStaging\WPStaging::getWPpath() . 'wp-admin' . DIRECTORY_SEPARATOR );
264
-
265
- // Exclude new line file names
266
- $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
267
-
268
- // Recursively iterate over content directory
269
- $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
270
-
271
- $this->log( "Scanning /wp-admin for its sub-directories and files" );
272
-
273
- // Write path line
274
- foreach ( $iterator as $item ) {
275
- if( $item->isFile() ) {
276
- if( $this->write( $files, 'wp-admin' . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
277
- $this->options->totalFiles++;
278
- // Add current file size
279
- $this->options->totalFileSize += $iterator->getSize();
280
- }
281
- }
282
- }
283
- } catch ( \Exception $e ) {
284
- $this->returnException( 'Error: ' . $e->getMessage() );
285
- //throw new \Exception('Error: ' . $e->getMessage());
286
- } catch ( \Exception $e ) {
287
- // Skip bad file permissions
288
- }
289
-
290
- // close the file handler
291
- $this->close( $files );
292
- return true;
293
- }
294
-
295
- /**
296
- * Step 4
297
- * Get WP Content Uploads Files multisite folder wp-content/uploads/sites or wp-content/blogs.dir/ID/files
298
- */
299
- private function getWpContentUploadsSites() {
300
-
301
- // Skip if main site is cloned
302
- if( is_main_site() ) {
303
- return true;
304
- }
305
-
306
- $blogId = get_current_blog_id();
307
-
308
- // Absolute path to uploads folder
309
- //$path = WP_CONTENT_DIR . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . $blogId . DIRECTORY_SEPARATOR;
310
- $path = $this->getAbsUploadPath();
311
-
312
- // Skip it
313
- if( !is_dir( $path ) ) {
314
- return true;
315
- }
316
-
317
- // Skip it
318
- if( $this->isDirectoryExcluded( $path ) ) {
319
- return true;
320
- }
321
-
322
-
323
- // open file handle
324
- $files = $this->open( $this->filename, 'a' );
325
-
326
- /**
327
- * Excluded folders relative to the folder to iterate
328
- */
329
- $excludePaths = array(
330
- 'cache',
331
- 'plugins' . DIRECTORY_SEPARATOR . 'wps-hide-login',
332
- 'uploads' . DIRECTORY_SEPARATOR . 'sites'
333
- );
334
-
335
- /**
336
- * Get user excluded folders
337
- */
338
- $directory = array();
339
- foreach ( $this->options->excludedDirectories as $dir ) {
340
- if( strpos( $dir, $path ) !== false ) {
341
- $directory[] = ltrim( str_replace( $path, '', $dir ), '/' );
342
- }
343
- }
344
-
345
- $excludePaths = array_merge( $excludePaths, $directory );
346
-
347
- try {
348
-
349
- // Iterate over content directory
350
- $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( $path );
351
-
352
- // Exclude new line file names
353
- $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
354
-
355
- // Exclude sites, uploads, plugins or themes
356
- $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, $excludePaths );
357
-
358
- // Recursively iterate over content directory
359
- $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
360
- $this->log( "Scanning /wp-content/uploads/sites/{$blogId} for its sub-directories and files" );
361
-
362
- // Write path line
363
- foreach ( $iterator as $item ) {
364
- if( $item->isFile() ) {
365
- //$file = 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . $blogId . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL;
366
- $file = $this->getRelUploadPath() . $iterator->getSubPathName() . PHP_EOL;
367
- if( $this->write( $files, $file ) ) {
368
- $this->options->totalFiles++;
369
-
370
- // Add current file size
371
- $this->options->totalFileSize += $iterator->getSize();
372
- }
373
- }
374
- }
375
- } catch ( \Exception $e ) {
376
- $this->returnException( 'Error: ' . $e->getMessage() );
377
- //throw new \Exception( 'Error: ' . $e->getMessage() );
378
- } catch ( \Exception $e ) {
379
- // Skip bad file permissions
380
- }
381
-
382
- // close the file handler
383
- $this->close( $files );
384
- return true;
385
- }
386
-
387
- /**
388
- * Get absolute path to the upload folder e.g. /srv/www/wp-content/blogs.dir/ID/files or /srv/www/wp-content/uploads/sites/ID/
389
- * @return type
390
- */
391
- private function getAbsUploadPath() {
392
- // Check first which method is used
393
- $uploads = wp_upload_dir();
394
- $basedir = $uploads['basedir'];
395
-
396
- return trailingslashit($basedir);
397
-
398
- // if( false === strpos( $basedir, 'blogs.dir' ) ) {
399
- // // Since WP 3.5
400
- // return 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . get_current_blog_id();
401
- // } else {
402
- // // old blog structure
403
- // return 'wp-content' . DIRECTORY_SEPARATOR . 'blogs.dir' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR . 'files';
404
- // }
405
- }
406
- /**
407
- * Get relative path to the upload folder like wp-content/uploads or wp-content/blogs.dir/2/files
408
- * @return string
409
- */
410
- private function getRelUploadPath() {
411
- $uploads = wp_upload_dir();
412
- $basedir = $uploads['basedir'];
413
-
414
- return trailingslashit(str_replace(\WPStaging\WPStaging::getWPpath(), '', $basedir));
415
- }
416
-
417
- /**
418
- * Step 5 - x
419
- * Get extra folders of the wp root level
420
- * Does not collect wp-includes, wp-admin and wp-content folder
421
- */
422
- private function getExtraFiles( $folder ) {
423
-
424
-
425
- // open file handle and attach data to end of file
426
- $files = $this->open( $this->filename, 'a' );
427
-
428
- try {
429
-
430
- // Iterate over extra directory
431
- $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( $folder );
432
-
433
- // Exclude new line file names
434
- $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
435
-
436
- // Exclude wp core folders
437
- // $exclude = array('wp-includes',
438
- // 'wp-admin',
439
- // 'wp-content');
440
- //
441
- // $excludeMore = array();
442
- // foreach ($this->options->excludedDirectories as $key => $value){
443
- // $excludeMore[] = $this->getLastElemAfterString('/', $value);
444
- // }
445
- //$exclude = array_merge($exclude, $excludeMore);
446
-
447
- $exclude = array();
448
-
449
- $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, $exclude );
450
- // Recursively iterate over content directory
451
- $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
452
-
453
- $strings = new Strings();
454
- $this->log( "Scanning {$strings->getLastElemAfterString( '/', $folder )} for its sub-directories and files" );
455
-
456
- // Write path line
457
- foreach ( $iterator as $item ) {
458
- if( $item->isFile() ) {
459
- //if( $this->write( $files, $strings->getLastElemAfterString( '/', $folder ) . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
460
- if( $this->write( $files, str_replace( \WPStaging\WPStaging::getWPpath(), '', $folder ) . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
461
- $this->options->totalFiles++;
462
- // Add current file size
463
- $this->options->totalFileSize += $iterator->getSize();
464
- }
465
- }
466
- }
467
- } catch ( \Exception $e ) {
468
- $this->returnException( 'Error: ' . $e->getMessage() );
469
- } catch ( \Exception $e ) {
470
- // Skip bad file permissions
471
- }
472
-
473
- // close the file handler
474
- $this->close( $files );
475
- return true;
476
- }
477
-
478
- /**
479
- * Closes a file handle
480
- *
481
- * @param resource $handle File handle to close
482
- * @return boolean
483
- */
484
- public function close( $handle ) {
485
- return @fclose( $handle );
486
- }
487
-
488
- /**
489
- * Opens a file in specified mode
490
- *
491
- * @param string $file Path to the file to open
492
- * @param string $mode Mode in which to open the file
493
- * @return resource
494
- * @throws Exception
495
- */
496
- public function open( $file, $mode ) {
497
-
498
- $file_handle = @fopen( $file, $mode );
499
- if( false === $file_handle ) {
500
- $this->returnException( sprintf( __( 'Unable to open %s with mode %s', 'wp-staging' ), $file, $mode ) );
501
- //throw new Exception(sprintf(__('Unable to open %s with mode %s', 'wp-staging'), $file, $mode));
502
- }
503
-
504
- return $file_handle;
505
- }
506
-
507
- /**
508
- * Write contents to a file
509
- *
510
- * @param resource $handle File handle to write to
511
- * @param string $content Contents to write to the file
512
- * @return integer
513
- * @throws Exception
514
- * @throws Exception
515
- */
516
- public function write( $handle, $content ) {
517
- $write_result = @fwrite( $handle, $content );
518
- if( false === $write_result ) {
519
- if( ( $meta = \stream_get_meta_data( $handle ) ) ) {
520
- //$this->returnException(sprintf(__('Unable to write to: %s', 'wp-staging'), $meta['uri']));
521
- throw new \Exception( sprintf( __( 'Unable to write to: %s', 'wp-staging' ), $meta['uri'] ) );
522
- }
523
- } elseif( strlen( $content ) !== $write_result ) {
524
- //$this->returnException(__('Out of disk space.', 'wp-staging'));
525
- throw new \Exception( __( 'Out of disk space.', 'wp-staging' ) );
526
- }
527
-
528
- return $write_result;
529
- }
530
-
531
- /**
532
- * Execute the Current Step
533
- * Returns false when over threshold limits are hit or when the job is done, true otherwise
534
- * @return bool
535
- */
536
- protected function execute() {
537
-
538
- // No job left to execute
539
- if( $this->isFinished() ) {
540
- $this->prepareResponse( true, false );
541
- return false;
542
- }
543
-
544
-
545
- if( $this->options->currentStep == 0 ) {
546
- $this->getWpRootFiles();
547
- $this->prepareResponse( false, true );
548
- return false;
549
- }
550
-
551
- if( $this->options->currentStep == 1 ) {
552
- $this->getWpContentFiles();
553
- $this->prepareResponse( false, true );
554
- return false;
555
- }
556
-
557
- if( $this->options->currentStep == 2 ) {
558
- $this->getWpIncludesFiles();
559
- $this->prepareResponse( false, true );
560
- return false;
561
- }
562
-
563
- if( $this->options->currentStep == 3 ) {
564
- $this->getWpAdminFiles();
565
- $this->prepareResponse( false, true );
566
- return false;
567
- }
568
-
569
- if( $this->options->currentStep == 4 ) {
570
- $this->getWpContentUploadsSites();
571
- $this->prepareResponse( false, true );
572
- return false;
573
- }
574
-
575
- if( isset( $this->options->extraDirectories[$this->options->currentStep - $this->total] ) ) {
576
- $this->getExtraFiles( $this->options->extraDirectories[$this->options->currentStep - $this->total] );
577
- $this->prepareResponse( false, true );
578
- return false;
579
- }
580
-
581
-
582
- // Prepare response
583
- $this->prepareResponse( false, true );
584
- // Not finished
585
- return true;
586
- }
587
-
588
- /**
589
- * Checks Whether There is Any Job to Execute or Not
590
- * @return bool
591
- */
592
- protected function isFinished() {
593
- if( $this->options->currentStep >= $this->options->totalSteps ) {
594
- return true;
595
- }
596
-
597
- // return (
598
- // //$this->options->currentStep > $this->total ||
599
- // $this->options->currentStep >= $this->options->totalSteps
600
- // );
601
- }
602
-
603
- /**
604
- * Save files
605
- * @return bool
606
- */
607
- protected function saveProgress() {
608
- return $this->saveOptions();
609
- }
610
-
611
- /**
612
- * Get files
613
- * @return void
614
- */
615
- protected function getFiles() {
616
- $fileName = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
617
-
618
- if( false === ($this->files = @file_get_contents( $fileName )) ) {
619
- $this->files = array();
620
- return;
621
- }
622
-
623
- $this->files = explode( PHP_EOL, $this->files );
624
- }
625
-
626
- /**
627
- * Replace forward slash with current directory separator
628
- *
629
- * @param string $path Path
630
- *
631
- * @return string
632
- */
633
- private function sanitizeDirectorySeparator( $path ) {
634
- $string = str_replace( "/", "\\", $path );
635
- return str_replace( '\\\\', '\\', $string );
636
- }
637
-
638
- /**
639
- * Check if directory is excluded
640
- * @param string $directory
641
- * @return bool
642
- */
643
- protected function isDirectoryExcluded( $directory ) {
644
- $directory = $this->sanitizeDirectorySeparator( $directory );
645
- foreach ( $this->options->excludedDirectories as $excludedDirectory ) {
646
- $excludedDirectory = $this->sanitizeDirectorySeparator( $excludedDirectory );
647
- if( strpos( trailingslashit( $directory ), trailingslashit( $excludedDirectory ) ) === 0 ) {
648
- return true;
649
- }
650
- }
651
-
652
- return false;
653
- }
654
-
655
- }
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
+
5
+ // No Direct Access
6
+ if( !defined( "WPINC" ) ) {
7
+ die;
8
+ }
9
+
10
+ use WPStaging\WPStaging;
11
+ use WPStaging\Utils\Logger;
12
+ use WPStaging\Utils\Strings;
13
+ use WPStaging\Iterators\RecursiveDirectoryIterator;
14
+ use WPStaging\Iterators\RecursiveFilterNewLine;
15
+ use WPStaging\Iterators\RecursiveFilterExclude;
16
+ use WPStaging\Backend\Modules\Jobs\JobExecutable;
17
+
18
+ /**
19
+ * Class Files
20
+ * @package WPStaging\Backend\Modules\Directories
21
+ */
22
+ class Directories extends JobExecutable {
23
+
24
+ /**
25
+ * @var array
26
+ */
27
+ private $files = array();
28
+
29
+ /**
30
+ * Total steps to do
31
+ * @var int
32
+ */
33
+ private $total = 5;
34
+
35
+ /**
36
+ * path to the cache file
37
+ * @var string
38
+ */
39
+ private $filename;
40
+
41
+ /**
42
+ * Initialize
43
+ */
44
+ public function initialize() {
45
+ $this->filename = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
46
+ }
47
+
48
+ /**
49
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
50
+ * @return void
51
+ */
52
+ protected function calculateTotalSteps() {
53
+
54
+ $this->options->totalSteps = $this->total + count( $this->options->extraDirectories );
55
+ }
56
+
57
+ /**
58
+ * Start Module
59
+ * @return object
60
+ */
61
+ public function start() {
62
+
63
+ // Execute steps
64
+ $this->run();
65
+
66
+ // Save option, progress
67
+ $this->saveProgress();
68
+
69
+ return ( object ) $this->response;
70
+ }
71
+
72
+ /**
73
+ * Step 0
74
+ * Get WP Root files
75
+ * Does not collect any sub folders
76
+ */
77
+ private function getWpRootFiles() {
78
+
79
+ // open file handle
80
+ $files = $this->open( $this->filename, 'a' );
81
+
82
+
83
+ try {
84
+
85
+ // Iterate over wp root directory
86
+ $iterator = new \DirectoryIterator( \WPStaging\WPStaging::getWPpath() );
87
+
88
+ $this->log( "Scanning / for files" );
89
+
90
+ // Write path line
91
+ foreach ( $iterator as $item ) {
92
+ if( !$item->isDot() && $item->isFile() ) {
93
+ if( $this->write( $files, $iterator->getFilename() . PHP_EOL ) ) {
94
+ $this->options->totalFiles++;
95
+
96
+ // Add current file size
97
+ $this->options->totalFileSize += $iterator->getSize();
98
+ }
99
+ }
100
+ }
101
+ } catch ( \Exception $e ) {
102
+ $this->returnException( 'Error: ' . $e->getMessage() );
103
+ } catch ( \Exception $e ) {
104
+ // Skip bad file permissions
105
+ }
106
+
107
+ $this->close( $files );
108
+ return true;
109
+ }
110
+
111
+ /**
112
+ * Step 2
113
+ * Get WP Content Files without multisite folder wp-content/uploads/sites
114
+ */
115
+ private function getWpContentFiles() {
116
+
117
+ // Skip it
118
+ if( $this->isDirectoryExcluded( WP_CONTENT_DIR ) ) {
119
+ $this->log( "Skip " . \WPStaging\WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR );
120
+ return true;
121
+ }
122
+ // open file handle
123
+ $files = $this->open( $this->filename, 'a' );
124
+
125
+ /**
126
+ * Excluded folders relative to the folder to iterate
127
+ */
128
+ $excludePaths = array(
129
+ 'cache',
130
+ 'plugins' . DIRECTORY_SEPARATOR . 'wps-hide-login',
131
+ 'uploads' . DIRECTORY_SEPARATOR . 'sites'
132
+ );
133
+
134
+ /**
135
+ * Get user excluded folders
136
+ */
137
+ $directory = array();
138
+ foreach ( $this->options->excludedDirectories as $dir ) {
139
+ if( strpos( $dir, WP_CONTENT_DIR ) !== false ) {
140
+ $directory[] = ltrim( str_replace( WP_CONTENT_DIR, '', $dir ), '/' );
141
+ }
142
+ }
143
+
144
+ $excludePaths = array_merge( $excludePaths, $directory );
145
+
146
+ // $excludeFolders = array(
147
+ // 'cache',
148
+ // 'node_modules',
149
+ // 'nbproject',
150
+ // 'wps-hide-login'
151
+ // );
152
+
153
+ try {
154
+
155
+ // Iterate over content directory
156
+ $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( WP_CONTENT_DIR );
157
+
158
+ // Exclude new line file names
159
+ $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
160
+
161
+ // Exclude sites, uploads, plugins or themes
162
+ $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, apply_filters( 'wpstg_clone_mu_excl_folders', $excludePaths ) );
163
+ // Recursively iterate over content directory
164
+ $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
165
+
166
+ $this->log( "Scanning /wp-content for its sub-directories and files" );
167
+
168
+ // Write path line
169
+ foreach ( $iterator as $item ) {
170
+ if( $item->isFile() ) {
171
+ $file = 'wp-content' . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL;
172
+ if( $this->write( $files, $file ) ) {
173
+ $this->options->totalFiles++;
174
+
175
+ // Add current file size
176
+ $this->options->totalFileSize += $iterator->getSize();
177
+ }
178
+ }
179
+ }
180
+ } catch ( \Exception $e ) {
181
+ $this->returnException( 'Error: ' . $e->getMessage() );
182
+ //throw new \Exception( 'Error: ' . $e->getMessage() );
183
+ } catch ( \Exception $e ) {
184
+ // Skip bad file permissions
185
+ }
186
+
187
+ // close the file handler
188
+ $this->close( $files );
189
+ return true;
190
+ }
191
+
192
+ /**
193
+ * Step 2
194
+ * @return boolean
195
+ * @throws \Exception
196
+ */
197
+ private function getWpIncludesFiles() {
198
+
199
+ // Skip it
200
+ if( $this->isDirectoryExcluded( \WPStaging\WPStaging::getWPpath() . 'wp-includes' . DIRECTORY_SEPARATOR ) ) {
201
+ $this->log( "Skip " . \WPStaging\WPStaging::getWPpath() . 'wp-includes' . DIRECTORY_SEPARATOR );
202
+ return true;
203
+ }
204
+
205
+ // open file handle and attach data to end of file
206
+ $files = $this->open( $this->filename, 'a' );
207
+
208
+ try {
209
+
210
+ // Iterate over wp-admin directory
211
+ $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( \WPStaging\WPStaging::getWPpath() . 'wp-includes' . DIRECTORY_SEPARATOR );
212
+
213
+ // Exclude new line file names
214
+ $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
215
+
216
+ // Recursively iterate over wp-includes directory
217
+ $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
218
+
219
+ $this->log( "Scanning /wp-includes for its sub-directories and files" );
220
+
221
+ // Write files
222
+ foreach ( $iterator as $item ) {
223
+ if( $item->isFile() ) {
224
+ if( $this->write( $files, 'wp-includes' . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
225
+ $this->options->totalFiles++;
226
+
227
+ // Add current file size
228
+ $this->options->totalFileSize += $iterator->getSize();
229
+ }
230
+ }
231
+ }
232
+ } catch ( \Exception $e ) {
233
+ //$this->returnException('Out of disk space.');
234
+ throw new \Exception( 'Error: ' . $e->getMessage() );
235
+ } catch ( \Exception $e ) {
236
+ // Skip bad file permissions
237
+ }
238
+
239
+ // close the file handler
240
+ $this->close( $files );
241
+ return true;
242
+ }
243
+
244
+ /**
245
+ * Step 3
246
+ * @return boolean
247
+ * @throws \Exception
248
+ */
249
+ private function getWpAdminFiles() {
250
+
251
+ // Skip it
252
+ if( $this->isDirectoryExcluded( \WPStaging\WPStaging::getWPpath() . 'wp-admin' . DIRECTORY_SEPARATOR ) ) {
253
+ $this->log( "Skip " . \WPStaging\WPStaging::getWPpath() . 'wp-admin' . DIRECTORY_SEPARATOR );
254
+ return true;
255
+ }
256
+
257
+ // open file handle and attach data to end of file
258
+ $files = $this->open( $this->filename, 'a' );
259
+
260
+ try {
261
+
262
+ // Iterate over wp-admin directory
263
+ $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( \WPStaging\WPStaging::getWPpath() . 'wp-admin' . DIRECTORY_SEPARATOR );
264
+
265
+ // Exclude new line file names
266
+ $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
267
+
268
+ // Recursively iterate over content directory
269
+ $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
270
+
271
+ $this->log( "Scanning /wp-admin for its sub-directories and files" );
272
+
273
+ // Write path line
274
+ foreach ( $iterator as $item ) {
275
+ if( $item->isFile() ) {
276
+ if( $this->write( $files, 'wp-admin' . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
277
+ $this->options->totalFiles++;
278
+ // Add current file size
279
+ $this->options->totalFileSize += $iterator->getSize();
280
+ }
281
+ }
282
+ }
283
+ } catch ( \Exception $e ) {
284
+ $this->returnException( 'Error: ' . $e->getMessage() );
285
+ //throw new \Exception('Error: ' . $e->getMessage());
286
+ } catch ( \Exception $e ) {
287
+ // Skip bad file permissions
288
+ }
289
+
290
+ // close the file handler
291
+ $this->close( $files );
292
+ return true;
293
+ }
294
+
295
+ /**
296
+ * Step 4
297
+ * Get WP Content Uploads Files multisite folder wp-content/uploads/sites or wp-content/blogs.dir/ID/files
298
+ */
299
+ private function getWpContentUploadsSites() {
300
+
301
+ // Skip if main site is cloned
302
+ if( is_main_site() ) {
303
+ return true;
304
+ }
305
+
306
+ $blogId = get_current_blog_id();
307
+
308
+ // Absolute path to uploads folder
309
+ //$path = WP_CONTENT_DIR . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . $blogId . DIRECTORY_SEPARATOR;
310
+ $path = $this->getAbsUploadPath();
311
+
312
+ // Skip it
313
+ if( !is_dir( $path ) ) {
314
+ return true;
315
+ }
316
+
317
+ // Skip it
318
+ if( $this->isDirectoryExcluded( $path ) ) {
319
+ return true;
320
+ }
321
+
322
+
323
+ // open file handle
324
+ $files = $this->open( $this->filename, 'a' );
325
+
326
+ /**
327
+ * Excluded folders relative to the folder to iterate
328
+ */
329
+ $excludePaths = array(
330
+ 'cache',
331
+ 'plugins' . DIRECTORY_SEPARATOR . 'wps-hide-login',
332
+ 'uploads' . DIRECTORY_SEPARATOR . 'sites'
333
+ );
334
+
335
+ /**
336
+ * Get user excluded folders
337
+ */
338
+ $directory = array();
339
+ foreach ( $this->options->excludedDirectories as $dir ) {
340
+ if( strpos( $dir, $path ) !== false ) {
341
+ $directory[] = ltrim( str_replace( $path, '', $dir ), '/' );
342
+ }
343
+ }
344
+
345
+ $excludePaths = array_merge( $excludePaths, $directory );
346
+
347
+ try {
348
+
349
+ // Iterate over content directory
350
+ $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( $path );
351
+
352
+ // Exclude new line file names
353
+ $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
354
+
355
+ // Exclude sites, uploads, plugins or themes
356
+ $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, $excludePaths );
357
+
358
+ // Recursively iterate over content directory
359
+ $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
360
+ $this->log( "Scanning /wp-content/uploads/sites/{$blogId} for its sub-directories and files" );
361
+
362
+ // Write path line
363
+ foreach ( $iterator as $item ) {
364
+ if( $item->isFile() ) {
365
+ //$file = 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . $blogId . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL;
366
+ $file = $this->getRelUploadPath() . $iterator->getSubPathName() . PHP_EOL;
367
+ if( $this->write( $files, $file ) ) {
368
+ $this->options->totalFiles++;
369
+
370
+ // Add current file size
371
+ $this->options->totalFileSize += $iterator->getSize();
372
+ }
373
+ }
374
+ }
375
+ } catch ( \Exception $e ) {
376
+ $this->returnException( 'Error: ' . $e->getMessage() );
377
+ //throw new \Exception( 'Error: ' . $e->getMessage() );
378
+ } catch ( \Exception $e ) {
379
+ // Skip bad file permissions
380
+ }
381
+
382
+ // close the file handler
383
+ $this->close( $files );
384
+ return true;
385
+ }
386
+
387
+ /**
388
+ * Get absolute path to the upload folder e.g. /srv/www/wp-content/blogs.dir/ID/files or /srv/www/wp-content/uploads/sites/ID/
389
+ * @return type
390
+ */
391
+ private function getAbsUploadPath() {
392
+ // Check first which method is used
393
+ $uploads = wp_upload_dir();
394
+ $basedir = $uploads['basedir'];
395
+
396
+ return trailingslashit($basedir);
397
+
398
+ // if( false === strpos( $basedir, 'blogs.dir' ) ) {
399
+ // // Since WP 3.5
400
+ // return 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . get_current_blog_id();
401
+ // } else {
402
+ // // old blog structure
403
+ // return 'wp-content' . DIRECTORY_SEPARATOR . 'blogs.dir' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR . 'files';
404
+ // }
405
+ }
406
+ /**
407
+ * Get relative path to the upload folder like wp-content/uploads or wp-content/blogs.dir/2/files
408
+ * @return string
409
+ */
410
+ private function getRelUploadPath() {
411
+ $uploads = wp_upload_dir();
412
+ $basedir = $uploads['basedir'];
413
+
414
+ return trailingslashit(str_replace(\WPStaging\WPStaging::getWPpath(), '', $basedir));
415
+ }
416
+
417
+ /**
418
+ * Step 5 - x
419
+ * Get extra folders of the wp root level
420
+ * Does not collect wp-includes, wp-admin and wp-content folder
421
+ */
422
+ private function getExtraFiles( $folder ) {
423
+
424
+
425
+ // open file handle and attach data to end of file
426
+ $files = $this->open( $this->filename, 'a' );
427
+
428
+ try {
429
+
430
+ // Iterate over extra directory
431
+ $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( $folder );
432
+
433
+ // Exclude new line file names
434
+ $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
435
+
436
+ // Exclude wp core folders
437
+ // $exclude = array('wp-includes',
438
+ // 'wp-admin',
439
+ // 'wp-content');
440
+ //
441
+ // $excludeMore = array();
442
+ // foreach ($this->options->excludedDirectories as $key => $value){
443
+ // $excludeMore[] = $this->getLastElemAfterString('/', $value);
444
+ // }
445
+ //$exclude = array_merge($exclude, $excludeMore);
446
+
447
+ $exclude = array();
448
+
449
+ $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, $exclude );
450
+ // Recursively iterate over content directory
451
+ $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
452
+
453
+ $strings = new Strings();
454
+ $this->log( "Scanning {$strings->getLastElemAfterString( '/', $folder )} for its sub-directories and files" );
455
+
456
+ // Write path line
457
+ foreach ( $iterator as $item ) {
458
+ if( $item->isFile() ) {
459
+ //if( $this->write( $files, $strings->getLastElemAfterString( '/', $folder ) . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
460
+ if( $this->write( $files, str_replace( \WPStaging\WPStaging::getWPpath(), '', $folder ) . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
461
+ $this->options->totalFiles++;
462
+ // Add current file size
463
+ $this->options->totalFileSize += $iterator->getSize();
464
+ }
465
+ }
466
+ }
467
+ } catch ( \Exception $e ) {
468
+ $this->returnException( 'Error: ' . $e->getMessage() );
469
+ } catch ( \Exception $e ) {
470
+ // Skip bad file permissions
471
+ }
472
+
473
+ // close the file handler
474
+ $this->close( $files );
475
+ return true;
476
+ }
477
+
478
+ /**
479
+ * Closes a file handle
480
+ *
481
+ * @param resource $handle File handle to close
482
+ * @return boolean
483
+ */
484
+ public function close( $handle ) {
485
+ return @fclose( $handle );
486
+ }
487
+
488
+ /**
489
+ * Opens a file in specified mode
490
+ *
491
+ * @param string $file Path to the file to open
492
+ * @param string $mode Mode in which to open the file
493
+ * @return resource
494
+ * @throws Exception
495
+ */
496
+ public function open( $file, $mode ) {
497
+
498
+ $file_handle = @fopen( $file, $mode );
499
+ if( false === $file_handle ) {
500
+ $this->returnException( sprintf( __( 'Unable to open %s with mode %s', 'wp-staging' ), $file, $mode ) );
501
+ //throw new Exception(sprintf(__('Unable to open %s with mode %s', 'wp-staging'), $file, $mode));
502
+ }
503
+
504
+ return $file_handle;
505
+ }
506
+
507
+ /**
508
+ * Write contents to a file
509
+ *
510
+ * @param resource $handle File handle to write to
511
+ * @param string $content Contents to write to the file
512
+ * @return integer
513
+ * @throws Exception
514
+ * @throws Exception
515
+ */
516
+ public function write( $handle, $content ) {
517
+ $write_result = @fwrite( $handle, $content );
518
+ if( false === $write_result ) {
519
+ if( ( $meta = \stream_get_meta_data( $handle ) ) ) {
520
+ //$this->returnException(sprintf(__('Unable to write to: %s', 'wp-staging'), $meta['uri']));
521
+ throw new \Exception( sprintf( __( 'Unable to write to: %s', 'wp-staging' ), $meta['uri'] ) );
522
+ }
523
+ } elseif( strlen( $content ) !== $write_result ) {
524
+ //$this->returnException(__('Out of disk space.', 'wp-staging'));
525
+ throw new \Exception( __( 'Out of disk space.', 'wp-staging' ) );
526
+ }
527
+
528
+ return $write_result;
529
+ }
530
+
531
+ /**
532
+ * Execute the Current Step
533
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
534
+ * @return bool
535
+ */
536
+ protected function execute() {
537
+
538
+ // No job left to execute
539
+ if( $this->isFinished() ) {
540
+ $this->prepareResponse( true, false );
541
+ return false;
542
+ }
543
+
544
+
545
+ if( $this->options->currentStep == 0 ) {
546
+ $this->getWpRootFiles();
547
+ $this->prepareResponse( false, true );
548
+ return false;
549
+ }
550
+
551
+ if( $this->options->currentStep == 1 ) {
552
+ $this->getWpContentFiles();
553
+ $this->prepareResponse( false, true );
554
+ return false;
555
+ }
556
+
557
+ if( $this->options->currentStep == 2 ) {
558
+ $this->getWpIncludesFiles();
559
+ $this->prepareResponse( false, true );
560
+ return false;
561
+ }
562
+
563
+ if( $this->options->currentStep == 3 ) {
564
+ $this->getWpAdminFiles();
565
+ $this->prepareResponse( false, true );
566
+ return false;
567
+ }
568
+
569
+ if( $this->options->currentStep == 4 ) {
570
+ $this->getWpContentUploadsSites();
571
+ $this->prepareResponse( false, true );
572
+ return false;
573
+ }
574
+
575
+ if( isset( $this->options->extraDirectories[$this->options->currentStep - $this->total] ) ) {
576
+ $this->getExtraFiles( $this->options->extraDirectories[$this->options->currentStep - $this->total] );
577
+ $this->prepareResponse( false, true );
578
+ return false;
579
+ }
580
+
581
+
582
+ // Prepare response
583
+ $this->prepareResponse( false, true );
584
+ // Not finished
585
+ return true;
586
+ }
587
+
588
+ /**
589
+ * Checks Whether There is Any Job to Execute or Not
590
+ * @return bool
591
+ */
592
+ protected function isFinished() {
593
+ if( $this->options->currentStep >= $this->options->totalSteps ) {
594
+ return true;
595
+ }
596
+
597
+ // return (
598
+ // //$this->options->currentStep > $this->total ||
599
+ // $this->options->currentStep >= $this->options->totalSteps
600
+ // );
601
+ }
602
+
603
+ /**
604
+ * Save files
605
+ * @return bool
606
+ */
607
+ protected function saveProgress() {
608
+ return $this->saveOptions();
609
+ }
610
+
611
+ /**
612
+ * Get files
613
+ * @return void
614
+ */
615
+ protected function getFiles() {
616
+ $fileName = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
617
+
618
+ if( false === ($this->files = @file_get_contents( $fileName )) ) {
619
+ $this->files = array();
620
+ return;
621
+ }
622
+
623
+ $this->files = explode( PHP_EOL, $this->files );
624
+ }
625
+
626
+ /**
627
+ * Replace forward slash with current directory separator
628
+ *
629
+ * @param string $path Path
630
+ *
631
+ * @return string
632
+ */
633
+ private function sanitizeDirectorySeparator( $path ) {
634
+ $string = str_replace( "/", "\\", $path );
635
+ return str_replace( '\\\\', '\\', $string );
636
+ }
637
+
638
+ /**
639
+ * Check if directory is excluded
640
+ * @param string $directory
641
+ * @return bool
642
+ */
643
+ protected function isDirectoryExcluded( $directory ) {
644
+ $directory = $this->sanitizeDirectorySeparator( $directory );
645
+ foreach ( $this->options->excludedDirectories as $excludedDirectory ) {
646
+ $excludedDirectory = $this->sanitizeDirectorySeparator( $excludedDirectory );
647
+ if( strpos( trailingslashit( $directory ), trailingslashit( $excludedDirectory ) ) === 0 ) {
648
+ return true;
649
+ }
650
+ }
651
+
652
+ return false;
653
+ }
654
+
655
+ }
apps/Backend/Modules/Jobs/Multisite/Files.php CHANGED
@@ -1,384 +1,383 @@
1
- <?php
2
-
3
- namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
-
5
- use WPStaging\Backend\Modules\Jobs\JobExecutable;
6
- // No Direct Access
7
- use WPStaging\Utils\Logger;
8
-
9
- if( !defined( "WPINC" ) ) {
10
- die;
11
- }
12
-
13
- /**
14
- * Class Files
15
- * @package WPStaging\Backend\Modules\Jobs
16
- */
17
- class Files extends JobExecutable {
18
-
19
- /**
20
- * @var \SplFileObject
21
- */
22
- private $file;
23
-
24
- /**
25
- * @var int
26
- */
27
- private $maxFilesPerRun;
28
-
29
- /**
30
- * @var string
31
- */
32
- private $destination;
33
-
34
- /**
35
- * Initialization
36
- */
37
- public function initialize() {
38
-
39
- $this->destination = \WPStaging\WPStaging::getWPpath() . $this->options->cloneDirectoryName . DIRECTORY_SEPARATOR;
40
-
41
- $filePath = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
42
-
43
- if( is_file( $filePath ) ) {
44
- $this->file = new \SplFileObject( $filePath, 'r' );
45
- }
46
-
47
- // Informational logs
48
- if( 0 == $this->options->currentStep ) {
49
- $this->log( "Copying files..." );
50
- }
51
-
52
- $this->settings->batchSize = $this->settings->batchSize * 1000000;
53
- $this->maxFilesPerRun = $this->settings->fileLimit;
54
- }
55
-
56
- /**
57
- * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
58
- * @return void
59
- */
60
- protected function calculateTotalSteps() {
61
- $this->options->totalSteps = ceil( $this->options->totalFiles / $this->maxFilesPerRun );
62
- }
63
-
64
- /**
65
- * Execute the Current Step
66
- * Returns false when over threshold limits are hit or when the job is done, true otherwise
67
- * @return bool
68
- */
69
- protected function execute() {
70
- // Finished
71
- if( $this->isFinished() ) {
72
- $this->log( "Copying files finished" );
73
- $this->prepareResponse( true, false );
74
- return false;
75
- }
76
-
77
- // Get files and copy'em
78
- if( !$this->getFilesAndCopy() ) {
79
- $this->prepareResponse( false, false );
80
- return false;
81
- }
82
-
83
- // Prepare and return response
84
- $this->prepareResponse();
85
-
86
- // Not finished
87
- return true;
88
- }
89
-
90
- /**
91
- * Get files and copy
92
- * @return bool
93
- */
94
- private function getFilesAndCopy() {
95
- // Over limits threshold
96
- if( $this->isOverThreshold() ) {
97
- // Prepare response and save current progress
98
- $this->prepareResponse( false, false );
99
- $this->saveOptions();
100
- return false;
101
- }
102
-
103
- // Go to last copied line and than to next one
104
- //if ($this->options->copiedFiles != 0) {
105
- if( isset( $this->options->copiedFiles ) && $this->options->copiedFiles != 0 ) {
106
- $this->file->seek( $this->options->copiedFiles - 1 );
107
- }
108
-
109
- $this->file->setFlags( \SplFileObject::SKIP_EMPTY | \SplFileObject::READ_AHEAD );
110
-
111
-
112
- // Loop x files at a time
113
- for ( $i = 0; $i < $this->maxFilesPerRun; $i++ ) {
114
-
115
- // Increment copied files
116
- // Do this anytime to make sure to not stuck in the same step / files
117
- $this->options->copiedFiles++;
118
-
119
- // End of file
120
- if( $this->file->eof() ) {
121
- break;
122
- }
123
-
124
-
125
- $file = $this->file->fgets();
126
-
127
- $this->copyFile( $file );
128
- }
129
-
130
- $totalFiles = $this->options->copiedFiles;
131
- // Log this only every 50 entries to keep the log small and to not block the rendering browser
132
- if( $this->options->copiedFiles % 50 == 0 ) {
133
- $this->log( "Total {$totalFiles} files processed" );
134
- }
135
-
136
- return true;
137
- }
138
-
139
- /**
140
- * Checks Whether There is Any Job to Execute or Not
141
- * @return bool
142
- */
143
- private function isFinished() {
144
- return (
145
- $this->options->currentStep > $this->options->totalSteps ||
146
- $this->options->copiedFiles >= $this->options->totalFiles
147
- );
148
- }
149
-
150
- /**
151
- * @param string $file
152
- * @return bool
153
- */
154
- private function copyFile( $file ) {
155
- $file = trim( \WPStaging\WPStaging::getWPpath() . $file );
156
-
157
- $directory = dirname( $file );
158
-
159
- // Get file size
160
- $fileSize = filesize( $file );
161
-
162
- // Directory is excluded
163
- if( $this->isDirectoryExcluded( $directory ) ) {
164
- $this->debugLog( "Skipping directory by rule: {$file}", Logger::TYPE_INFO );
165
- return false;
166
- }
167
-
168
- // File is excluded
169
- if( $this->isFileExcluded( $file ) ) {
170
- $this->debugLog( "File Excluded: {$file}", Logger::TYPE_INFO );
171
- return false;
172
- }
173
-
174
- // Path + File is excluded
175
- if( $this->isFileExcludedFullPath( $file ) ) {
176
- $this->debugLog( "File Excluded Full Path: {$file}", Logger::TYPE_INFO );
177
- return false;
178
- }
179
-
180
- // File is over maximum allowed file size (8MB)
181
- if( $fileSize >= $this->settings->maxFileSize * 1000000 ) {
182
- $this->log( "Skipping big file: {$file}", Logger::TYPE_INFO );
183
- return false;
184
- }
185
-
186
- // Invalid file, skipping it as if succeeded
187
- if( !is_file( $file ) || !is_readable( $file ) ) {
188
- $this->log( "Can't read file or file doesn't exist {$file}" );
189
- return true;
190
- }
191
-
192
- // Failed to get destination
193
- if( false === ($destination = $this->getDestination( $file )) ) {
194
- $this->log( "Can't get the destination of {$file}" );
195
- return false;
196
- }
197
-
198
- // File is over batch size
199
- if( $fileSize >= $this->settings->batchSize ) {
200
- $this->log( "Trying to copy big file: {$file} -> {$destination}", Logger::TYPE_INFO );
201
- return $this->copyBig( $file, $destination, $this->settings->batchSize );
202
- }
203
-
204
- // Attempt to copy
205
- if( !@copy( $file, $destination ) ) {
206
- $errors = error_get_last();
207
- $this->log( "Files: Failed to copy file to destination. Error: {$errors['message']} {$file} -> {$destination}", Logger::TYPE_ERROR );
208
- return false;
209
- }
210
-
211
- return true;
212
-
213
- // Good old PHP
214
- //return $this->copy($file, $destination);
215
- }
216
-
217
- /**
218
- * Gets destination file and checks if the directory exists, if it does not attempts to create it.
219
- * If creating destination directory fails, it returns false, gives destination full path otherwise
220
- * @param string $file
221
- * @return bool|string
222
- */
223
- private function getDestination( $file ) {
224
- $file = $this->getMultisiteUploadFolder( $file );
225
- $relativePath = str_replace( \WPStaging\WPStaging::getWPpath(), null, $file );
226
- $destinationPath = $this->destination . $relativePath;
227
- $destinationDirectory = dirname( $destinationPath );
228
-
229
- if( !is_dir( $destinationDirectory ) && !@mkdir( $destinationDirectory, 0755, true ) ) {
230
- $this->log( "Files: Can not create directory {$destinationDirectory}", Logger::TYPE_ERROR );
231
- return false;
232
- }
233
-
234
- return $destinationPath;
235
- }
236
-
237
- /**
238
- * Replace relative path of file if its located in multisite upload folder
239
- * wp-content/uploads/sites/SITEID or old wordpress structure wp-content/blogs.dir/SITEID/files
240
- * @return boolean
241
- */
242
- private function getMultisiteUploadFolder( $file ) {
243
- // Check first which method is used
244
- $uploads = wp_upload_dir();
245
- $basedir = $uploads['basedir'];
246
-
247
- if( false === strpos( $basedir, 'blogs.dir' ) ) {
248
- // Since WP 3.5
249
- $search = 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . get_current_blog_id();
250
- $replace = 'wp-content' . DIRECTORY_SEPARATOR . 'uploads';
251
- $uploadsFolder = str_replace( $search, $replace, $file );
252
- } else {
253
- // old blog structure
254
- $search = 'wp-content' . DIRECTORY_SEPARATOR . 'blogs.dir' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR . 'files';
255
- $replace = 'wp-content' . DIRECTORY_SEPARATOR . 'uploads';
256
- $uploadsFolder = str_replace( $search, $replace, $file );
257
- }
258
-
259
- return $uploadsFolder;
260
- }
261
-
262
- /**
263
- * Copy bigger files than $this->settings->batchSize
264
- * @param string $src
265
- * @param string $dst
266
- * @param int $buffersize
267
- * @return boolean
268
- */
269
- private function copyBig( $src, $dst, $buffersize ) {
270
- $src = fopen( $src, 'r' );
271
- $dest = fopen( $dst, 'w' );
272
-
273
- // Try first method:
274
- while ( !feof( $src ) ) {
275
- if( false === fwrite( $dest, fread( $src, $buffersize ) ) ) {
276
- $error = true;
277
- }
278
- }
279
- // Try second method if first one failed
280
- if( isset( $error ) && ($error === true) ) {
281
- while ( !feof( $src ) ) {
282
- if( false === stream_copy_to_stream( $src, $dest, 1024 ) ) {
283
- $this->log( "Can not copy big file; {$src} -> {$dest}" );
284
- fclose( $src );
285
- fclose( $dest );
286
- return false;
287
- }
288
- }
289
- }
290
- // Close any open handler
291
- fclose( $src );
292
- fclose( $dest );
293
- return true;
294
- }
295
-
296
- /**
297
- * Check if file is excluded from copying process
298
- *
299
- * @param string $file filename including ending
300
- * @return boolean
301
- */
302
- private function isFileExcluded( $file ) {
303
-
304
- if( in_array( basename( $file ), $this->options->excludedFiles ) ) {
305
- return true;
306
- }
307
- // Do not copy wp-config.php if the clone gets updated. This is for security purposes,
308
- // because if the updating process fails, the staging site is not accessable any longer
309
- if( isset( $this->options->mainJob ) && $this->options->mainJob == "updating" && stripos( strrev( $file ), strrev( "wp-config.php" ) ) === 0 ) {
310
- return true;
311
- }
312
-
313
-
314
- return false;
315
- }
316
-
317
- /**
318
- * Check if certain file is excluded from copying process
319
- *
320
- * @param string $file filename including ending + (part) path e.g wp-content/db.php
321
- * @return boolean
322
- */
323
- private function isFileExcludedFullPath( $file ) {
324
- // If path + file exists
325
- foreach ( $this->options->excludedFilesFullPath as $excludedFile ) {
326
- if( false !== strpos( $file, $excludedFile ) ) {
327
- return true;
328
- }
329
- }
330
-
331
- return false;
332
- }
333
-
334
- /**
335
- * Replace forward slash with current directory separator
336
- * Windows Compatibility Fix
337
- * @param string $path Path
338
- *
339
- * @return string
340
- */
341
- private function sanitizeDirectorySeparator( $path ) {
342
- $string = str_replace( "/", "\\", $path );
343
- return str_replace( '\\\\', '\\', $string );
344
- }
345
-
346
- /**
347
- * Check if directory is excluded from copying
348
- * @param string $directory
349
- * @return bool
350
- */
351
- private function isDirectoryExcluded( $directory ) {
352
- // Make sure that wp-staging-pro directory / plugin is never excluded
353
- if( false !== strpos( $directory, 'wp-staging' ) || false !== strpos( $directory, 'wp-staging-pro' ) ) {
354
- return false;
355
- }
356
-
357
- $directory = $this->sanitizeDirectorySeparator( $directory );
358
-
359
- foreach ( $this->options->excludedDirectories as $excludedDirectory ) {
360
- $excludedDirectory = $this->sanitizeDirectorySeparator( $excludedDirectory );
361
- if( strpos( $directory, $excludedDirectory ) === 0 && !$this->isExtraDirectory( $directory ) ) {
362
- return true;
363
- }
364
- }
365
-
366
- return false;
367
- }
368
-
369
- /**
370
- * Check if directory is an extra directory and should be copied
371
- * @param string $directory
372
- * @return boolean
373
- */
374
- private function isExtraDirectory( $directory ) {
375
- foreach ( $this->options->extraDirectories as $extraDirectory ) {
376
- if( strpos( $directory, $extraDirectory ) === 0 ) {
377
- return true;
378
- }
379
- }
380
-
381
- return false;
382
- }
383
-
384
- }
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
+
5
+ use WPStaging\Backend\Modules\Jobs\JobExecutable;
6
+ // No Direct Access
7
+ use WPStaging\Utils\Logger;
8
+
9
+ if( !defined( "WPINC" ) ) {
10
+ die;
11
+ }
12
+
13
+ /**
14
+ * Class Files
15
+ * @package WPStaging\Backend\Modules\Jobs
16
+ */
17
+ class Files extends JobExecutable {
18
+
19
+ /**
20
+ * @var \SplFileObject
21
+ */
22
+ private $file;
23
+
24
+ /**
25
+ * @var int
26
+ */
27
+ private $maxFilesPerRun;
28
+
29
+ /**
30
+ * @var string
31
+ */
32
+ private $destination;
33
+
34
+ /**
35
+ * Initialization
36
+ */
37
+ public function initialize() {
38
+
39
+ $this->destination = \WPStaging\WPStaging::getWPpath() . $this->options->cloneDirectoryName . DIRECTORY_SEPARATOR;
40
+
41
+ $filePath = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
42
+
43
+ if( is_file( $filePath ) ) {
44
+ $this->file = new \SplFileObject( $filePath, 'r' );
45
+ }
46
+
47
+ // Informational logs
48
+ if( 0 == $this->options->currentStep ) {
49
+ $this->log( "Copying files..." );
50
+ }
51
+
52
+ $this->settings->batchSize = $this->settings->batchSize * 1000000;
53
+ $this->maxFilesPerRun = $this->settings->fileLimit;
54
+ }
55
+
56
+ /**
57
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
58
+ * @return void
59
+ */
60
+ protected function calculateTotalSteps() {
61
+ $this->options->totalSteps = ceil( $this->options->totalFiles / $this->maxFilesPerRun );
62
+ }
63
+
64
+ /**
65
+ * Execute the Current Step
66
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
67
+ * @return bool
68
+ */
69
+ protected function execute() {
70
+ // Finished
71
+ if( $this->isFinished() ) {
72
+ $this->log( "Copying files finished" );
73
+ $this->prepareResponse( true, false );
74
+ return false;
75
+ }
76
+
77
+ // Get files and copy'em
78
+ if( !$this->getFilesAndCopy() ) {
79
+ $this->prepareResponse( false, false );
80
+ return false;
81
+ }
82
+
83
+ // Prepare and return response
84
+ $this->prepareResponse();
85
+
86
+ // Not finished
87
+ return true;
88
+ }
89
+
90
+ /**
91
+ * Get files and copy
92
+ * @return bool
93
+ */
94
+ private function getFilesAndCopy() {
95
+ // Over limits threshold
96
+ if( $this->isOverThreshold() ) {
97
+ // Prepare response and save current progress
98
+ $this->prepareResponse( false, false );
99
+ $this->saveOptions();
100
+ return false;
101
+ }
102
+
103
+ // Go to last copied line and than to next one
104
+ //if ($this->options->copiedFiles != 0) {
105
+ if( isset( $this->options->copiedFiles ) && $this->options->copiedFiles != 0 ) {
106
+ $this->file->seek( $this->options->copiedFiles - 1 );
107
+ }
108
+
109
+ $this->file->setFlags( \SplFileObject::SKIP_EMPTY | \SplFileObject::READ_AHEAD );
110
+
111
+
112
+ // Loop x files at a time
113
+ for ( $i = 0; $i < $this->maxFilesPerRun; $i++ ) {
114
+
115
+ // Increment copied files
116
+ // Do this anytime to make sure to not stuck in the same step / files
117
+ $this->options->copiedFiles++;
118
+
119
+ // End of file
120
+ if( $this->file->eof() ) {
121
+ break;
122
+ }
123
+
124
+
125
+ $file = $this->file->fgets();
126
+
127
+ $this->copyFile( $file );
128
+ }
129
+
130
+ $totalFiles = $this->options->copiedFiles;
131
+ // Log this only every 50 entries to keep the log small and to not block the rendering browser
132
+ if( $this->options->copiedFiles % 50 == 0 ) {
133
+ $this->log( "Total {$totalFiles} files processed" );
134
+ }
135
+
136
+ return true;
137
+ }
138
+
139
+ /**
140
+ * Checks Whether There is Any Job to Execute or Not
141
+ * @return bool
142
+ */
143
+ private function isFinished() {
144
+ return (
145
+ $this->options->currentStep > $this->options->totalSteps ||
146
+ $this->options->copiedFiles >= $this->options->totalFiles
147
+ );
148
+ }
149
+
150
+ /**
151
+ * @param string $file
152
+ * @return bool
153
+ */
154
+ private function copyFile( $file ) {
155
+ $file = trim( \WPStaging\WPStaging::getWPpath() . $file );
156
+
157
+ $directory = dirname( $file );
158
+
159
+ // Get file size
160
+ $fileSize = filesize( $file );
161
+
162
+ // Directory is excluded
163
+ if( $this->isDirectoryExcluded( $directory ) ) {
164
+ $this->debugLog( "Skipping directory by rule: {$file}", Logger::TYPE_INFO );
165
+ return false;
166
+ }
167
+
168
+ // File is excluded
169
+ if( $this->isFileExcluded( $file ) ) {
170
+ $this->log( "Skipping file by rule: {$file}", Logger::TYPE_INFO );
171
+ return false;
172
+ }
173
+ // Path + File is excluded
174
+ if( $this->isFileExcludedFullPath( $file ) ) {
175
+ $this->log( "Skipping file by rule: {$file}", Logger::TYPE_INFO );
176
+ return false;
177
+ }
178
+
179
+ // File is over maximum allowed file size (8MB)
180
+ if( $fileSize >= $this->settings->maxFileSize * 1000000 ) {
181
+ $this->log( "Skipping big file: {$file}", Logger::TYPE_INFO );
182
+ return false;
183
+ }
184
+
185
+ // Invalid file, skipping it as if succeeded
186
+ if( !is_file( $file ) || !is_readable( $file ) ) {
187
+ $this->log( "Can't read file or file doesn't exist {$file}" );
188
+ return true;
189
+ }
190
+
191
+ // Failed to get destination
192
+ if( false === ($destination = $this->getDestination( $file )) ) {
193
+ $this->log( "Can't get the destination of {$file}" );
194
+ return false;
195
+ }
196
+
197
+ // File is over batch size
198
+ if( $fileSize >= $this->settings->batchSize ) {
199
+ $this->log( "Trying to copy big file: {$file} -> {$destination}", Logger::TYPE_INFO );
200
+ return $this->copyBig( $file, $destination, $this->settings->batchSize );
201
+ }
202
+
203
+ // Attempt to copy
204
+ if( !@copy( $file, $destination ) ) {
205
+ $errors = error_get_last();
206
+ $this->log( "Files: Failed to copy file to destination. Error: {$errors['message']} {$file} -> {$destination}", Logger::TYPE_ERROR );
207
+ return false;
208
+ }
209
+
210
+ return true;
211
+
212
+ // Good old PHP
213
+ //return $this->copy($file, $destination);
214
+ }
215
+
216
+ /**
217
+ * Gets destination file and checks if the directory exists, if it does not attempts to create it.
218
+ * If creating destination directory fails, it returns false, gives destination full path otherwise
219
+ * @param string $file
220
+ * @return bool|string
221
+ */
222
+ private function getDestination( $file ) {
223
+ $file = $this->getMultisiteUploadFolder( $file );
224
+ $relativePath = str_replace( \WPStaging\WPStaging::getWPpath(), null, $file );
225
+ $destinationPath = $this->destination . $relativePath;
226
+ $destinationDirectory = dirname( $destinationPath );
227
+
228
+ if( !is_dir( $destinationDirectory ) && !@mkdir( $destinationDirectory, 0775, true ) ) {
229
+ $this->log( "Files: Can not create directory {$destinationDirectory}", Logger::TYPE_ERROR );
230
+ return false;
231
+ }
232
+
233
+ return $destinationPath;
234
+ }
235
+
236
+ /**
237
+ * Replace relative path of file if its located in multisite upload folder
238
+ * wp-content/uploads/sites/SITEID or old wordpress structure wp-content/blogs.dir/SITEID/files
239
+ * @return boolean
240
+ */
241
+ private function getMultisiteUploadFolder( $file ) {
242
+ // Check first which method is used
243
+ $uploads = wp_upload_dir();
244
+ $basedir = $uploads['basedir'];
245
+
246
+ if( false === strpos( $basedir, 'blogs.dir' ) ) {
247
+ // Since WP 3.5
248
+ $search = 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . get_current_blog_id();
249
+ $replace = 'wp-content' . DIRECTORY_SEPARATOR . 'uploads';
250
+ $uploadsFolder = str_replace( $search, $replace, $file );
251
+ } else {
252
+ // old blog structure
253
+ $search = 'wp-content' . DIRECTORY_SEPARATOR . 'blogs.dir' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR . 'files';
254
+ $replace = 'wp-content' . DIRECTORY_SEPARATOR . 'uploads';
255
+ $uploadsFolder = str_replace( $search, $replace, $file );
256
+ }
257
+
258
+ return $uploadsFolder;
259
+ }
260
+
261
+ /**
262
+ * Copy bigger files than $this->settings->batchSize
263
+ * @param string $src
264
+ * @param string $dst
265
+ * @param int $buffersize
266
+ * @return boolean
267
+ */
268
+ private function copyBig( $src, $dst, $buffersize ) {
269
+ $src = fopen( $src, 'r' );
270
+ $dest = fopen( $dst, 'w' );
271
+
272
+ // Try first method:
273
+ while ( !feof( $src ) ) {
274
+ if( false === fwrite( $dest, fread( $src, $buffersize ) ) ) {
275
+ $error = true;
276
+ }
277
+ }
278
+ // Try second method if first one failed
279
+ if( isset( $error ) && ($error === true) ) {
280
+ while ( !feof( $src ) ) {
281
+ if( false === stream_copy_to_stream( $src, $dest, 1024 ) ) {
282
+ $this->log( "Can not copy big file; {$src} -> {$dest}" );
283
+ fclose( $src );
284
+ fclose( $dest );
285
+ return false;
286
+ }
287
+ }
288
+ }
289
+ // Close any open handler
290
+ fclose( $src );
291
+ fclose( $dest );
292
+ return true;
293
+ }
294
+
295
+ /**
296
+ * Check if certain file is excluded from copying process
297
+ *
298
+ * @param string $file filename including ending without full path
299
+ * @return boolean
300
+ */
301
+ private function isFileExcluded( $file ) {
302
+ // If file name exists
303
+ if ( in_array( basename($file), $this->options->excludedFiles )){
304
+ return true;
305
+ }
306
+
307
+ // Do not copy wp-config.php if the clone gets updated. This is for security purposes,
308
+ // because if the updating process fails, the staging site is not accessable any longer
309
+ if( isset( $this->options->mainJob ) && $this->options->mainJob == "updating" && stripos( strrev( $file ), strrev( "wp-config.php" ) ) === 0 ) {
310
+ return true;
311
+ }
312
+
313
+ return false;
314
+ }
315
+
316
+ /**
317
+ * Check if certain file is excluded from copying process
318
+ *
319
+ * @param string $file filename including ending + (part) path e.g wp-content/db.php
320
+ * @return boolean
321
+ */
322
+ private function isFileExcludedFullPath( $file ) {
323
+ // If path + file exists
324
+ foreach ($this->options->excludedFilesFullPath as $excludedFile){
325
+ if( false !== strpos( $file, $excludedFile )){
326
+ return true;
327
+ }
328
+ }
329
+
330
+ return false;
331
+ }
332
+
333
+ /**
334
+ * Replace forward slash with current directory separator
335
+ * Windows Compatibility Fix
336
+ * @param string $path Path
337
+ *
338
+ * @return string
339
+ */
340
+ private function sanitizeDirectorySeparator( $path ) {
341
+ $string = str_replace( "/", "\\", $path );
342
+ return str_replace( '\\\\', '\\', $string );
343
+ }
344
+
345
+ /**
346
+ * Check if directory is excluded from copying
347
+ * @param string $directory
348
+ * @return bool
349
+ */
350
+ private function isDirectoryExcluded( $directory ) {
351
+ // Make sure that wp-staging-pro directory / plugin is never excluded
352
+ if( false !== strpos( $directory, 'wp-staging' ) || false !== strpos( $directory, 'wp-staging-pro' ) ) {
353
+ return false;
354
+ }
355
+
356
+ $directory = $this->sanitizeDirectorySeparator( $directory );
357
+
358
+ foreach ( $this->options->excludedDirectories as $excludedDirectory ) {
359
+ $excludedDirectory = $this->sanitizeDirectorySeparator( $excludedDirectory );
360
+ if( strpos( $directory, $excludedDirectory ) === 0 && !$this->isExtraDirectory( $directory ) ) {
361
+ return true;
362
+ }
363
+ }
364
+
365
+ return false;
366
+ }
367
+
368
+ /**
369
+ * Check if directory is an extra directory and should be copied
370
+ * @param string $directory
371
+ * @return boolean
372
+ */
373
+ private function isExtraDirectory( $directory ) {
374
+ foreach ( $this->options->extraDirectories as $extraDirectory ) {
375
+ if( strpos( $directory, $extraDirectory ) === 0 ) {
376
+ return true;
377
+ }
378
+ }
379
+
380
+ return false;
381
+ }
382
+
383
+ }
 
apps/Backend/Modules/Jobs/Multisite/Finish.php CHANGED
@@ -1,116 +1,121 @@
1
- <?php
2
- namespace WPStaging\Backend\Modules\Jobs\Multisite;
3
-
4
- use WPStaging\WPStaging;
5
- use WPStaging\Backend\Modules\Jobs\Job;
6
- use WPStaging\Utils\Multisite;
7
-
8
- //error_reporting( E_ALL );
9
-
10
- /**
11
- * Class Finish
12
- * @package WPStaging\Backend\Modules\Jobs
13
- */
14
- class Finish extends Job
15
- {
16
- /**
17
- * Clone Key
18
- * @var string
19
- */
20
- private $clone = '';
21
-
22
-
23
-
24
- /**
25
- * Start Module
26
- * @return object
27
- */
28
- public function start()
29
- {
30
- // sanitize the clone name before saving
31
- $this->clone = preg_replace("#\W+#", '-', strtolower($this->options->clone));
32
-
33
- // Delete Cache Files
34
- $this->deleteCacheFiles();
35
-
36
- // Prepare clone records & save scanned directories for delete job later
37
- $this->prepareCloneDataRecords();
38
-
39
- $multisite = new Multisite;
40
-
41
- $return = array(
42
- "directoryName" => $this->options->cloneDirectoryName,
43
- "path" => ABSPATH . $this->options->cloneDirectoryName,
44
- "url" => $this->multisiteHomeDomain . '/' . $this->options->cloneDirectoryName,
45
- //"url" => $this->multisiteUrl . '/' . $this->options->cloneDirectoryName,
46
- "number" => $this->options->cloneNumber,
47
- "version" => \WPStaging\WPStaging::VERSION,
48
- "status" => 'finished',
49
- "prefix" => $this->options->prefix,
50
- "last_msg" => $this->logger->getLastLogMsg(),
51
- "job" => $this->options->currentJob,
52
- "percentage" => 100
53
-
54
- );
55
-
56
- //$this->flush();
57
-
58
- return (object) $return;
59
- }
60
-
61
- /**
62
- * Delete Cache Files
63
- */
64
- protected function deleteCacheFiles()
65
- {
66
- $this->log("Finish: Deleting clone job's cache files...");
67
-
68
- // Clean cache files
69
- $this->cache->delete("clone_options");
70
- $this->cache->delete("files_to_copy");
71
-
72
- $this->log("Finish: Clone job's cache files have been deleted!");
73
- }
74
-
75
- /**
76
- * Prepare clone records
77
- * @return bool
78
- */
79
- protected function prepareCloneDataRecords()
80
- {
81
- // Check if clones still exist
82
- $this->log("Finish: Verifying existing clones...");
83
-
84
- // Clone data already exists
85
- if (isset($this->options->existingClones[$this->options->clone]))
86
- {
87
- $this->log("Finish: Clone data already exists, no need to update, the job finished");
88
- return true;
89
- }
90
-
91
- // Save new clone data
92
- $this->log("Finish: {$this->options->clone}'s clone job's data is not in database, generating data");
93
-
94
- // sanitize the clone name before saving
95
- //$clone = preg_replace("#\W+#", '-', strtolower($this->options->clone));
96
-
97
- $this->options->existingClones[$this->clone] = array(
98
- "directoryName" => $this->options->cloneDirectoryName,
99
- "path" => ABSPATH . $this->options->cloneDirectoryName,
100
- //"url" => get_site_url() . '/' . $this->options->cloneDirectoryName,
101
- "url" => $this->multisiteHomeDomain . '/' . $this->options->cloneDirectoryName,
102
- "number" => $this->options->cloneNumber,
103
- "version" => \WPStaging\WPStaging::VERSION,
104
- "status" => false,
105
- "prefix" => $this->options->prefix,
106
- );
107
-
108
- if (false === update_option("wpstg_existing_clones_beta", $this->options->existingClones))
109
- {
110
- $this->log("Finish: Failed to save {$this->options->clone}'s clone job data to database'");
111
- return false;
112
- }
113
-
114
- return true;
115
- }
 
 
 
 
 
116
  }
1
+ <?php
2
+ namespace WPStaging\Backend\Modules\Jobs\Multisite;
3
+
4
+ use WPStaging\WPStaging;
5
+ use WPStaging\Backend\Modules\Jobs\Job;
6
+ use WPStaging\Utils\Multisite;
7
+
8
+
9
+ /**
10
+ * Class Finish
11
+ * @package WPStaging\Backend\Modules\Jobs
12
+ */
13
+ class Finish extends Job
14
+ {
15
+ /**
16
+ * Clone Key
17
+ * @var string
18
+ */
19
+ private $clone = '';
20
+
21
+
22
+
23
+ /**
24
+ * Start Module
25
+ * @return object
26
+ */
27
+ public function start()
28
+ {
29
+ // sanitize the clone name before saving
30
+ $this->clone = preg_replace("#\W+#", '-', strtolower($this->options->clone));
31
+
32
+ // Delete Cache Files
33
+ $this->deleteCacheFiles();
34
+
35
+ // Prepare clone records & save scanned directories for delete job later
36
+ $this->prepareCloneDataRecords();
37
+
38
+ $multisite = new Multisite;
39
+
40
+ $return = array(
41
+ "directoryName" => $this->options->cloneDirectoryName,
42
+ "path" => \WPStaging\WPStaging::getWPpath() . $this->options->cloneDirectoryName,
43
+ "url" => $this->multisiteHomeDomain . '/' . $this->options->cloneDirectoryName,
44
+ "number" => $this->options->cloneNumber,
45
+ "version" => \WPStaging\WPStaging::VERSION,
46
+ "status" => 'finished',
47
+ "prefix" => $this->options->prefix,
48
+ "last_msg" => $this->logger->getLastLogMsg(),
49
+ "job" => $this->options->currentJob,
50
+ "percentage" => 100
51
+
52
+ );
53
+
54
+ //$this->flush();
55
+
56
+ return (object) $return;
57
+ }
58
+
59
+ /**
60
+ * Delete Cache Files
61
+ */
62
+ protected function deleteCacheFiles()
63
+ {
64
+ $this->log("Finish: Deleting clone job's cache files...");
65
+
66
+ // Clean cache files
67
+ $this->cache->delete("clone_options");
68
+ $this->cache->delete("files_to_copy");
69
+
70
+ $this->log("Finish: Clone job's cache files have been deleted!");
71
+ }
72
+
73
+ /**
74
+ * Prepare clone records
75
+ * @return bool
76
+ */
77
+ protected function prepareCloneDataRecords()
78
+ {
79
+ // Check if clones still exist
80
+ $this->log("Finish: Verifying existing clones...");
81
+
82
+ // Clone data already exists
83
+ if (isset($this->options->existingClones[$this->options->clone]))
84
+ {
85
+ $this->options->existingClones[$this->options->clone]['datetime'] = time();
86
+ update_option("wpstg_existing_clones_beta", $this->options->existingClones);
87
+ $this->log("Finish: The job finished!");
88
+ return true;
89
+ }
90
+
91
+ // Save new clone data
92
+ $this->log("Finish: {$this->options->clone}'s clone job's data is not in database, generating data");
93
+
94
+ // sanitize the clone name before saving
95
+ //$clone = preg_replace("#\W+#", '-', strtolower($this->options->clone));
96
+
97
+ $this->options->existingClones[$this->clone] = array(
98
+ "directoryName" => $this->options->cloneDirectoryName,
99
+ "path" => \WPStaging\WPStaging::getWPpath() . $this->options->cloneDirectoryName,
100
+ "url" => $this->multisiteHomeDomain . '/' . $this->options->cloneDirectoryName,
101
+ "number" => $this->options->cloneNumber,
102
+ "version" => \WPStaging\WPStaging::VERSION,
103
+ "status" => false,
104
+ "prefix" => $this->options->prefix,
105
+ "datetime" => time(),
106
+ "databaseUser" => $this->options->databaseUser,
107
+ "databasePassword" => $this->options->databasePassword,
108
+ "databaseDatabase" => $this->options->databaseDatabase,
109
+ "databaseServer" => $this->options->databaseServer,
110
+ "databasePrefix" => $this->options->databasePrefix,
111
+ );
112
+
113
+ if (false === update_option("wpstg_existing_clones_beta", $this->options->existingClones))
114
+ {
115
+ $this->log("Finish: Failed to save {$this->options->clone}'s clone job data to database'");
116
+ return false;
117
+ }
118
+
119
+ return true;
120
+ }
121
  }
apps/Backend/Modules/Jobs/Multisite/SearchReplace.php CHANGED
@@ -1,776 +1,775 @@
1
- <?php
2
-
3
- namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
-
5
- // No Direct Access
6
- if( !defined( "WPINC" ) ) {
7
- die;
8
- }
9
-
10
- use WPStaging\WPStaging;
11
- use WPStaging\Utils\Strings;
12
- use WPStaging\Utils\Helper;
13
- use WPStaging\Utils\Multisite;
14
- use WPStaging\Backend\Modules\Jobs\JobExecutable;
15
-
16
- /**
17
- * Class Database
18
- * @package WPStaging\Backend\Modules\Jobs
19
- */
20
- class SearchReplace extends JobExecutable {
21
-
22
- /**
23
- * @var int
24
- */
25
- private $total = 0;
26
-
27
- /**
28
- * @var \WPDB
29
- */
30
- public $db;
31
-
32
- /**
33
- * The prefix of the new database tables which are used for the live site after updating tables
34
- * @var string
35
- */
36
- public $tmpPrefix;
37
-
38
- /**
39
- * Initialize
40
- */
41
- public function initialize() {
42
- $this->total = count( $this->options->tables );
43
- $this->db = WPStaging::getInstance()->get( "wpdb" );
44
- $this->tmpPrefix = $this->options->prefix;
45
- }
46
-
47
- public function start() {
48
- // Skip job. Nothing to do
49
- if( $this->options->totalSteps === 0 ) {
50
- $this->prepareResponse( true, false );
51
- }
52
-
53
- $this->run();
54
-
55
- // Save option, progress
56
- $this->saveOptions();
57
-
58
- return ( object ) $this->response;
59
- }
60
-
61
- /**
62
- * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
63
- * @return void
64
- */
65
- protected function calculateTotalSteps() {
66
- $this->options->totalSteps = $this->total;
67
- }
68
-
69
- /**
70
- * Execute the Current Step
71
- * Returns false when over threshold limits are hit or when the job is done, true otherwise
72
- * @return bool
73
- */
74
- protected function execute() {
75
- // Over limits threshold
76
- if( $this->isOverThreshold() ) {
77
- // Prepare response and save current progress
78
- $this->prepareResponse( false, false );
79
- $this->saveOptions();
80
- return false;
81
- }
82
-
83
- // No more steps, finished
84
- if( $this->options->currentStep > $this->total || !isset( $this->options->tables[$this->options->currentStep] ) ) {
85
- $this->prepareResponse( true, false );
86
- return false;
87
- }
88
-
89
- // Table is excluded
90
- if( in_array( $this->options->tables[$this->options->currentStep], $this->options->excludedTables ) ) {
91
- $this->prepareResponse();
92
- return true;
93
- }
94
-
95
- // Search & Replace
96
- if( !$this->stopExecution() && !$this->updateTable( $this->options->tables[$this->options->currentStep] ) ) {
97
- // Prepare Response
98
- $this->prepareResponse( false, false );
99
-
100
- // Not finished
101
- return true;
102
- }
103
-
104
-
105
- // Prepare Response
106
- $this->prepareResponse();
107
-
108
- // Not finished
109
- return true;
110
- }
111
-
112
- /**
113
- * Stop Execution immediately
114
- * return mixed bool | json
115
- */
116
- private function stopExecution() {
117
- if( $this->db->prefix == $this->tmpPrefix ) {
118
- $this->returnException( 'Fatal Error 9: Prefix ' . $this->db->prefix . ' is used for the live site hence it can not be used for the staging site as well. Please ask support@wp-staging.com how to resolve this.' );
119
- }
120
- return false;
121
- }
122
-
123
- /**
124
- * Copy Tables
125
- * @param string $tableName
126
- * @return bool
127
- */
128
- private function updateTable( $tableName ) {
129
- $strings = new Strings();
130
- $table = $strings->str_replace_first( $this->db->prefix, '', $tableName );
131
- $newTableName = $this->tmpPrefix . $table;
132
-
133
- // Save current job
134
- $this->setJob( $newTableName );
135
-
136
- // Beginning of the job
137
- if( !$this->startJob( $newTableName, $tableName ) ) {
138
- return true;
139
- }
140
- // Copy data
141
- $this->startReplace( $newTableName );
142
-
143
- // Finis the step
144
- return $this->finishStep();
145
- }
146
-
147
- /**
148
- * Start search replace job
149
- * @param string $new
150
- * @param string $old
151
- */
152
- private function startReplace( $new ) {
153
- $rows = $this->options->job->start + $this->settings->querySRLimit;
154
- $this->log(
155
- "DB Processing: Table {$new} {$this->options->job->start} to {$rows} records"
156
- );
157
-
158
- // Search & Replace
159
- $this->searchReplace( $new, $rows, array() );
160
-
161
- // Set new offset
162
- $this->options->job->start += $this->settings->querySRLimit;
163
- }
164
-
165
- /**
166
- * Returns the number of pages in a table.
167
- * @access public
168
- * @return int
169
- */
170
- private function get_pages_in_table( $table ) {
171
- $table = esc_sql( $table );
172
- $rows = $this->db->get_var( "SELECT COUNT(*) FROM $table" );
173
- $pages = ceil( $rows / $this->settings->querySRLimit );
174
- return absint( $pages );
175
- }
176
-
177
- /**
178
- * Gets the columns in a table.
179
- * @access public
180
- * @param string $table The table to check.
181
- * @return array
182
- */
183
- private function get_columns( $table ) {
184
- $primary_key = null;
185
- $columns = array();
186
- $fields = $this->db->get_results( 'DESCRIBE ' . $table );
187
- if( is_array( $fields ) ) {
188
- foreach ( $fields as $column ) {
189
- $columns[] = $column->Field;
190
- if( $column->Key == 'PRI' ) {
191
- $primary_key = $column->Field;
192
- }
193
- }
194
- }
195
- return array($primary_key, $columns);
196
- }
197
-
198
- /**
199
- * Adapated from interconnect/it's search/replace script, adapted from Better Search Replace
200
- *
201
- * Modified to use WordPress wpdb functions instead of PHP's native mysql/pdo functions,
202
- * and to be compatible with batch processing.
203
- *
204
- * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
205
- *
206
- * @access public
207
- * @param string $table The table to run the replacement on.
208
- * @param int $page The page/block to begin the query on.
209
- * @param array $args An associative array containing arguements for this run.
210
- * @return array
211
- */
212
- private function searchReplace( $table, $page, $args ) {
213
-
214
- if( $this->thirdParty->isSearchReplaceExcluded( $table ) ) {
215
- $this->log( "DB Processing: Skip {$table}", \WPStaging\Utils\Logger::TYPE_INFO );
216
- return true;
217
- }
218
-
219
- // Load up the default settings for this chunk.
220
- $table = esc_sql( $table );
221
- $current_page = $this->options->job->start + $this->settings->querySRLimit;
222
- $pages = $this->get_pages_in_table( $table );
223
- //$done = false;
224
-
225
-
226
- if( $this->isSubDir() ) {
227
- // Search URL example.com/staging and root path to staging site /var/www/htdocs/staging
228
- $args['search_for'] = array(
229
- rtrim( $this->multisiteHomeUrlWithoutScheme, "/" ) . $this->getSubDir(),
230
- ABSPATH,
231
- str_replace( '/', '\/', rtrim( $this->multisiteHomeUrlWithoutScheme, '/' ) ) . str_replace( '/', '\/', $this->getSubDir() ), // // Used by revslider and several visual editors
232
- $this->getImagePathLive()
233
- );
234
-
235
-
236
- $args['replace_with'] = array(
237
- rtrim( $this->multisiteDomainWithoutScheme, "/" ) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName,
238
- rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName,
239
- str_replace( '/', '\/', rtrim( $this->multisiteDomainWithoutScheme, "/" ) ) . str_replace( '/', '\/', $this->getSubDir() ) . '\/' . $this->options->cloneDirectoryName, // Used by revslider and several visual editors
240
- $this->getImagePathStaging()
241
- );
242
- } else {
243
- $args['search_for'] = array(
244
- rtrim( $this->multisiteHomeUrlWithoutScheme, '/' ),
245
- ABSPATH,
246
- str_replace( '/', '\/', rtrim( $this->multisiteHomeUrlWithoutScheme, '/' ) ),
247
- $this->getImagePathLive()
248
- );
249
- $args['replace_with'] = array(
250
- rtrim( $this->multisiteDomainWithoutScheme, '/' ) . '/' . $this->options->cloneDirectoryName,
251
- rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName,
252
- str_replace( '/', '\/', rtrim( $this->multisiteDomainWithoutScheme, '/' ) ) . '\/' . $this->options->cloneDirectoryName,
253
- $this->getImagePathStaging()
254
- );
255
- }
256
-
257
- //$this->log( 'Search: ' . $this->multisiteHomeUrlWithoutScheme . ' Replace: ' . rtrim( $this->multisiteDomainWithoutScheme, '/' ) . '/' . $this->options->cloneDirectoryName );
258
-
259
-
260
- $args['replace_guids'] = 'off';
261
- $args['dry_run'] = 'off';
262
- $args['case_insensitive'] = false;
263
- $args['replace_guids'] = 'off';
264
- $args['replace_mails'] = 'off';
265
-
266
- // Allow filtering of search & replace parameters
267
- $args = apply_filters( 'wpstg_clone_searchreplace_params', $args );
268
-
269
- // Get a list of columns in this table.
270
- list( $primary_key, $columns ) = $this->get_columns( $table );
271
-
272
- // Bail out early if there isn't a primary key.
273
- // We commented this to search & replace through tables which have no primary keys like wp_revslider_slides
274
- // @todo test this carefully. If it causes (performance) issues we need to activate it again!
275
- // @since 2.4.4
276
- // if( null === $primary_key ) {
277
- // return false;
278
- // }
279
-
280
- $current_row = 0;
281
- $start = $this->options->job->start;
282
- $end = $this->settings->querySRLimit;
283
-
284
- // Grab the content of the table.
285
- $data = $this->db->get_results( "SELECT * FROM $table LIMIT $start, $end", ARRAY_A );
286
-
287
- // Filter certain rows option_name in wpstg_options
288
- $filter = array(
289
- 'Admin_custome_login_Slidshow',
290
- 'Admin_custome_login_Social',
291
- 'Admin_custome_login_logo',
292
- 'Admin_custome_login_text',
293
- 'Admin_custome_login_login',
294
- 'Admin_custome_login_top',
295
- 'Admin_custome_login_dashboard',
296
- 'Admin_custome_login_Version',
297
- 'upload_path',
298
- );
299
-
300
- apply_filters( 'wpstg_clone_searchreplace_excl_rows', $filter );
301
-
302
- // Loop through the data.
303
- foreach ( $data as $row ) {
304
- $current_row++;
305
- $update_sql = array();
306
- $where_sql = array();
307
- $upd = false;
308
-
309
- // Skip rows below
310
- if( isset( $row['option_name'] ) && in_array( $row['option_name'], $filter ) ) {
311
- continue;
312
- }
313
-
314
- // Skip rows with transients (They can store huge data and we need to save memory)
315
- if( isset( $row['option_name'] ) && strpos( $row['option_name'], '_transient' ) === 0 ) {
316
- continue;
317
- }
318
-
319
-
320
- foreach ( $columns as $column ) {
321
-
322
- $dataRow = $row[$column];
323
-
324
- if( $column == $primary_key ) {
325
- $where_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
326
- continue;
327
- }
328
-
329
- // Skip GUIDs by default.
330
- if( 'on' !== $args['replace_guids'] && 'guid' == $column ) {
331
- continue;
332
- }
333
-
334
-
335
- // Skip mail addresses
336
- if( 'off' === $args['replace_mails'] && false !== strpos( $dataRow, '@' . $this->multisiteDomainWithoutScheme ) ) {
337
- continue;
338
- }
339
-
340
- // Check options table
341
- if( $this->options->prefix . 'options' === $table ) {
342
-
343
- // Skip certain options
344
- if( isset( $should_skip ) && true === $should_skip ) {
345
- $should_skip = false;
346
- continue;
347
- }
348
-
349
- // Skip this row
350
- if( 'wpstg_existing_clones_beta' === $dataRow ||
351
- 'wpstg_existing_clones' === $dataRow ||
352
- 'wpstg_settings' === $dataRow ||
353
- 'wpstg_license_status' === $dataRow ||
354
- 'siteurl' === $dataRow ||
355
- 'home' === $dataRow
356
- ) {
357
- $should_skip = true;
358
- }
359
- }
360
-
361
- // Check the path delimiter for / or \/ and remove one of those which prevents from resulting in wrong syntax like domain.com/staging\/.
362
- // 1. local.wordpress.test -> local.wordpress.test/staging
363
- // 2. local.wordpress.test\/ -> local.wordpress.test\/staging\/
364
- $tmp = $args;
365
- if( false === strpos( $dataRow, $tmp['search_for'][0] ) ) {
366
- array_shift( $tmp['search_for'] ); // rtrim( $this->homeUrl, '/' ),
367
- array_shift( $tmp['replace_with'] ); // rtrim( $this->homeUrl, '/' ) . '/' . $this->options->cloneDirectoryName,
368
- } else {
369
- unset( $tmp['search_for'][1] );
370
- unset( $tmp['replace_with'][1] );
371
- // recount array
372
- $tmp['search_for'] = array_values( $tmp['search_for'] );
373
- $tmp['replace_with'] = array_values( $tmp['replace_with'] );
374
- }
375
-
376
- // Run a search replace on the data row and respect the serialisation.
377
- $i = 0;
378
- foreach ( $tmp['search_for'] as $replace ) {
379
- //$this->log( "Search for: {$tmp['search_for'][$i]} Replace with {$tmp['replace_with'][$i]}", \WPStaging\Utils\Logger::TYPE_ERROR );
380
- $dataRow = $this->recursive_unserialize_replace( $tmp['search_for'][$i], $tmp['replace_with'][$i], $dataRow, false, $args['case_insensitive'] );
381
- $i++;
382
- }
383
- unset( $replace );
384
- unset( $i );
385
- unset( $tmp );
386
-
387
- // Something was changed
388
- if( $row[$column] != $dataRow ) {
389
- $update_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
390
- $upd = true;
391
- }
392
- }
393
-
394
- // Determine what to do with updates.
395
- if( $args['dry_run'] === 'on' ) {
396
- // Don't do anything if a dry run
397
- } elseif( $upd && !empty( $where_sql ) ) {
398
- // If there are changes to make, run the query.
399
- $sql = 'UPDATE ' . $table . ' SET ' . implode( ', ', $update_sql ) . ' WHERE ' . implode( ' AND ', array_filter( $where_sql ) );
400
- $result = $this->db->query( $sql );
401
-
402
- if( !$result ) {
403
- $this->log( "Error updating row {$current_row} SQL: {$sql}", \WPStaging\Utils\Logger::TYPE_ERROR );
404
- }
405
- }
406
- } // end row loop
407
- unset( $row );
408
- unset( $update_sql );
409
- unset( $where_sql );
410
- unset( $sql );
411
-
412
-
413
- // DB Flush
414
- $this->db->flush();
415
- return true;
416
- }
417
-
418
- /**
419
- * Get path to multisite image folder e.g. wp-content/blogs.dir/ID/files or wp-content/uploads/sites/ID
420
- * @return string
421
- */
422
- private function getImagePathLive() {
423
- // Check first which structure is used
424
- $uploads = wp_upload_dir();
425
- $basedir = $uploads['basedir'];
426
- $blogId = get_current_blog_id();
427
-
428
- if( false === strpos( $basedir, 'blogs.dir' ) ) {
429
- // Since WP 3.5
430
- $path = $blogId > 1 ?
431
- 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR :
432
- 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
433
- } else {
434
- // old blog structure
435
- $path = $blogId > 1 ?
436
- 'wp-content' . DIRECTORY_SEPARATOR . 'blogs.dir' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR . 'files' . DIRECTORY_SEPARATOR :
437
- 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
438
- }
439
- return $path;
440
- }
441
-
442
- /**
443
- * Get path to staging site image path wp-content/uploads
444
- * @return string
445
- */
446
- private function getImagePathStaging() {
447
- return 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
448
- }
449
-
450
- /**
451
- * Adapted from interconnect/it's search/replace script.
452
- *
453
- * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
454
- *
455
- * Take a serialised array and unserialise it replacing elements as needed and
456
- * unserialising any subordinate arrays and performing the replace on those too.
457
- *
458
- * @access private
459
- * @param string $from String we're looking to replace.
460
- * @param string $to What we want it to be replaced with
461
- * @param array $data Used to pass any subordinate arrays back to in.
462
- * @param boolean $serialized Does the array passed via $data need serialising.
463
- * @param sting|boolean $case_insensitive Set to 'on' if we should ignore case, false otherwise.
464
- *
465
- * @return string|array The original array with all elements replaced as needed.
466
- */
467
- private function recursive_unserialize_replace( $from = '', $to = '', $data = '', $serialized = false, $case_insensitive = false ) {
468
- try {
469
- // Some unserialized data cannot be re-serialized eg. SimpleXMLElements
470
- if( is_serialized( $data ) && ( $unserialized = @unserialize( $data ) ) !== false ) {
471
- $data = $this->recursive_unserialize_replace( $from, $to, $unserialized, true, $case_insensitive );
472
- } elseif( is_array( $data ) ) {
473
- $tmp = array();
474
- foreach ( $data as $key => $value ) {
475
- $tmp[$key] = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
476
- }
477
-
478
- $data = $tmp;
479
- unset( $tmp );
480
- } elseif( is_object( $data ) ) {
481
- $tmp = $data;
482
- $props = get_object_vars( $data );
483
- foreach ( $props as $key => $value ) {
484
- if( $key === '' || ord( $key[0] ) === 0 ) {
485
- continue;
486
- }
487
- $tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
488
- }
489
-
490
- $data = $tmp;
491
- unset( $tmp );
492
- } else {
493
- if( is_string( $data ) ) {
494
- if( !empty( $from ) && !empty( $to ) ) {
495
- $data = $this->str_replace( $from, $to, $data, $case_insensitive );
496
- }
497
- }
498
- }
499
-
500
- if( $serialized ) {
501
- return serialize( $data );
502
- }
503
- } catch ( Exception $error ) {
504
-
505
- }
506
-
507
- return $data;
508
- }
509
-
510
- // private function recursive_unserialize_replace_( $from = '', $to = '', $data = '', $serialised = false, $case_insensitive = false ) {
511
- // try {
512
- //
513
- // // Check first if its an object and repair it if necessary
514
- // //$data = $this->fixObject($data);
515
- //
516
- // if( is_string( $data ) && !is_serialized_string( $data ) && ( $unserialized = $this->unserialize( $data ) ) !== false ) {
517
- // $data = $this->recursive_unserialize_replace( $from, $to, $unserialized, true, $case_insensitive );
518
- // } elseif( is_array( $data ) ) {
519
- // $_tmp = array();
520
- // foreach ( $data as $key => $value ) {
521
- // $_tmp[$key] = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
522
- // }
523
- //
524
- // $data = $_tmp;
525
- // unset( $_tmp );
526
- // }
527
- //
528
- // // Submitted by Tina Matter
529
- // elseif( $this->isValidObject( $data ) ) {
530
- // $_tmp = $data; // new $data_class( );
531
- // $props = get_object_vars( $data );
532
- // foreach ( $props as $key => $value ) {
533
- // if( $key === '' || ord( $key[0] ) === 0 ) {
534
- // continue;
535
- // }
536
- // $_tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
537
- // }
538
- //
539
- // $data = $_tmp;
540
- // unset( $_tmp );
541
- // } elseif( is_serialized_string( $data ) ) {
542
- // if( false !== ($data = $this->unserialize( $data )) ) {
543
- // $data = $this->str_replace( $from, $to, $data, $case_insensitive );
544
- // $data = serialize( $data );
545
- // }
546
- // } else {
547
- // if( is_string( $data ) ) {
548
- // $data = $this->str_replace( $from, $to, $data, $case_insensitive );
549
- // }
550
- // }
551
- //
552
- // if( $serialised ) {
553
- // return serialize( $data );
554
- // }
555
- // } catch ( Exception $error ) {
556
- //
557
- // }
558
- //
559
- // return $data;
560
- // }
561
-
562
-
563
-
564
- /**
565
- * Check if the object is a valid one and not __PHP_Incomplete_Class_Name
566
- * Can not use is_object alone because in php 7.2 it's returning true even though object is __PHP_Incomplete_Class_Name
567
- * @return boolean
568
- */
569
- // private function isValidObject( $data ) {
570
- // if( !is_object( $data ) || gettype( $data ) != 'object' ) {
571
- // return false;
572
- // }
573
- //
574
- // $invalid_class_props = get_object_vars( $data );
575
- //
576
- // if( !isset( $invalid_class_props['__PHP_Incomplete_Class_Name'] ) ) {
577
- // // Assume it must be an valid object
578
- // return true;
579
- // }
580
- //
581
- // $invalid_object_class = $invalid_class_props['__PHP_Incomplete_Class_Name'];
582
- //
583
- // if( !empty( $invalid_object_class ) ) {
584
- // return false;
585
- // }
586
- //
587
- // // Assume it must be an valid object
588
- // return true;
589
- // }
590
-
591
- /**
592
- * Mimics the mysql_real_escape_string function. Adapted from a post by 'feedr' on php.net.
593
- * @link http://php.net/manual/en/function.mysql-real-escape-string.php#101248
594
- * @access public
595
- * @param string $input The string to escape.
596
- * @return string
597
- */
598
- private function mysql_escape_mimic( $input ) {
599
- if( is_array( $input ) ) {
600
- return array_map( __METHOD__, $input );
601
- }
602
- if( !empty( $input ) && is_string( $input ) ) {
603
- return str_replace( array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $input );
604
- }
605
-
606
- return $input;
607
- }
608
-
609
- /**
610
- * Return unserialized object or array
611
- *
612
- * @param string $serialized_string Serialized string.
613
- * @param string $method The name of the caller method.
614
- *
615
- * @return mixed, false on failure
616
- */
617
- private static function unserialize( $serialized_string ) {
618
- if( !is_serialized( $serialized_string ) ) {
619
- return false;
620
- }
621
-
622
- $serialized_string = trim( $serialized_string );
623
- $unserialized_string = @unserialize( $serialized_string );
624
-
625
- return $unserialized_string;
626
- }
627
-
628
- /**
629
- * Wrapper for str_replace
630
- *
631
- * @param string $from
632
- * @param string $to
633
- * @param string $data
634
- * @param string|bool $case_insensitive
635
- *
636
- * @return string
637
- */
638
- private function str_replace( $from, $to, $data, $case_insensitive = false ) {
639
-
640
- // Add filter
641
- $excludes = apply_filters( 'wpstg_clone_searchreplace_excl', array() );
642
-
643
- // Build pattern
644
- $regexExclude = '';
645
- foreach ( $excludes as $exclude ) {
646
- $regexExclude .= $exclude . '(*SKIP)(FAIL)|';
647
- }
648
-
649
- if( 'on' === $case_insensitive ) {
650
- //$data = str_ireplace( $from, $to, $data );
651
- $data = preg_replace( '#' . $regexExclude . preg_quote( $from ) . '#i', $to, $data );
652
- } else {
653
- //$data = str_replace( $from, $to, $data );
654
- $data = preg_replace( '#' . $regexExclude . preg_quote( $from ) . '#', $to, $data );
655
- }
656
-
657
- return $data;
658
- }
659
-
660
- /**
661
- * Set the job
662
- * @param string $table
663
- */
664
- private function setJob( $table ) {
665
- if( !empty( $this->options->job->current ) ) {
666
- return;
667
- }
668
-
669
- $this->options->job->current = $table;
670
- $this->options->job->start = 0;
671
- }
672
-
673
- /**
674
- * Start Job
675
- * @param string $new
676
- * @param string $old
677
- * @return bool
678
- */
679
- private function startJob( $new, $old ) {
680
- if( 0 != $this->options->job->start ) {
681
- return true;
682
- }
683
-
684
- $this->options->job->total = ( int ) $this->db->get_var( "SELECT COUNT(1) FROM {$old}" );
685
-
686
- if( 0 == $this->options->job->total ) {
687
- $this->finishStep();
688
- return false;
689
- }
690
-
691
- return true;
692
- }
693
-
694
- /**
695
- * Finish the step
696
- */
697
- private function finishStep() {
698
- // This job is not finished yet
699
- if( $this->options->job->total > $this->options->job->start ) {
700
- return false;
701
- }
702
-
703
- // Add it to cloned tables listing
704
- $this->options->clonedTables[] = $this->options->tables[$this->options->currentStep];
705
-
706
- // Reset job
707
- $this->options->job = new \stdClass();
708
-
709
- return true;
710
- }
711
-
712
- /**
713
- * Drop table if necessary
714
- * @param string $new
715
- */
716
- private function dropTable( $new ) {
717
- $old = $this->db->get_var( $this->db->prepare( "SHOW TABLES LIKE %s", $new ) );
718
-
719
- if( !$this->shouldDropTable( $new, $old ) ) {
720
- return;
721
- }
722
-
723
- $this->log( "DB Processing: {$new} already exists, dropping it first" );
724
- $this->db->query( "DROP TABLE {$new}" );
725
- }
726
-
727
- /**
728
- * Check if table needs to be dropped
729
- * @param string $new
730
- * @param string $old
731
- * @return bool
732
- */
733
- private function shouldDropTable( $new, $old ) {
734
- return (
735
- $old == $new &&
736
- (
737
- !isset( $this->options->job->current ) ||
738
- !isset( $this->options->job->start ) ||
739
- 0 == $this->options->job->start
740
- )
741
- );
742
- }
743
-
744
- /**
745
- * Check if WP is installed in subdir
746
- * @return boolean
747
- */
748
- private function isSubDir() {
749
- // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
750
- // This is happening much more often than you would expect
751
- $siteurl = preg_replace( '#^https?://#', '', rtrim( get_option( 'siteurl' ), '/' ) );
752
- $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
753
-
754
- if( $home !== $siteurl ) {
755
- return true;
756
- }
757
- return false;
758
- }
759
-
760
- /**
761
- * Get the install sub directory if WP is installed in sub directory
762
- * @return string
763
- */
764
- private function getSubDir() {
765
- $home = get_option( 'home' );
766
- $siteurl = get_option( 'siteurl' );
767
-
768
- if( empty( $home ) || empty( $siteurl ) ) {
769
- return '/';
770
- }
771
-
772
- $dir = str_replace( $home, '', $siteurl );
773
- return '/' . str_replace( '/', '', $dir );
774
- }
775
-
776
- }
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
+
5
+ // No Direct Access
6
+ if( !defined( "WPINC" ) ) {
7
+ die;
8
+ }
9
+
10
+ use WPStaging\WPStaging;
11
+ use WPStaging\Utils\Strings;
12
+ use WPStaging\Utils\Helper;
13
+ use WPStaging\Utils\Multisite;
14
+ use WPStaging\Backend\Modules\Jobs\JobExecutable;
15
+
16
+ /**
17
+ * Class Database
18
+ * @package WPStaging\Backend\Modules\Jobs
19
+ */
20
+ class SearchReplace extends JobExecutable {
21
+
22
+ /**
23
+ * @var int
24
+ */
25
+ private $total = 0;
26
+
27
+ /**
28
+ * @var \WPDB
29
+ */
30
+ public $db;
31
+
32
+ /**
33
+ * The prefix of the new database tables which are used for the live site after updating tables
34
+ * @var string
35
+ */
36
+ public $tmpPrefix;
37
+
38
+ /**
39
+ * Initialize
40
+ */
41
+ public function initialize() {
42
+ $this->total = count( $this->options->tables );
43
+ $this->db = WPStaging::getInstance()->get( "wpdb" );
44
+ $this->tmpPrefix = $this->options->prefix;
45
+ }
46
+
47
+ public function start() {
48
+ // Skip job. Nothing to do
49
+ if( $this->options->totalSteps === 0 ) {
50
+ $this->prepareResponse( true, false );
51
+ }
52
+
53
+ $this->run();
54
+
55
+ // Save option, progress
56
+ $this->saveOptions();
57
+
58
+ return ( object ) $this->response;
59
+ }
60
+
61
+ /**
62
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
63
+ * @return void
64
+ */
65
+ protected function calculateTotalSteps() {
66
+ $this->options->totalSteps = $this->total;
67
+ }
68
+
69
+ /**
70
+ * Execute the Current Step
71
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
72
+ * @return bool
73
+ */
74
+ protected function execute() {
75
+ // Over limits threshold
76
+ if( $this->isOverThreshold() ) {
77
+ // Prepare response and save current progress
78
+ $this->prepareResponse( false, false );
79
+ $this->saveOptions();
80
+ return false;
81
+ }
82
+
83
+ // No more steps, finished
84
+ if( $this->options->currentStep > $this->total || !isset( $this->options->tables[$this->options->currentStep] ) ) {
85
+ $this->prepareResponse( true, false );
86
+ return false;
87
+ }
88
+
89
+ // Table is excluded
90
+ if( in_array( $this->options->tables[$this->options->currentStep], $this->options->excludedTables ) ) {
91
+ $this->prepareResponse();
92
+ return true;
93
+ }
94
+
95
+ // Search & Replace
96
+ if( !$this->stopExecution() && !$this->updateTable( $this->options->tables[$this->options->currentStep] ) ) {
97
+ // Prepare Response
98
+ $this->prepareResponse( false, false );
99
+
100
+ // Not finished
101
+ return true;
102
+ }
103
+
104
+
105
+ // Prepare Response
106
+ $this->prepareResponse();
107
+
108
+ // Not finished
109
+ return true;
110
+ }
111
+
112
+ /**
113
+ * Stop Execution immediately
114
+ * return mixed bool | json
115
+ */
116
+ private function stopExecution() {
117
+ if( $this->db->prefix == $this->tmpPrefix ) {
118
+ $this->returnException( 'Fatal Error 9: Prefix ' . $this->db->prefix . ' is used for the live site hence it can not be used for the staging site as well. Please ask support@wp-staging.com how to resolve this.' );
119
+ }
120
+ return false;
121
+ }
122
+
123
+ /**
124
+ * Copy Tables
125
+ * @param string $tableName
126
+ * @return bool
127
+ */
128
+ private function updateTable( $tableName ) {
129
+ $strings = new Strings();
130
+ $table = $strings->str_replace_first( $this->db->prefix, '', $tableName );
131
+ $newTableName = $this->tmpPrefix . $table;
132
+
133
+ // Save current job
134
+ $this->setJob( $newTableName );
135
+
136
+ // Beginning of the job
137
+ if( !$this->startJob( $newTableName, $tableName ) ) {
138
+ return true;
139
+ }
140
+ // Copy data
141
+ $this->startReplace( $newTableName );
142
+
143
+ // Finis the step
144
+ return $this->finishStep();
145
+ }
146
+
147
+ /**
148
+ * Start search replace job
149
+ * @param string $new
150
+ * @param string $old
151
+ */
152
+ private function startReplace( $new ) {
153
+ $rows = $this->options->job->start + $this->settings->querySRLimit;
154
+ $this->log(
155
+ "DB Processing: Table {$new} {$this->options->job->start} to {$rows} records"
156
+ );
157
+
158
+ // Search & Replace
159
+ $this->searchReplace( $new, $rows, array() );
160
+
161
+ // Set new offset
162
+ $this->options->job->start += $this->settings->querySRLimit;
163
+ }
164
+
165
+ /**
166
+ * Returns the number of pages in a table.
167
+ * @access public
168
+ * @return int
169
+ */
170
+ private function get_pages_in_table( $table ) {
171
+ $table = esc_sql( $table );
172
+ $rows = $this->db->get_var( "SELECT COUNT(*) FROM $table" );
173
+ $pages = ceil( $rows / $this->settings->querySRLimit );
174
+ return absint( $pages );
175
+ }
176
+
177
+ /**
178
+ * Gets the columns in a table.
179
+ * @access public
180
+ * @param string $table The table to check.
181
+ * @return array
182
+ */
183
+ private function get_columns( $table ) {
184
+ $primary_key = null;
185
+ $columns = array();
186
+ $fields = $this->db->get_results( 'DESCRIBE ' . $table );
187
+ if( is_array( $fields ) ) {
188
+ foreach ( $fields as $column ) {
189
+ $columns[] = $column->Field;
190
+ if( $column->Key == 'PRI' ) {
191
+ $primary_key = $column->Field;
192
+ }
193
+ }
194
+ }
195
+ return array($primary_key, $columns);
196
+ }
197
+
198
+ /**
199
+ * Adapated from interconnect/it's search/replace script, adapted from Better Search Replace
200
+ *
201
+ * Modified to use WordPress wpdb functions instead of PHP's native mysql/pdo functions,
202
+ * and to be compatible with batch processing.
203
+ *
204
+ * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
205
+ *
206
+ * @access public
207
+ * @param string $table The table to run the replacement on.
208
+ * @param int $page The page/block to begin the query on.
209
+ * @param array $args An associative array containing arguements for this run.
210
+ * @return array
211
+ */
212
+ private function searchReplace( $table, $page, $args ) {
213
+
214
+ if( $this->thirdParty->isSearchReplaceExcluded( $table ) ) {
215
+ $this->log( "DB Processing: Skip {$table}", \WPStaging\Utils\Logger::TYPE_INFO );
216
+ return true;
217
+ }
218
+
219
+ // Load up the default settings for this chunk.
220
+ $table = esc_sql( $table );
221
+ $current_page = $this->options->job->start + $this->settings->querySRLimit;
222
+ $pages = $this->get_pages_in_table( $table );
223
+ //$done = false;
224
+
225
+
226
+ if( $this->isSubDir() ) {
227
+ // Search URL example.com/staging and root path to staging site /var/www/htdocs/staging
228
+ $args['search_for'] = array(
229
+ rtrim( $this->multisiteHomeUrlWithoutScheme, "/" ) . $this->getSubDir(),
230
+ ABSPATH,
231
+ str_replace( '/', '\/', rtrim( $this->multisiteHomeUrlWithoutScheme, '/' ) ) . str_replace( '/', '\/', $this->getSubDir() ), // // Used by revslider and several visual editors
232
+ $this->getImagePathLive()
233
+ );
234
+
235
+
236
+ $args['replace_with'] = array(
237
+ rtrim( $this->multisiteDomainWithoutScheme, "/" ) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName,
238
+ rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName,
239
+ str_replace( '/', '\/', rtrim( $this->multisiteDomainWithoutScheme, "/" ) ) . str_replace( '/', '\/', $this->getSubDir() ) . '\/' . $this->options->cloneDirectoryName, // Used by revslider and several visual editors
240
+ $this->getImagePathStaging()
241
+ );
242
+ } else {
243
+ $args['search_for'] = array(
244
+ rtrim( $this->multisiteHomeUrlWithoutScheme, '/' ),
245
+ ABSPATH,
246
+ str_replace( '/', '\/', rtrim( $this->multisiteHomeUrlWithoutScheme, '/' ) ),
247
+ $this->getImagePathLive()
248
+ );
249
+ $args['replace_with'] = array(
250
+ rtrim( $this->multisiteDomainWithoutScheme, '/' ) . '/' . $this->options->cloneDirectoryName,
251
+ rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName,
252
+ str_replace( '/', '\/', rtrim( $this->multisiteDomainWithoutScheme, '/' ) ) . '\/' . $this->options->cloneDirectoryName,
253
+ $this->getImagePathStaging()
254
+ );
255
+ }
256
+
257
+ //$this->log( 'Search: ' . $this->multisiteHomeUrlWithoutScheme . ' Replace: ' . rtrim( $this->multisiteDomainWithoutScheme, '/' ) . '/' . $this->options->cloneDirectoryName );
258
+
259
+
260
+ $args['replace_guids'] = 'off';
261
+ $args['dry_run'] = 'off';
262
+ $args['case_insensitive'] = false;
263
+ $args['replace_mails'] = 'off';
264
+
265
+ // Allow filtering of search & replace parameters
266
+ $args = apply_filters( 'wpstg_clone_searchreplace_params', $args );
267
+
268
+ // Get a list of columns in this table.
269
+ list( $primary_key, $columns ) = $this->get_columns( $table );
270
+
271
+ // Bail out early if there isn't a primary key.
272
+ // We commented this to search & replace through tables which have no primary keys like wp_revslider_slides
273
+ // @todo test this carefully. If it causes (performance) issues we need to activate it again!
274
+ // @since 2.4.4
275
+ // if( null === $primary_key ) {
276
+ // return false;
277
+ // }
278
+
279
+ $current_row = 0;
280
+ $start = $this->options->job->start;
281
+ $end = $this->settings->querySRLimit;
282
+
283
+ // Grab the content of the table.
284
+ $data = $this->db->get_results( "SELECT * FROM $table LIMIT $start, $end", ARRAY_A );
285
+
286
+ // Filter certain rows option_name in wpstg_options
287
+ $filter = array(
288
+ 'Admin_custome_login_Slidshow',
289
+ 'Admin_custome_login_Social',
290
+ 'Admin_custome_login_logo',
291
+ 'Admin_custome_login_text',
292
+ 'Admin_custome_login_login',
293
+ 'Admin_custome_login_top',
294
+ 'Admin_custome_login_dashboard',
295
+ 'Admin_custome_login_Version',
296
+ 'upload_path',
297
+ );
298
+
299
+ apply_filters( 'wpstg_clone_searchreplace_excl_rows', $filter );
300
+
301
+ // Loop through the data.
302
+ foreach ( $data as $row ) {
303
+ $current_row++;
304
+ $update_sql = array();
305
+ $where_sql = array();
306
+ $upd = false;
307
+
308
+ // Skip rows below
309
+ if( isset( $row['option_name'] ) && in_array( $row['option_name'], $filter ) ) {
310
+ continue;
311
+ }
312
+
313
+ // Skip rows with transients (They can store huge data and we need to save memory)
314
+ if( isset( $row['option_name'] ) && strpos( $row['option_name'], '_transient' ) === 0 ) {
315
+ continue;
316
+ }
317
+
318
+
319
+ foreach ( $columns as $column ) {
320
+
321
+ $dataRow = $row[$column];
322
+
323
+ if( $column == $primary_key ) {
324
+ $where_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
325
+ continue;
326
+ }
327
+
328
+ // Skip GUIDs by default.
329
+ if( 'on' !== $args['replace_guids'] && 'guid' == $column ) {
330
+ continue;
331
+ }
332
+
333
+
334
+ // Skip mail addresses
335
+ if( 'off' === $args['replace_mails'] && false !== strpos( $dataRow, '@' . $this->multisiteDomainWithoutScheme ) ) {
336
+ continue;
337
+ }
338
+
339
+ // Check options table
340
+ if( $this->options->prefix . 'options' === $table ) {
341
+
342
+ // Skip certain options
343
+ if( isset( $should_skip ) && true === $should_skip ) {
344
+ $should_skip = false;
345
+ continue;
346
+ }
347
+
348
+ // Skip this row
349
+ if( 'wpstg_existing_clones_beta' === $dataRow ||
350
+ 'wpstg_existing_clones' === $dataRow ||
351
+ 'wpstg_settings' === $dataRow ||
352
+ 'wpstg_license_status' === $dataRow ||
353
+ 'siteurl' === $dataRow ||
354
+ 'home' === $dataRow
355
+ ) {
356
+ $should_skip = true;
357
+ }
358
+ }
359
+
360
+ // Check the path delimiter for / or \/ and remove one of those which prevents from resulting in wrong syntax like domain.com/staging\/.
361
+ // 1. local.wordpress.test -> local.wordpress.test/staging
362
+ // 2. local.wordpress.test\/ -> local.wordpress.test\/staging\/
363
+ $tmp = $args;
364
+ if( false === strpos( $dataRow, $tmp['search_for'][0] ) ) {
365
+ array_shift( $tmp['search_for'] ); // rtrim( $this->homeUrl, '/' ),
366
+ array_shift( $tmp['replace_with'] ); // rtrim( $this->homeUrl, '/' ) . '/' . $this->options->cloneDirectoryName,
367
+ } else {
368
+ unset( $tmp['search_for'][1] );
369
+ unset( $tmp['replace_with'][1] );
370
+ // recount array
371
+ $tmp['search_for'] = array_values( $tmp['search_for'] );
372
+ $tmp['replace_with'] = array_values( $tmp['replace_with'] );
373
+ }
374
+
375
+ // Run a search replace on the data row and respect the serialisation.
376
+ $i = 0;
377
+ foreach ( $tmp['search_for'] as $replace ) {
378
+ //$this->log( "Search for: {$tmp['search_for'][$i]} Replace with {$tmp['replace_with'][$i]}", \WPStaging\Utils\Logger::TYPE_ERROR );
379
+ $dataRow = $this->recursive_unserialize_replace( $tmp['search_for'][$i], $tmp['replace_with'][$i], $dataRow, false, $args['case_insensitive'] );
380
+ $i++;
381
+ }
382
+ unset( $replace );
383
+ unset( $i );
384
+ unset( $tmp );
385
+
386
+ // Something was changed
387
+ if( $row[$column] != $dataRow ) {
388
+ $update_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
389
+ $upd = true;
390
+ }
391
+ }
392
+
393
+ // Determine what to do with updates.
394
+ if( $args['dry_run'] === 'on' ) {
395
+ // Don't do anything if a dry run
396
+ } elseif( $upd && !empty( $where_sql ) ) {
397
+ // If there are changes to make, run the query.
398
+ $sql = 'UPDATE ' . $table . ' SET ' . implode( ', ', $update_sql ) . ' WHERE ' . implode( ' AND ', array_filter( $where_sql ) );
399
+ $result = $this->db->query( $sql );
400
+
401
+ if( !$result ) {
402
+ $this->log( "Error updating row {$current_row} SQL: {$sql}", \WPStaging\Utils\Logger::TYPE_ERROR );
403
+ }
404
+ }
405
+ } // end row loop
406
+ unset( $row );
407
+ unset( $update_sql );
408
+ unset( $where_sql );
409
+ unset( $sql );
410
+
411
+
412
+ // DB Flush
413
+ $this->db->flush();
414
+ return true;
415
+ }
416
+
417
+ /**
418
+ * Get path to multisite image folder e.g. wp-content/blogs.dir/ID/files or wp-content/uploads/sites/ID
419
+ * @return string
420
+ */
421
+ private function getImagePathLive() {
422
+ // Check first which structure is used
423
+ $uploads = wp_upload_dir();
424
+ $basedir = $uploads['basedir'];
425
+ $blogId = get_current_blog_id();
426
+
427
+ if( false === strpos( $basedir, 'blogs.dir' ) ) {
428
+ // Since WP 3.5
429
+ $path = $blogId > 1 ?
430
+ 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR :
431
+ 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
432
+ } else {
433
+ // old blog structure
434
+ $path = $blogId > 1 ?
435
+ 'wp-content' . DIRECTORY_SEPARATOR . 'blogs.dir' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR . 'files' . DIRECTORY_SEPARATOR :
436
+ 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
437
+ }
438
+ return $path;
439
+ }
440
+
441
+ /**
442
+ * Get path to staging site image path wp-content/uploads
443
+ * @return string
444
+ */
445
+ private function getImagePathStaging() {
446
+ return 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
447
+ }
448
+
449
+ /**
450
+ * Adapted from interconnect/it's search/replace script.
451
+ *
452
+ * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
453
+ *
454
+ * Take a serialised array and unserialise it replacing elements as needed and
455
+ * unserialising any subordinate arrays and performing the replace on those too.
456
+ *
457
+ * @access private
458
+ * @param string $from String we're looking to replace.
459
+ * @param string $to What we want it to be replaced with
460
+ * @param array $data Used to pass any subordinate arrays back to in.
461
+ * @param boolean $serialized Does the array passed via $data need serialising.
462
+ * @param sting|boolean $case_insensitive Set to 'on' if we should ignore case, false otherwise.
463
+ *
464
+ * @return string|array The original array with all elements replaced as needed.
465
+ */
466
+ private function recursive_unserialize_replace( $from = '', $to = '', $data = '', $serialized = false, $case_insensitive = false ) {
467
+ try {
468
+ // Some unserialized data cannot be re-serialized eg. SimpleXMLElements
469
+ if( is_serialized( $data ) && ( $unserialized = @unserialize( $data ) ) !== false ) {
470
+ $data = $this->recursive_unserialize_replace( $from, $to, $unserialized, true, $case_insensitive );
471
+ } elseif( is_array( $data ) ) {
472
+ $tmp = array();
473
+ foreach ( $data as $key => $value ) {
474
+ $tmp[$key] = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
475
+ }
476
+
477
+ $data = $tmp;
478
+ unset( $tmp );
479
+ } elseif( is_object( $data ) ) {
480
+ $tmp = $data;
481
+ $props = get_object_vars( $data );
482
+ foreach ( $props as $key => $value ) {
483
+ if( $key === '' || ord( $key[0] ) === 0 ) {
484
+ continue;
485
+ }
486
+ $tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
487
+ }
488
+
489
+ $data = $tmp;
490
+ unset( $tmp );
491
+ } else {
492
+ if( is_string( $data ) ) {
493
+ if( !empty( $from ) && !empty( $to ) ) {
494
+ $data = $this->str_replace( $from, $to, $data, $case_insensitive );
495
+ }
496
+ }
497
+ }
498
+
499
+ if( $serialized ) {
500
+ return serialize( $data );
501
+ }
502
+ } catch ( Exception $error ) {
503
+
504
+ }
505
+
506
+ return $data;
507
+ }
508
+
509
+ // private function recursive_unserialize_replace_( $from = '', $to = '', $data = '', $serialised = false, $case_insensitive = false ) {
510
+ // try {
511
+ //
512
+ // // Check first if its an object and repair it if necessary
513
+ // //$data = $this->fixObject($data);
514
+ //
515
+ // if( is_string( $data ) && !is_serialized_string( $data ) && ( $unserialized = $this->unserialize( $data ) ) !== false ) {
516
+ // $data = $this->recursive_unserialize_replace( $from, $to, $unserialized, true, $case_insensitive );
517
+ // } elseif( is_array( $data ) ) {
518
+ // $_tmp = array();
519
+ // foreach ( $data as $key => $value ) {
520
+ // $_tmp[$key] = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
521
+ // }
522
+ //
523
+ // $data = $_tmp;
524
+ // unset( $_tmp );
525
+ // }
526
+ //
527
+ // // Submitted by Tina Matter
528
+ // elseif( $this->isValidObject( $data ) ) {
529
+ // $_tmp = $data; // new $data_class( );
530
+ // $props = get_object_vars( $data );
531
+ // foreach ( $props as $key => $value ) {
532
+ // if( $key === '' || ord( $key[0] ) === 0 ) {
533
+ // continue;
534
+ // }
535
+ // $_tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
536
+ // }
537
+ //
538
+ // $data = $_tmp;
539
+ // unset( $_tmp );
540
+ // } elseif( is_serialized_string( $data ) ) {
541
+ // if( false !== ($data = $this->unserialize( $data )) ) {
542
+ // $data = $this->str_replace( $from, $to, $data, $case_insensitive );
543
+ // $data = serialize( $data );
544
+ // }
545
+ // } else {
546
+ // if( is_string( $data ) ) {
547
+ // $data = $this->str_replace( $from, $to, $data, $case_insensitive );
548
+ // }
549
+ // }
550
+ //
551
+ // if( $serialised ) {
552
+ // return serialize( $data );
553
+ // }
554
+ // } catch ( Exception $error ) {
555
+ //
556
+ // }
557
+ //
558
+ // return $data;
559
+ // }
560
+
561
+
562
+
563
+ /**
564
+ * Check if the object is a valid one and not __PHP_Incomplete_Class_Name
565
+ * Can not use is_object alone because in php 7.2 it's returning true even though object is __PHP_Incomplete_Class_Name
566
+ * @return boolean
567
+ */
568
+ // private function isValidObject( $data ) {
569
+ // if( !is_object( $data ) || gettype( $data ) != 'object' ) {
570
+ // return false;
571
+ // }
572
+ //
573
+ // $invalid_class_props = get_object_vars( $data );
574
+ //
575
+ // if( !isset( $invalid_class_props['__PHP_Incomplete_Class_Name'] ) ) {
576
+ // // Assume it must be an valid object
577
+ // return true;
578
+ // }
579
+ //
580
+ // $invalid_object_class = $invalid_class_props['__PHP_Incomplete_Class_Name'];
581
+ //
582
+ // if( !empty( $invalid_object_class ) ) {
583
+ // return false;
584
+ // }
585
+ //
586
+ // // Assume it must be an valid object
587
+ // return true;
588
+ // }
589
+
590
+ /**
591
+ * Mimics the mysql_real_escape_string function. Adapted from a post by 'feedr' on php.net.
592
+ * @link http://php.net/manual/en/function.mysql-real-escape-string.php#101248
593
+ * @access public
594
+ * @param string $input The string to escape.
595
+ * @return string
596
+ */
597
+ private function mysql_escape_mimic( $input ) {
598
+ if( is_array( $input ) ) {
599
+ return array_map( __METHOD__, $input );
600
+ }
601
+ if( !empty( $input ) && is_string( $input ) ) {
602
+ return str_replace( array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $input );
603
+ }
604
+
605
+ return $input;
606
+ }
607
+
608
+ /**
609
+ * Return unserialized object or array
610
+ *
611
+ * @param string $serialized_string Serialized string.
612
+ * @param string $method The name of the caller method.
613
+ *
614
+ * @return mixed, false on failure
615
+ */
616
+ private static function unserialize( $serialized_string ) {
617
+ if( !is_serialized( $serialized_string ) ) {
618
+ return false;
619
+ }
620
+
621
+ $serialized_string = trim( $serialized_string );
622
+ $unserialized_string = @unserialize( $serialized_string );
623
+
624
+ return $unserialized_string;
625
+ }
626
+
627
+ /**
628
+ * Wrapper for str_replace
629
+ *
630
+ * @param string $from
631
+ * @param string $to
632
+ * @param string $data
633
+ * @param string|bool $case_insensitive
634
+ *
635
+ * @return string
636
+ */
637
+ private function str_replace( $from, $to, $data, $case_insensitive = false ) {
638
+
639
+ // Add filter
640
+ $excludes = apply_filters( 'wpstg_clone_searchreplace_excl', array() );
641
+
642
+ // Build pattern
643
+ $regexExclude = '';
644
+ foreach ( $excludes as $exclude ) {
645
+ $regexExclude .= $exclude . '(*SKIP)(FAIL)|';
646
+ }
647
+
648
+ if( 'on' === $case_insensitive ) {
649
+ //$data = str_ireplace( $from, $to, $data );
650
+ $data = preg_replace( '#' . $regexExclude . preg_quote( $from ) . '#i', $to, $data );
651
+ } else {
652
+ //$data = str_replace( $from, $to, $data );
653
+ $data = preg_replace( '#' . $regexExclude . preg_quote( $from ) . '#', $to, $data );
654
+ }
655
+
656
+ return $data;
657
+ }
658
+
659
+ /**
660
+ * Set the job
661
+ * @param string $table
662
+ */
663
+ private function setJob( $table ) {
664
+ if( !empty( $this->options->job->current ) ) {
665
+ return;
666
+ }
667
+
668
+ $this->options->job->current = $table;
669
+ $this->options->job->start = 0;
670
+ }
671
+
672
+ /**
673
+ * Start Job
674
+ * @param string $new
675
+ * @param string $old
676
+ * @return bool
677
+ */
678
+ private function startJob( $new, $old ) {
679
+ if( 0 != $this->options->job->start ) {
680
+ return true;
681
+ }
682
+
683
+ $this->options->job->total = ( int ) $this->db->get_var( "SELECT COUNT(1) FROM {$old}" );
684
+
685
+ if( 0 == $this->options->job->total ) {
686
+ $this->finishStep();
687
+ return false;
688
+ }
689
+
690
+ return true;
691
+ }
692
+
693
+ /**
694
+ * Finish the step
695
+ */
696
+ private function finishStep() {
697
+ // This job is not finished yet
698
+ if( $this->options->job->total > $this->options->job->start ) {
699
+ return false;
700
+ }
701
+
702
+ // Add it to cloned tables listing
703
+ $this->options->clonedTables[] = $this->options->tables[$this->options->currentStep];
704
+
705
+ // Reset job
706
+ $this->options->job = new \stdClass();
707
+
708
+ return true;
709
+ }
710
+
711
+ /**
712
+ * Drop table if necessary
713
+ * @param string $new
714
+ */
715
+ private function dropTable( $new ) {
716
+ $old = $this->db->get_var( $this->db->prepare( "SHOW TABLES LIKE %s", $new ) );
717
+
718
+ if( !$this->shouldDropTable( $new, $old ) ) {
719
+ return;
720
+ }
721
+
722
+ $this->log( "DB Processing: {$new} already exists, dropping it first" );
723
+ $this->db->query( "DROP TABLE {$new}" );
724
+ }
725
+
726
+ /**
727
+ * Check if table needs to be dropped
728
+ * @param string $new
729
+ * @param string $old
730
+ * @return bool
731
+ */
732
+ private function shouldDropTable( $new, $old ) {
733
+ return (
734
+ $old == $new &&
735
+ (
736
+ !isset( $this->options->job->current ) ||
737
+ !isset( $this->options->job->start ) ||
738
+ 0 == $this->options->job->start
739
+ )
740
+ );
741
+ }
742
+
743
+ /**
744
+ * Check if WP is installed in subdir
745
+ * @return boolean
746
+ */
747
+ private function isSubDir() {
748
+ // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
749
+ // This is happening much more often than you would expect
750
+ $siteurl = preg_replace( '#^https?://#', '', rtrim( get_option( 'siteurl' ), '/' ) );
751
+ $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
752
+
753
+ if( $home !== $siteurl ) {
754
+ return true;
755
+ }
756
+ return false;
757
+ }
758
+
759
+ /**
760
+ * Get the install sub directory if WP is installed in sub directory
761
+ * @return string
762
+ */
763
+ private function getSubDir() {
764
+ $home = get_option( 'home' );
765
+ $siteurl = get_option( 'siteurl' );
766
+
767
+ if( empty( $home ) || empty( $siteurl ) ) {
768
+ return '/';
769
+ }
770
+
771
+ $dir = str_replace( $home, '', $siteurl );
772
+ return '/' . str_replace( '/', '', $dir );
773
+ }
774
+
775
+ }
 
apps/Backend/Modules/Jobs/Multisite/SearchReplaceExternal.php ADDED
@@ -0,0 +1,727 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
+
5
+ // No Direct Access
6
+ if( !defined( "WPINC" ) ) {
7
+ die;
8
+ }
9
+
10
+ use WPStaging\WPStaging;
11
+ use WPStaging\Utils\Strings;
12
+ use WPStaging\Utils\Helper;
13
+ use WPStaging\Utils\Multisite;
14
+ use WPStaging\Backend\Modules\Jobs\JobExecutable;
15
+
16
+ /**
17
+ * Class Database
18
+ * @package WPStaging\Backend\Modules\Jobs
19
+ */
20
+ class SearchReplaceExternal extends JobExecutable {
21
+
22
+ /**
23
+ * @var int
24
+ */
25
+ private $total = 0;
26
+
27
+ /**
28
+ * @var \WPDB
29
+ */
30
+ public $db;
31
+
32
+ /**
33
+ * The prefix of the new database tables which are used for the live site after updating tables
34
+ * @var string
35
+ */
36
+ public $tmpPrefix;
37
+
38
+ /**
39
+ * Initialize
40
+ */
41
+ public function initialize() {
42
+ $this->total = count( $this->options->tables );
43
+ $this->db = $this->getStagingDB();
44
+ $this->tmpPrefix = $this->options->prefix;
45
+ }
46
+
47
+ /**
48
+ * Get database object to interact with
49
+ */
50
+ private function getStagingDB() {
51
+ return new \wpdb( $this->options->databaseUser, $this->options->databasePassword, $this->options->databaseDatabase, $this->options->databaseServer );
52
+ }
53
+
54
+ public function start() {
55
+ // Skip job. Nothing to do
56
+ if( $this->options->totalSteps === 0 ) {
57
+ $this->prepareResponse( true, false );
58
+ }
59
+
60
+ $this->run();
61
+
62
+ // Save option, progress
63
+ $this->saveOptions();
64
+
65
+ return ( object ) $this->response;
66
+ }
67
+
68
+ /**
69
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
70
+ * @return void
71
+ */
72
+ protected function calculateTotalSteps() {
73
+ $this->options->totalSteps = $this->total;
74
+ }
75
+
76
+ /**
77
+ * Execute the Current Step
78
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
79
+ * @return bool
80
+ */
81
+ protected function execute() {
82
+ // Over limits threshold
83
+ if( $this->isOverThreshold() ) {
84
+ // Prepare response and save current progress
85
+ $this->prepareResponse( false, false );
86
+ $this->saveOptions();
87
+ return false;
88
+ }
89
+
90
+ // No more steps, finished
91
+ if( $this->options->currentStep > $this->total || !isset( $this->options->tables[$this->options->currentStep] ) ) {
92
+ $this->prepareResponse( true, false );
93
+ return false;
94
+ }
95
+
96
+ // Table is excluded
97
+ if( in_array( $this->options->tables[$this->options->currentStep], $this->options->excludedTables ) ) {
98
+ $this->prepareResponse();
99
+ return true;
100
+ }
101
+
102
+ // Search & Replace
103
+ if( !$this->stopExecution() && !$this->updateTable( $this->options->tables[$this->options->currentStep] ) ) {
104
+ // Prepare Response
105
+ $this->prepareResponse( false, false );
106
+
107
+ // Not finished
108
+ return true;
109
+ }
110
+
111
+
112
+ // Prepare Response
113
+ $this->prepareResponse();
114
+
115
+ // Not finished
116
+ return true;
117
+ }
118
+
119
+ /**
120
+ * Stop Execution immediately
121
+ * return mixed bool | json
122
+ */
123
+ private function stopExecution() {
124
+ // if( $this->db->prefix == $this->tmpPrefix ) {
125
+ // $this->returnException( 'Fatal Error 9: Prefix ' . $this->db->prefix . ' is used for the live site hence it can not be used for the staging site as well. Please ask support@wp-staging.com how to resolve this.' );
126
+ // }
127
+ return false;
128
+ }
129
+
130
+ /**
131
+ * Copy Tables
132
+ * @param string $tableName
133
+ * @return bool
134
+ */
135
+ private function updateTable( $tableName ) {
136
+ $strings = new Strings();
137
+ $table = $strings->str_replace_first( $this->options->databasePrefix, '', $tableName );
138
+ $newTableName = $this->tmpPrefix . $table;
139
+
140
+ // Save current job
141
+ $this->setJob( $newTableName );
142
+
143
+ // Beginning of the job
144
+ if( !$this->startJob( $newTableName, $tableName ) ) {
145
+ return true;
146
+ }
147
+ // Copy data
148
+ $this->startReplace( $newTableName );
149
+
150
+ // Finis the step
151
+ return $this->finishStep();
152
+ }
153
+
154
+ /**
155
+ * Start search replace job
156
+ * @param string $new
157
+ * @param string $old
158
+ */
159
+ private function startReplace( $new ) {
160
+ $rows = $this->options->job->start + $this->settings->querySRLimit;
161
+ $this->log(
162
+ "DB Processing: Table {$new} {$this->options->job->start} to {$rows} records"
163
+ );
164
+
165
+ // Search & Replace
166
+ $this->searchReplace( $new, $rows, array() );
167
+
168
+ // Set new offset
169
+ $this->options->job->start += $this->settings->querySRLimit;
170
+ }
171
+
172
+ /**
173
+ * Returns the number of pages in a table.
174
+ * @access public
175
+ * @return int
176
+ */
177
+ private function get_pages_in_table( $table ) {
178
+ $table = esc_sql( $table );
179
+ $rows = $this->db->get_var( "SELECT COUNT(*) FROM $table" );
180
+ $pages = ceil( $rows / $this->settings->querySRLimit );
181
+ return absint( $pages );
182
+ }
183
+
184
+ /**
185
+ * Gets the columns in a table.
186
+ * @access public
187
+ * @param string $table The table to check.
188
+ * @return array
189
+ */
190
+ private function get_columns( $table ) {
191
+ $primary_key = null;
192
+ $columns = array();
193
+ $fields = $this->db->get_results( 'DESCRIBE ' . $table );
194
+ if( is_array( $fields ) ) {
195
+ foreach ( $fields as $column ) {
196
+ $columns[] = $column->Field;
197
+ if( $column->Key == 'PRI' ) {
198
+ $primary_key = $column->Field;
199
+ }
200
+ }
201
+ }
202
+ return array($primary_key, $columns);
203
+ }
204
+
205
+ /**
206
+ * Adapated from interconnect/it's search/replace script, adapted from Better Search Replace
207
+ *
208
+ * Modified to use WordPress wpdb functions instead of PHP's native mysql/pdo functions,
209
+ * and to be compatible with batch processing.
210
+ *
211
+ * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
212
+ *
213
+ * @access public
214
+ * @param string $table The table to run the replacement on.
215
+ * @param int $page The page/block to begin the query on.
216
+ * @param array $args An associative array containing arguments for this run.
217
+ * @return array
218
+ */
219
+ private function searchReplace( $table, $page, $args ) {
220
+
221
+ if( $this->thirdParty->isSearchReplaceExcluded( $table ) ) {
222
+ $this->log( "DB Processing: Skip {$table}", \WPStaging\Utils\Logger::TYPE_INFO );
223
+ return true;
224
+ }
225
+
226
+ // Load up the default settings for this chunk.
227
+ $table = esc_sql( $table );
228
+ $current_page = $this->options->job->start + $this->settings->querySRLimit;
229
+ $pages = $this->get_pages_in_table( $table );
230
+
231
+
232
+ if( $this->isSubDir() ) {
233
+ // Search URL example.com/staging and root path to staging site /var/www/htdocs/staging
234
+ $args['search_for'] = array(
235
+ rtrim( $this->multisiteHomeUrlWithoutScheme, "/" ) . $this->getSubDir(),
236
+ ABSPATH,
237
+ str_replace( '/', '\/', rtrim( $this->multisiteHomeUrlWithoutScheme, '/' ) ) . str_replace( '/', '\/', $this->getSubDir() ), // // Used by revslider and several visual editors
238
+ $this->getImagePathLive()
239
+ );
240
+
241
+
242
+ $args['replace_with'] = array(
243
+ rtrim( $this->multisiteDomainWithoutScheme, "/" ) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName,
244
+ rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName,
245
+ str_replace( '/', '\/', rtrim( $this->multisiteDomainWithoutScheme, "/" ) ) . str_replace( '/', '\/', $this->getSubDir() ) . '\/' . $this->options->cloneDirectoryName, // Used by revslider and several visual editors
246
+ $this->getImagePathStaging()
247
+ );
248
+ } else {
249
+ $args['search_for'] = array(
250
+ rtrim( $this->multisiteHomeUrlWithoutScheme, '/' ),
251
+ ABSPATH,
252
+ str_replace( '/', '\/', rtrim( $this->multisiteHomeUrlWithoutScheme, '/' ) ),
253
+ $this->getImagePathLive()
254
+ );
255
+ $args['replace_with'] = array(
256
+ rtrim( $this->multisiteDomainWithoutScheme, '/' ) . '/' . $this->options->cloneDirectoryName,
257
+ rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName,
258
+ str_replace( '/', '\/', rtrim( $this->multisiteDomainWithoutScheme, '/' ) ) . '\/' . $this->options->cloneDirectoryName,
259
+ $this->getImagePathStaging()
260
+ );
261
+ }
262
+
263
+ //$this->log( 'Search: ' . $this->multisiteHomeUrlWithoutScheme . ' Replace: ' . rtrim( $this->multisiteDomainWithoutScheme, '/' ) . '/' . $this->options->cloneDirectoryName );
264
+
265
+
266
+ $args['replace_guids'] = 'off';
267
+ $args['dry_run'] = 'off';
268
+ $args['case_insensitive'] = false;
269
+ $args['replace_mails'] = 'off';
270
+ $args['skip_transients'] = 'on';
271
+
272
+
273
+ // Allow filtering of search & replace parameters
274
+ $args = apply_filters( 'wpstg_clone_searchreplace_params', $args );
275
+
276
+ // Get a list of columns in this table.
277
+ list( $primary_key, $columns ) = $this->get_columns( $table );
278
+
279
+ // Bail out early if there isn't a primary key.
280
+ // We commented this to search & replace through tables which have no primary keys like wp_revslider_slides
281
+ // @todo test this carefully. If it causes (performance) issues we need to activate it again!
282
+ // @since 2.4.4
283
+ // if( null === $primary_key ) {
284
+ // return false;
285
+ // }
286
+
287
+ $current_row = 0;
288
+ $start = $this->options->job->start;
289
+ $end = $this->settings->querySRLimit;
290
+
291
+ // Grab the content of the table.
292
+ $data = $this->db->get_results( "SELECT * FROM $table LIMIT $start, $end", ARRAY_A );
293
+
294
+ // Filter certain rows (of other plugins)
295
+ $filter = array(
296
+ 'Admin_custome_login_Slidshow',
297
+ 'Admin_custome_login_Social',
298
+ 'Admin_custome_login_logo',
299
+ 'Admin_custome_login_text',
300
+ 'Admin_custome_login_login',
301
+ 'Admin_custome_login_top',
302
+ 'Admin_custome_login_dashboard',
303
+ 'Admin_custome_login_Version',
304
+ 'upload_path',
305
+ );
306
+
307
+ $filter = apply_filters( 'wpstg_clone_searchreplace_excl_rows', $filter );
308
+
309
+ // Loop through the data.
310
+ foreach ( $data as $row ) {
311
+ $current_row++;
312
+ $update_sql = array();
313
+ $where_sql = array();
314
+ $upd = false;
315
+
316
+ // Skip rows below
317
+ if( isset( $row['option_name'] ) && in_array( $row['option_name'], $filter ) ) {
318
+ continue;
319
+ }
320
+
321
+ // Skip rows with transients (They can store huge data and we need to save memory)
322
+ if( isset( $row['option_name'] ) && 'on' === $args['skip_transients'] && false !== strpos( $row['option_name'], '_transient' ) ) {
323
+ continue;
324
+ }
325
+
326
+ foreach ( $columns as $column ) {
327
+
328
+ $dataRow = $row[$column];
329
+
330
+ if( $column == $primary_key ) {
331
+ $where_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
332
+ continue;
333
+ }
334
+
335
+ // Skip GUIDs by default.
336
+ if( 'on' !== $args['replace_guids'] && 'guid' == $column ) {
337
+ continue;
338
+ }
339
+
340
+ // Skip mail addresses
341
+ if( 'off' === $args['replace_mails'] && false !== strpos( $dataRow, '@' . $this->multisiteDomainWithoutScheme ) ) {
342
+ continue;
343
+ }
344
+
345
+ // Check options table
346
+ if( $this->options->prefix . 'options' === $table ) {
347
+
348
+ // Skip certain options
349
+ if( isset( $should_skip ) && true === $should_skip ) {
350
+ $should_skip = false;
351
+ continue;
352
+ }
353
+
354
+ // Skip this row
355
+ if( 'wpstg_existing_clones_beta' === $dataRow ||
356
+ 'wpstg_existing_clones' === $dataRow ||
357
+ 'wpstg_settings' === $dataRow ||
358
+ 'wpstg_license_status' === $dataRow ||
359
+ 'siteurl' === $dataRow ||
360
+ 'home' === $dataRow
361
+ ) {
362
+ $should_skip = true;
363
+ }
364
+ }
365
+
366
+ // Check the path delimiter for / or \/ and remove one of those which prevents from resulting in wrong syntax like domain.com/staging\/.
367
+ // 1. local.wordpress.test -> local.wordpress.test/staging
368
+ // 2. local.wordpress.test\/ -> local.wordpress.test\/staging\/
369
+ $tmp = $args;
370
+ if( false === strpos( $dataRow, $tmp['search_for'][0] ) ) {
371
+ array_shift( $tmp['search_for'] ); // rtrim( $this->homeUrl, '/' ),
372
+ array_shift( $tmp['replace_with'] ); // rtrim( $this->homeUrl, '/' ) . '/' . $this->options->cloneDirectoryName,
373
+ } else {
374
+ unset( $tmp['search_for'][1] );
375
+ unset( $tmp['replace_with'][1] );
376
+ // recount array
377
+ $tmp['search_for'] = array_values( $tmp['search_for'] );
378
+ $tmp['replace_with'] = array_values( $tmp['replace_with'] );
379
+ }
380
+
381
+ // Run a search replace on the data row and respect the serialisation.
382
+ $i = 0;
383
+ foreach ( $tmp['search_for'] as $replace ) {
384
+ //$this->log( "Search for: {$tmp['search_for'][$i]} Replace with {$tmp['replace_with'][$i]}", \WPStaging\Utils\Logger::TYPE_ERROR );
385
+ $dataRow = $this->recursive_unserialize_replace( $tmp['search_for'][$i], $tmp['replace_with'][$i], $dataRow, false, $args['case_insensitive'] );
386
+ $i++;
387
+ }
388
+ unset( $replace );
389
+ unset( $i );
390
+ unset( $tmp );
391
+
392
+ // Something was changed
393
+ if( $row[$column] != $dataRow ) {
394
+ $update_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
395
+ $upd = true;
396
+ }
397
+ }
398
+
399
+ // Determine what to do with updates.
400
+ if( $args['dry_run'] === 'on' ) {
401
+ // Don't do anything if a dry run
402
+ } elseif( $upd && !empty( $where_sql ) ) {
403
+ // If there are changes to make, run the query.
404
+ $sql = 'UPDATE ' . $table . ' SET ' . implode( ', ', $update_sql ) . ' WHERE ' . implode( ' AND ', array_filter( $where_sql ) );
405
+ $result = $this->db->query( $sql );
406
+
407
+ if( !$result ) {
408
+ $this->log( "Error updating row {$current_row} SQL: {$sql}", \WPStaging\Utils\Logger::TYPE_ERROR );
409
+ }
410
+ }
411
+ } // end row loop
412
+ unset( $row );
413
+ unset( $update_sql );
414
+ unset( $where_sql );
415
+ unset( $sql );
416
+
417
+
418
+ // DB Flush
419
+ $this->db->flush();
420
+ return true;
421
+ }
422
+
423
+ /**
424
+ * Get path to multisite image folder e.g. wp-content/blogs.dir/ID/files or wp-content/uploads/sites/ID
425
+ * @return string
426
+ */
427
+ private function getImagePathLive() {
428
+ // Check first which structure is used
429
+ $uploads = wp_upload_dir();
430
+ $basedir = $uploads['basedir'];
431
+ $blogId = get_current_blog_id();
432
+
433
+ if( false === strpos( $basedir, 'blogs.dir' ) ) {
434
+ // Since WP 3.5
435
+ $path = $blogId > 1 ?
436
+ 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR :
437
+ 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
438
+ } else {
439
+ // old blog structure
440
+ $path = $blogId > 1 ?
441
+ 'wp-content' . DIRECTORY_SEPARATOR . 'blogs.dir' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR . 'files' . DIRECTORY_SEPARATOR :
442
+ 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
443
+ }
444
+ return $path;
445
+ }
446
+
447
+ /**
448
+ * Get path to staging site image path wp-content/uploads
449
+ * @return string
450
+ */
451
+ private function getImagePathStaging() {
452
+ return 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
453
+ }
454
+
455
+ /**
456
+ * Adapted from interconnect/it's search/replace script.
457
+ *
458
+ * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
459
+ *
460
+ * Take a serialised array and unserialise it replacing elements as needed and
461
+ * unserialising any subordinate arrays and performing the replace on those too.
462
+ *
463
+ * @access private
464
+ * @param string $from String we're looking to replace.
465
+ * @param string $to What we want it to be replaced with
466
+ * @param array $data Used to pass any subordinate arrays back to in.
467
+ * @param boolean $serialized Does the array passed via $data need serialising.
468
+ * @param sting|boolean $case_insensitive Set to 'on' if we should ignore case, false otherwise.
469
+ *
470
+ * @return string|array The original array with all elements replaced as needed.
471
+ */
472
+ private function recursive_unserialize_replace( $from = '', $to = '', $data = '', $serialized = false, $case_insensitive = false ) {
473
+ try {
474
+ // Some unserialized data cannot be re-serialized eg. SimpleXMLElements
475
+ if( is_serialized( $data ) && ( $unserialized = @unserialize( $data ) ) !== false ) {
476
+ $data = $this->recursive_unserialize_replace( $from, $to, $unserialized, true, $case_insensitive );
477
+ } elseif( is_array( $data ) ) {
478
+ $tmp = array();
479
+ foreach ( $data as $key => $value ) {
480
+ $tmp[$key] = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
481
+ }
482
+
483
+ $data = $tmp;
484
+ unset( $tmp );
485
+ } elseif( is_object( $data ) ) {
486
+ $tmp = $data;
487
+ $props = get_object_vars( $data );
488
+ foreach ( $props as $key => $value ) {
489
+ if( $key === '' || ord( $key[0] ) === 0 ) {
490
+ continue;
491
+ }
492
+ $tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
493
+ }
494
+
495
+ $data = $tmp;
496
+ unset( $tmp );
497
+ } else {
498
+ if( is_string( $data ) ) {
499
+ if( !empty( $from ) && !empty( $to ) ) {
500
+ $data = $this->str_replace( $from, $to, $data, $case_insensitive );
501
+ }
502
+ }
503
+ }
504
+
505
+ if( $serialized ) {
506
+ return serialize( $data );
507
+ }
508
+ } catch ( Exception $error ) {
509
+
510
+ }
511
+
512
+ return $data;
513
+ }
514
+
515
+ /**
516
+ * Check if the object is a valid one and not __PHP_Incomplete_Class_Name
517
+ * Can not use is_object alone because in php 7.2 it's returning true even though object is __PHP_Incomplete_Class_Name
518
+ * @return boolean
519
+ */
520
+ // private function isValidObject( $data ) {
521
+ // if( !is_object( $data ) || gettype( $data ) != 'object' ) {
522
+ // return false;
523
+ // }
524
+ //
525
+ // $invalid_class_props = get_object_vars( $data );
526
+ //
527
+ // if (!isset($invalid_class_props['__PHP_Incomplete_Class_Name'])){
528
+ // // Assume it must be an valid object
529
+ // return true;
530
+ // }
531
+ //
532
+ // $invalid_object_class = $invalid_class_props['__PHP_Incomplete_Class_Name'];
533
+ //
534
+ // if( !empty( $invalid_object_class ) ) {
535
+ // return false;
536
+ // }
537
+ //
538
+ // // Assume it must be an valid object
539
+ // return true;
540
+ // }
541
+
542
+ /**
543
+ * Mimics the mysql_real_escape_string function. Adapted from a post by 'feedr' on php.net.
544
+ * @link http://php.net/manual/en/function.mysql-real-escape-string.php#101248
545
+ * @access public
546
+ * @param string $input The string to escape.
547
+ * @return string
548
+ */
549
+ private function mysql_escape_mimic( $input ) {
550
+ if( is_array( $input ) ) {
551
+ return array_map( __METHOD__, $input );
552
+ }
553
+ if( !empty( $input ) && is_string( $input ) ) {
554
+ return str_replace( array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $input );
555
+ }
556
+
557
+ return $input;
558
+ }
559
+
560
+ /**
561
+ * Return unserialized object or array
562
+ *
563
+ * @param string $serialized_string Serialized string.
564
+ * @param string $method The name of the caller method.
565
+ *
566
+ * @return mixed, false on failure
567
+ */
568
+ private static function unserialize( $serialized_string ) {
569
+ if( !is_serialized( $serialized_string ) ) {
570
+ return false;
571
+ }
572
+
573
+ $serialized_string = trim( $serialized_string );
574
+ $unserialized_string = @unserialize( $serialized_string );
575
+
576
+ return $unserialized_string;
577
+ }
578
+
579
+ /**
580
+ * Wrapper for str_replace
581
+ *
582
+ * @param string $from
583
+ * @param string $to
584
+ * @param string $data
585
+ * @param string|bool $case_insensitive
586
+ *
587
+ * @return string
588
+ */
589
+ private function str_replace( $from, $to, $data, $case_insensitive = false ) {
590
+
591
+ // Add filter
592
+ $excludes = apply_filters( 'wpstg_clone_searchreplace_excl', array() );
593
+
594
+ // Build pattern
595
+ $regexExclude = '';
596
+ foreach ( $excludes as $exclude ) {
597
+ $regexExclude .= $exclude . '(*SKIP)(FAIL)|';
598
+ }
599
+
600
+ if( 'on' === $case_insensitive ) {
601
+ //$data = str_ireplace( $from, $to, $data );
602
+ $data = preg_replace( '#' . $regexExclude . preg_quote( $from ) . '#i', $to, $data );
603
+ } else {
604
+ //$data = str_replace( $from, $to, $data );
605
+ $data = preg_replace( '#' . $regexExclude . preg_quote( $from ) . '#', $to, $data );
606
+ }
607
+
608
+ return $data;
609
+ }
610
+
611
+ /**
612
+ * Set the job
613
+ * @param string $table
614
+ */
615
+ private function setJob( $table ) {
616
+ if( !empty( $this->options->job->current ) ) {
617
+ return;
618
+ }
619
+
620
+ $this->options->job->current = $table;
621
+ $this->options->job->start = 0;
622
+ }
623
+
624
+ /**
625
+ * Start Job
626
+ * @param string $new
627
+ * @param string $old
628
+ * @return bool
629
+ */
630
+ private function startJob( $new, $old ) {
631
+ if( 0 != $this->options->job->start ) {
632
+ return true;
633
+ }
634
+
635
+ $this->options->job->total = ( int ) $this->db->get_var( "SELECT COUNT(1) FROM {$old}" );
636
+
637
+ if( 0 == $this->options->job->total ) {
638
+ $this->finishStep();
639
+ return false;
640
+ }
641
+
642
+ return true;
643
+ }
644
+
645
+ /**
646
+ * Finish the step
647
+ */
648
+ private function finishStep() {
649
+ // This job is not finished yet
650
+ if( $this->options->job->total > $this->options->job->start ) {
651
+ return false;
652
+ }
653
+
654
+ // Add it to cloned tables listing
655
+ $this->options->clonedTables[] = $this->options->tables[$this->options->currentStep];
656
+
657
+ // Reset job
658
+ $this->options->job = new \stdClass();
659
+
660
+ return true;
661
+ }
662
+
663
+ /**
664
+ * Drop table if necessary
665
+ * @param string $new
666
+ */
667
+ private function dropTable( $new ) {
668
+ $old = $this->db->get_var( $this->db->prepare( "SHOW TABLES LIKE %s", $new ) );
669
+
670
+ if( !$this->shouldDropTable( $new, $old ) ) {
671
+ return;
672
+ }
673
+
674
+ $this->log( "DB Processing: {$new} already exists, dropping it first" );
675
+ $this->db->query( "DROP TABLE {$new}" );
676
+ }
677
+
678
+ /**
679
+ * Check if table needs to be dropped
680
+ * @param string $new
681
+ * @param string $old
682
+ * @return bool
683
+ */
684
+ private function shouldDropTable( $new, $old ) {
685
+ return (
686
+ $old == $new &&
687
+ (
688
+ !isset( $this->options->job->current ) ||
689
+ !isset( $this->options->job->start ) ||
690
+ 0 == $this->options->job->start
691
+ )
692
+ );
693
+ }
694
+
695
+ /**
696
+ * Check if WP is installed in subdir
697
+ * @return boolean
698
+ */
699
+ private function isSubDir() {
700
+ // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
701
+ // This is happening much more often than you would expect
702
+ $siteurl = preg_replace( '#^https?://#', '', rtrim( get_option( 'siteurl' ), '/' ) );
703
+ $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
704
+
705
+ if( $home !== $siteurl ) {
706
+ return true;
707
+ }
708
+ return false;
709
+ }
710
+
711
+ /**
712
+ * Get the install sub directory if WP is installed in sub directory
713
+ * @return string
714
+ */
715
+ private function getSubDir() {
716
+ $home = get_option( 'home' );
717
+ $siteurl = get_option( 'siteurl' );
718
+
719
+ if( empty( $home ) || empty( $siteurl ) ) {
720
+ return '/';
721
+ }
722
+
723
+ $dir = str_replace( $home, '', $siteurl );
724
+ return '/' . str_replace( '/', '', $dir );
725
+ }
726
+
727
+ }
apps/Backend/Modules/Jobs/Scan.php CHANGED
@@ -7,8 +7,9 @@ if( !defined( "WPINC" ) ) {
7
  die;
8
  }
9
 
10
- use WPStaging\Utils\Directories;
11
  use WPStaging\WPStaging;
 
 
12
 
13
  /**
14
  * Class Scan
@@ -38,6 +39,10 @@ class Scan extends Job {
38
  // Get directories
39
  $this->directories();
40
 
 
 
 
 
41
  $this->db = WPStaging::getInstance()->get( 'wpdb' );
42
  $this->prefix = $this->db->prefix;
43
  }
@@ -93,6 +98,14 @@ class Scan extends Job {
93
  return $this;
94
  }
95
 
 
 
 
 
 
 
 
 
96
  /**
97
  * Format bytes into human readable form
98
  * @param int $bytes
@@ -158,7 +171,6 @@ class Scan extends Job {
158
  // Extra class to differentiate between wp core and non core folders
159
  $class = !$isDisabled ? 'wpstg-root' : 'wpstg-extra';
160
 
161
-
162
  $output .= "<div class='wpstg-dir'>";
163
  $output .= "<input type='checkbox' class='wpstg-check-dir " . $class . "'";
164
 
7
  die;
8
  }
9
 
 
10
  use WPStaging\WPStaging;
11
+ use WPStaging\Utils\Directories;
12
+ use WPStaging\Backend\Optimizer\Optimizer;
13
 
14
  /**
15
  * Class Scan
39
  // Get directories
40
  $this->directories();
41
 
42
+ // Install Optimizer
43
+ $this->installOptimizer();
44
+
45
+
46
  $this->db = WPStaging::getInstance()->get( 'wpdb' );
47
  $this->prefix = $this->db->prefix;
48
  }
98
  return $this;
99
  }
100
 
101
+ /**
102
+ * Make sure the Optimizer mu plugin is installed before cloning or pushing
103
+ */
104
+ private function installOptimizer(){
105
+ $optimizer = new Optimizer();
106
+ $optimizer->installOptimizer();
107
+ }
108
+
109
  /**
110
  * Format bytes into human readable form
111
  * @param int $bytes
171
  // Extra class to differentiate between wp core and non core folders
172
  $class = !$isDisabled ? 'wpstg-root' : 'wpstg-extra';
173
 
 
174
  $output .= "<div class='wpstg-dir'>";
175
  $output .= "<input type='checkbox' class='wpstg-check-dir " . $class . "'";
176
 
apps/Backend/Modules/Jobs/SearchReplace.php CHANGED
@@ -46,7 +46,7 @@ class SearchReplace extends JobExecutable {
46
  $this->total = count( $this->options->tables );
47
  $this->db = WPStaging::getInstance()->get( "wpdb" );
48
  $this->tmpPrefix = $this->options->prefix;
49
- $helper = new Helper();
50
  $this->homeUrl = $helper->get_home_url_without_scheme();
51
  }
52
 
@@ -159,7 +159,7 @@ class SearchReplace extends JobExecutable {
159
  $rows = $this->options->job->start + $this->settings->querySRLimit;
160
  $this->log(
161
  "DB Processing: Table {$new} {$this->options->job->start} to {$rows} records"
162
- );
163
 
164
  // Search & Replace
165
  $this->searchReplace( $new, $rows, array() );
@@ -216,32 +216,30 @@ class SearchReplace extends JobExecutable {
216
  * @return array
217
  */
218
  private function searchReplace( $table, $page, $args ) {
219
-
220
- if( $this->thirdParty->isSearchReplaceExcluded($table) ) {
221
  $this->log( "DB Processing: Skip {$table}", \WPStaging\Utils\Logger::TYPE_INFO );
222
  return true;
223
  }
224
-
225
-
226
  // Load up the default settings for this chunk.
227
  $table = esc_sql( $table );
228
  $current_page = $this->options->job->start + $this->settings->querySRLimit;
229
  $pages = $this->get_pages_in_table( $table );
230
 
231
-
232
  if( $this->isSubDir() ) {
233
- // Search URL example.com/staging and root path to staging site /var/www/htdocs/staging
234
- $args['search_for'] = array(
235
- $this->homeUrl . str_replace( '/', '\/', $this->getSubDir() ), // // Used by revslider and several visual editors
236
- rtrim( $this->homeUrl, "/" ) . $this->getSubDir(),
237
- rtrim( ABSPATH, '/' ),
238
- );
239
 
240
- $args['replace_with'] = array(
241
  $this->homeUrl . str_replace( '/', '\/', $this->getSubDir() ) . '\/' . $this->options->cloneDirectoryName, // Used by revslider and several visual editors
242
  rtrim( $this->homeUrl, "/" ) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName,
243
- rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName,
244
-
245
  );
246
  } else {
247
  $args['search_for'] = array(
@@ -252,22 +250,20 @@ class SearchReplace extends JobExecutable {
252
  $args['replace_with'] = array(
253
  $this->homeUrl . '\/' . $this->options->cloneDirectoryName . '\/',
254
  rtrim( $this->homeUrl, '/' ) . '/' . $this->options->cloneDirectoryName,
255
- rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName,
256
-
257
- );
258
  }
259
 
260
 
261
  $args['replace_guids'] = 'off';
262
  $args['dry_run'] = 'off';
263
  $args['case_insensitive'] = false;
264
- $args['replace_guids'] = 'off';
265
  $args['replace_mails'] = 'off';
266
  $args['skip_transients'] = 'on';
267
 
268
-
269
  // Allow filtering of search & replace parameters
270
- $args = apply_filters('wpstg_clone_searchreplace_params', $args);
271
 
272
  // Get a list of columns in this table.
273
  list( $primary_key, $columns ) = $this->get_columns( $table );
@@ -286,7 +282,7 @@ class SearchReplace extends JobExecutable {
286
 
287
  // Grab the content of the table.
288
  $data = $this->db->get_results( "SELECT * FROM $table LIMIT $start, $end", ARRAY_A );
289
-
290
  // Filter certain rows (of other plugins)
291
  $filter = array(
292
  'Admin_custome_login_Slidshow',
@@ -298,10 +294,10 @@ class SearchReplace extends JobExecutable {
298
  'Admin_custome_login_dashboard',
299
  'Admin_custome_login_Version',
300
  'upload_path',
301
- );
302
 
303
  $filter = apply_filters( 'wpstg_clone_searchreplace_excl_rows', $filter );
304
-
305
  // Loop through the data.
306
  foreach ( $data as $row ) {
307
  $current_row++;
@@ -313,7 +309,7 @@ class SearchReplace extends JobExecutable {
313
  if( isset( $row['option_name'] ) && in_array( $row['option_name'], $filter ) ) {
314
  continue;
315
  }
316
-
317
  // Skip rows with transients (They can store huge data and we need to save memory)
318
  if( isset( $row['option_name'] ) && 'on' === $args['skip_transients'] && false !== strpos( $row['option_name'], '_transient' ) ) {
319
  continue;
@@ -332,12 +328,12 @@ class SearchReplace extends JobExecutable {
332
  if( 'on' !== $args['replace_guids'] && 'guid' == $column ) {
333
  continue;
334
  }
335
-
336
  // Skip mail addresses
337
  if( 'off' === $args['replace_mails'] && false !== strpos( $dataRow, '@' . $this->homeUrl ) ) {
338
  continue;
339
  }
340
-
341
 
342
  // Check options table
343
  if( $this->options->prefix . 'options' === $table ) {
@@ -363,19 +359,19 @@ class SearchReplace extends JobExecutable {
363
  // Check the path delimiter for / or \/ and remove one of those which prevents from resulting in wrong syntax like domain.com/staging\/.
364
  // 1. local.wordpress.test -> local.wordpress.test/staging
365
  // 2. local.wordpress.test\/ -> local.wordpress.test\/staging\/
366
- if ( false === strpos( $dataRow, $tmp['search_for'][0] )){
367
- array_shift($tmp['search_for']); // rtrim( $this->homeUrl, '/' ),
368
- array_shift($tmp['replace_with']); // rtrim( $this->homeUrl, '/' ) . '/' . $this->options->cloneDirectoryName,
369
  } else {
370
- unset($tmp['search_for'][1]);
371
- unset($tmp['replace_with'][1]);
372
  // recount array
373
- $tmp['search_for'] = array_values($tmp['search_for']);
374
- $tmp['replace_with'] = array_values($tmp['replace_with']);
375
  }
376
 
377
 
378
-
379
  // Run a search replace on the data row and respect the serialisation.
380
  $i = 0;
381
  foreach ( $tmp['search_for'] as $replace ) {
@@ -434,7 +430,7 @@ class SearchReplace extends JobExecutable {
434
  *
435
  * @return string|array The original array with all elements replaced as needed.
436
  */
437
- private function recursive_unserialize_replace( $from = '', $to = '', $data = '', $serialized = false, $case_insensitive = false ) {
438
  try {
439
  // Some unserialized data cannot be re-serialized eg. SimpleXMLElements
440
  if( is_serialized( $data ) && ( $unserialized = @unserialize( $data ) ) !== false ) {
@@ -462,7 +458,7 @@ class SearchReplace extends JobExecutable {
462
  } else {
463
  if( is_string( $data ) ) {
464
  if( !empty( $from ) && !empty( $to ) ) {
465
- $data = $this->str_replace( $from, $to, $data, $case_insensitive );
466
  }
467
  }
468
  }
@@ -561,13 +557,13 @@ class SearchReplace extends JobExecutable {
561
  foreach ( $excludes as $exclude ) {
562
  $regexExclude .= $exclude . '(*SKIP)(FAIL)|';
563
  }
564
-
565
  if( 'on' === $case_insensitive ) {
566
  //$data = str_ireplace( $from, $to, $data );
567
- $data = preg_replace( '#' . $regexExclude . preg_quote ( $from) . '#i', $to, $data );
568
  } else {
569
  //$data = str_replace( $from, $to, $data );
570
- $data = preg_replace( '#' . $regexExclude . preg_quote($from) . '#', $to, $data );
571
  }
572
 
573
  return $data;
@@ -657,10 +653,10 @@ class SearchReplace extends JobExecutable {
657
  );
658
  }
659
 
660
- /**
661
- * Check if WP is installed in subdir
662
- * @return boolean
663
- */
664
  private function isSubDir() {
665
  // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
666
  // This is happening much more often than you would expect
@@ -668,26 +664,25 @@ class SearchReplace extends JobExecutable {
668
  $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
669
 
670
  if( $home !== $siteurl ) {
671
- return true;
672
- }
673
- return false;
674
- }
675
-
676
- /**
677
- * Get the install sub directory if WP is installed in sub directory
678
- * @return string
679
- */
680
  private function getSubDir() {
681
  $home = get_option( 'home' );
682
  $siteurl = get_option( 'siteurl' );
683
-
684
  if( empty( $home ) || empty( $siteurl ) ) {
685
- return '/';
686
- }
687
-
688
  $dir = str_replace( $home, '', $siteurl );
689
  return '/' . str_replace( '/', '', $dir );
690
- }
691
-
692
 
693
  }
46
  $this->total = count( $this->options->tables );
47
  $this->db = WPStaging::getInstance()->get( "wpdb" );
48
  $this->tmpPrefix = $this->options->prefix;
49
+ $helper = new Helper();
50
  $this->homeUrl = $helper->get_home_url_without_scheme();
51
  }
52
 
159
  $rows = $this->options->job->start + $this->settings->querySRLimit;
160
  $this->log(
161
  "DB Processing: Table {$new} {$this->options->job->start} to {$rows} records"
162
+ );
163
 
164
  // Search & Replace
165
  $this->searchReplace( $new, $rows, array() );
216
  * @return array
217
  */
218
  private function searchReplace( $table, $page, $args ) {
219
+
220
+ if( $this->thirdParty->isSearchReplaceExcluded( $table ) ) {
221
  $this->log( "DB Processing: Skip {$table}", \WPStaging\Utils\Logger::TYPE_INFO );
222
  return true;
223
  }
224
+
 
225
  // Load up the default settings for this chunk.
226
  $table = esc_sql( $table );
227
  $current_page = $this->options->job->start + $this->settings->querySRLimit;
228
  $pages = $this->get_pages_in_table( $table );
229
 
230
+
231
  if( $this->isSubDir() ) {
232
+ // Search URL example.com/staging and root path to staging site /var/www/htdocs/staging
233
+ $args['search_for'] = array(
234
+ $this->homeUrl . str_replace( '/', '\/', $this->getSubDir() ), // // Used by revslider and several visual editors
235
+ rtrim( $this->homeUrl, "/" ) . $this->getSubDir(),
236
+ rtrim( ABSPATH, '/' ),
237
+ );
238
 
239
+ $args['replace_with'] = array(
240
  $this->homeUrl . str_replace( '/', '\/', $this->getSubDir() ) . '\/' . $this->options->cloneDirectoryName, // Used by revslider and several visual editors
241
  rtrim( $this->homeUrl, "/" ) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName,
242
+ rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName,
 
243
  );
244
  } else {
245
  $args['search_for'] = array(
250
  $args['replace_with'] = array(
251
  $this->homeUrl . '\/' . $this->options->cloneDirectoryName . '\/',
252
  rtrim( $this->homeUrl, '/' ) . '/' . $this->options->cloneDirectoryName,
253
+ rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName,
254
+ );
 
255
  }
256
 
257
 
258
  $args['replace_guids'] = 'off';
259
  $args['dry_run'] = 'off';
260
  $args['case_insensitive'] = false;
 
261
  $args['replace_mails'] = 'off';
262
  $args['skip_transients'] = 'on';
263
 
264
+
265
  // Allow filtering of search & replace parameters
266
+ $args = apply_filters( 'wpstg_clone_searchreplace_params', $args );
267
 
268
  // Get a list of columns in this table.
269
  list( $primary_key, $columns ) = $this->get_columns( $table );
282
 
283
  // Grab the content of the table.
284
  $data = $this->db->get_results( "SELECT * FROM $table LIMIT $start, $end", ARRAY_A );
285
+
286
  // Filter certain rows (of other plugins)
287
  $filter = array(
288
  'Admin_custome_login_Slidshow',
294
  'Admin_custome_login_dashboard',
295
  'Admin_custome_login_Version',
296
  'upload_path',
297
+ );
298
 
299
  $filter = apply_filters( 'wpstg_clone_searchreplace_excl_rows', $filter );
300
+
301
  // Loop through the data.
302
  foreach ( $data as $row ) {
303
  $current_row++;
309
  if( isset( $row['option_name'] ) && in_array( $row['option_name'], $filter ) ) {
310
  continue;
311
  }
312
+
313
  // Skip rows with transients (They can store huge data and we need to save memory)
314
  if( isset( $row['option_name'] ) && 'on' === $args['skip_transients'] && false !== strpos( $row['option_name'], '_transient' ) ) {
315
  continue;
328
  if( 'on' !== $args['replace_guids'] && 'guid' == $column ) {
329
  continue;
330
  }
331
+
332
  // Skip mail addresses
333
  if( 'off' === $args['replace_mails'] && false !== strpos( $dataRow, '@' . $this->homeUrl ) ) {
334
  continue;
335
  }
336
+
337
 
338
  // Check options table
339
  if( $this->options->prefix . 'options' === $table ) {
359
  // Check the path delimiter for / or \/ and remove one of those which prevents from resulting in wrong syntax like domain.com/staging\/.
360
  // 1. local.wordpress.test -> local.wordpress.test/staging
361
  // 2. local.wordpress.test\/ -> local.wordpress.test\/staging\/
362
+ if( false === strpos( $dataRow, $tmp['search_for'][0] ) ) {
363
+ array_shift( $tmp['search_for'] ); // rtrim( $this->homeUrl, '/' ),
364
+ array_shift( $tmp['replace_with'] ); // rtrim( $this->homeUrl, '/' ) . '/' . $this->options->cloneDirectoryName,
365
  } else {
366
+ unset( $tmp['search_for'][1] );
367
+ unset( $tmp['replace_with'][1] );
368
  // recount array
369
+ $tmp['search_for'] = array_values( $tmp['search_for'] );
370
+ $tmp['replace_with'] = array_values( $tmp['replace_with'] );
371
  }
372
 
373
 
374
+
375
  // Run a search replace on the data row and respect the serialisation.
376
  $i = 0;
377
  foreach ( $tmp['search_for'] as $replace ) {
430
  *
431
  * @return string|array The original array with all elements replaced as needed.
432
  */
433
+ private function recursive_unserialize_replace( $from = '', $to = '', $data = '', $serialized = false, $case_insensitive = false ) {
434
  try {
435
  // Some unserialized data cannot be re-serialized eg. SimpleXMLElements
436
  if( is_serialized( $data ) && ( $unserialized = @unserialize( $data ) ) !== false ) {
458
  } else {
459
  if( is_string( $data ) ) {
460
  if( !empty( $from ) && !empty( $to ) ) {
461
+ $data = $this->str_replace( $from, $to, $data, $case_insensitive );
462
  }
463
  }
464
  }
557
  foreach ( $excludes as $exclude ) {
558
  $regexExclude .= $exclude . '(*SKIP)(FAIL)|';
559
  }
560
+
561
  if( 'on' === $case_insensitive ) {
562
  //$data = str_ireplace( $from, $to, $data );
563
+ $data = preg_replace( '#' . $regexExclude . preg_quote( $from ) . '#i', $to, $data );
564
  } else {
565
  //$data = str_replace( $from, $to, $data );
566
+ $data = preg_replace( '#' . $regexExclude . preg_quote( $from ) . '#', $to, $data );
567
  }
568
 
569
  return $data;
653
  );
654
  }
655
 
656
+ /**
657
+ * Check if WP is installed in subdir
658
+ * @return boolean
659
+ */
660
  private function isSubDir() {
661
  // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
662
  // This is happening much more often than you would expect
664
  $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
665
 
666
  if( $home !== $siteurl ) {
667
+ return true;
668
+ }
669
+ return false;
670
+ }
671
+
672
+ /**
673
+ * Get the install sub directory if WP is installed in sub directory
674
+ * @return string
675
+ */
676
  private function getSubDir() {
677
  $home = get_option( 'home' );
678
  $siteurl = get_option( 'siteurl' );
679
+
680
  if( empty( $home ) || empty( $siteurl ) ) {
681
+ return '/';
682
+ }
683
+
684
  $dir = str_replace( $home, '', $siteurl );
685
  return '/' . str_replace( '/', '', $dir );
686
+ }
 
687
 
688
  }
apps/Backend/Modules/SystemInfo.php CHANGED
@@ -165,6 +165,7 @@ class SystemInfo extends InjectionAware {
165
 
166
  $output = "-- WP Staging Settings" . PHP_EOL . PHP_EOL;
167
  $output .= $this->info( "Query Limit:", isset( $settings->queryLimit ) ? $settings->queryLimit : 'undefined' );
 
168
  $output .= $this->info( "File Copy Limit:", isset( $settings->fileLimit ) ? $settings->fileLimit : 'undefined' );
169
  $output .= $this->info( "Batch Size:", isset( $settings->batchSize ) ? $settings->batchSize : 'undefined' );
170
  $output .= $this->info( "CPU Load:", isset( $settings->cpuLoad ) ? $settings->cpuLoad : 'undefined' );
165
 
166
  $output = "-- WP Staging Settings" . PHP_EOL . PHP_EOL;
167
  $output .= $this->info( "Query Limit:", isset( $settings->queryLimit ) ? $settings->queryLimit : 'undefined' );
168
+ $output .= $this->info( "DB Search & Replace Limit:", isset( $settings->querySRLimit ) ? $settings->querySRLimit : 'undefined' );
169
  $output .= $this->info( "File Copy Limit:", isset( $settings->fileLimit ) ? $settings->fileLimit : 'undefined' );
170
  $output .= $this->info( "Batch Size:", isset( $settings->batchSize ) ? $settings->batchSize : 'undefined' );
171
  $output .= $this->info( "CPU Load:", isset( $settings->cpuLoad ) ? $settings->cpuLoad : 'undefined' );
apps/Backend/public/css/wpstg-admin.css CHANGED
@@ -751,6 +751,9 @@ color:#777777;
751
  text-align: center;
752
  margin-top: 20px;
753
  }
 
 
 
754
  #wpstg-welcome li {
755
  font-size: 18px;
756
  line-height: 29px;
@@ -802,7 +805,7 @@ color:#777777;
802
 
803
  .wpstg-staging-info {
804
  clear:both;
805
- float: right;
806
  color:grey;
807
  font-size: 12px;
808
  }
@@ -950,4 +953,40 @@ color:#777777;
950
 
951
  #wpstg-resume-cloning {
952
  display:none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
953
  }
751
  text-align: center;
752
  margin-top: 20px;
753
  }
754
+ .wpstg-button.green:hover {
755
+ background-color: #8ed122;
756
+ }
757
  #wpstg-welcome li {
758
  font-size: 18px;
759
  line-height: 29px;
805
 
806
  .wpstg-staging-info {
807
  clear:both;
808
+ float: left;
809
  color:grey;
810
  font-size: 12px;
811
  }
953
 
954
  #wpstg-resume-cloning {
955
  display:none;
956
+ }
957
+
958
+ #wpstg-external-db th {
959
+ text-align: left;
960
+ }
961
+
962
+ #wpstg-db-connect{
963
+ font-weight: normal;
964
+ }
965
+
966
+ #wpstg-db-status {
967
+ display:block;
968
+ margin-top:5px;
969
+ padding: 5px;
970
+ margin-bottom: 20px;
971
+ border: 1px solid transparent;
972
+ border-radius: 4px;
973
+ text-decoration: none;
974
+ text-align:center;
975
+ }
976
+
977
+ .wpstg-success {
978
+ color: #3c763d;
979
+ background-color: #dff0d8;
980
+ border-color: #d6e9c6;
981
+ }
982
+ .wpstg-failed {
983
+ color: #a94442;
984
+ background-color: #f2dede;
985
+ border-color: #ebccd1;
986
+ }
987
+
988
+ #wpstg-external-db tr th{
989
+ font-weight:500;
990
+ color:grey;
991
+ opacity: 0.8;
992
  }
apps/Backend/public/js/wpstg-admin.js CHANGED
@@ -171,6 +171,9 @@ var WPStaging = (function ($)
171
  // Check the max length of the clone name and if the clone name already exists
172
  .on("keyup", "#wpstg-new-clone-id", function () {
173
 
 
 
 
174
  // This request was already sent, clear it up!
175
  if ("number" === typeof (timer))
176
  {
@@ -267,10 +270,6 @@ var WPStaging = (function ($)
267
  })
268
  // Cancel update cloning
269
  .on("click", "#wpstg-cancel-cloning-update", function () {
270
- // if (!confirm("Are you sure you want to cancel clone updating process?"))
271
- // {
272
- // return false;
273
- // }
274
 
275
  var $this = $(this);
276
 
@@ -404,10 +403,10 @@ var WPStaging = (function ($)
404
  // return false;
405
  // }
406
 
407
- var errorCode = "undefined" === typeof(xhr.status) ? "Unknown" : xhr.status;
408
 
409
  showError(
410
- "Fatal Error: "+ errorCode +" Please try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report."
411
  );
412
  },
413
  success: function (data) {
@@ -419,17 +418,17 @@ var WPStaging = (function ($)
419
  statusCode: {
420
  404: function (data) {
421
  showError(
422
- "Something went wrong! can't find ajax request URL! Please try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report."
423
  );
424
 
425
  },
426
  500: function () {
427
  showError(
428
- "Something went wrong! Internal server error while processing the request! Please try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report."
429
  );
430
  },
431
  504: function () {
432
- showError("It looks like your server is rate limiting ajax requests. Please try to resume after a minute. If this still not works try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report.\n\ ");
433
  var obj = new Object();
434
  obj.status = false;
435
  obj.error = 'custom error';
@@ -437,7 +436,7 @@ var WPStaging = (function ($)
437
 
438
  },
439
  429: function () {
440
- showError("It looks like your server is rate limiting ajax requests. Please try to resume after a minute. If this still not works try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report.\n\ ");
441
  var obj = new Object();
442
  obj.status = false;
443
  obj.error = 'custom error';
@@ -628,6 +627,7 @@ var WPStaging = (function ($)
628
  return extraDirectories;
629
  };
630
 
 
631
  /**
632
  * Get Included Extra Directories
633
  * @returns {Array}
@@ -667,6 +667,11 @@ var WPStaging = (function ($)
667
  that.data.includedDirectories = getIncludedDirectories();
668
  that.data.excludedDirectories = getExcludedDirectories();
669
  that.data.extraDirectories = getIncludedExtraDirectories();
 
 
 
 
 
670
  console.log(that.data);
671
 
672
  };
@@ -755,7 +760,7 @@ var WPStaging = (function ($)
755
  // Error
756
  if ("undefined" !== typeof response.error && "undefined" !== typeof response.message) {
757
  showError(
758
- "Something went wrong! Error:" + response.message + ". Please try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report."
759
  );
760
  console.log(response.message);
761
  }
@@ -997,7 +1002,6 @@ var WPStaging = (function ($)
997
  // Start the process
998
  start();
999
 
1000
-
1001
  // Functions
1002
  // Start
1003
  function start()
@@ -1008,6 +1012,8 @@ var WPStaging = (function ($)
1008
  cache.get("#wpstg-loader").show();
1009
  cache.get("#wpstg-cancel-cloning").text('Cancel');
1010
  cache.get("#wpstg-resume-cloning").hide();
 
 
1011
 
1012
  // Clone Database
1013
  setTimeout(function () {
@@ -1047,7 +1053,6 @@ var WPStaging = (function ($)
1047
  {
1048
  action: "wpstg_clone_database",
1049
  nonce: wpstg.nonce,
1050
- //clone: cloneID,
1051
  excludedTables: getExcludedTables(),
1052
  includedDirectories: getIncludedDirectories(),
1053
  excludedDirectories: getExcludedDirectories(),
@@ -1094,6 +1099,8 @@ var WPStaging = (function ($)
1094
 
1095
  } else if (true === response.status && 'finished' !== response.status) {
1096
  //console.log('Processing...');
 
 
1097
  progressBar(response, true);
1098
  processing();
1099
  } else if ('finished' === response.status || ("undefined" !== typeof (response.job_done) && response.job_done)) {
@@ -1249,7 +1256,7 @@ jQuery(document).ready(function ($) {
1249
  * Close Success Modal
1250
  */
1251
 
1252
- $('body').on('click', '#wpstg-success-button', function(e){
1253
  e.preventDefault();
1254
  $('.wpstg-report-issue-form').removeClass('wpstg-report-show');
1255
  });
171
  // Check the max length of the clone name and if the clone name already exists
172
  .on("keyup", "#wpstg-new-clone-id", function () {
173
 
174
+ // Hide previous errors
175
+ document.getElementById('wpstg-error-details').style.display = "none";
176
+
177
  // This request was already sent, clear it up!
178
  if ("number" === typeof (timer))
179
  {
270
  })
271
  // Cancel update cloning
272
  .on("click", "#wpstg-cancel-cloning-update", function () {
 
 
 
 
273
 
274
  var $this = $(this);
275
 
403
  // return false;
404
  // }
405
 
406
+ var errorCode = "undefined" === typeof (xhr.status) ? "Unknown" : xhr.status;
407
 
408
  showError(
409
+ "Fatal Error: " + errorCode + " Please try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report."
410
  );
411
  },
412
  success: function (data) {
418
  statusCode: {
419
  404: function (data) {
420
  showError(
421
+ "Error 404 - Can't find ajax request URL! Please try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report."
422
  );
423
 
424
  },
425
  500: function () {
426
  showError(
427
+ "Fatal Error 500 - Internal server error while processing the request! Please try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report."
428
  );
429
  },
430
  504: function () {
431
+ showError("Error 504 - It looks like your server is rate limiting ajax requests. Please try to resume after a minute. If this still not works try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report.\n\ ");
432
  var obj = new Object();
433
  obj.status = false;
434
  obj.error = 'custom error';
436
 
437
  },
438
  429: function () {
439
+ showError("Error 429 - It looks like your server is rate limiting ajax requests. Please try to resume after a minute. If this still not works try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report.\n\ ");
440
  var obj = new Object();
441
  obj.status = false;
442
  obj.error = 'custom error';
627
  return extraDirectories;
628
  };
629
 
630
+
631
  /**
632
  * Get Included Extra Directories
633
  * @returns {Array}
667
  that.data.includedDirectories = getIncludedDirectories();
668
  that.data.excludedDirectories = getExcludedDirectories();
669
  that.data.extraDirectories = getIncludedExtraDirectories();
670
+ that.data.databaseServer = $("#wpstg_db_server").val();
671
+ that.data.databaseUser = $("#wpstg_db_username").val();
672
+ that.data.databasePassword = $("#wpstg_db_password").val();
673
+ that.data.databaseDatabase = $("#wpstg_db_database").val();
674
+ that.data.databasePrefix = $("#wpstg_db_prefix").val();
675
  console.log(that.data);
676
 
677
  };
760
  // Error
761
  if ("undefined" !== typeof response.error && "undefined" !== typeof response.message) {
762
  showError(
763
+ "Something went wrong! Error: " + response.message + ". Please try the <a href='https://wp-staging.com/docs/wp-staging-settings-for-small-servers/' target='_blank'>WP Staging Small Server Settings</a> or submit an error report."
764
  );
765
  console.log(response.message);
766
  }
1002
  // Start the process
1003
  start();
1004
 
 
1005
  // Functions
1006
  // Start
1007
  function start()
1012
  cache.get("#wpstg-loader").show();
1013
  cache.get("#wpstg-cancel-cloning").text('Cancel');
1014
  cache.get("#wpstg-resume-cloning").hide();
1015
+ cache.get("#wpstg-error-details").hide();
1016
+
1017
 
1018
  // Clone Database
1019
  setTimeout(function () {
1053
  {
1054
  action: "wpstg_clone_database",
1055
  nonce: wpstg.nonce,
 
1056
  excludedTables: getExcludedTables(),
1057
  includedDirectories: getIncludedDirectories(),
1058
  excludedDirectories: getExcludedDirectories(),
1099
 
1100
  } else if (true === response.status && 'finished' !== response.status) {
1101
  //console.log('Processing...');
1102
+ cache.get("#wpstg-error-details").hide();
1103
+ cache.get("#wpstg-error-wrapper").hide();
1104
  progressBar(response, true);
1105
  processing();
1106
  } else if ('finished' === response.status || ("undefined" !== typeof (response.job_done) && response.job_done)) {
1256
  * Close Success Modal
1257
  */
1258
 
1259
+ $('body').on('click', '#wpstg-success-button', function (e) {
1260
  e.preventDefault();
1261
  $('.wpstg-report-issue-form').removeClass('wpstg-report-show');
1262
  });
apps/Backend/views/_includes/messages/rating.php CHANGED
@@ -1,4 +1,4 @@
1
- <div class="wpstg_fivestar updated" style="box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);border-left:none;background-color:#59a7f7;color:white;">
2
  <p><?php _e(' Awesome, you\'ve been using <strong>WP Staging </strong> for more than 1 week.
3
  May I ask you to give it a <strong>5-star</strong> rating on Wordpress?', 'wp-staging'); ?>
4
  <br><br>
1
+ <div class="wpstg_fivestar" style="box-shadow: 0 1px 1px 0 rgba(0,0,0,.1);border-left:none;background-color:#59a7f7;color:white;padding: 10px;margin: 10px;margin-left: 0px;">
2
  <p><?php _e(' Awesome, you\'ve been using <strong>WP Staging </strong> for more than 1 week.
3
  May I ask you to give it a <strong>5-star</strong> rating on Wordpress?', 'wp-staging'); ?>
4
  <br><br>
apps/Backend/views/clone/ajax/delete-confirmation.php CHANGED
@@ -6,9 +6,20 @@
6
  </h4>
7
 
8
  <p>
9
- <?php _e('Clone name:', 'wp-staging'); ?>
10
- <span style="background-color:#575757;color:#fff;">
11
- <?php echo $clone->directoryName; ?>
 
 
 
 
 
 
 
 
 
 
 
12
  </span>
13
  </p>
14
 
@@ -43,7 +54,7 @@
43
  <?php echo $table->name?>
44
  </label>
45
  <span class="wpstg-size-info">
46
- <?php echo $table->size?>
47
  </span>
48
  </div>
49
  <?php endforeach ?>
@@ -69,8 +80,8 @@
69
  <div class="wpstg-dir">
70
  <label>
71
  <input id="deleteDirectory" type="checkbox" class="wpstg-check-dir" name="deleteDirectory" value="1" checked>
72
- <?php echo $clone->path; ?>
73
- <span class="wpstg-size-info"></span>
74
  </label>
75
  </div>
76
  </div>
6
  </h4>
7
 
8
  <p>
9
+ <?php _e('Clone Name:', 'wp-staging'); ?>
10
+ <span style="background-color:#5b9dd9;color:#fff;padding: 2px;border-radius: 3px;">
11
+ <?php
12
+ echo $clone->directoryName;
13
+ ?>
14
+ </span>
15
+ </p>
16
+ <p>
17
+ <?php _e('Database Name:', 'wp-staging'); ?>
18
+ <span style="background-color:#5b9dd9;color:#fff;padding: 2px;border-radius: 3px;">
19
+ <?php
20
+ $database = empty($clone->databaseDatabase) ? "{$dbname} (Main Database)" : $clone->databaseDatabase;
21
+ echo $database;
22
+ ?>
23
  </span>
24
  </p>
25
 
54
  <?php echo $table->name?>
55
  </label>
56
  <span class="wpstg-size-info">
57
+ <?php echo isset($table->size) ? $table->size : '';?>
58
  </span>
59
  </div>
60
  <?php endforeach ?>
80
  <div class="wpstg-dir">
81
  <label>
82
  <input id="deleteDirectory" type="checkbox" class="wpstg-check-dir" name="deleteDirectory" value="1" checked>
83
+ <?php echo $clone->path;?>
84
+ <span class="wpstg-size-info"><?php echo isset($clone->size) ? $clone->size : ''; ?></span>
85
  </label>
86
  </div>
87
  </div>
apps/Backend/views/clone/ajax/external-database.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <fieldset disabled style="opacity:0.8;">
2
+ <p><?php _e('Clone the staging site to another separate database. You need to create the database in advance!
3
+ Leave User and Password empty to clone the staging site to the current main database.', 'wp-staging'); ?></p>
4
+ <table cellspacing="0" id="wpstg-external-db">
5
+ <tbody>
6
+ <tr><th>Server</th><td><input type="text" name="wpstg_db_server" id="wpstg_db_server" value="" title="wpstg_db_server" placeholder="localhost" autocapitalize="off" readonly>
7
+ </td></tr>
8
+ <tr><th>User</th><td><input type="text" name="wpstg_db_username" id="wpstg_db_username" value="" autocapitalize="off" class="" readonly>
9
+ </td></tr>
10
+ <tr><th>Password</th><td><input type="password" name="wpstg_db_password" id="wpstg_db_password" class="" readonly>
11
+ </td></tr>
12
+ <tr><th>Database</th><td><input type="text" name="wpstg_db_database" id="wpstg_db_database" value="" autocapitalize="off" readonly>
13
+ </td></tr>
14
+ <tr><th>Database Prefix</th><td><input type="text" name="wpstg_db_prefix" id="wpstg_db_prefix" value="" placeholder="<?php echo $db->prefix; ?>" autocapitalize="off" readonly>
15
+ </td></tr>
16
+ <tr><th><a href="#" id="wpstg-db-connect">Test Database Connection</a></th><td>
17
+ </td></tr>
18
+ </tbody>
19
+ </table>
20
+ </fieldset>
21
+ <p style="font-weight:bold;background-color:#e6e6e6;padding:15px;"><?php _e('This is a WP Staging Pro feature', 'wp-staging'); ?>
22
+ <br>
23
+ <a href="https://wp-staging.com/?utm_source=wp-admin&utm_medium=wp-admin&utm_campaign=db-external&utm_term=db-external" target="_blank" class="quads-button green wpstg-button" style="border-radius:2px;font-size: 14px;">Get WP Staging Pro</a>
24
+ </p>
apps/Backend/views/clone/ajax/scan.php CHANGED
@@ -93,26 +93,17 @@
93
  </p>
94
  </div>
95
 
96
- <!-- <a href="#" class="wpstg-tab-header" data-id="#wpstg-advanced-settings">
97
  <span class="wpstg-tab-triangle">&#9658;</span>
98
- <?php //echo __("Login Options", "wp-staging")?>
99
- </a>-->
 
 
100
 
101
- <div class="wpstg-tab-section" id="wpstg-advanced-settings">
102
- <p>
103
  <?php
104
- _e('<strong>Important:</strong> Are you using a custom login url?', 'wp-staging');
105
- echo '<br/>';
106
- echo sprintf(__('Set up first <a href="%1$s"><strong>Login Custom Link</strong></a> if login to the admin dashboard is not reachable from the default url below:<pre>%2$s</pre>', 'wp-staging'),
107
- admin_url() . '/admin.php?page=wpstg-settings#wpstg_settings[loginSlug]',
108
- admin_url()
109
- );
110
- _e('<strong>If you do not do that step, the staging site could be unavailable!</strong>', 'wp-staging');
111
- //$form = $this->di->get("forms")->get("general");
112
- //echo $form->label("wpstg_settings['loginPostId']");
113
- //echo $form->render("wpstg_settings['loginPostId']");
114
  ?>
115
- </p>
116
  </div>
117
 
118
  </div>
93
  </p>
94
  </div>
95
 
96
+ <a href="#" class="wpstg-tab-header" data-id="#wpstg-advanced-settings" style="display:none;">
97
  <span class="wpstg-tab-triangle">&#9658;</span>
98
+ <?php echo __("Advanced Settings", "wp-staging"); ?>
99
+ </a>
100
+
101
+ <div class="wpstg-tab-section" id="wpstg-advanced-settings" style="display:none;">
102
 
 
 
103
  <?php
104
+ require_once (__DIR__ . DIRECTORY_SEPARATOR . 'external-database.php');
 
 
 
 
 
 
 
 
 
105
  ?>
106
+
107
  </div>
108
 
109
  </div>
apps/Backend/views/clone/ajax/single-overview.php CHANGED
@@ -14,8 +14,7 @@
14
  <?php foreach ($availableClones as $name => $data):?>
15
  <div id="<?php echo $data["directoryName"]; ?>" class="wpstg-clone">
16
 
17
- <?php //$urlLogin = $data["url"] . "/wp-login.php";?>
18
- <?php $urlLogin = $data["url"] . "/?";?>
19
 
20
  <a href="<?php echo $urlLogin?>" class="wpstg-clone-title" target="_blank">
21
  <?php //echo $name?>
@@ -39,9 +38,15 @@
39
  <?php echo apply_filters("wpstg_after_stage_buttons", $html = '', $name, $data)?>
40
  <div class="wpstg-staging-info">
41
  <?php
42
- $prefix = isset ($data['prefix']) ? __("DB prefix: <span class='wpstg-bold'>" . $data['prefix'], "wp-staging") . '</span> ' : '&nbsp;&nbsp;&nbsp;';
43
- //$path = isset ($data['directoryName']) ? __("Subdir: <span class='wpstg-bold'>" . $data['directoryName'], "wp-staging") . '</span>' : '';
44
  echo $prefix;
 
 
 
 
 
 
 
45
  ?>
46
  </div>
47
  </div>
14
  <?php foreach ($availableClones as $name => $data):?>
15
  <div id="<?php echo $data["directoryName"]; ?>" class="wpstg-clone">
16
 
17
+ <?php $urlLogin = $data["url"];?>
 
18
 
19
  <a href="<?php echo $urlLogin?>" class="wpstg-clone-title" target="_blank">
20
  <?php //echo $name?>
38
  <?php echo apply_filters("wpstg_after_stage_buttons", $html = '', $name, $data)?>
39
  <div class="wpstg-staging-info">
40
  <?php
41
+ $prefix = !empty ($data['prefix']) ? __("DB Prefix: <span class='wpstg-bold'>" . $data['prefix'], "wp-staging") . '</span> ' : '&nbsp;&nbsp;&nbsp;';
 
42
  echo $prefix;
43
+ echo '</br>';
44
+ $dbname = !empty ($data['databaseDatabase']) ? __("DB Name: <span class='wpstg-bold'>" . $data['databaseDatabase'], "wp-staging") . '</span></br> ' : '';
45
+ echo $dbname;
46
+ $datetime = !empty ($data['datetime']) ? __("Updated: <span>" . date("D, d M Y H:i:s T",$data['datetime']) , "wp-staging") . '</span> ' : '&nbsp;&nbsp;&nbsp;';
47
+ echo $datetime;
48
+
49
+
50
  ?>
51
  </div>
52
  </div>
apps/Backend/views/clone/includes/footer.php CHANGED
@@ -1,4 +1,5 @@
1
- <div style="clear:both;">Something not working? Open a <a href="https://wp-staging.com/support" target="_blank" rel="external nofollow"> support ticket</a> and we help you to fix it quickly.</div>
2
  <div id="wpstg-error-wrapper">
3
  <div id="wpstg-error-details"></div>
4
- </div>
 
 
 
1
  <div id="wpstg-error-wrapper">
2
  <div id="wpstg-error-details"></div>
3
+ </div>
4
+ <div style="clear:both;padding-top:20px;">Something not working? <br>Open a support ticket at <a href="https://wp-staging.com/support" target="_blank" rel="external nofollow">https://wp-staging.com/support</a> and we will resolve it quickly.</div>
5
+
apps/Core/Utils/requirements-check.php CHANGED
@@ -1,70 +1,211 @@
1
  <?php
 
 
 
 
 
2
 
 
 
 
3
  class Wpstg_Requirements_Check {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
- private $title = '';
6
- private $php = '5.2.4';
7
- private $wp = '3.8';
8
- private $file;
9
-
10
- public function __construct( $args ) {
11
- foreach ( array('title', 'php', 'wp', 'file') as $setting ) {
12
- if( isset( $args[$setting] ) ) {
13
- $this->$setting = $args[$setting];
14
- }
15
- }
16
- }
17
-
18
- public function passes() {
19
- $passes = $this->php_passes() && $this->wp_passes();
20
- if( !$passes ) {
21
- add_action( 'admin_notices', array($this, 'deactivate') );
22
- }
23
- return $passes;
24
- }
25
-
26
- public function deactivate() {
27
- if( isset( $this->file ) ) {
28
- deactivate_plugins( plugin_basename( $this->file ) );
29
- }
30
- }
31
-
32
- private function php_passes() {
33
- if( $this->__php_at_least( $this->php ) ) {
34
- return true;
35
- } else {
36
- add_action( 'admin_notices', array($this, 'php_version_notice') );
37
- return false;
38
- }
39
- }
40
-
41
- private static function __php_at_least( $min_version ) {
42
- return version_compare( phpversion(), $min_version, '>=' );
43
- }
44
-
45
- public function php_version_notice() {
46
- echo '<div class="error">';
47
- echo "<p>The &#8220;" . esc_html( $this->title ) . "&#8221; plugin cannot run on PHP versions older than " . $this->php . '. Please contact your host and ask them to upgrade.</p>';
48
- echo '</div>';
49
- }
50
-
51
- private function wp_passes() {
52
- if( $this->__wp_at_least( $this->wp ) ) {
53
- return true;
54
- } else {
55
- add_action( 'admin_notices', array($this, 'wp_version_notice') );
56
- return false;
57
- }
58
- }
59
-
60
- private static function __wp_at_least( $min_version ) {
61
- return version_compare( get_bloginfo( 'version' ), $min_version, '>=' );
62
- }
63
-
64
- public function wp_version_notice() {
65
- echo '<div class="error">';
66
- echo "<p>The &#8220;" . esc_html( $this->title ) . "&#8221; plugin cannot run on WordPress versions older than " . $this->wp . '. Please update WordPress.</p>';
67
- echo '</div>';
68
- }
69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  }
1
  <?php
2
+ /**
3
+ * Simple requirements checking class.
4
+ *
5
+ * @package WP_Requirements_Check
6
+ */
7
 
8
+ /**
9
+ * Simple requirements checking class.
10
+ */
11
  class Wpstg_Requirements_Check {
12
+ /**
13
+ * @since 1.1.0
14
+ *
15
+ * @var array $args {
16
+ * Requirement arguments.
17
+ *
18
+ * @type string $title Name of the plugin.
19
+ * @type string $php Minimum required PHP version.
20
+ * @type string $wp Minimum required WordPress version.
21
+ * @type string $file Path to the main plugin file.
22
+ * @type array $i18n {
23
+ * @type string $php PHP version mismatch error message.
24
+ * @type string $wp WP version mismatch error message.
25
+ * }
26
+ * }
27
+ */
28
+ private $args;
29
 
30
+ /**
31
+ * Constructor.
32
+ *
33
+ * @since 1.0.0
34
+ * @access public
35
+ *
36
+ * @param array $args {
37
+ * An array of arguments to overwrite the default requirements.
38
+ *
39
+ * @type string $title Name of the plugin.
40
+ * @type string $php Minimum required PHP version.
41
+ * @type string $wp Minimum required WordPress version.
42
+ * @type string $file Path to the main plugin file.
43
+ * @type array $i18n {
44
+ * @type string $php PHP version mismatch error message.
45
+ * @type string $wp WP version mismatch error message.
46
+ * }
47
+ * }
48
+ */
49
+ public function __construct( $args ) {
50
+ $args = (array) $args;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
+ $this->args = wp_parse_args(
53
+ $args,
54
+ array(
55
+ 'title' => '',
56
+ 'php' => '5.2.4',
57
+ 'wp' => '3.8',
58
+ 'file' => null,
59
+ 'i18n' => array()
60
+ )
61
+ );
62
+
63
+ $this->args['i18n'] = wp_parse_args(
64
+ $this->args['i18n'],
65
+ array(
66
+ 'php' => 'The &#8220;%1$s&#8221; plugin cannot run on PHP versions older than %2$s. Please contact your host and ask them to upgrade.',
67
+ 'wp' => 'The &#8220;%1$s&#8221; plugin cannot run on WordPress versions older than %2$s. Please update your WordPress.',
68
+ )
69
+ );
70
+ }
71
+
72
+ /**
73
+ * Check if the install passes the requirements.
74
+ *
75
+ * @since 1.0.0
76
+ * @access public
77
+ *
78
+ * @return bool True if the install passes the requirements, false otherwise.
79
+ */
80
+ public function passes() {
81
+ $passes = $this->php_passes() && $this->wp_passes();
82
+
83
+ if ( ! $passes ) {
84
+ add_action( 'admin_notices', array( $this, 'deactivate' ) );
85
+ }
86
+
87
+ return $passes;
88
+ }
89
+
90
+ /**
91
+ * Deactivates the plugin again.
92
+ *
93
+ * @since 1.0.0
94
+ * @access public
95
+ */
96
+ public function deactivate() {
97
+ if ( null !== $this->args['file'] ) {
98
+ deactivate_plugins( plugin_basename( $this->args['file'] ) );
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Checks if the PHP version passes the requirement.
104
+ *
105
+ * @since 1.0.0
106
+ * @access protected
107
+ *
108
+ * @return bool True if the PHP version is high enough, false otherwise.
109
+ */
110
+ protected function php_passes() {
111
+ if ( self::_php_at_least( $this->args['php'] ) ) {
112
+ return true;
113
+ }
114
+
115
+ add_action( 'admin_notices', array( $this, 'php_version_notice' ) );
116
+
117
+ return false;
118
+ }
119
+
120
+ /**
121
+ * Compares the current PHP version with the minimum required version.
122
+ *
123
+ * @since 1.0.0
124
+ * @access protected
125
+ *
126
+ * @param string $min_version The minimum required version.
127
+ * @return bool True if the PHP version is high enough, false otherwise.
128
+ */
129
+ protected static function _php_at_least( $min_version ) {
130
+ return version_compare( PHP_VERSION, $min_version, '>=' );
131
+ }
132
+
133
+ /**
134
+ * Displays the PHP version notice.
135
+ *
136
+ * @since 1.0.0
137
+ * @access public
138
+ */
139
+ public function php_version_notice() {
140
+ /**
141
+ * Filters the notice for outdated PHP versions.
142
+ *
143
+ * @since 1.1.0
144
+ *
145
+ * @param string $message The error message.
146
+ * @param string $title The plugin name.
147
+ * @param string $php The WordPress version.
148
+ */
149
+ $message = apply_filters( 'wp_requirements_check_php_notice', $this->args['i18n']['php'], $this->args['title'], $this->args['php'] );
150
+ ?>
151
+ <div class="error">
152
+ <p><?php printf( $message, esc_html( $this->args['title'] ), $this->args['php'] ); ?></p>
153
+ </div>
154
+ <?php
155
+ }
156
+
157
+ /**
158
+ * Check if the WordPress version passes the requirement.
159
+ *
160
+ * @since 1.0.0
161
+ * @access protected
162
+ *
163
+ * @return bool True if the WordPress version is high enough, false otherwise.
164
+ */
165
+ protected function wp_passes() {
166
+ if ( self::_wp_at_least( $this->args['wp'] ) ) {
167
+ return true;
168
+ }
169
+
170
+ add_action( 'admin_notices', array( $this, 'wp_version_notice' ) );
171
+
172
+ return false;
173
+ }
174
+
175
+ /**
176
+ * Compare the current WordPress version with the minimum required version.
177
+ *
178
+ * @since 1.0.0
179
+ * @access protected
180
+ *
181
+ * @param string $min_version Minimum required WordPress version.
182
+ * @return bool True if the WordPress version is high enough, false otherwise.
183
+ */
184
+ protected static function _wp_at_least( $min_version ) {
185
+ return version_compare( get_bloginfo( 'version' ), $min_version, '>=' );
186
+ }
187
+
188
+ /**
189
+ * Show the WordPress version notice.
190
+ *
191
+ * @since 1.0.0
192
+ * @access public
193
+ */
194
+ public function wp_version_notice() {
195
+ /**
196
+ * Filters the notice for outdated WordPress versions.
197
+ *
198
+ * @since 1.1.0
199
+ *
200
+ * @param string $message The error message.
201
+ * @param string $title The plugin name.
202
+ * @param string $php The WordPress version.
203
+ */
204
+ $message = apply_filters( 'wp_requirements_check_wordpress_notice', $this->args['i18n']['wp'], $this->args['title'], $this->args['wp'] );
205
+ ?>
206
+ <div class="error">
207
+ <p><?php printf( $message, esc_html( $this->args['title'] ), $this->args['wp'] ); ?></p>
208
+ </div>
209
+ <?php
210
+ }
211
  }
apps/Core/WPStaging.php CHANGED
@@ -29,7 +29,7 @@ final class WPStaging {
29
  /**
30
  * Plugin version
31
  */
32
- const VERSION = "2.3.8";
33
 
34
  /**
35
  * Plugin name
29
  /**
30
  * Plugin version
31
  */
32
+ const VERSION = "2.3.9";
33
 
34
  /**
35
  * Plugin name
readme.txt CHANGED
@@ -9,7 +9,7 @@ License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
  Tags: staging, duplication, cloning, clone, migration, sandbox, test site, testing, backup, post, admin, administration, duplicate posts
10
  Requires at least: 3.6+
11
  Tested up to: 4.9
12
- Stable tag: 2.3.8
13
  Requires PHP: 5.3
14
 
15
  A duplicator plugin! Clone, duplicate and migrate live sites to independent staging and development sites that are available only to administrators.
@@ -146,6 +146,16 @@ https://wp-staging.com
146
 
147
  == Changelog ==
148
 
 
 
 
 
 
 
 
 
 
 
149
  = 2.3.8 =
150
  * Tweak: Lowering default performance settings for more reliability during cloning
151
  * New: Set WP_CACHE to false in wp-config.php after cloning to prevent log in issues to staging site
@@ -185,23 +195,11 @@ https://wp-staging.com
185
  * New: Check if clone subfolder already exists before creating clone
186
  * Fix: Changing file copy limit not working
187
 
188
- = 2.3.4 =
189
- * New: Compatible up to WordPress 4.9.8
190
- * New: Support for Windows Azure cloud servers
191
- * Fix: Missing http(s) scheme after cloning multisites results in not working clones
192
-
193
- = 2.3.3 =
194
- * New: Compatible up to WordPress 4.9.8
195
- * New: Better error reporting
196
- * New: Detect if wp-config.php has been moved one folder level up
197
- * New: Login options not needed any more - removed
198
- * Fix: Remove term 'Error' from login page
199
-
200
 
201
  Complete changelog: [https://wp-staging.com/wp-staging-changelog](https://wp-staging.com/wp-staging-changelog)
202
 
203
  == Upgrade Notice ==
204
 
205
- = 2.3.8 =
206
  * New: Compatible to WordPress 4.9.8. Important fixes!
207
 
9
  Tags: staging, duplication, cloning, clone, migration, sandbox, test site, testing, backup, post, admin, administration, duplicate posts
10
  Requires at least: 3.6+
11
  Tested up to: 4.9
12
+ Stable tag: 2.3.9
13
  Requires PHP: 5.3
14
 
15
  A duplicator plugin! Clone, duplicate and migrate live sites to independent staging and development sites that are available only to administrators.
146
 
147
  == Changelog ==
148
 
149
+ = 2.3.9 =
150
+ * New: Support for wp-config.php located in one level up of the root folder for multisites
151
+ * New: Allow exclusion of custom options from wp_options from beeing pushed
152
+ * New: Make sure Optimizer is installed and activated
153
+ * New: Show date of site creation/update in list of staging sites
154
+ * Tweak: Better looking UI elements
155
+ * Fix: Requirements Check not working as intended
156
+ * Fix: Filesize() failed if file to copy is not a regular file
157
+ * Fix: remove ? parameter from staging site
158
+
159
  = 2.3.8 =
160
  * Tweak: Lowering default performance settings for more reliability during cloning
161
  * New: Set WP_CACHE to false in wp-config.php after cloning to prevent log in issues to staging site
195
  * New: Check if clone subfolder already exists before creating clone
196
  * Fix: Changing file copy limit not working
197
 
 
 
 
 
 
 
 
 
 
 
 
 
198
 
199
  Complete changelog: [https://wp-staging.com/wp-staging-changelog](https://wp-staging.com/wp-staging-changelog)
200
 
201
  == Upgrade Notice ==
202
 
203
+ = 2.3.9 =
204
  * New: Compatible to WordPress 4.9.8. Important fixes!
205
 
uninstall.php CHANGED
@@ -55,6 +55,7 @@ class uninstall {
55
  delete_option("wpstg_is_staging_site");
56
  delete_option("wpstg_settings");
57
  delete_option("wpstg_rmpermalinks_executed");
 
58
 
59
  /* Do not delete these fields without actually deleting the staging site
60
  * @create a delete routine which deletes the staging sites first
55
  delete_option("wpstg_is_staging_site");
56
  delete_option("wpstg_settings");
57
  delete_option("wpstg_rmpermalinks_executed");
58
+ delete_option("wpstg_activation_redirect");
59
 
60
  /* Do not delete these fields without actually deleting the staging site
61
  * @create a delete routine which deletes the staging sites first
wp-staging.php CHANGED
@@ -7,7 +7,7 @@
7
  * Author: WP-Staging
8
  * Author URI: https://wp-staging.com
9
  * Contributors: ReneHermi, ilgityildirim
10
- * Version: 2.3.8
11
  * Text Domain: wp-staging
12
  * Domain Path: /languages/
13
 
@@ -50,8 +50,8 @@ if( !defined( 'WPSTG_PLUGIN_URL' ) ) {
50
  }
51
 
52
  // Version
53
- if( !defined( 'WPSTGPRO_VERSION' ) ) {
54
- define( 'WPSTGPRO_VERSION', '2.3.8' );
55
  }
56
 
57
  /**
@@ -60,6 +60,7 @@ if( !defined( 'WPSTGPRO_VERSION' ) ) {
60
  if( !class_exists( 'Wpstg_Requirements_Check' ) ) {
61
  include( dirname( __FILE__ ) . '/apps/Core/Utils/requirements-check.php' );
62
  }
 
63
  $plugin_requirements = new Wpstg_Requirements_Check( array(
64
  'title' => 'WP STAGING',
65
  'php' => '5.3',
@@ -110,11 +111,12 @@ if( $plugin_requirements->passes() ) {
110
  * Inititalize WPStaging
111
  */
112
  $wpStaging->run();
113
- }
114
 
115
- /**
116
- * Installation Hooks
117
- */
118
- if( !class_exists( 'WPStaging\Install' ) ) {
119
- require_once plugin_dir_path( __FILE__ ) . "/install.php";
 
 
120
  }
7
  * Author: WP-Staging
8
  * Author URI: https://wp-staging.com
9
  * Contributors: ReneHermi, ilgityildirim
10
+ * Version: 2.3.9
11
  * Text Domain: wp-staging
12
  * Domain Path: /languages/
13
 
50
  }
51
 
52
  // Version
53
+ if( !defined( 'WPSTG_VERSION' ) ) {
54
+ define( 'WPSTG_VERSION', '2.3.9' );
55
  }
56
 
57
  /**
60
  if( !class_exists( 'Wpstg_Requirements_Check' ) ) {
61
  include( dirname( __FILE__ ) . '/apps/Core/Utils/requirements-check.php' );
62
  }
63
+
64
  $plugin_requirements = new Wpstg_Requirements_Check( array(
65
  'title' => 'WP STAGING',
66
  'php' => '5.3',
111
  * Inititalize WPStaging
112
  */
113
  $wpStaging->run();
 
114
 
115
+
116
+ /**
117
+ * Installation Hooks
118
+ */
119
+ if( !class_exists( 'WPStaging\Install' ) ) {
120
+ require_once plugin_dir_path( __FILE__ ) . "/install.php";
121
+ }
122
  }