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

Version Description

  • New Support for WordPress 5.2.4
  • New: Allow wildcards for excluding files
  • New: Add hook "wpstg_clone_action_staging" to execute code on staging site after cloning
  • Tweak: Improved support for custom uploads folder if user customized UPLOADS constant or upload_path in DB
  • Fix: Better compatibility with Windows IIS server
  • Fix: External links are broken after cloning if ABSPATH is equal to /www/
  • Fix: use an alternative method for file_put_contents as it is not supported on all systems due to file permission issues
  • Fix: Redundant and duplicated update comments in wp-config.php in staging site
Download this release

Release Info

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

Code changes from version 2.6.2 to 2.6.3

apps/Backend/Modules/Jobs/Data.php CHANGED
@@ -75,7 +75,7 @@ class Data extends JobExecutable {
75
  * @return void
76
  */
77
  protected function calculateTotalSteps() {
78
- $this->options->totalSteps = 18;
79
  }
80
 
81
  /**
@@ -219,7 +219,7 @@ class Data extends JobExecutable {
219
  }
220
  }
221
 
222
- $this->log( "Preparing Data Step0: Successfull", Logger::TYPE_INFO );
223
  return true;
224
  }
225
 
@@ -279,7 +279,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
279
 
280
  $content = str_replace( $search, $replace, $content );
281
 
282
- if( false === @file_put_contents( $source, $content ) ) {
283
  $this->log( "Preparing Data: Can't save wp-config.php", Logger::TYPE_ERROR );
284
  return false;
285
  }
@@ -389,11 +389,6 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
389
  return true;
390
  }
391
 
392
- // $result = $this->db->query(
393
- // $this->db->prepare(
394
- // "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_is_staging_site'", "true"
395
- // )
396
- // );
397
  $delete = $this->db->query(
398
  $this->db->prepare(
399
  "DELETE FROM `{$this->prefix}options` WHERE `option_name` = %s;", 'wpstg_is_staging_site'
@@ -505,12 +500,19 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
505
  }
506
 
507
  // Replace table prefix
508
- $content = str_replace( '$table_prefix', '$table_prefix = \'' . $this->prefix . '\';//', $content );
 
 
 
 
 
 
 
509
 
510
  // Replace URLs
511
  $content = str_replace( $this->homeUrl, $this->getStagingSiteUrl(), $content );
512
 
513
- if( false === @file_put_contents( $path, $content ) ) {
514
  $this->log( "Preparing Data Step5: Failed to update $table_prefix in {$path} to " . $this->prefix . ". Can't save contents", Logger::TYPE_ERROR );
515
  return false;
516
  }
@@ -548,11 +550,9 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
548
  }
549
  $this->log( "Preparing Data: WP installation is in a subdirectory. Progressing..." );
550
 
551
- $pattern = "/require(.*) dirname(.*) __FILE__ (.*) \. '(.*)wp-blog-header.php'(.*);/";
552
-
553
- $replace = "require( dirname( __FILE__ ) . '/wp-blog-header.php' ); // " . $matches[0];
554
- $replace.= " // Changed by WP-Staging";
555
 
 
556
 
557
 
558
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
@@ -560,7 +560,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
560
  return false;
561
  }
562
 
563
- if( false === @file_put_contents( $path, $content ) ) {
564
  $this->log( "Preparing Data: Failed to reset index.php for sub directory; can't save contents", Logger::TYPE_ERROR );
565
  return false;
566
  }
@@ -695,10 +695,10 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
695
  if( !empty( $matches[1] ) ) {
696
  $matches[1];
697
 
698
- $pattern = "/define\s*\(\s*['\"]WP_HOME['\"]\s*,\s*(.*)\s*\);/";
699
 
700
- $replace = "define('WP_HOME','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
701
- $replace.= " // Changed by WP-Staging";
702
 
703
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
704
  $this->log( "Preparing Data: Failed to update WP_HOME", Logger::TYPE_ERROR );
@@ -708,7 +708,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
708
  $this->log( "Preparing Data Step10: WP_HOME not defined in wp-config.php. Skipping this step." );
709
  }
710
 
711
- if( false === @file_put_contents( $path, $content ) ) {
712
  $this->log( "Preparing Data Step10: Failed to update WP_HOME. Can't save contents", Logger::TYPE_ERROR );
713
  return false;
714
  }
@@ -737,10 +737,10 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
737
  if( !empty( $matches[1] ) ) {
738
  $matches[1];
739
 
740
- $pattern = "/define\s*\(\s*['\"]WP_SITEURL['\"]\s*,\s*(.*)\s*\);/";
741
 
742
- $replace = "define('WP_SITEURL','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
743
- $replace.= " // Changed by WP-Staging";
744
 
745
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
746
  $this->log( "Preparing Data Step11: Failed to update WP_SITEURL to " . $this->getStagingSiteUrl(), Logger::TYPE_ERROR );
@@ -751,7 +751,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
751
  }
752
 
753
 
754
- if( false === @file_put_contents( $path, $content ) ) {
755
  $this->log( "Preparing Data Step11: Failed to update WP_SITEURL to " . $this->getStagingSiteUrl() . " Can't save contents", Logger::TYPE_ERROR );
756
  return false;
757
  }
@@ -759,72 +759,6 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
759
  return true;
760
  }
761
 
762
- /**
763
- * Set true WP_DEBUG & WP_DEBUG_DISPLAY in wp-config.php
764
- * @return bool
765
- */
766
- // protected function step12() {
767
- // $path = $this->getAbsDestination() . $this->options->cloneDirectoryName . "/wp-config.php";
768
- //
769
- // $this->log( "Preparing Data Step12: Set WP_DEBUG to true in wp-config.php." );
770
- //
771
- // if( false === ($content = file_get_contents( $path )) ) {
772
- // $this->log( "Preparing Data Step12: Failed to update WP_DEBUG in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
773
- // return false;
774
- // }
775
- //
776
- //
777
- // // Get WP_DEBUG from wp-config.php
778
- // preg_match( "/define\s*\(\s*'WP_DEBUG'\s*,\s*(.*)\s*\);/", $content, $matches );
779
- //
780
- // // Found it!
781
- // if( !empty( $matches[1] ) ) {
782
- // $matches[1];
783
- //
784
- // $pattern = "/define\s*\(\s*'WP_DEBUG'\s*,\s*(.*)\s*\);/";
785
- //
786
- // $replace = "define('WP_DEBUG',true); // " . $matches[1];
787
- // $replace.= " // Changed by WP-Staging";
788
- //
789
- // if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
790
- // $this->log( "Preparing Data Step12: Failed to update WP_DEBUG", Logger::TYPE_ERROR );
791
- // return false;
792
- // }
793
- // } else {
794
- // $this->log( "Preparing Data Step12: WP_DEBUG not defined in wp-config.php. Skip it." );
795
- // }
796
- //
797
- //
798
- //
799
- // // Get WP_DEBUG_DISPLAY
800
- // preg_match( "/define\s*\(\s*'WP_DEBUG_DISPLAY'\s*,\s*(.*)\s*\);/", $content, $matches );
801
- //
802
- // // Found it!
803
- // if( !empty( $matches[1] ) ) {
804
- // $matches[1];
805
- //
806
- // $pattern = "/define\s*\(\s*'WP_DEBUG_DISPLAY'\s*,\s*(.*)\s*\);/";
807
- //
808
- // $replace = "define('WP_DEBUG_DISPLAY',true); // " . $matches[1];
809
- // $replace.= " // Changed by WP-Staging";
810
- //
811
- // if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
812
- // $this->log( "Preparing Data Step12: Failed to update WP_DEBUG", Logger::TYPE_ERROR );
813
- // return false;
814
- // }
815
- // } else {
816
- // $this->log( "Preparing Data Step12: WP_DEBUG not defined in wp-config.php. Skip it." );
817
- // }
818
- //
819
- //
820
- // if( false === @file_put_contents( $path, $content ) ) {
821
- // $this->log( "Preparing Data Step12: Failed to update WP_DEBUG. Can't save content in wp-config.php", Logger::TYPE_ERROR );
822
- // return false;
823
- // }
824
- // $this->Log( "Preparing Data: Finished Step 12 successfully" );
825
- // return true;
826
- // }
827
-
828
  /**
829
  * Update Table Prefix in wp_options
830
  * @return bool
@@ -903,11 +837,14 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
903
 
904
  $error = !empty( $this->db->last_error ) ? 'Last error: ' . $this->db->last_error : '';
905
 
 
 
906
  // $updateOptions = $this->db->query(
907
  // $this->db->prepare(
908
  // "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'upload_path'", $newUploadPath
909
  // )
910
  // );
 
911
  $updateOptions = $this->db->query(
912
  $this->db->prepare(
913
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'upload_path'", ""
@@ -942,10 +879,10 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
942
 
943
  if( !empty( $matches[1] ) ) {
944
 
945
- $pattern = "/define\s*\(\s*['\"]WP_CACHE['\"]\s*,\s*(.*)\s*\);/";
946
 
947
- $replace = "define('WP_CACHE',false); // " . $matches[1];
948
- $replace.= " // Changed by WP-Staging";
949
 
950
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
951
  $this->log( "Preparing Data: Failed to change WP_CACHE", Logger::TYPE_ERROR );
@@ -955,7 +892,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
955
  $this->log( "Preparing Data Step14: WP_CACHE not defined in wp-config.php. Skipping this step." );
956
  }
957
 
958
- if( false === @file_put_contents( $path, $content ) ) {
959
  $this->log( "Preparing Data Step14: Failed to update WP_CACHE. Can't save contents", Logger::TYPE_ERROR );
960
  return false;
961
  }
@@ -995,7 +932,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
995
  $this->log( "Preparing Data Step15: WP_CONTENT_DIR not defined in wp-config.php. Skipping this step." );
996
  }
997
 
998
- if( false === @file_put_contents( $path, $content ) ) {
999
  $this->log( "Preparing Data Step15: Failed to update WP_CONTENT_DIR. Can't save contents", Logger::TYPE_ERROR );
1000
  return false;
1001
  }
@@ -1035,7 +972,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1035
  $this->log( "Preparing Data Step16: WP_CONTENT_URL not defined in wp-config.php. Skipping this step." );
1036
  }
1037
 
1038
- if( false === @file_put_contents( $path, $content ) ) {
1039
  $this->log( "Preparing Data Step16: Failed to update WP_CONTENT_URL. Can't save contents", Logger::TYPE_ERROR );
1040
  return false;
1041
  }
@@ -1043,46 +980,6 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1043
  return true;
1044
  }
1045
 
1046
- /**
1047
- * Remove UPLOADS constant in wp-config.php to reset default image folder
1048
- * @return bool
1049
- */
1050
- // protected function step17() {
1051
- // $path = $this->options->destinationDir . "wp-config.php";
1052
- //
1053
- // $this->log( "Preparing Data Step17: Remove UPLOADS in wp-config.php" );
1054
- //
1055
- // if( false === ($content = file_get_contents( $path )) ) {
1056
- // $this->log( "Preparing Data Step17: Failed to get UPLOADS in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
1057
- // return false;
1058
- // }
1059
- //
1060
- //
1061
- // // Get UPLOADS from wp-config.php
1062
- // preg_match( "/define\s*\(\s*['\"]UPLOADS['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
1063
- //
1064
- // if( !empty( $matches[0] ) ) {
1065
- //
1066
- // $pattern = "/define\s*\(\s*'UPLOADS'\s*,\s*(.*)\s*\);/";
1067
- //
1068
- // $replace = "";
1069
- //
1070
- // if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1071
- // $this->log( "Preparing Data: Failed to change UPLOADS", Logger::TYPE_ERROR );
1072
- // return false;
1073
- // }
1074
- // } else {
1075
- // $this->log( "Preparing Data Step17: UPLOADS not defined in wp-config.php. Skipping this step." );
1076
- // }
1077
- //
1078
- // if( false === @file_put_contents( $path, $content ) ) {
1079
- // $this->log( "Preparing Data Step17: Failed to update UPLOADS. Can't save contents", Logger::TYPE_ERROR );
1080
- // return false;
1081
- // }
1082
- // $this->Log( "Preparing Data Step17: Finished successfully" );
1083
- // return true;
1084
- // }
1085
-
1086
  /**
1087
  * Add UPLOADS constant in wp-config.php or change it to correct destination.
1088
  * This is important when a custom uploads folder is used
@@ -1100,6 +997,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1100
  $uploadFolder = wpstg_get_rel_upload_dir();
1101
  if( !empty( $matches[0] ) ) {
1102
  $pattern = "/define\s*\(\s*'UPLOADS'\s*,\s*(.*)\s*\);/";
 
1103
  $replace = "define('UPLOADS', '" . $uploadFolder . "');";
1104
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1105
  $this->log( "Preparing Data Step17: Failed to change UPLOADS", Logger::TYPE_ERROR );
@@ -1122,7 +1020,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1122
  $this->log( "Preparing Data Step 17: Can not add UPLOAD constant to wp-config.php. Can not find free position to add it.", Logger::TYPE_ERROR );
1123
  }
1124
  }
1125
- if( false === @file_put_contents( $path, $content ) ) {
1126
  $this->log( "Preparing Data Step17: Failed to update UPLOADS. Can't save contents", Logger::TYPE_ERROR );
1127
  return false;
1128
  }
@@ -1131,49 +1029,131 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1131
  }
1132
 
1133
  /**
1134
- * Empty wpstg_existing_clones_beta on the staging site
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1135
  * @return bool
1136
  */
1137
- // protected function step18() {
1138
- //
1139
- // $this->log( "Preparing Data Step18: Clean up list of staging sites" );
1140
  //
 
1141
  // if( false === $this->isTable( $this->prefix . 'options' ) ) {
1142
  // return true;
1143
  // }
1144
  //
1145
  // // Skip - Table is not selected or updated
1146
  // if( !in_array( $this->prefix . 'options', $this->tables ) ) {
1147
- // $this->log( "Preparing Data Step18: Skipping" );
1148
  // return true;
1149
  // }
1150
  //
1151
- // $result = $this->db->query(
1152
- // $this->db->prepare(
1153
- // "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_existing_clones_beta'", 'a:0:{}'
1154
- // )
 
 
 
 
 
 
 
 
 
 
 
1155
  // );
1156
  //
1157
- // // All good
1158
- // if( false === $result ) {
1159
- // $this->log( "Preparing Data Step18: Can not update listing of staging sites.", Logger::TYPE_WARNING );
1160
- // return true;
 
 
 
 
 
 
 
 
 
1161
  // }
1162
  //
1163
- // $this->Log( "Preparing Data Step18: Finished successfully" );
 
 
 
 
1164
  // return true;
1165
  // }
1166
 
1167
  /**
1168
- * Create a robots.txt in path of the staging site
 
1169
  */
1170
- // protected function step18() {
1171
- // $file = new RobotsTxt();
1172
- // if( false === $file->create( $this->options->destinationDir . "robots.txt" ) ) {
1173
- // $this->log( "Preparing Data Step18: Failed to create robots.txt", Logger::TYPE_ERROR );
1174
- // }
1175
- // return true;
1176
- // }
 
 
 
1177
 
1178
  /**
1179
  * Change upload path of staging site if it has been customized on production site
@@ -1200,7 +1180,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1200
  return false;
1201
  }
1202
 
1203
- $customSlug = str_replace( \WPStaging\WPStaging::getWPpath(), '', $uploadPath );
1204
 
1205
  $newUploadPath = $this->options->destinationDir . $customSlug;
1206
 
75
  * @return void
76
  */
77
  protected function calculateTotalSteps() {
78
+ $this->options->totalSteps = 19;
79
  }
80
 
81
  /**
219
  }
220
  }
221
 
222
+ $this->log( "Preparing Data Step0: Successful", Logger::TYPE_INFO );
223
  return true;
224
  }
225
 
279
 
280
  $content = str_replace( $search, $replace, $content );
281
 
282
+ if( false === @wpstg_put_contents( $source, $content ) ) {
283
  $this->log( "Preparing Data: Can't save wp-config.php", Logger::TYPE_ERROR );
284
  return false;
285
  }
389
  return true;
390
  }
391
 
 
 
 
 
 
392
  $delete = $this->db->query(
393
  $this->db->prepare(
394
  "DELETE FROM `{$this->prefix}options` WHERE `option_name` = %s;", 'wpstg_is_staging_site'
500
  }
501
 
502
  // Replace table prefix
503
+ $pattern = '/\$table_prefix\s*=\s*(.*)/';
504
+ $replacement = '$table_prefix = \'' . $this->prefix . '\'; // Changed by WP Staging';
505
+ $content = preg_replace( $pattern, $replacement, $content );
506
+
507
+ if( null === $content ) {
508
+ $this->log( "Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR );
509
+ return false;
510
+ }
511
 
512
  // Replace URLs
513
  $content = str_replace( $this->homeUrl, $this->getStagingSiteUrl(), $content );
514
 
515
+ if( false === @wpstg_put_contents( $path, $content ) ) {
516
  $this->log( "Preparing Data Step5: Failed to update $table_prefix in {$path} to " . $this->prefix . ". Can't save contents", Logger::TYPE_ERROR );
517
  return false;
518
  }
550
  }
551
  $this->log( "Preparing Data: WP installation is in a subdirectory. Progressing..." );
552
 
553
+ $pattern = "/require(.*) dirname(.*) __FILE__ (.*) \. '(.*)wp-blog-header.php'(.*);.*/";
 
 
 
554
 
555
+ $replace = "require( dirname( __FILE__ ) . '/wp-blog-header.php' ); // " . $matches[0] . " Changed by WP-Staging";
556
 
557
 
558
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
560
  return false;
561
  }
562
 
563
+ if( false === @wpstg_put_contents( $path, $content ) ) {
564
  $this->log( "Preparing Data: Failed to reset index.php for sub directory; can't save contents", Logger::TYPE_ERROR );
565
  return false;
566
  }
695
  if( !empty( $matches[1] ) ) {
696
  $matches[1];
697
 
698
+ $pattern = "/define\s*\(\s*['\"]WP_HOME['\"]\s*,\s*(.*)\s*\);.*/";
699
 
700
+ $replace = "define('WP_HOME','" . $this->getStagingSiteUrl() . "'); // " . $matches[1] . " Changed by WP-Staging";
701
+ //$replace.= " // Changed by WP-Staging";
702
 
703
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
704
  $this->log( "Preparing Data: Failed to update WP_HOME", Logger::TYPE_ERROR );
708
  $this->log( "Preparing Data Step10: WP_HOME not defined in wp-config.php. Skipping this step." );
709
  }
710
 
711
+ if( false === @wpstg_put_contents( $path, $content ) ) {
712
  $this->log( "Preparing Data Step10: Failed to update WP_HOME. Can't save contents", Logger::TYPE_ERROR );
713
  return false;
714
  }
737
  if( !empty( $matches[1] ) ) {
738
  $matches[1];
739
 
740
+ $pattern = "/define\s*\(\s*['\"]WP_SITEURL['\"]\s*,\s*(.*)\s*\);.*/";
741
 
742
+ $replace = "define('WP_SITEURL','" . $this->getStagingSiteUrl() . "'); // " . $matches[1] . " Changed by WP-Staging";
743
+ //$replace.= " // Changed by WP-Staging";
744
 
745
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
746
  $this->log( "Preparing Data Step11: Failed to update WP_SITEURL to " . $this->getStagingSiteUrl(), Logger::TYPE_ERROR );
751
  }
752
 
753
 
754
+ if( false === @wpstg_put_contents( $path, $content ) ) {
755
  $this->log( "Preparing Data Step11: Failed to update WP_SITEURL to " . $this->getStagingSiteUrl() . " Can't save contents", Logger::TYPE_ERROR );
756
  return false;
757
  }
759
  return true;
760
  }
761
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
762
  /**
763
  * Update Table Prefix in wp_options
764
  * @return bool
837
 
838
  $error = !empty( $this->db->last_error ) ? 'Last error: ' . $this->db->last_error : '';
839
 
840
+ $this->log( "Updating upload_path in {$this->prefix}options. {$error}" );
841
+
842
  // $updateOptions = $this->db->query(
843
  // $this->db->prepare(
844
  // "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'upload_path'", $newUploadPath
845
  // )
846
  // );
847
+ // remove upload_path value and use UPLOADS constant instead. (upload_path is deprecated and should not be used any longer)
848
  $updateOptions = $this->db->query(
849
  $this->db->prepare(
850
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'upload_path'", ""
879
 
880
  if( !empty( $matches[1] ) ) {
881
 
882
+ $pattern = "/define\s*\(\s*['\"]WP_CACHE['\"]\s*,\s*(.*)\s*\);.*/";
883
 
884
+ $replace = "define('WP_CACHE',false); // " . $matches[1] . " Changed by WP-Staging";
885
+ //$replace.= " // Changed by WP-Staging";
886
 
887
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
888
  $this->log( "Preparing Data: Failed to change WP_CACHE", Logger::TYPE_ERROR );
892
  $this->log( "Preparing Data Step14: WP_CACHE not defined in wp-config.php. Skipping this step." );
893
  }
894
 
895
+ if( false === @wpstg_put_contents( $path, $content ) ) {
896
  $this->log( "Preparing Data Step14: Failed to update WP_CACHE. Can't save contents", Logger::TYPE_ERROR );
897
  return false;
898
  }
932
  $this->log( "Preparing Data Step15: WP_CONTENT_DIR not defined in wp-config.php. Skipping this step." );
933
  }
934
 
935
+ if( false === @wpstg_put_contents( $path, $content ) ) {
936
  $this->log( "Preparing Data Step15: Failed to update WP_CONTENT_DIR. Can't save contents", Logger::TYPE_ERROR );
937
  return false;
938
  }
972
  $this->log( "Preparing Data Step16: WP_CONTENT_URL not defined in wp-config.php. Skipping this step." );
973
  }
974
 
975
+ if( false === @wpstg_put_contents( $path, $content ) ) {
976
  $this->log( "Preparing Data Step16: Failed to update WP_CONTENT_URL. Can't save contents", Logger::TYPE_ERROR );
977
  return false;
978
  }
980
  return true;
981
  }
982
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
983
  /**
984
  * Add UPLOADS constant in wp-config.php or change it to correct destination.
985
  * This is important when a custom uploads folder is used
997
  $uploadFolder = wpstg_get_rel_upload_dir();
998
  if( !empty( $matches[0] ) ) {
999
  $pattern = "/define\s*\(\s*'UPLOADS'\s*,\s*(.*)\s*\);/";
1000
+
1001
  $replace = "define('UPLOADS', '" . $uploadFolder . "');";
1002
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1003
  $this->log( "Preparing Data Step17: Failed to change UPLOADS", Logger::TYPE_ERROR );
1020
  $this->log( "Preparing Data Step 17: Can not add UPLOAD constant to wp-config.php. Can not find free position to add it.", Logger::TYPE_ERROR );
1021
  }
1022
  }
1023
+ if( false === @wpstg_put_contents( $path, $content ) ) {
1024
  $this->log( "Preparing Data Step17: Failed to update UPLOADS. Can't save contents", Logger::TYPE_ERROR );
1025
  return false;
1026
  }
1029
  }
1030
 
1031
  /**
1032
+ * Save hostname of parent production site in option_name wpstg_connection
1033
+ * @return boolean
1034
+ */
1035
+ protected function step18() {
1036
+
1037
+ $table = $this->prefix . 'options';
1038
+
1039
+ $siteurl = get_site_url();
1040
+
1041
+ $connection = json_encode( array('prodHostname' => $siteurl) );
1042
+
1043
+ $data = array(
1044
+ 'option_name' => 'wpstg_connection',
1045
+ 'option_value' => $connection
1046
+ );
1047
+
1048
+ $format = array('%s', '%s');
1049
+
1050
+ $result = $this->db->replace( $table, $data, $format );
1051
+
1052
+ if( false === $result ) {
1053
+ $this->Log( "Preparing Data Step18: Could not save {$siteurl} in {$table}", Logger::TYPE_ERROR );
1054
+ }
1055
+ return true;
1056
+ }
1057
+
1058
+ /**
1059
+ * Add option_name wpstg_execute and set it to true
1060
+ * This option is used to determine if the staging website has not been loaded initiall for executing certain custom actions from \WPStaging\initActions()
1061
+ * @return boolean
1062
+ */
1063
+ protected function step19() {
1064
+
1065
+ $table = $this->prefix . 'options';
1066
+
1067
+ // Skip - Table does not exist
1068
+ if( false === $this->isTable( $table ) ) {
1069
+ return true;
1070
+ }
1071
+
1072
+
1073
+ $result = $this->db->query(
1074
+ $this->db->prepare(
1075
+ "INSERT INTO {$this->prefix}options (option_name,option_value) VALUES ('wpstg_execute',%s) ON DUPLICATE KEY UPDATE option_value = %s", "true", "true"
1076
+ )
1077
+ );
1078
+
1079
+ if( false === $result ) {
1080
+ $this->Log( "Preparing Data Step19: Could not save wpstg_execute in {$table}", Logger::TYPE_ERROR );
1081
+ }
1082
+ return true;
1083
+ }
1084
+
1085
+ /**
1086
+ * Preserve data and prevents data in wp_options from beeing cloned to staging site
1087
  * @return bool
1088
  */
1089
+ // protected function step20() {
1090
+ // $this->log( "Preparing Data Step20: Preserve Data in " . $this->prefix . "options" );
 
1091
  //
1092
+ // // Skip - Table does not exist
1093
  // if( false === $this->isTable( $this->prefix . 'options' ) ) {
1094
  // return true;
1095
  // }
1096
  //
1097
  // // Skip - Table is not selected or updated
1098
  // if( !in_array( $this->prefix . 'options', $this->tables ) ) {
1099
+ // $this->log( "Preparing Data Step20: Skipped" );
1100
  // return true;
1101
  // }
1102
  //
1103
+ // $sql = '';
1104
+ //
1105
+ // $preserved_option_names = array('wpstg_existing_clones_beta');
1106
+ //
1107
+ // $preserved_option_names = apply_filters( 'wpstg_preserved_options_cloning', $preserved_option_names );
1108
+ // $preserved_options_escaped = esc_sql( $preserved_option_names );
1109
+ //
1110
+ // $preserved_options_data = array();
1111
+ //
1112
+ // // Get preserved data in wp_options tables
1113
+ // $table = $this->db->prefix . 'options';
1114
+ // $preserved_options_data[$this->prefix . 'options'] = $this->db->get_results(
1115
+ // sprintf(
1116
+ // "SELECT * FROM `{$table}` WHERE `option_name` IN ('%s')", implode( "','", $preserved_options_escaped )
1117
+ // ), ARRAY_A
1118
  // );
1119
  //
1120
+ // // Create preserved data queries for options tables
1121
+ // foreach ( $preserved_options_data as $key => $value ) {
1122
+ // if( false === empty( $value ) ) {
1123
+ // foreach ( $value as $option ) {
1124
+ // $sql .= $this->db->prepare(
1125
+ // "DELETE FROM `{$key}` WHERE `option_name` = %s;\n", $option['option_name']
1126
+ // );
1127
+ //
1128
+ // $sql .= $this->db->prepare(
1129
+ // "INSERT INTO `{$key}` ( `option_id`, `option_name`, `option_value`, `autoload` ) VALUES ( NULL , %s, %s, %s );\n", $option['option_name'], $option['option_value'], $option['autoload']
1130
+ // );
1131
+ // }
1132
+ // }
1133
  // }
1134
  //
1135
+ // $this->debugLog( "Preparing Data Step20: Preserve values " . json_encode( $preserved_options_data ), Logger::TYPE_INFO );
1136
+ //
1137
+ // $this->executeSql( $sql );
1138
+ //
1139
+ // $this->log( "Preparing Data Step20: Successful!" );
1140
  // return true;
1141
  // }
1142
 
1143
  /**
1144
+ * Execute a batch of sql queries
1145
+ * @param string $sqlbatch
1146
  */
1147
+ private function executeSql( $sqlbatch ) {
1148
+ $queries = array_filter( explode( ";\n", $sqlbatch ) );
1149
+
1150
+ foreach ( $queries as $query ) {
1151
+ if( false === $this->db->query( $query ) ) {
1152
+ $this->log( "Data Crunching Warning: Can not execute query {$query}", Logger::TYPE_WARNING );
1153
+ }
1154
+ }
1155
+ return true;
1156
+ }
1157
 
1158
  /**
1159
  * Change upload path of staging site if it has been customized on production site
1180
  return false;
1181
  }
1182
 
1183
+ $customSlug = str_replace( wpstg_replace_windows_directory_separator( \WPStaging\WPStaging::getWPpath() ), '', wpstg_replace_windows_directory_separator( $uploadPath ) );
1184
 
1185
  $newUploadPath = $this->options->destinationDir . $customSlug;
1186
 
apps/Backend/Modules/Jobs/Directories.php CHANGED
@@ -164,7 +164,8 @@ class Directories extends JobExecutable {
164
  foreach ( $iterator as $item ) {
165
  if( $item->isFile() ) {
166
  $wpContentDir = str_replace( ABSPATH, '', WP_CONTENT_DIR );
167
- if( $this->write( $files, $wpContentDir . '/' . $iterator->getSubPathName() . PHP_EOL ) ) {
 
168
  $this->options->totalFiles++;
169
 
170
  // Too much cpu time
@@ -315,7 +316,7 @@ class Directories extends JobExecutable {
315
  foreach ( $iterator as $item ) {
316
  if( $item->isFile() ) {
317
  //if( $this->write( $files, $strings->getLastElemAfterString( '/', $folder ) . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
318
- if( $this->write( $files, str_replace( \WPStaging\WPStaging::getWPpath(), '', $folder ) . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
319
  $this->options->totalFiles++;
320
  // Too much cpu time
321
  //$this->options->totalFileSize += $iterator->getSize();
164
  foreach ( $iterator as $item ) {
165
  if( $item->isFile() ) {
166
  $wpContentDir = str_replace( ABSPATH, '', WP_CONTENT_DIR );
167
+ $file = $wpContentDir . '/' . $iterator->getSubPathName() . PHP_EOL;
168
+ if( $this->write( $files, $file ) ) {
169
  $this->options->totalFiles++;
170
 
171
  // Too much cpu time
316
  foreach ( $iterator as $item ) {
317
  if( $item->isFile() ) {
318
  //if( $this->write( $files, $strings->getLastElemAfterString( '/', $folder ) . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
319
+ if( $this->write( $files, str_replace( wpstg_replace_windows_directory_separator(\WPStaging\WPStaging::getWPpath()), '', $folder ) . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
320
  $this->options->totalFiles++;
321
  // Too much cpu time
322
  //$this->options->totalFileSize += $iterator->getSize();
apps/Backend/Modules/Jobs/Files.php CHANGED
@@ -135,9 +135,7 @@ class Files extends JobExecutable {
135
 
136
  $file = $this->file->fgets();
137
 
138
- // if( false !== strpos( $file, 'index.php' ) ) {
139
- // $test = $file;
140
- // }
141
  $this->copyFile( $file );
142
  }
143
 
@@ -172,6 +170,8 @@ class Files extends JobExecutable {
172
 
173
  $file = trim( \WPStaging\WPStaging::getWPpath() . $file );
174
 
 
 
175
  $directory = dirname( $file );
176
 
177
  // Directory is excluded
@@ -224,9 +224,6 @@ class Files extends JobExecutable {
224
  return $this->copyBig( $file, $destination, $this->settings->batchSize );
225
  }
226
 
227
- $this->debugLog( "Try to copy: {$file} to {$destination}", Logger::TYPE_INFO );
228
-
229
-
230
  // Attempt to copy
231
  if( !@copy( $file, $destination ) ) {
232
  $errors = error_get_last();
@@ -242,34 +239,6 @@ class Files extends JobExecutable {
242
  return true;
243
  }
244
 
245
- /**
246
- * Get wp-content and wp-content/uploads destination dir
247
- * Necessary if these folders were customized and changed from the default ones.
248
- *
249
- * @return string
250
- */
251
- // protected function getWpContentPath( $file ) {
252
- // // Get absolute custom upload dir
253
- // $uploads = wp_upload_dir();
254
- //
255
- // // Get absolute upload dir from ABSPATH
256
- // $uploadsAbsPath = trailingslashit( $uploads['basedir'] );
257
- //
258
- // // Get absolute custom wp-content dir
259
- // $wpContentDir = trailingslashit( WP_CONTENT_DIR );
260
- //
261
- // // Check if there is a custom upload directory and do a search $ replace
262
- // $file = str_replace( $uploadsAbsPath, ABSPATH . 'wp-content/uploads/', $file, $count );
263
- //
264
- // // If there is no custom upload directory do a search & replace of the custom wp-content directory
265
- // if( empty( $count ) || $count === 0 ) {
266
- // $file = str_replace( $wpContentDir, ABSPATH . 'wp-content/', $file );
267
- // }
268
- //
269
- //
270
- // return $file;
271
- // }
272
-
273
  /**
274
  * Get wp-content and wp-content/uploads destination dir
275
  * Necessary if these folders were customized and changed from the default ones.
@@ -278,7 +247,7 @@ class Files extends JobExecutable {
278
  */
279
  protected function getWpContentPath( $file ) {
280
  // Get upload directory information
281
- $uploads = wp_upload_dir();
282
 
283
  // Get absolute path to wordpress uploads directory e.g srv/www/htdocs/sitename/wp-content/uploads
284
  $uploadsAbsPath = trailingslashit( $uploads['basedir'] );
@@ -287,7 +256,7 @@ class Files extends JobExecutable {
287
  $uploadsRelPath = wpstg_get_rel_upload_dir();
288
 
289
  // Get absolute path to wp-content directory e.g srv/www/htdocs/sitename/wp-content
290
- $wpContentDir = trailingslashit( WP_CONTENT_DIR );
291
 
292
  // Check if there is a custom uploads directory, then do a search $ replace. Do this only if custom upload path is not identical to WP_CONTENT_DIR
293
  if( $uploadsAbsPath != $wpContentDir ) {
@@ -299,6 +268,8 @@ class Files extends JobExecutable {
299
  //$file = str_replace( $wpContentDir, ABSPATH . 'wp-content/', $file );
300
  $file = str_replace( $wpContentDir, ABSPATH . 'wp-content/', $file );
301
  }
 
 
302
  return $file;
303
  }
304
 
@@ -322,15 +293,11 @@ class Files extends JobExecutable {
322
  * @return bool|string
323
  */
324
  private function getDestination( $file ) {
325
-
326
- // Get custom wp-content and uploads folder
327
- $file = $this->getWpContentPath( $file );
328
-
329
- // remove ABSPATH and get last part of the path
330
- $relativePath = str_replace( \WPStaging\WPStaging::getWPpath(), null, $file );
331
- // add destination path
332
  $destinationPath = $this->destination . $relativePath;
333
- // get folder
334
  $destinationDirectory = dirname( $destinationPath );
335
 
336
  if( !is_dir( $destinationDirectory ) && !@mkdir( $destinationDirectory, wpstg_get_permissions_for_directory(), true ) ) {
@@ -382,11 +349,31 @@ class Files extends JobExecutable {
382
  * @return boolean
383
  */
384
  private function isFileExcluded( $file ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
385
  // If file name exists
386
- if( in_array( basename( $file ), $this->options->excludedFiles ) ) {
387
  return true;
388
  }
389
 
 
 
 
 
 
 
 
390
  // Do not copy wp-config.php if the clone gets updated. This is for security purposes,
391
  // because if the updating process fails, the staging site would not be accessable any longer
392
  if( isset( $this->options->mainJob ) && $this->options->mainJob == "updating" && stripos( strrev( $file ), strrev( "wp-config.php" ) ) === 0 ) {
@@ -397,6 +384,41 @@ class Files extends JobExecutable {
397
  return false;
398
  }
399
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
  /**
401
  * Check if certain file is excluded from copying process
402
  *
@@ -422,10 +444,6 @@ class Files extends JobExecutable {
422
  * @return string
423
  */
424
  private function sanitizeDirectorySeparator( $path ) {
425
- //$string = str_replace( '\\', '/', $path );
426
- //$string = str_replace( "/", "\\", $path );
427
- //return str_replace( '\\\\', '\\', $string );
428
- //return preg_replace( '/[\\\\]+/', '\\\\\\\\', $string );
429
  return preg_replace( '/[\\\\]+/', '/', $path );
430
  }
431
 
135
 
136
  $file = $this->file->fgets();
137
 
138
+
 
 
139
  $this->copyFile( $file );
140
  }
141
 
170
 
171
  $file = trim( \WPStaging\WPStaging::getWPpath() . $file );
172
 
173
+ $file = wpstg_replace_windows_directory_separator($file);
174
+
175
  $directory = dirname( $file );
176
 
177
  // Directory is excluded
224
  return $this->copyBig( $file, $destination, $this->settings->batchSize );
225
  }
226
 
 
 
 
227
  // Attempt to copy
228
  if( !@copy( $file, $destination ) ) {
229
  $errors = error_get_last();
239
  return true;
240
  }
241
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  /**
243
  * Get wp-content and wp-content/uploads destination dir
244
  * Necessary if these folders were customized and changed from the default ones.
247
  */
248
  protected function getWpContentPath( $file ) {
249
  // Get upload directory information
250
+ $uploads = wp_upload_dir();
251
 
252
  // Get absolute path to wordpress uploads directory e.g srv/www/htdocs/sitename/wp-content/uploads
253
  $uploadsAbsPath = trailingslashit( $uploads['basedir'] );
256
  $uploadsRelPath = wpstg_get_rel_upload_dir();
257
 
258
  // Get absolute path to wp-content directory e.g srv/www/htdocs/sitename/wp-content
259
+ $wpContentDir = trailingslashit( WP_CONTENT_DIR );
260
 
261
  // Check if there is a custom uploads directory, then do a search $ replace. Do this only if custom upload path is not identical to WP_CONTENT_DIR
262
  if( $uploadsAbsPath != $wpContentDir ) {
268
  //$file = str_replace( $wpContentDir, ABSPATH . 'wp-content/', $file );
269
  $file = str_replace( $wpContentDir, ABSPATH . 'wp-content/', $file );
270
  }
271
+
272
+
273
  return $file;
274
  }
275
 
293
  * @return bool|string
294
  */
295
  private function getDestination( $file ) {
296
+ //$file = $this->getMultisiteUploadFolder( $file );
297
+ $file = wpstg_replace_windows_directory_separator($file);
298
+ $rootPath = wpstg_replace_windows_directory_separator(\WPStaging\WPStaging::getWPpath());
299
+ $relativePath = str_replace( $rootPath, null, $file );
 
 
 
300
  $destinationPath = $this->destination . $relativePath;
 
301
  $destinationDirectory = dirname( $destinationPath );
302
 
303
  if( !is_dir( $destinationDirectory ) && !@mkdir( $destinationDirectory, wpstg_get_permissions_for_directory(), true ) ) {
349
  * @return boolean
350
  */
351
  private function isFileExcluded( $file ) {
352
+
353
+ $excludedFiles = ( array ) $this->options->excludedFiles;
354
+
355
+ $basenameFile = basename( $file );
356
+
357
+
358
+ // Remove .htaccess and web.config from array excludedFiles if staging site is copied to a subdomain
359
+ //if( $this->isCustomDirectory() ) {
360
+ if( false === $this->isIdenticalHostname() ) {
361
+ $excludedFiles = \array_diff( $excludedFiles, array("web.config", ".htaccess") );
362
+ }
363
+
364
+
365
  // If file name exists
366
+ if( in_array( $basenameFile, $excludedFiles ) ) {
367
  return true;
368
  }
369
 
370
+ // Check for wildcards
371
+ foreach ($excludedFiles as $pattern){
372
+ if(fnmatch($pattern, $basenameFile)){
373
+ return true;
374
+ }
375
+ }
376
+
377
  // Do not copy wp-config.php if the clone gets updated. This is for security purposes,
378
  // because if the updating process fails, the staging site would not be accessable any longer
379
  if( isset( $this->options->mainJob ) && $this->options->mainJob == "updating" && stripos( strrev( $file ), strrev( "wp-config.php" ) ) === 0 ) {
384
  return false;
385
  }
386
 
387
+ /**
388
+ * Check if custom target directory is used
389
+ * @return boolean
390
+ */
391
+ // private function isCustomDirectory() {
392
+ //
393
+ // if( empty( $this->options->cloneDir ) ) {
394
+ // return false;
395
+ // }
396
+ // return true;
397
+ // }
398
+
399
+ /**
400
+ * Check if production and staging hostname are identical
401
+ * If they are not identical we assume website is cloned to a subdomain and not into a subfolder
402
+ * @return boolean
403
+ */
404
+ private function isIdenticalHostname() {
405
+ // hostname of production site without scheme
406
+ $siteurl = get_site_url();
407
+ $url = parse_url( $siteurl );
408
+ $productionHostname = $url['host'];
409
+
410
+ // hostname of staging site without scheme
411
+ $cloneUrl = empty($this->options->cloneHostname) ? $url : parse_url( $this->options->cloneHostname );
412
+ $targetHostname = $cloneUrl['host'];
413
+
414
+ // Check if target hostname beginns with the production hostname
415
+ // Only compare the hostname without path
416
+ if( wpstg_starts_with( $productionHostname, $targetHostname ) ) {
417
+ return true;
418
+ }
419
+ return false;
420
+ }
421
+
422
  /**
423
  * Check if certain file is excluded from copying process
424
  *
444
  * @return string
445
  */
446
  private function sanitizeDirectorySeparator( $path ) {
 
 
 
 
447
  return preg_replace( '/[\\\\]+/', '/', $path );
448
  }
449
 
apps/Backend/Modules/Jobs/Multisite/Data.php CHANGED
@@ -40,9 +40,12 @@ class Data extends JobExecutable {
40
  * Initialize
41
  */
42
  public function initialize() {
43
- $this->db = WPStaging::getInstance()->get( "wpdb" );
 
44
  $this->prefix = $this->options->prefix;
 
45
  $this->getTables();
 
46
  // Fix current step
47
  if( 0 == $this->options->currentStep ) {
48
  $this->options->currentStep = 0;
@@ -68,7 +71,7 @@ class Data extends JobExecutable {
68
  * @return void
69
  */
70
  protected function calculateTotalSteps() {
71
- $this->options->totalSteps = 18;
72
  }
73
 
74
  /**
@@ -78,8 +81,10 @@ class Data extends JobExecutable {
78
  public function start() {
79
  // Execute steps
80
  $this->run();
 
81
  // Save option, progress
82
  $this->saveOptions();
 
83
  return ( object ) $this->response;
84
  }
85
 
@@ -93,6 +98,7 @@ class Data extends JobExecutable {
93
  if( $this->isRoot() ) {
94
  return false;
95
  }
 
96
  // Over limits threshold
97
  if( $this->isOverThreshold() ) {
98
  // Prepare response and save current progress
@@ -100,19 +106,23 @@ class Data extends JobExecutable {
100
  $this->saveOptions();
101
  return false;
102
  }
 
103
  // No more steps, finished
104
  if( $this->isFinished() ) {
105
  $this->prepareResponse( true, false );
106
  return false;
107
  }
 
108
  // Execute step
109
  $stepMethodName = "step" . $this->options->currentStep;
110
  if( !$this->{$stepMethodName}() ) {
111
  $this->prepareResponse( false, false );
112
  return false;
113
  }
 
114
  // Prepare Response
115
  $this->prepareResponse();
 
116
  // Not finished
117
  return true;
118
  }
@@ -134,20 +144,24 @@ class Data extends JobExecutable {
134
  * @return boolean
135
  */
136
  protected function isRoot() {
 
137
  // Prefix is the same as the one of live site
138
  //$wpdb = WPStaging::getInstance()->get( "wpdb" );
139
  if( $this->db->prefix === $this->prefix ) {
140
  return true;
141
  }
 
142
  // CloneName is empty
143
  $name = ( array ) $this->options->cloneDirectoryName;
144
  if( empty( $name ) ) {
145
  return true;
146
  }
 
147
  // Live domain === Staging domain
148
  if( $this->multisiteHomeDomain . $this->options->cloneDirectoryName === $this->multisiteHomeDomain ) {
149
  return true;
150
  }
 
151
  return false;
152
  }
153
 
@@ -171,14 +185,19 @@ class Data extends JobExecutable {
171
  */
172
  protected function step0() {
173
  $this->log( "Preparing Data Step0: Copy wp-config.php file", Logger::TYPE_INFO );
174
- $dir = trailingslashit( dirname( ABSPATH ) );
175
- $source = $dir . 'wp-config.php';
 
 
 
176
  $destination = $this->options->destinationDir . 'wp-config.php';
 
177
  // Check if there is already a valid wp-config.php in root of staging site
178
  if( $this->isValidWpConfig( $destination ) ) {
179
  $this->log( "Preparing Data Step0: Found wp-config.php file in folder {$destination}", Logger::TYPE_INFO );
180
  return true;
181
  }
 
182
  // Check if there is a valid wp-config.php outside root of wp production site
183
  if( $this->isValidWpConfig( $source ) ) {
184
  // Copy it to staging site
@@ -187,9 +206,12 @@ class Data extends JobExecutable {
187
  return true;
188
  }
189
  }
 
190
  // No valid wp-config.php found so let's copy wp stagings default wp-config.php to staging site
191
  $source = WPSTG_PLUGIN_DIR . "apps/Backend/helpers/wp-config.php";
 
192
  $this->log( "Preparing Data Step0: Copy default wp-config.php file from source {$source} to {$destination}", Logger::TYPE_INFO );
 
193
  if( $this->copy( $source, $destination ) ) {
194
  // add missing db credentials to wp-config.php
195
  if( !$this->alterWpConfig( $destination ) ) {
@@ -197,6 +219,7 @@ class Data extends JobExecutable {
197
  return false;
198
  }
199
  }
 
200
  $this->log( "Preparing Data Step0: Successful", Logger::TYPE_INFO );
201
  return true;
202
  }
@@ -217,12 +240,14 @@ class Data extends JobExecutable {
217
  return false;
218
  }
219
  }
 
220
  // Copy file
221
  if( !@copy( $source, $destination ) ) {
222
  $errors = error_get_last();
223
  $this->log( "Preparing Data Step0: Failed to copy {$source}! Error: {$errors['message']} {$source} -> {$destination}", Logger::TYPE_ERROR );
224
  return false;
225
  }
 
226
  return true;
227
  }
228
 
@@ -232,12 +257,16 @@ class Data extends JobExecutable {
232
  * @return boolean
233
  */
234
  protected function alterWpConfig( $source ) {
235
- $this->log( "Preparing Data: Alter wp-config.php", Logger::TYPE_ERROR );
 
236
  $content = file_get_contents( $source );
 
237
  if( false === ($content = file_get_contents( $source )) ) {
238
  return false;
239
  }
240
- $search = "// ** MySQL settings ** //";
 
 
241
  $replace = "// ** MySQL settings ** //\r\n
242
  define( 'DB_NAME', '" . DB_NAME . "' );\r\n
243
  /** MySQL database username */\r\n
@@ -250,11 +279,14 @@ define( 'DB_HOST', '" . DB_HOST . "' );\r\n
250
  define( 'DB_CHARSET', '" . DB_CHARSET . "' );\r\n
251
  /** The Database Collate type. Don't change this if in doubt. */\r\n
252
  define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
 
253
  $content = str_replace( $search, $replace, $content );
254
- if( false === @file_put_contents( $source, $content ) ) {
 
255
  $this->log( "Preparing Data: Can't save wp-config.php", Logger::TYPE_ERROR );
256
  return false;
257
  }
 
258
  return true;
259
  }
260
 
@@ -264,35 +296,46 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
264
  * @return boolean
265
  */
266
  protected function isValidWpConfig( $source ) {
 
267
  if( !is_file( $source ) && !is_link( $source ) ) {
268
  $this->log( "Preparing Data Step0: Can not find {$source}", Logger::TYPE_INFO );
269
  return false;
270
  }
 
271
  $content = file_get_contents( $source );
 
272
  if( false === ($content = file_get_contents( $source )) ) {
273
  $this->log( "Preparing Data Step0: Can not read {$source}", Logger::TYPE_INFO );
274
  return false;
275
  }
 
276
  // Get DB_NAME from wp-config.php
277
  preg_match( "/define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
 
278
  if( empty( $matches[1] ) ) {
279
  $this->log( "Preparing Data Step0: Can not find DB_NAME in wp-config.php", Logger::TYPE_INFO );
280
  return false;
281
  }
 
282
  // Get DB_USER from wp-config.php
283
  preg_match( "/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
 
284
  if( empty( $matches[1] ) ) {
285
  $this->log( "Preparing Data Step0: Can not find DB_USER in wp-config.php", Logger::TYPE_INFO );
286
  return false;
287
  }
 
288
  // Get DB_PASSWORD from wp-config.php
289
  preg_match( "/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
 
290
  if( empty( $matches[1] ) ) {
291
  $this->log( "Preparing Data Step0: Can not find DB_PASSWORD in wp-config.php", Logger::TYPE_INFO );
292
  return false;
293
  }
 
294
  // Get DB_HOST from wp-config.php
295
  preg_match( "/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
 
296
  if( empty( $matches[1] ) ) {
297
  $this->log( "Preparing Data Step0: Can not find DB_HOST in wp-config.php", Logger::TYPE_INFO );
298
  return false;
@@ -306,6 +349,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
306
  */
307
  protected function step1() {
308
  $this->log( "Preparing Data Step1: Updating siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_INFO );
 
309
  // Skip - Table does not exist
310
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
311
  return true;
@@ -315,6 +359,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
315
  $this->log( "Preparing Data Step1: Skipping" );
316
  return true;
317
  }
 
318
  $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . $this->getStagingSiteUrl() );
319
  // Replace URLs
320
  $result = $this->db->query(
@@ -322,10 +367,14 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
322
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", $this->getStagingSiteUrl()
323
  )
324
  );
 
 
 
325
  // All good
326
  if( $result ) {
327
  return true;
328
  }
 
329
  $this->log( "Preparing Data Step1: Skip updating siteurl and homeurl in {$this->prefix}options. Probably already did! {$this->db->last_error}", Logger::TYPE_WARNING );
330
  return true;
331
  }
@@ -335,7 +384,9 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
335
  * @return bool
336
  */
337
  protected function step2() {
 
338
  $this->log( "Preparing Data Step2: Updating row wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}" );
 
339
  // Skip - Table does not exist
340
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
341
  $this->log( "Preparing Data Step2: Skipping" );
@@ -346,11 +397,13 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
346
  $this->log( "Preparing Data Step2: Skipping" );
347
  return true;
348
  }
 
349
  $result = $this->db->query(
350
  $this->db->prepare(
351
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_is_staging_site'", "true"
352
  )
353
  );
 
354
  // No errors but no option name such as wpstg_is_staging_site
355
  if( '' === $this->db->last_error && 0 == $result ) {
356
  $result = $this->db->query(
@@ -359,10 +412,12 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
359
  )
360
  );
361
  }
 
362
  // All good
363
  if( $result ) {
364
  return true;
365
  }
 
366
  $this->log( "Preparing Data Step2: Failed to update wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
367
  return false;
368
  }
@@ -372,30 +427,37 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
372
  * @return bool
373
  */
374
  protected function step3() {
 
375
  $this->log( "Preparing Data Step3: Updating rewrite_rules in {$this->prefix}options {$this->db->last_error}" );
 
376
  // Keep Permalinks
377
  if( isset( $this->settings->keepPermalinks ) && $this->settings->keepPermalinks === "1" ) {
378
  $this->log( "Preparing Data Step3: Skipping" );
379
  return true;
380
  }
 
381
  // Skip - Table does not exist
382
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
383
  return true;
384
  }
 
385
  // Skip - Table is not selected or updated
386
  if( !in_array( $this->prefix . 'options', $this->tables ) ) {
387
  $this->log( "Preparing Data Step3: Skipping" );
388
  return true;
389
  }
 
390
  $result = $this->db->query(
391
  $this->db->prepare(
392
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'rewrite_rules'", ' '
393
  )
394
  );
 
395
  // All good
396
  if( $result ) {
397
  return true;
398
  }
 
399
  //$this->log( "Preparing Data Step3: Failed to update rewrite_rules in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
400
  return true;
401
  }
@@ -406,20 +468,38 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
406
  */
407
  protected function step4() {
408
  $this->log( "Preparing Data Step4: Updating db prefix in {$this->prefix}usermeta. " );
 
409
  // Skip - Table does not exist
410
  if( false === $this->isTable( $this->prefix . 'usermeta' ) ) {
411
  return true;
412
  }
 
413
  // Skip - Table is not selected or updated
414
  if( !in_array( $this->prefix . 'usermeta', $this->tables ) ) {
415
  $this->log( "Preparing Data Step4: Skipping" );
416
  return true;
417
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
418
  $update = $this->db->query(
419
  $this->db->prepare(
420
  "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 . "_%"
421
  )
422
  );
 
423
  if( false === $update ) {
424
  $this->log( "Preparing Data Step4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}", Logger::TYPE_ERROR );
425
  $this->returnException( "Data Crunching Step 4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}" );
@@ -433,20 +513,32 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
433
  * @return bool
434
  */
435
  protected function step5() {
436
- $path = $this->options->destinationDir . "wp-config.php";
 
437
  $this->log( "Preparing Data Step5: Updating table_prefix in {$path} to " . $this->prefix );
438
  if( false === ($content = file_get_contents( $path )) ) {
439
  $this->log( "Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR );
440
  return false;
441
  }
 
442
  // Replace table prefix
443
- $content = str_replace( '$table_prefix', '$table_prefix = \'' . $this->prefix . '\';//', $content );
 
 
 
 
 
 
 
 
444
  // Replace URLs
445
  $content = str_replace( $this->multisiteHomeDomain, $this->getStagingSiteUrl(), $content );
446
- if( false === @file_put_contents( $path, $content ) ) {
 
447
  $this->log( "Preparing Data Step5: Failed to update $table_prefix in {$path} to " . $this->prefix . ". Can't save contents", Logger::TYPE_ERROR );
448
  return false;
449
  }
 
450
  return true;
451
  }
452
 
@@ -458,15 +550,20 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
458
  * @return bool
459
  */
460
  protected function step6() {
 
461
  if( !$this->isSubDir() ) {
462
  $this->debugLog( "Preparing Data Step6: WP installation is not in a subdirectory! All good, skipping this step" );
463
  return true;
464
  }
465
- $path = $this->options->destinationDir . "index.php";
 
 
466
  if( false === ($content = file_get_contents( $path )) ) {
467
  $this->log( "Preparing Data Step6: Failed to reset {$path} for sub directory; can't read contents", Logger::TYPE_ERROR );
468
  return false;
469
  }
 
 
470
  if( !preg_match( "/(require(.*)wp-blog-header.php' \);)/", $content, $matches ) ) {
471
  $this->log(
472
  "Preparing Data Step6: Failed to reset index.php for sub directory; wp-blog-header.php is missing", Logger::TYPE_ERROR
@@ -474,14 +571,20 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
474
  return false;
475
  }
476
  $this->log( "Preparing Data: WP installation is in a subdirectory. Progressing..." );
477
- $pattern = "/require(.*) dirname(.*) __FILE__ (.*) \. '(.*)wp-blog-header.php'(.*);/";
 
 
478
  $replace = "require( dirname( __FILE__ ) . '/wp-blog-header.php' ); // " . $matches[0];
479
- $replace .= " // Changed by WP-Staging";
 
 
 
480
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
481
  $this->log( "Preparing Data: Failed to reset index.php for sub directory; replacement failed", Logger::TYPE_ERROR );
482
  return false;
483
  }
484
- if( false === @file_put_contents( $path, $content ) ) {
 
485
  $this->log( "Preparing Data: Failed to reset index.php for sub directory; can't save contents", Logger::TYPE_ERROR );
486
  return false;
487
  }
@@ -494,21 +597,26 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
494
  * @return bool
495
  */
496
  protected function step7() {
 
497
  $this->log( "Preparing Data Step7: Updating wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}" );
 
498
  // Skip - Table does not exist
499
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
500
  return true;
501
  }
 
502
  // Skip - Table is not selected or updated
503
  if( !in_array( $this->prefix . 'options', $this->tables ) ) {
504
  $this->log( "Preparing Data Step7: Skipping" );
505
  return true;
506
  }
 
507
  $result = $this->db->query(
508
  $this->db->prepare(
509
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_rmpermalinks_executed'", ' '
510
  )
511
  );
 
512
  $this->Log( "Preparing Data Step7: Finished successfully" );
513
  return true;
514
  }
@@ -518,31 +626,38 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
518
  * @return bool
519
  */
520
  protected function step8() {
 
521
  $this->log( "Preparing Data Step8: Updating permalink_structure in {$this->prefix}options {$this->db->last_error}" );
 
522
  // Keep Permalinks
523
  if( isset( $this->settings->keepPermalinks ) && $this->settings->keepPermalinks === "1" ) {
524
  $this->log( "Preparing Data Step8: Skipping" );
525
  return true;
526
  }
 
527
  // Skip - Table does not exist
528
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
529
  return true;
530
  }
 
531
  // Skip - Table is not selected or updated
532
  if( !in_array( $this->prefix . 'options', $this->tables ) ) {
533
  $this->log( "Preparing Data Step8: Skipping" );
534
  return true;
535
  }
 
536
  $result = $this->db->query(
537
  $this->db->prepare(
538
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'permalink_structure'", ' '
539
  )
540
  );
 
541
  // All good
542
  if( $result ) {
543
  $this->Log( "Preparing Data Step8: Finished successfully" );
544
  return true;
545
  }
 
546
  $this->log( "Failed to update permalink_structure in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
547
  return true;
548
  }
@@ -552,25 +667,31 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
552
  * @return bool
553
  */
554
  protected function step9() {
 
555
  $this->log( "Preparing Data Step9: Set staging site to noindex" );
 
556
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
557
  return true;
558
  }
 
559
  // Skip - Table is not selected or updated
560
  if( !in_array( $this->prefix . 'options', $this->tables ) ) {
561
  $this->log( "Preparing Data Step9: Skipping" );
562
  return true;
563
  }
 
564
  $result = $this->db->query(
565
  $this->db->prepare(
566
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'blog_public'", '0'
567
  )
568
  );
 
569
  // All good
570
  if( $result ) {
571
  $this->Log( "Preparing Data Step9: Finished successfully" );
572
  return true;
573
  }
 
574
  $this->log( "Can not update staging site to noindex. Possible already done!", Logger::TYPE_WARNING );
575
  return true;
576
  }
@@ -580,19 +701,27 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
580
  * @return bool
581
  */
582
  protected function step10() {
583
- $path = $this->options->destinationDir . "wp-config.php";
 
584
  $this->log( "Preparing Data Step10: Updating WP_HOME in wp-config.php to " . $this->getStagingSiteUrl() );
 
585
  if( false === ($content = file_get_contents( $path )) ) {
586
  $this->log( "Preparing Data Step10: Failed to update WP_HOME in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
587
  return false;
588
  }
 
 
589
  // Get WP_HOME from wp-config.php
590
  preg_match( "/define\s*\(\s*['\"]WP_HOME['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
 
591
  if( !empty( $matches[1] ) ) {
592
  $matches[1];
593
- $pattern = "/define\s*\(\s*['\"]WP_HOME['\"]\s*,\s*(.*)\s*\);/";
 
 
594
  $replace = "define('WP_HOME','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
595
- $replace .= " // Changed by WP-Staging";
 
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;
@@ -600,7 +729,8 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
600
  } else {
601
  $this->log( "Preparing Data Step10: WP_HOME not defined in wp-config.php. Skipping this step." );
602
  }
603
- if( false === @file_put_contents( $path, $content ) ) {
 
604
  $this->log( "Preparing Data Step10: Failed to update WP_HOME. Can't save contents", Logger::TYPE_ERROR );
605
  return false;
606
  }
@@ -613,19 +743,27 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
613
  * @return bool
614
  */
615
  protected function step11() {
616
- $path = $this->options->destinationDir . "wp-config.php";
 
617
  $this->log( "Preparing Data Step11: Updating WP_SITEURL in wp-config.php to " . $this->getStagingSiteUrl() );
 
618
  if( false === ($content = file_get_contents( $path )) ) {
619
  $this->log( "Preparing Data Step11: Failed to update WP_SITEURL in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
620
  return false;
621
  }
 
 
622
  // Get WP_SITEURL from wp-config.php
623
  preg_match( "/define\s*\(\s*['\"]WP_SITEURL['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
 
624
  if( !empty( $matches[1] ) ) {
625
  $matches[1];
626
- $pattern = "/define\s*\(\s*['\"]WP_SITEURL['\"]\s*,\s*(.*)\s*\);/";
 
 
627
  $replace = "define('WP_SITEURL','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
628
- $replace .= " // Changed by WP-Staging";
 
629
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
630
  $this->log( "Preparing Data Step11: Failed to update WP_SITEURL", Logger::TYPE_ERROR );
631
  return false;
@@ -633,7 +771,9 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
633
  } else {
634
  $this->log( "Preparing Data Step11: WP_SITEURL not defined in wp-config.php. Skipping this step." );
635
  }
636
- if( false === @file_put_contents( $path, $content ) ) {
 
 
637
  $this->log( "Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR );
638
  return false;
639
  }
@@ -646,19 +786,27 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
646
  * @return bool
647
  */
648
  protected function step12() {
649
- $path = $this->options->destinationDir . "wp-config.php";
 
650
  $this->log( "Preparing Data Step12: Updating WP_ALLOW_MULTISITE in wp-config.php to false" );
 
651
  if( false === ($content = file_get_contents( $path )) ) {
652
  $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
653
  return false;
654
  }
 
 
655
  // Get WP_SITEURL from wp-config.php
656
  preg_match( "/define\s*\(\s*['\"]WP_ALLOW_MULTISITE['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
 
657
  if( !empty( $matches[1] ) ) {
658
  $matches[1];
659
- $pattern = "/define\s*\(\s*['\"]WP_ALLOW_MULTISITE['\"]\s*,\s*(.*)\s*\);/";
 
 
660
  $replace = "define('WP_ALLOW_MULTISITE',false); // " . $matches[1];
661
- $replace .= " // Changed by WP-Staging";
 
662
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
663
  $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE", Logger::TYPE_ERROR );
664
  return false;
@@ -666,7 +814,9 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
666
  } else {
667
  $this->log( "Preparing Data Step12: WP_ALLOW_MULTISITE not defined in wp-config.php. Skipping this step." );
668
  }
669
- if( false === @file_put_contents( $path, $content ) ) {
 
 
670
  $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE. Can't save contents", Logger::TYPE_ERROR );
671
  return false;
672
  }
@@ -679,19 +829,27 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
679
  * @return bool
680
  */
681
  protected function step13() {
682
- $path = $this->options->destinationDir . "wp-config.php";
 
683
  $this->log( "Preparing Data Step13: Updating MULTISITE in wp-config.php to false" );
 
684
  if( false === ($content = file_get_contents( $path )) ) {
685
  $this->log( "Preparing Data Step13: Failed to update MULTISITE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
686
  return false;
687
  }
 
 
688
  // Get WP_SITEURL from wp-config.php
689
  preg_match( "/define\s*\(\s*['\"]MULTISITE['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
 
690
  if( !empty( $matches[1] ) ) {
691
  $matches[1];
692
- $pattern = "/define\s*\(\s*['\"]MULTISITE['\"]\s*,\s*(.*)\s*\);/";
 
 
693
  $replace = "define('MULTISITE',false); // " . $matches[1];
694
- $replace .= " // Changed by WP-Staging";
 
695
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
696
  $this->log( "Preparing Data Step13: Failed to update MULTISITE", Logger::TYPE_ERROR );
697
  return false;
@@ -699,7 +857,9 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
699
  } else {
700
  $this->log( "Preparing Data Step13: MULTISITE not defined in wp-config.php. Skipping this step." );
701
  }
702
- if( false === @file_put_contents( $path, $content ) ) {
 
 
703
  $this->log( "Preparing Data Step13: Failed to update MULTISITE. Can't save contents", Logger::TYPE_ERROR );
704
  return false;
705
  }
@@ -712,41 +872,55 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
712
  * Merge both arrays and copy them to the staging site into active_plugins
713
  */
714
  protected function step14() {
 
 
715
  $this->log( "Data Crunching Step14: Updating active_plugins" );
 
716
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
717
  $this->log( 'Data Crunching Step14: Fatal Error ' . $this->prefix . 'options does not exist' );
718
  $this->returnException( 'Data Crunching Step14: Fatal Error ' . $this->prefix . 'options does not exist' );
719
  return false;
720
  }
 
721
  // Skip - Table is not selected or updated
722
  if( !in_array( $this->prefix . 'options', $this->tables ) ) {
723
  $this->log( "Preparing Data Step14: Skipping" );
724
  return true;
725
  }
 
726
  // Get active_plugins value from sub site options table
727
  $active_plugins = $this->db->get_var( "SELECT option_value FROM {$this->db->prefix}options WHERE option_name = 'active_plugins' " );
 
728
  if( !$active_plugins ) {
729
  $this->log( "Data Crunching Step14: Option active_plugins are empty " );
730
  $active_plugins = array();
731
  }
732
  // Get active_sitewide_plugins value from main multisite wp_sitemeta table
733
  $active_sitewide_plugins = $this->db->get_var( "SELECT meta_value FROM {$this->db->base_prefix}sitemeta WHERE meta_key = 'active_sitewide_plugins' " );
 
734
  if( !$active_sitewide_plugins ) {
735
  $this->log( "Data Crunching Step14: Options {$this->db->base_prefix}active_sitewide_plugins is empty " );
736
  $active_sitewide_plugins = array();
737
  }
 
738
  $active_sitewide_plugins = unserialize( $active_sitewide_plugins );
739
  $active_plugins = unserialize( $active_plugins );
740
- $all_plugins = array_merge( $active_plugins, array_keys( $active_sitewide_plugins ) );
 
 
741
  sort( $all_plugins );
 
 
742
  // Update active_plugins
743
- $update = $this->db->query(
744
  "UPDATE {$this->prefix}options SET option_value = '" . serialize( $all_plugins ) . "' WHERE option_name = 'active_plugins'"
745
  );
 
746
  if( false === $update ) {
747
  $this->log( "Data Crunching Step14: Can not update option active_plugins in {$this->prefix}options", Logger::TYPE_WARNING );
748
  return false;
749
  }
 
750
  $this->log( "Data Crunching Step14: Successful!" );
751
  return true;
752
  }
@@ -757,37 +931,48 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
757
  */
758
  protected function step15() {
759
  $this->log( "Preparing Data Step15: Updating db prefix in {$this->prefix}options." );
 
760
  // Skip - Table does not exist
761
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
762
  return true;
763
  }
 
764
  // Skip - Table is not selected or updated
765
  if( !in_array( $this->prefix . 'options', $this->tables ) ) {
766
  $this->log( "Preparing Data Step4: Skipping" );
767
  return true;
768
  }
 
 
769
  $this->log( "Updating db option_names in {$this->prefix}options. " );
 
770
  // Filter the rows below. Do not update them!
771
  $filters = array(
772
  'wp_mail_smtp',
773
  'wp_mail_smtp_version',
774
  'wp_mail_smtp_debug',
775
  );
 
776
  $filters = apply_filters( 'wpstg_data_excl_rows', $filters );
777
- $where = "";
 
778
  foreach ( $filters as $filter ) {
779
  $where .= " AND option_name <> '" . $filter . "'";
780
  }
 
781
  $updateOptions = $this->db->query(
782
  $this->db->prepare(
783
  "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 . "_%"
784
  )
785
  );
 
786
  if( false === $updateOptions ) {
787
  $this->log( "Preparing Data Step15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_WARNING );
788
  //$this->returnException( "Data Crunching Step 15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
789
  return true;
790
  }
 
 
791
  return true;
792
  }
793
 
@@ -797,27 +982,35 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
797
  */
798
  protected function step16() {
799
  $this->log( "Preparing Data Step16: Updating upload_path {$this->prefix}options." );
 
800
  // Skip - Table does not exist
801
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
802
  return true;
803
  }
 
804
  $newUploadPath = $this->getNewUploadPath();
 
805
  if( false === $newUploadPath ) {
806
  $this->log( "Preparing Data Step16: Skipping" );
807
  return true;
808
  }
 
809
  // Skip - Table is not selected or updated
810
  if( !in_array( $this->prefix . 'options', $this->tables ) ) {
811
  $this->log( "Preparing Data Step16: Skipping" );
812
  return true;
813
  }
814
- $error = isset( $this->db->last_error ) ? 'Last error: ' . $this->db->last_error : '';
 
 
815
  $this->log( "Updating upload_path in {$this->prefix}options. {$error}" );
 
816
  $updateOptions = $this->db->query(
817
  $this->db->prepare(
818
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'upload_path'", $newUploadPath
819
  )
820
  );
 
821
  if( false === $updateOptions ) {
822
  $this->log( "Preparing Data Step16: Failed to update upload_path in {$this->prefix}options. {$error}", Logger::TYPE_ERROR );
823
  return true;
@@ -831,19 +1024,27 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
831
  * @return bool
832
  */
833
  protected function step17() {
834
- $path = $this->options->destinationDir . "wp-config.php";
 
835
  $this->log( "Preparing Data Step17: Set WP_CACHE in wp-config.php to false" );
 
836
  if( false === ($content = file_get_contents( $path )) ) {
837
  $this->log( "Preparing Data Step17: Failed to update WP_CACHE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
838
  return false;
839
  }
 
 
840
  // Get WP_CACHE from wp-config.php
841
  preg_match( "/define\s*\(\s*['\"]WP_CACHE['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
 
842
  if( !empty( $matches[1] ) ) {
843
  $matches[1];
844
- $pattern = "/define\s*\(\s*['\"]WP_CACHE['\"]\s*,\s*(.*)\s*\);/";
 
 
845
  $replace = "define('WP_CACHE',false); // " . $matches[1];
846
- $replace .= " // Changed by WP-Staging";
 
847
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
848
  $this->log( "Preparing Data: Failed to change WP_CACHE", Logger::TYPE_ERROR );
849
  return false;
@@ -851,7 +1052,8 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
851
  } else {
852
  $this->log( "Preparing Data Step17: WP_CACHE not defined in wp-config.php. Skipping this step." );
853
  }
854
- if( false === @file_put_contents( $path, $content ) ) {
 
855
  $this->log( "Preparing Data Step17: Failed to update WP_CACHE. Can't save contents", Logger::TYPE_ERROR );
856
  return false;
857
  }
@@ -875,7 +1077,9 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
875
  $uploadFolder = $this->getMultisiteUploadFolder();
876
  if( !empty( $matches[0] ) ) {
877
  $pattern = "/define\s*\(\s*'UPLOADS'\s*,\s*(.*)\s*\);/";
 
878
  $replace = "define('UPLOADS', '" . $uploadFolder . "');";
 
879
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
880
  $this->log( "Preparing Data Step 18: Failed to change UPLOADS", Logger::TYPE_ERROR );
881
  return false;
@@ -889,6 +1093,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
889
  $pattern = "/if\s*\(\s*\s*!\s*defined\s*\(\s*['\"]ABSPATH['\"]\s*(.*)\s*\)\s*\)/";
890
  $replace = "define('UPLOADS', '" . $uploadFolder . "'); \n" .
891
  "if ( ! defined( 'ABSPATH' ) )";
 
892
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
893
  $this->log( "Preparing Data Step 18: Failed to change UPLOADS", Logger::TYPE_ERROR );
894
  return false;
@@ -897,7 +1102,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
897
  $this->log( "Preparing Data Step 18: Can not add UPLOAD constant to wp-config.php. Can not find free position to add it.", Logger::TYPE_ERROR );
898
  }
899
  }
900
- if( false === @file_put_contents( $path, $content ) ) {
901
  $this->log( "Preparing Data Step18: Failed to update UPLOADS. Can't save contents", Logger::TYPE_ERROR );
902
  return false;
903
  }
@@ -906,140 +1111,132 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
906
  }
907
 
908
  /**
909
- * Update database credentials in wp-config.php
910
- * @return bool
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
911
  */
912
- // protected function step19() {
913
- // $path = $this->options->destinationDir . "wp-config.php";
914
- //
915
- // $this->log("Preparing Data Step19: Change database credentials in wp-config.php");
916
- //
917
- // if (false === ($content = file_get_contents($path))) {
918
- // $this->log("Preparing Data Step19: Failed to update database credentials in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR);
919
- // return false;
920
- // }
921
- //
922
- //
923
- // // Get DB_NAME from wp-config.php
924
- // preg_match("/define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*(.*)\s*\);/", $content, $matches);
925
- //
926
- // if (!empty($matches[1])) {
927
- // $matches[1];
928
- //
929
- // $pattern = "/define\s*\(\s*'DB_NAME'\s*,\s*(.*)\s*\);/";
930
- //
931
- // $replace = "define('DB_NAME','{$this->options->databaseDatabase}'); // " . $matches[1];
932
- // $replace .= " // Changed by WP-Staging";
933
- //
934
- // if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
935
- // $this->log("Preparing Data: Failed to change DB_NAME", Logger::TYPE_ERROR);
936
- // return false;
937
- // }
938
- // } else {
939
- // $this->log("Preparing Data Step19: DB_NAME not defined in wp-config.php. Skipping this step.");
940
- // }
941
- // // Get DB_USER from wp-config.php
942
- // preg_match("/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*(.*)\s*\);/", $content, $matches);
943
- //
944
- // if (!empty($matches[1])) {
945
- // $matches[1];
946
- //
947
- // $pattern = "/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*(.*)\s*\);/";
948
- //
949
- // $replace = "define('DB_USER','{$this->options->databaseUser}'); // " . $matches[1];
950
- // $replace .= " // Changed by WP-Staging";
951
- //
952
- // if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
953
- // $this->log("Preparing Data: Failed to change DB_USER", Logger::TYPE_ERROR);
954
- // return false;
955
- // }
956
- // } else {
957
- // $this->log("Preparing Data Step19: DB_USER not defined in wp-config.php. Skipping this step.");
958
- // }
959
- // // Get DB_PASSWORD from wp-config.php
960
- // preg_match("/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*(.*)\s*\);/", $content, $matches);
961
- //
962
- // if (!empty($matches[1])) {
963
- // $matches[1];
964
- //
965
- // $pattern = "/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*(.*)\s*\);/";
966
- //
967
- // $replace = "define('DB_PASSWORD','{$this->options->databasePassword}'); // " . $matches[1];
968
- // $replace .= " // Changed by WP-Staging";
969
- //
970
- // if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
971
- // $this->log("Preparing Data: Failed to change DB_PASSWORD", Logger::TYPE_ERROR);
972
- // return false;
973
- // }
974
- // } else {
975
- // $this->log("Preparing Data Step19: DB_PASSWORD not defined in wp-config.php. Skipping this step.");
976
- // }
977
- // // Get DB_HOST from wp-config.php
978
- // preg_match("/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*(.*)\s*\);/", $content, $matches);
979
- //
980
- // if (!empty($matches[1])) {
981
- // $matches[1];
982
- //
983
- // $pattern = "/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*(.*)\s*\);/";
984
- //
985
- // $replace = "define('DB_HOST','{$this->options->databaseServer}'); // " . $matches[1];
986
- // $replace .= " // Changed by WP-Staging";
987
- //
988
- // if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
989
- // $this->log("Preparing Data: Failed to change DB_HOST", Logger::TYPE_ERROR);
990
- // return false;
991
- // }
992
- // } else {
993
- // $this->log("Preparing Data Step19: DB_HOST not defined in wp-config.php. Skipping this step.");
994
- // }
995
- //
996
- //
997
- // if (false === @file_put_contents($path, $content)) {
998
- // $this->log("Preparing Data Step19: Failed to update database credentials in wp-config.php. Can't save contents", Logger::TYPE_ERROR);
999
- // return false;
1000
- // }
1001
- // $this->Log("Preparing Data Step 19: Finished successfully");
1002
- // return true;
1003
- // }
1004
  /**
1005
- * Remove UPLOADS constant in wp-config.php to reset default image folder
1006
  * @return bool
1007
  */
1008
- // protected function step20() {
1009
- // $path = $this->options->destinationDir . "wp-config.php";
1010
- //
1011
- // $this->log( "Preparing Data Step20: Remove UPLOADS in wp-config.php" );
1012
- //
1013
- // if( false === ($content = file_get_contents( $path )) ) {
1014
- // $this->log( "Preparing Data Step20: Failed to get UPLOADS in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
1015
- // return false;
1016
- // }
1017
- //
1018
- //
1019
- // // Get UPLOADS from wp-config.php
1020
- // preg_match( "/define\s*\(\s*['\"]UPLOADS['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
1021
- //
1022
- // if( !empty( $matches[0] ) ) {
1023
- //
1024
- // $pattern = "/define\s*\(\s*'UPLOADS'\s*,\s*(.*)\s*\);/";
1025
- //
1026
- // $replace = "";
1027
- //
1028
- // if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1029
- // $this->log( "Preparing Data: Failed to change UPLOADS", Logger::TYPE_ERROR );
1030
- // return false;
1031
- // }
1032
- // } else {
1033
- // $this->log( "Preparing Data Step19: UPLOADS not defined in wp-config.php. Skipping this step." );
1034
- // }
1035
- //
1036
- // if( false === @file_put_contents( $path, $content ) ) {
1037
- // $this->log( "Preparing Data Step20: Failed to update UPLOADS. Can't save contents", Logger::TYPE_ERROR );
1038
- // return false;
1039
- // }
1040
- // $this->Log( "Preparing Data Step 20: Finished successfully" );
1041
- // return true;
1042
- // }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1043
  /**
1044
  * Get relative path to the uploads media folder of multisite e.g.
1045
  * wp-content/uploads/sites/SITEID or old wordpress structure wp-content/blogs.dir/SITEID/files
@@ -1049,9 +1246,9 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1049
  $strings = new Strings();
1050
  // Get absolute path to uploads folder
1051
  $uploads = wp_upload_dir();
1052
- $basedir = $strings->sanitizeDirectorySeparator( $uploads['basedir'] );
1053
  // Get relative upload path
1054
- $relDir = str_replace( ABSPATH, null, $basedir );
1055
  return $relDir;
1056
  }
1057
 
@@ -1061,11 +1258,15 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1061
  */
1062
  protected function getNewUploadPath() {
1063
  $uploadPath = get_option( 'upload_path' );
 
1064
  if( !$uploadPath ) {
1065
  return false;
1066
  }
1067
- $customSlug = str_replace( \WPStaging\WPStaging::getWPpath(), '', $uploadPath );
1068
- $newUploadPath = \WPStaging\WPStaging::getWPpath() . $this->options->cloneDirectoryName . DIRECTORY_SEPARATOR . $customSlug;
 
 
 
1069
  return $newUploadPath;
1070
  }
1071
 
@@ -1074,17 +1275,19 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1074
  * @return string
1075
  */
1076
  protected function getStagingSiteUrl() {
 
1077
  if( !empty( $this->options->cloneHostname ) ) {
1078
  return $this->options->cloneHostname;
1079
  }
 
1080
  if( $this->isSubDir() ) {
1081
  return trailingslashit( $this->multisiteHomeDomain ) . trailingslashit( $this->getSubDir() ) . $this->options->cloneDirectoryName;
1082
  }
 
1083
  // Get the path to the main multisite without appending and trailingslash e.g. wordpress
1084
  $multisitePath = defined( 'PATH_CURRENT_SITE' ) ? PATH_CURRENT_SITE : '/';
1085
  $url = rtrim( $this->multisiteHomeDomain, '/\\' ) . $multisitePath . $this->options->cloneDirectoryName;
1086
- //$multisitePath = defined( 'PATH_CURRENT_SITE') ? str_replace('/', '', PATH_CURRENT_SITE) : '';
1087
- //$url = trailingslashit( $this->multisiteHomeDomain ) . $multisitePath . '/' . $this->options->cloneDirectoryName;
1088
  return $url;
1089
  }
1090
 
@@ -1097,6 +1300,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1097
  // This is happening much more often than you would expect
1098
  $siteurl = preg_replace( '#^https?://#', '', rtrim( get_option( 'siteurl' ), '/' ) );
1099
  $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
 
1100
  if( $home !== $siteurl ) {
1101
  return true;
1102
  }
@@ -1110,9 +1314,11 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1110
  protected function getSubDir() {
1111
  $home = get_option( 'home' );
1112
  $siteurl = get_option( 'siteurl' );
 
1113
  if( empty( $home ) || empty( $siteurl ) ) {
1114
  return '';
1115
  }
 
1116
  $dir = str_replace( $home, '', $siteurl );
1117
  return str_replace( '/', '', $dir );
1118
  }
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;
71
  * @return void
72
  */
73
  protected function calculateTotalSteps() {
74
+ $this->options->totalSteps = 21;
75
  }
76
 
77
  /**
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
 
98
  if( $this->isRoot() ) {
99
  return false;
100
  }
101
+
102
  // Over limits threshold
103
  if( $this->isOverThreshold() ) {
104
  // Prepare response and save current progress
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
  }
144
  * @return boolean
145
  */
146
  protected function isRoot() {
147
+
148
  // Prefix is the same as the one of live site
149
  //$wpdb = WPStaging::getInstance()->get( "wpdb" );
150
  if( $this->db->prefix === $this->prefix ) {
151
  return true;
152
  }
153
+
154
  // CloneName is empty
155
  $name = ( array ) $this->options->cloneDirectoryName;
156
  if( empty( $name ) ) {
157
  return true;
158
  }
159
+
160
  // Live domain === Staging domain
161
  if( $this->multisiteHomeDomain . $this->options->cloneDirectoryName === $this->multisiteHomeDomain ) {
162
  return true;
163
  }
164
+
165
  return false;
166
  }
167
 
185
  */
186
  protected function step0() {
187
  $this->log( "Preparing Data Step0: Copy wp-config.php file", Logger::TYPE_INFO );
188
+
189
+ $dir = trailingslashit( dirname( ABSPATH ) );
190
+
191
+ $source = $dir . 'wp-config.php';
192
+
193
  $destination = $this->options->destinationDir . 'wp-config.php';
194
+
195
  // Check if there is already a valid wp-config.php in root of staging site
196
  if( $this->isValidWpConfig( $destination ) ) {
197
  $this->log( "Preparing Data Step0: Found wp-config.php file in folder {$destination}", Logger::TYPE_INFO );
198
  return true;
199
  }
200
+
201
  // Check if there is a valid wp-config.php outside root of wp production site
202
  if( $this->isValidWpConfig( $source ) ) {
203
  // Copy it to staging site
206
  return true;
207
  }
208
  }
209
+
210
  // No valid wp-config.php found so let's copy wp stagings default wp-config.php to staging site
211
  $source = WPSTG_PLUGIN_DIR . "apps/Backend/helpers/wp-config.php";
212
+
213
  $this->log( "Preparing Data Step0: Copy default wp-config.php file from source {$source} to {$destination}", Logger::TYPE_INFO );
214
+
215
  if( $this->copy( $source, $destination ) ) {
216
  // add missing db credentials to wp-config.php
217
  if( !$this->alterWpConfig( $destination ) ) {
219
  return false;
220
  }
221
  }
222
+
223
  $this->log( "Preparing Data Step0: Successful", Logger::TYPE_INFO );
224
  return true;
225
  }
240
  return false;
241
  }
242
  }
243
+
244
  // Copy file
245
  if( !@copy( $source, $destination ) ) {
246
  $errors = error_get_last();
247
  $this->log( "Preparing Data Step0: Failed to copy {$source}! Error: {$errors['message']} {$source} -> {$destination}", Logger::TYPE_ERROR );
248
  return false;
249
  }
250
+
251
  return true;
252
  }
253
 
257
  * @return boolean
258
  */
259
  protected function alterWpConfig( $source ) {
260
+ $this->log( "Preparing Data: Alter wp-config.php", Logger::TYPE_INFO );
261
+
262
  $content = file_get_contents( $source );
263
+
264
  if( false === ($content = file_get_contents( $source )) ) {
265
  return false;
266
  }
267
+
268
+ $search = "// ** MySQL settings ** //";
269
+
270
  $replace = "// ** MySQL settings ** //\r\n
271
  define( 'DB_NAME', '" . DB_NAME . "' );\r\n
272
  /** MySQL database username */\r\n
279
  define( 'DB_CHARSET', '" . DB_CHARSET . "' );\r\n
280
  /** The Database Collate type. Don't change this if in doubt. */\r\n
281
  define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
282
+
283
  $content = str_replace( $search, $replace, $content );
284
+
285
+ if( false === @wpstg_put_contents( $source, $content ) ) {
286
  $this->log( "Preparing Data: Can't save wp-config.php", Logger::TYPE_ERROR );
287
  return false;
288
  }
289
+
290
  return true;
291
  }
292
 
296
  * @return boolean
297
  */
298
  protected function isValidWpConfig( $source ) {
299
+
300
  if( !is_file( $source ) && !is_link( $source ) ) {
301
  $this->log( "Preparing Data Step0: Can not find {$source}", Logger::TYPE_INFO );
302
  return false;
303
  }
304
+
305
  $content = file_get_contents( $source );
306
+
307
  if( false === ($content = file_get_contents( $source )) ) {
308
  $this->log( "Preparing Data Step0: Can not read {$source}", Logger::TYPE_INFO );
309
  return false;
310
  }
311
+
312
  // Get DB_NAME from wp-config.php
313
  preg_match( "/define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
314
+
315
  if( empty( $matches[1] ) ) {
316
  $this->log( "Preparing Data Step0: Can not find DB_NAME in wp-config.php", Logger::TYPE_INFO );
317
  return false;
318
  }
319
+
320
  // Get DB_USER from wp-config.php
321
  preg_match( "/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
322
+
323
  if( empty( $matches[1] ) ) {
324
  $this->log( "Preparing Data Step0: Can not find DB_USER in wp-config.php", Logger::TYPE_INFO );
325
  return false;
326
  }
327
+
328
  // Get DB_PASSWORD from wp-config.php
329
  preg_match( "/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
330
+
331
  if( empty( $matches[1] ) ) {
332
  $this->log( "Preparing Data Step0: Can not find DB_PASSWORD in wp-config.php", Logger::TYPE_INFO );
333
  return false;
334
  }
335
+
336
  // Get DB_HOST from wp-config.php
337
  preg_match( "/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
338
+
339
  if( empty( $matches[1] ) ) {
340
  $this->log( "Preparing Data Step0: Can not find DB_HOST in wp-config.php", Logger::TYPE_INFO );
341
  return false;
349
  */
350
  protected function step1() {
351
  $this->log( "Preparing Data Step1: Updating siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_INFO );
352
+
353
  // Skip - Table does not exist
354
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
355
  return true;
359
  $this->log( "Preparing Data Step1: Skipping" );
360
  return true;
361
  }
362
+
363
  $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . $this->getStagingSiteUrl() );
364
  // Replace URLs
365
  $result = $this->db->query(
367
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", $this->getStagingSiteUrl()
368
  )
369
  );
370
+
371
+
372
+
373
  // All good
374
  if( $result ) {
375
  return true;
376
  }
377
+
378
  $this->log( "Preparing Data Step1: Skip updating siteurl and homeurl in {$this->prefix}options. Probably already did! {$this->db->last_error}", Logger::TYPE_WARNING );
379
  return true;
380
  }
384
  * @return bool
385
  */
386
  protected function step2() {
387
+
388
  $this->log( "Preparing Data Step2: Updating row wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}" );
389
+
390
  // Skip - Table does not exist
391
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
392
  $this->log( "Preparing Data Step2: Skipping" );
397
  $this->log( "Preparing Data Step2: Skipping" );
398
  return true;
399
  }
400
+
401
  $result = $this->db->query(
402
  $this->db->prepare(
403
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_is_staging_site'", "true"
404
  )
405
  );
406
+
407
  // No errors but no option name such as wpstg_is_staging_site
408
  if( '' === $this->db->last_error && 0 == $result ) {
409
  $result = $this->db->query(
412
  )
413
  );
414
  }
415
+
416
  // All good
417
  if( $result ) {
418
  return true;
419
  }
420
+
421
  $this->log( "Preparing Data Step2: Failed to update wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
422
  return false;
423
  }
427
  * @return bool
428
  */
429
  protected function step3() {
430
+
431
  $this->log( "Preparing Data Step3: Updating rewrite_rules in {$this->prefix}options {$this->db->last_error}" );
432
+
433
  // Keep Permalinks
434
  if( isset( $this->settings->keepPermalinks ) && $this->settings->keepPermalinks === "1" ) {
435
  $this->log( "Preparing Data Step3: Skipping" );
436
  return true;
437
  }
438
+
439
  // Skip - Table does not exist
440
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
441
  return true;
442
  }
443
+
444
  // Skip - Table is not selected or updated
445
  if( !in_array( $this->prefix . 'options', $this->tables ) ) {
446
  $this->log( "Preparing Data Step3: Skipping" );
447
  return true;
448
  }
449
+
450
  $result = $this->db->query(
451
  $this->db->prepare(
452
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'rewrite_rules'", ' '
453
  )
454
  );
455
+
456
  // All good
457
  if( $result ) {
458
  return true;
459
  }
460
+
461
  //$this->log( "Preparing Data Step3: Failed to update rewrite_rules in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
462
  return true;
463
  }
468
  */
469
  protected function step4() {
470
  $this->log( "Preparing Data Step4: Updating db prefix in {$this->prefix}usermeta. " );
471
+
472
  // Skip - Table does not exist
473
  if( false === $this->isTable( $this->prefix . 'usermeta' ) ) {
474
  return true;
475
  }
476
+
477
  // Skip - Table is not selected or updated
478
  if( !in_array( $this->prefix . 'usermeta', $this->tables ) ) {
479
  $this->log( "Preparing Data Step4: Skipping" );
480
  return true;
481
  }
482
+
483
+ // Change the base table prefix of the main network site
484
+ $this->debugLog( "Preparing Data Step4: UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, {$this->db->base_prefix}, {$this->prefix}) WHERE meta_key LIKE {$this->db->base_prefix}_%" );
485
+ $update = $this->db->query(
486
+ $this->db->prepare(
487
+ "UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, %s, %s) WHERE meta_key LIKE %s", $this->db->base_prefix, $this->prefix, $this->db->base_prefix . "_%"
488
+ )
489
+ );
490
+
491
+ if( false === $update ) {
492
+ $this->log( "Preparing Data Step4a: Skip updating {$this->prefix}usermeta meta_key database base_prefix; {$this->db->last_error}", Logger::TYPE_INFO );
493
+ }
494
+
495
+ // Change the table prefix of the child network site
496
+ $this->debugLog( "Preparing Data Step4: UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, {$this->db->prefix}, {$this->prefix}) WHERE meta_key LIKE {$this->db->prefix}_%" );
497
  $update = $this->db->query(
498
  $this->db->prepare(
499
  "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 . "_%"
500
  )
501
  );
502
+
503
  if( false === $update ) {
504
  $this->log( "Preparing Data Step4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}", Logger::TYPE_ERROR );
505
  $this->returnException( "Data Crunching Step 4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}" );
513
  * @return bool
514
  */
515
  protected function step5() {
516
+ $path = $this->options->destinationDir . "wp-config.php";
517
+
518
  $this->log( "Preparing Data Step5: Updating table_prefix in {$path} to " . $this->prefix );
519
  if( false === ($content = file_get_contents( $path )) ) {
520
  $this->log( "Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR );
521
  return false;
522
  }
523
+
524
  // Replace table prefix
525
+ $pattern = '/\$table_prefix\s*=\s*(.*).*/';
526
+ $replacement = '$table_prefix = \'' . $this->prefix . '\'; // Changed by WP Staging';
527
+ $content = preg_replace( $pattern, $replacement, $content );
528
+
529
+ if( null === $content ) {
530
+ $this->log( "Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR );
531
+ return false;
532
+ }
533
+
534
  // Replace URLs
535
  $content = str_replace( $this->multisiteHomeDomain, $this->getStagingSiteUrl(), $content );
536
+
537
+ if( false === @wpstg_put_contents( $path, $content ) ) {
538
  $this->log( "Preparing Data Step5: Failed to update $table_prefix in {$path} to " . $this->prefix . ". Can't save contents", Logger::TYPE_ERROR );
539
  return false;
540
  }
541
+
542
  return true;
543
  }
544
 
550
  * @return bool
551
  */
552
  protected function step6() {
553
+
554
  if( !$this->isSubDir() ) {
555
  $this->debugLog( "Preparing Data Step6: WP installation is not in a subdirectory! All good, skipping this step" );
556
  return true;
557
  }
558
+
559
+ $path = $this->options->destinationDir . "index.php";
560
+
561
  if( false === ($content = file_get_contents( $path )) ) {
562
  $this->log( "Preparing Data Step6: Failed to reset {$path} for sub directory; can't read contents", Logger::TYPE_ERROR );
563
  return false;
564
  }
565
+
566
+
567
  if( !preg_match( "/(require(.*)wp-blog-header.php' \);)/", $content, $matches ) ) {
568
  $this->log(
569
  "Preparing Data Step6: Failed to reset index.php for sub directory; wp-blog-header.php is missing", Logger::TYPE_ERROR
571
  return false;
572
  }
573
  $this->log( "Preparing Data: WP installation is in a subdirectory. Progressing..." );
574
+
575
+ $pattern = "/require(.*) dirname(.*) __FILE__ (.*) \. '(.*)wp-blog-header.php'(.*);.*/";
576
+
577
  $replace = "require( dirname( __FILE__ ) . '/wp-blog-header.php' ); // " . $matches[0];
578
+ //$replace .= " // Changed by WP-Staging";
579
+
580
+
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
+
587
+ if( false === @wpstg_put_contents( $path, $content ) ) {
588
  $this->log( "Preparing Data: Failed to reset index.php for sub directory; can't save contents", Logger::TYPE_ERROR );
589
  return false;
590
  }
597
  * @return bool
598
  */
599
  protected function step7() {
600
+
601
  $this->log( "Preparing Data Step7: Updating wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}" );
602
+
603
  // Skip - Table does not exist
604
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
605
  return true;
606
  }
607
+
608
  // Skip - Table is not selected or updated
609
  if( !in_array( $this->prefix . 'options', $this->tables ) ) {
610
  $this->log( "Preparing Data Step7: Skipping" );
611
  return true;
612
  }
613
+
614
  $result = $this->db->query(
615
  $this->db->prepare(
616
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_rmpermalinks_executed'", ' '
617
  )
618
  );
619
+
620
  $this->Log( "Preparing Data Step7: Finished successfully" );
621
  return true;
622
  }
626
  * @return bool
627
  */
628
  protected function step8() {
629
+
630
  $this->log( "Preparing Data Step8: Updating permalink_structure in {$this->prefix}options {$this->db->last_error}" );
631
+
632
  // Keep Permalinks
633
  if( isset( $this->settings->keepPermalinks ) && $this->settings->keepPermalinks === "1" ) {
634
  $this->log( "Preparing Data Step8: Skipping" );
635
  return true;
636
  }
637
+
638
  // Skip - Table does not exist
639
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
640
  return true;
641
  }
642
+
643
  // Skip - Table is not selected or updated
644
  if( !in_array( $this->prefix . 'options', $this->tables ) ) {
645
  $this->log( "Preparing Data Step8: Skipping" );
646
  return true;
647
  }
648
+
649
  $result = $this->db->query(
650
  $this->db->prepare(
651
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'permalink_structure'", ' '
652
  )
653
  );
654
+
655
  // All good
656
  if( $result ) {
657
  $this->Log( "Preparing Data Step8: Finished successfully" );
658
  return true;
659
  }
660
+
661
  $this->log( "Failed to update permalink_structure in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
662
  return true;
663
  }
667
  * @return bool
668
  */
669
  protected function step9() {
670
+
671
  $this->log( "Preparing Data Step9: Set staging site to noindex" );
672
+
673
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
674
  return true;
675
  }
676
+
677
  // Skip - Table is not selected or updated
678
  if( !in_array( $this->prefix . 'options', $this->tables ) ) {
679
  $this->log( "Preparing Data Step9: Skipping" );
680
  return true;
681
  }
682
+
683
  $result = $this->db->query(
684
  $this->db->prepare(
685
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'blog_public'", '0'
686
  )
687
  );
688
+
689
  // All good
690
  if( $result ) {
691
  $this->Log( "Preparing Data Step9: Finished successfully" );
692
  return true;
693
  }
694
+
695
  $this->log( "Can not update staging site to noindex. Possible already done!", Logger::TYPE_WARNING );
696
  return true;
697
  }
701
  * @return bool
702
  */
703
  protected function step10() {
704
+ $path = $this->options->destinationDir . "wp-config.php";
705
+
706
  $this->log( "Preparing Data Step10: Updating WP_HOME in wp-config.php to " . $this->getStagingSiteUrl() );
707
+
708
  if( false === ($content = file_get_contents( $path )) ) {
709
  $this->log( "Preparing Data Step10: Failed to update WP_HOME in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
710
  return false;
711
  }
712
+
713
+
714
  // Get WP_HOME from wp-config.php
715
  preg_match( "/define\s*\(\s*['\"]WP_HOME['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
716
+
717
  if( !empty( $matches[1] ) ) {
718
  $matches[1];
719
+
720
+ $pattern = "/define\s*\(\s*['\"]WP_HOME['\"]\s*,\s*(.*)\s*\);.*/";
721
+
722
  $replace = "define('WP_HOME','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
723
+ //$replace .= " // Changed by WP-Staging";
724
+
725
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
726
  $this->log( "Preparing Data: Failed to update WP_HOME", Logger::TYPE_ERROR );
727
  return false;
729
  } else {
730
  $this->log( "Preparing Data Step10: WP_HOME not defined in wp-config.php. Skipping this step." );
731
  }
732
+
733
+ if( false === @wpstg_put_contents( $path, $content ) ) {
734
  $this->log( "Preparing Data Step10: Failed to update WP_HOME. Can't save contents", Logger::TYPE_ERROR );
735
  return false;
736
  }
743
  * @return bool
744
  */
745
  protected function step11() {
746
+ $path = $this->options->destinationDir . "wp-config.php";
747
+
748
  $this->log( "Preparing Data Step11: Updating WP_SITEURL in wp-config.php to " . $this->getStagingSiteUrl() );
749
+
750
  if( false === ($content = file_get_contents( $path )) ) {
751
  $this->log( "Preparing Data Step11: Failed to update WP_SITEURL in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
752
  return false;
753
  }
754
+
755
+
756
  // Get WP_SITEURL from wp-config.php
757
  preg_match( "/define\s*\(\s*['\"]WP_SITEURL['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
758
+
759
  if( !empty( $matches[1] ) ) {
760
  $matches[1];
761
+
762
+ $pattern = "/define\s*\(\s*['\"]WP_SITEURL['\"]\s*,\s*(.*)\s*\);.*/";
763
+
764
  $replace = "define('WP_SITEURL','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
765
+ //$replace .= " // Changed by WP-Staging";
766
+
767
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
768
  $this->log( "Preparing Data Step11: Failed to update WP_SITEURL", Logger::TYPE_ERROR );
769
  return false;
771
  } else {
772
  $this->log( "Preparing Data Step11: WP_SITEURL not defined in wp-config.php. Skipping this step." );
773
  }
774
+
775
+
776
+ if( false === @wpstg_put_contents( $path, $content ) ) {
777
  $this->log( "Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR );
778
  return false;
779
  }
786
  * @return bool
787
  */
788
  protected function step12() {
789
+ $path = $this->options->destinationDir . "wp-config.php";
790
+
791
  $this->log( "Preparing Data Step12: Updating WP_ALLOW_MULTISITE in wp-config.php to false" );
792
+
793
  if( false === ($content = file_get_contents( $path )) ) {
794
  $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
795
  return false;
796
  }
797
+
798
+
799
  // Get WP_SITEURL from wp-config.php
800
  preg_match( "/define\s*\(\s*['\"]WP_ALLOW_MULTISITE['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
801
+
802
  if( !empty( $matches[1] ) ) {
803
  $matches[1];
804
+
805
+ $pattern = "/define\s*\(\s*['\"]WP_ALLOW_MULTISITE['\"]\s*,\s*(.*)\s*\);.*/";
806
+
807
  $replace = "define('WP_ALLOW_MULTISITE',false); // " . $matches[1];
808
+ //$replace .= " // Changed by WP-Staging";
809
+
810
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
811
  $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE", Logger::TYPE_ERROR );
812
  return false;
814
  } else {
815
  $this->log( "Preparing Data Step12: WP_ALLOW_MULTISITE not defined in wp-config.php. Skipping this step." );
816
  }
817
+
818
+
819
+ if( false === @wpstg_put_contents( $path, $content ) ) {
820
  $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE. Can't save contents", Logger::TYPE_ERROR );
821
  return false;
822
  }
829
  * @return bool
830
  */
831
  protected function step13() {
832
+ $path = $this->options->destinationDir . "wp-config.php";
833
+
834
  $this->log( "Preparing Data Step13: Updating MULTISITE in wp-config.php to false" );
835
+
836
  if( false === ($content = file_get_contents( $path )) ) {
837
  $this->log( "Preparing Data Step13: Failed to update MULTISITE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
838
  return false;
839
  }
840
+
841
+
842
  // Get WP_SITEURL from wp-config.php
843
  preg_match( "/define\s*\(\s*['\"]MULTISITE['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
844
+
845
  if( !empty( $matches[1] ) ) {
846
  $matches[1];
847
+
848
+ $pattern = "/define\s*\(\s*['\"]MULTISITE['\"]\s*,\s*(.*)\s*\);.*/";
849
+
850
  $replace = "define('MULTISITE',false); // " . $matches[1];
851
+ //$replace .= " // Changed by WP-Staging";
852
+
853
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
854
  $this->log( "Preparing Data Step13: Failed to update MULTISITE", Logger::TYPE_ERROR );
855
  return false;
857
  } else {
858
  $this->log( "Preparing Data Step13: MULTISITE not defined in wp-config.php. Skipping this step." );
859
  }
860
+
861
+
862
+ if( false === @wpstg_put_contents( $path, $content ) ) {
863
  $this->log( "Preparing Data Step13: Failed to update MULTISITE. Can't save contents", Logger::TYPE_ERROR );
864
  return false;
865
  }
872
  * Merge both arrays and copy them to the staging site into active_plugins
873
  */
874
  protected function step14() {
875
+
876
+
877
  $this->log( "Data Crunching Step14: Updating active_plugins" );
878
+
879
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
880
  $this->log( 'Data Crunching Step14: Fatal Error ' . $this->prefix . 'options does not exist' );
881
  $this->returnException( 'Data Crunching Step14: Fatal Error ' . $this->prefix . 'options does not exist' );
882
  return false;
883
  }
884
+
885
  // Skip - Table is not selected or updated
886
  if( !in_array( $this->prefix . 'options', $this->tables ) ) {
887
  $this->log( "Preparing Data Step14: Skipping" );
888
  return true;
889
  }
890
+
891
  // Get active_plugins value from sub site options table
892
  $active_plugins = $this->db->get_var( "SELECT option_value FROM {$this->db->prefix}options WHERE option_name = 'active_plugins' " );
893
+
894
  if( !$active_plugins ) {
895
  $this->log( "Data Crunching Step14: Option active_plugins are empty " );
896
  $active_plugins = array();
897
  }
898
  // Get active_sitewide_plugins value from main multisite wp_sitemeta table
899
  $active_sitewide_plugins = $this->db->get_var( "SELECT meta_value FROM {$this->db->base_prefix}sitemeta WHERE meta_key = 'active_sitewide_plugins' " );
900
+
901
  if( !$active_sitewide_plugins ) {
902
  $this->log( "Data Crunching Step14: Options {$this->db->base_prefix}active_sitewide_plugins is empty " );
903
  $active_sitewide_plugins = array();
904
  }
905
+
906
  $active_sitewide_plugins = unserialize( $active_sitewide_plugins );
907
  $active_plugins = unserialize( $active_plugins );
908
+
909
+ $all_plugins = array_merge( $active_plugins, array_keys( $active_sitewide_plugins ) );
910
+
911
  sort( $all_plugins );
912
+
913
+
914
  // Update active_plugins
915
+ $update = $this->db->query(
916
  "UPDATE {$this->prefix}options SET option_value = '" . serialize( $all_plugins ) . "' WHERE option_name = 'active_plugins'"
917
  );
918
+
919
  if( false === $update ) {
920
  $this->log( "Data Crunching Step14: Can not update option active_plugins in {$this->prefix}options", Logger::TYPE_WARNING );
921
  return false;
922
  }
923
+
924
  $this->log( "Data Crunching Step14: Successful!" );
925
  return true;
926
  }
931
  */
932
  protected function step15() {
933
  $this->log( "Preparing Data Step15: Updating db prefix in {$this->prefix}options." );
934
+
935
  // Skip - Table does not exist
936
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
937
  return true;
938
  }
939
+
940
  // Skip - Table is not selected or updated
941
  if( !in_array( $this->prefix . 'options', $this->tables ) ) {
942
  $this->log( "Preparing Data Step4: Skipping" );
943
  return true;
944
  }
945
+
946
+
947
  $this->log( "Updating db option_names in {$this->prefix}options. " );
948
+
949
  // Filter the rows below. Do not update them!
950
  $filters = array(
951
  'wp_mail_smtp',
952
  'wp_mail_smtp_version',
953
  'wp_mail_smtp_debug',
954
  );
955
+
956
  $filters = apply_filters( 'wpstg_data_excl_rows', $filters );
957
+
958
+ $where = "";
959
  foreach ( $filters as $filter ) {
960
  $where .= " AND option_name <> '" . $filter . "'";
961
  }
962
+
963
  $updateOptions = $this->db->query(
964
  $this->db->prepare(
965
  "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 . "_%"
966
  )
967
  );
968
+
969
  if( false === $updateOptions ) {
970
  $this->log( "Preparing Data Step15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_WARNING );
971
  //$this->returnException( "Data Crunching Step 15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
972
  return true;
973
  }
974
+
975
+
976
  return true;
977
  }
978
 
982
  */
983
  protected function step16() {
984
  $this->log( "Preparing Data Step16: Updating upload_path {$this->prefix}options." );
985
+
986
  // Skip - Table does not exist
987
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
988
  return true;
989
  }
990
+
991
  $newUploadPath = $this->getNewUploadPath();
992
+
993
  if( false === $newUploadPath ) {
994
  $this->log( "Preparing Data Step16: Skipping" );
995
  return true;
996
  }
997
+
998
  // Skip - Table is not selected or updated
999
  if( !in_array( $this->prefix . 'options', $this->tables ) ) {
1000
  $this->log( "Preparing Data Step16: Skipping" );
1001
  return true;
1002
  }
1003
+
1004
+ $error = isset( $this->db->last_error ) ? 'Last error: ' . $this->db->last_error : '';
1005
+
1006
  $this->log( "Updating upload_path in {$this->prefix}options. {$error}" );
1007
+
1008
  $updateOptions = $this->db->query(
1009
  $this->db->prepare(
1010
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'upload_path'", $newUploadPath
1011
  )
1012
  );
1013
+
1014
  if( false === $updateOptions ) {
1015
  $this->log( "Preparing Data Step16: Failed to update upload_path in {$this->prefix}options. {$error}", Logger::TYPE_ERROR );
1016
  return true;
1024
  * @return bool
1025
  */
1026
  protected function step17() {
1027
+ $path = $this->options->destinationDir . "wp-config.php";
1028
+
1029
  $this->log( "Preparing Data Step17: Set WP_CACHE in wp-config.php to false" );
1030
+
1031
  if( false === ($content = file_get_contents( $path )) ) {
1032
  $this->log( "Preparing Data Step17: Failed to update WP_CACHE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
1033
  return false;
1034
  }
1035
+
1036
+
1037
  // Get WP_CACHE from wp-config.php
1038
  preg_match( "/define\s*\(\s*['\"]WP_CACHE['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
1039
+
1040
  if( !empty( $matches[1] ) ) {
1041
  $matches[1];
1042
+
1043
+ $pattern = "/define\s*\(\s*['\"]WP_CACHE['\"]\s*,\s*(.*)\s*\);.*/";
1044
+
1045
  $replace = "define('WP_CACHE',false); // " . $matches[1];
1046
+ //$replace .= " // Changed by WP-Staging";
1047
+
1048
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1049
  $this->log( "Preparing Data: Failed to change WP_CACHE", Logger::TYPE_ERROR );
1050
  return false;
1052
  } else {
1053
  $this->log( "Preparing Data Step17: WP_CACHE not defined in wp-config.php. Skipping this step." );
1054
  }
1055
+
1056
+ if( false === @wpstg_put_contents( $path, $content ) ) {
1057
  $this->log( "Preparing Data Step17: Failed to update WP_CACHE. Can't save contents", Logger::TYPE_ERROR );
1058
  return false;
1059
  }
1077
  $uploadFolder = $this->getMultisiteUploadFolder();
1078
  if( !empty( $matches[0] ) ) {
1079
  $pattern = "/define\s*\(\s*'UPLOADS'\s*,\s*(.*)\s*\);/";
1080
+
1081
  $replace = "define('UPLOADS', '" . $uploadFolder . "');";
1082
+ $this->log( "Preparing Data Step18: Change UPLOADS constant in wp-config.php to {$uploadFolder}." );
1083
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1084
  $this->log( "Preparing Data Step 18: Failed to change UPLOADS", Logger::TYPE_ERROR );
1085
  return false;
1093
  $pattern = "/if\s*\(\s*\s*!\s*defined\s*\(\s*['\"]ABSPATH['\"]\s*(.*)\s*\)\s*\)/";
1094
  $replace = "define('UPLOADS', '" . $uploadFolder . "'); \n" .
1095
  "if ( ! defined( 'ABSPATH' ) )";
1096
+ $this->log( "Preparing Data Step18: Change UPLOADS constant in wp-config.php to {$uploadFolder}." );
1097
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1098
  $this->log( "Preparing Data Step 18: Failed to change UPLOADS", Logger::TYPE_ERROR );
1099
  return false;
1102
  $this->log( "Preparing Data Step 18: Can not add UPLOAD constant to wp-config.php. Can not find free position to add it.", Logger::TYPE_ERROR );
1103
  }
1104
  }
1105
+ if( false === @wpstg_put_contents( $path, $content ) ) {
1106
  $this->log( "Preparing Data Step18: Failed to update UPLOADS. Can't save contents", Logger::TYPE_ERROR );
1107
  return false;
1108
  }
1111
  }
1112
 
1113
  /**
1114
+ * Save hostname of parent production site in option_name wpstg_connection
1115
+ * @return boolean
1116
+ */
1117
+ protected function step19() {
1118
+
1119
+ $table = $this->prefix . 'options';
1120
+
1121
+ $siteurl = get_site_url();
1122
+
1123
+ $connection = json_encode( array('prodHostname' => $siteurl) );
1124
+
1125
+ $data = array(
1126
+ 'option_name' => 'wpstg_connection',
1127
+ 'option_value' => $connection
1128
+ );
1129
+
1130
+ $format = array('%s', '%s');
1131
+
1132
+ $result = $this->db->replace( $table, $data, $format );
1133
+
1134
+ if( false === $result ) {
1135
+ $this->Log( "Preparing Data Step19: Could not save {$siteurl} in {$table}", Logger::TYPE_ERROR );
1136
+ }
1137
+ return true;
1138
+ }
1139
+
1140
+ /**
1141
+ * Add option_name wpstg_execute and set it to true
1142
+ * This option is used to determine if the staging website has not been loaded initiall for executing certain custom actions from \WPStaging\initActions()
1143
+ * @return boolean
1144
  */
1145
+ protected function step20() {
1146
+
1147
+ $table = $this->prefix . 'options';
1148
+
1149
+ // Skip - Table does not exist
1150
+ if( false === $this->isTable( $table ) ) {
1151
+ return true;
1152
+ }
1153
+
1154
+
1155
+ $result = $this->db->query(
1156
+ $this->db->prepare(
1157
+ "INSERT INTO {$this->prefix}options (option_name,option_value) VALUES ('wpstg_execute',%s) ON DUPLICATE KEY UPDATE option_value = %s", "true", "true"
1158
+ )
1159
+ );
1160
+
1161
+ if( false === $result ) {
1162
+ $this->Log( "Preparing Data Step20: Could not save wpstg_execute in {$table}", Logger::TYPE_ERROR );
1163
+ }
1164
+ return true;
1165
+ }
1166
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1167
  /**
1168
+ * Preserve data and prevents data in wp_options from beeing cloned to staging site
1169
  * @return bool
1170
  */
1171
+ protected function step21() {
1172
+ $this->log( "Preparing Data Step21: Preserve Data in " . $this->prefix . "options" );
1173
+
1174
+ // Skip - Table does not exist
1175
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
1176
+ return true;
1177
+ }
1178
+
1179
+ // Skip - Table is not selected or updated
1180
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
1181
+ $this->log( "Preparing Data Step21: Skipped" );
1182
+ return true;
1183
+ }
1184
+
1185
+ $sql = '';
1186
+
1187
+ $preserved_option_names = array('wpstg_existing_clones_beta');
1188
+
1189
+ $preserved_option_names = apply_filters( 'wpstg_preserved_options_cloning', $preserved_option_names );
1190
+ $preserved_options_escaped = esc_sql( $preserved_option_names );
1191
+
1192
+ $preserved_options_data = array();
1193
+
1194
+ // Get preserved data in wp_options tables
1195
+ $table = $this->db->prefix . 'options';
1196
+ $preserved_options_data[$this->prefix . 'options'] = $this->db->get_results(
1197
+ sprintf(
1198
+ "SELECT * FROM `{$table}` WHERE `option_name` IN ('%s')", implode( "','", $preserved_options_escaped )
1199
+ ), ARRAY_A
1200
+ );
1201
+
1202
+ // Create preserved data queries for options tables
1203
+ foreach ( $preserved_options_data as $key => $value ) {
1204
+ if( false === empty( $value ) ) {
1205
+ foreach ( $value as $option ) {
1206
+ $sql .= $this->db->prepare(
1207
+ "DELETE FROM `{$key}` WHERE `option_name` = %s;\n", $option['option_name']
1208
+ );
1209
+
1210
+ $sql .= $this->db->prepare(
1211
+ "INSERT INTO `{$key}` ( `option_id`, `option_name`, `option_value`, `autoload` ) VALUES ( NULL , %s, %s, %s );\n", $option['option_name'], $option['option_value'], $option['autoload']
1212
+ );
1213
+ }
1214
+ }
1215
+ }
1216
+
1217
+ $this->debugLog( "Preparing Data Step21: Preserve values " . json_encode( $preserved_options_data ), Logger::TYPE_INFO );
1218
+
1219
+ $this->executeSql( $sql );
1220
+
1221
+ $this->log( "Preparing Data Step21: Successful!" );
1222
+ return true;
1223
+ }
1224
+
1225
+ /**
1226
+ * Execute a batch of sql queries
1227
+ * @param string $sqlbatch
1228
+ */
1229
+ private function executeSql( $sqlbatch ) {
1230
+ $queries = array_filter( explode( ";\n", $sqlbatch ) );
1231
+
1232
+ foreach ( $queries as $query ) {
1233
+ if( false === $this->db->query( $query ) ) {
1234
+ $this->log( "Data Crunching Warning: Can not execute query {$query}", Logger::TYPE_WARNING );
1235
+ }
1236
+ }
1237
+ return true;
1238
+ }
1239
+
1240
  /**
1241
  * Get relative path to the uploads media folder of multisite e.g.
1242
  * wp-content/uploads/sites/SITEID or old wordpress structure wp-content/blogs.dir/SITEID/files
1246
  $strings = new Strings();
1247
  // Get absolute path to uploads folder
1248
  $uploads = wp_upload_dir();
1249
+ $basedir = $uploads['basedir'];
1250
  // Get relative upload path
1251
+ $relDir = str_replace( wpstg_replace_windows_directory_separator( ABSPATH ), null, wpstg_replace_windows_directory_separator( $basedir ) );
1252
  return $relDir;
1253
  }
1254
 
1258
  */
1259
  protected function getNewUploadPath() {
1260
  $uploadPath = get_option( 'upload_path' );
1261
+
1262
  if( !$uploadPath ) {
1263
  return false;
1264
  }
1265
+
1266
+ $customSlug = str_replace( wpstg_replace_windows_directory_separator( \WPStaging\WPStaging::getWPpath() ), '', wpstg_replace_windows_directory_separator( $uploadPath ) );
1267
+
1268
+ $newUploadPath = wpstg_replace_windows_directory_separator( \WPStaging\WPStaging::getWPpath() ) . $this->options->cloneDirectoryName . '/' . $customSlug;
1269
+
1270
  return $newUploadPath;
1271
  }
1272
 
1275
  * @return string
1276
  */
1277
  protected function getStagingSiteUrl() {
1278
+
1279
  if( !empty( $this->options->cloneHostname ) ) {
1280
  return $this->options->cloneHostname;
1281
  }
1282
+
1283
  if( $this->isSubDir() ) {
1284
  return trailingslashit( $this->multisiteHomeDomain ) . trailingslashit( $this->getSubDir() ) . $this->options->cloneDirectoryName;
1285
  }
1286
+
1287
  // Get the path to the main multisite without appending and trailingslash e.g. wordpress
1288
  $multisitePath = defined( 'PATH_CURRENT_SITE' ) ? PATH_CURRENT_SITE : '/';
1289
  $url = rtrim( $this->multisiteHomeDomain, '/\\' ) . $multisitePath . $this->options->cloneDirectoryName;
1290
+
 
1291
  return $url;
1292
  }
1293
 
1300
  // This is happening much more often than you would expect
1301
  $siteurl = preg_replace( '#^https?://#', '', rtrim( get_option( 'siteurl' ), '/' ) );
1302
  $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
1303
+
1304
  if( $home !== $siteurl ) {
1305
  return true;
1306
  }
1314
  protected function getSubDir() {
1315
  $home = get_option( 'home' );
1316
  $siteurl = get_option( 'siteurl' );
1317
+
1318
  if( empty( $home ) || empty( $siteurl ) ) {
1319
  return '';
1320
  }
1321
+
1322
  $dir = str_replace( $home, '', $siteurl );
1323
  return str_replace( '/', '', $dir );
1324
  }
apps/Backend/Modules/Jobs/Multisite/DataExternal.php CHANGED
@@ -1,1135 +1,1430 @@
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 = $this->getStagingDB();
44
- $this->productionDb = WPStaging::getInstance()->get( "wpdb" );
45
- $this->prefix = $this->options->prefix;
46
- $this->db->prefix = $this->options->databasePrefix;
47
-
48
- $this->getTables();
49
-
50
- // Fix current step
51
- if( 0 == $this->options->currentStep ) {
52
- $this->options->currentStep = 0;
53
- }
54
- }
55
-
56
- /**
57
- * Get database object to interact with
58
- */
59
- private function getStagingDB() {
60
- return new \wpdb( $this->options->databaseUser, $this->options->databasePassword, $this->options->databaseDatabase, $this->options->databaseServer );
61
- }
62
-
63
- /**
64
- * Get a list of tables to copy
65
- */
66
- private function getTables() {
67
- $strings = new Strings();
68
- $this->tables = array();
69
- foreach ( $this->options->tables as $table ) {
70
- $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->productionDb->prefix, null, $table );
71
- }
72
- // Add extra global tables from main multisite (wpstgx_users and wpstgx_usermeta)
73
- $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->productionDb->prefix, null, 'users' );
74
- $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->productionDb->prefix, null, 'usermeta' );
75
- }
76
-
77
- /**
78
- * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
79
- * @return void
80
- */
81
- protected function calculateTotalSteps() {
82
- $this->options->totalSteps = 19;
83
- }
84
-
85
- /**
86
- * Start Module
87
- * @return object
88
- */
89
- public function start() {
90
- // Execute steps
91
- $this->run();
92
-
93
- // Save option, progress
94
- $this->saveOptions();
95
-
96
- return ( object ) $this->response;
97
- }
98
-
99
- /**
100
- * Execute the Current Step
101
- * Returns false when over threshold limits are hit or when the job is done, true otherwise
102
- * @return bool
103
- */
104
- protected function execute() {
105
- // Fatal error. Let this happen never and break here immediately
106
- if( $this->isRoot() ) {
107
- return false;
108
- }
109
-
110
- // Over limits threshold
111
- if( $this->isOverThreshold() ) {
112
- // Prepare response and save current progress
113
- $this->prepareResponse( false, false );
114
- $this->saveOptions();
115
- return false;
116
- }
117
-
118
- // No more steps, finished
119
- if( $this->isFinished() ) {
120
- $this->prepareResponse( true, false );
121
- return false;
122
- }
123
-
124
- // Execute step
125
- $stepMethodName = "step" . $this->options->currentStep;
126
- if( !$this->{$stepMethodName}() ) {
127
- $this->prepareResponse( false, false );
128
- return false;
129
- }
130
-
131
- // Prepare Response
132
- $this->prepareResponse();
133
-
134
- // Not finished
135
- return true;
136
- }
137
-
138
- /**
139
- * Checks Whether There is Any Job to Execute or Not
140
- * @return bool
141
- */
142
- protected function isFinished() {
143
- return (
144
- $this->options->currentStep > $this->options->totalSteps ||
145
- !method_exists( $this, "step" . $this->options->currentStep )
146
- );
147
- }
148
-
149
- /**
150
- * Check if current operation is done on the root folder or on the live DB
151
- * @return boolean
152
- */
153
- protected function isRoot() {
154
-
155
- // Prefix is the same as the one of live site
156
- // $wpdb = WPStaging::getInstance()->get( "wpdb" );
157
- // if( $wpdb->prefix === $this->prefix ) {
158
- // return true;
159
- // }
160
- // CloneName is empty
161
- $name = ( array ) $this->options->cloneDirectoryName;
162
- if( empty( $name ) ) {
163
- return true;
164
- }
165
-
166
- // Live domain === Staging domain
167
- if( $this->multisiteHomeDomain . $this->options->cloneDirectoryName === $this->multisiteHomeDomain ) {
168
- return true;
169
- }
170
-
171
- return false;
172
- }
173
-
174
- /**
175
- * Check if table exists
176
- * @param string $table
177
- * @return boolean
178
- */
179
- protected function isTable( $table ) {
180
- if( $this->db->get_var( "SHOW TABLES LIKE '{$table}'" ) != $table ) {
181
- $this->log( "Table {$table} does not exist", Logger::TYPE_ERROR );
182
- return false;
183
- }
184
- return true;
185
- }
186
-
187
- /**
188
- * Return absolute destination path
189
- * @return string
190
- */
191
- // private function getAbsDestination() {
192
- // if( empty( $this->options->cloneDir ) ) {
193
- // return \WPStaging\WPStaging::getWPpath();
194
- // }
195
- // return trailingslashit( $this->options->cloneDir );
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 = $this->options->destinationDir . '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
- $errors = error_get_last();
224
- $this->log( "Preparing Data Step0: Failed to copy wp-config.php! Error: {$errors['message']} {$source} -> {$destination}", Logger::TYPE_ERROR );
225
- return true;
226
- }
227
- }
228
-
229
- // Copy file wp-config.php
230
- if( !@copy( $source, $destination ) ) {
231
- $errors = error_get_last();
232
- $this->log( "Preparing Data Step0: Failed to copy wp-config.php! Error: {$errors['message']} {$source} -> {$destination}", Logger::TYPE_ERROR );
233
- return true;
234
- }
235
- $this->log( "Preparing Data Step0: Successfull", Logger::TYPE_INFO );
236
- return true;
237
- }
238
-
239
- /**
240
- * Replace "siteurl" and "home"
241
- * @return bool
242
- */
243
- protected function step1() {
244
- $this->log( "Preparing Data Step1: Updating siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_INFO );
245
-
246
- // Skip - Table does not exist
247
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
248
- return true;
249
- }
250
- // Skip - Table is not selected or updated
251
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
252
- $this->log( "Preparing Data Step1: Skipping" );
253
- return true;
254
- }
255
-
256
- // Installed in sub-directory
257
- // if( $this->isSubDir() ) {
258
- // $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . rtrim( $this->multisiteHomeDomain, "/" ) . '/' . $this->options->cloneDirectoryName );
259
- // // Replace URLs
260
- // $result = $this->db->query(
261
- // $this->db->prepare(
262
- // "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", rtrim( $this->multisiteHomeDomain, "/" ) . '/' . $this->options->cloneDirectoryName
263
- // )
264
- // );
265
- // } else
266
- // {
267
- // $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . rtrim( $this->multisiteHomeDomain, "/" ) . '/' . $this->options->cloneDirectoryName );
268
- // // Replace URLs
269
- // $result = $this->db->query(
270
- // $this->db->prepare(
271
- // "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", $this->multisiteHomeDomain . '/' . $this->options->cloneDirectoryName
272
- // )
273
- // );
274
- // }
275
-
276
- $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . $this->getStagingSiteUrl() );
277
- // Replace URLs
278
- $result = $this->db->query(
279
- $this->db->prepare(
280
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", $this->getStagingSiteUrl()
281
- )
282
- );
283
-
284
-
285
-
286
- // All good
287
- if( $result ) {
288
- return true;
289
- }
290
-
291
- $this->log( "Preparing Data Step1: Failed to update siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
292
- return true;
293
- }
294
-
295
- /**
296
- * Update "wpstg_is_staging_site"
297
- * @return bool
298
- */
299
- protected function step2() {
300
-
301
- $this->log( "Preparing Data Step2: Updating row wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}" );
302
-
303
- // Skip - Table does not exist
304
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
305
- return true;
306
- }
307
- // Skip - Table is not selected or updated
308
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
309
- $this->log( "Preparing Data Step2: Skipping" );
310
- return true;
311
- }
312
-
313
- $result = $this->db->query(
314
- $this->db->prepare(
315
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_is_staging_site'", "true"
316
- )
317
- );
318
-
319
- // No errors but no option name such as wpstg_is_staging_site
320
- if( '' === $this->db->last_error && 0 == $result ) {
321
- $result = $this->db->query(
322
- $this->db->prepare(
323
- "INSERT INTO {$this->prefix}options (option_name,option_value) VALUES ('wpstg_is_staging_site',%s)", "true"
324
- )
325
- );
326
- }
327
-
328
- // All good
329
- if( $result ) {
330
- return true;
331
- }
332
-
333
- $this->log( "Preparing Data Step2: Failed to update wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
334
- return false;
335
- }
336
-
337
- /**
338
- * Update rewrite_rules
339
- * @return bool
340
- */
341
- protected function step3() {
342
-
343
- $this->log( "Preparing Data Step3: Updating rewrite_rules in {$this->prefix}options {$this->db->last_error}" );
344
-
345
- // Skip - Table does not exist
346
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
347
- return true;
348
- }
349
-
350
- // Skip - Table is not selected or updated
351
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
352
- $this->log( "Preparing Data Step3: Skipping" );
353
- return true;
354
- }
355
-
356
- $result = $this->db->query(
357
- $this->db->prepare(
358
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'rewrite_rules'", ' '
359
- )
360
- );
361
-
362
- // All good
363
- if( $result ) {
364
- return true;
365
- }
366
-
367
- $this->log( "Preparing Data Step3: Failed to update rewrite_rules in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
368
- return true;
369
- }
370
-
371
- /**
372
- * Update Table Prefix in wp_usermeta
373
- * @return bool
374
- */
375
- protected function step4() {
376
- $this->log( "Preparing Data Step4: Updating db prefix in {$this->prefix}usermeta. " );
377
-
378
- // Skip - Table does not exist
379
- if( false === $this->isTable( $this->prefix . 'usermeta' ) ) {
380
- return true;
381
- }
382
-
383
- // Skip - Table is not selected or updated
384
- if( !in_array( $this->prefix . 'usermeta', $this->tables ) ) {
385
- $this->log( "Preparing Data Step4: Skipping" );
386
- return true;
387
- }
388
-
389
- // Skip, prefixes are identical. No change needed
390
- // if( $this->db->prefix === $this->prefix ) {
391
- // $this->log( "Preparing Data Step4: Skipping" );
392
- // return true;
393
- // }
394
-
395
- $update = $this->db->query(
396
- $this->db->prepare(
397
- "UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, %s, %s) WHERE meta_key LIKE %s", $this->productionDb->base_prefix, $this->prefix, $this->productionDb->base_prefix . "_%"
398
- )
399
- );
400
-
401
- if( !$update ) {
402
- $this->log( "Preparing Data Step4a: Skip updating {$this->prefix}usermeta meta_key database base_prefix; {$this->db->last_error}", Logger::TYPE_INFO );
403
- //return true;
404
- }
405
-
406
- $update = $this->db->query(
407
- $this->db->prepare(
408
- "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 . "_%"
409
- )
410
- );
411
-
412
- if( !$update ) {
413
- $this->log( "Preparing Data Step4: Skip updating {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}", Logger::TYPE_INFO );
414
- //$this->returnException( "Data Crunching Step 4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}" );
415
- return true;
416
- }
417
- return true;
418
- }
419
-
420
- /**
421
- * Update $table_prefix in wp-config.php
422
- * @return bool
423
- */
424
- protected function step5() {
425
- $path = $this->options->destinationDir . "wp-config.php";
426
-
427
- $this->log( "Preparing Data Step5: Updating table_prefix in {$path} to " . $this->prefix );
428
- if( false === ($content = file_get_contents( $path )) ) {
429
- $this->log( "Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR );
430
- return false;
431
- }
432
-
433
- // Replace table prefix
434
- $content = str_replace( '$table_prefix', '$table_prefix = \'' . $this->prefix . '\';//', $content );
435
-
436
- // Replace URLs
437
- $content = str_replace( $this->multisiteHomeDomain, $this->getStagingSiteUrl(), $content );
438
-
439
- if( false === @file_put_contents( $path, $content ) ) {
440
- $this->log( "Preparing Data Step5: Failed to update $table_prefix in {$path} to " . $this->prefix . ". Can't save contents", Logger::TYPE_ERROR );
441
- return false;
442
- }
443
-
444
- return true;
445
- }
446
-
447
- /**
448
- * Reset index.php to original file
449
- * This is needed if live site is located in subfolder
450
- * Check first if main wordpress is used in subfolder and index.php in parent directory
451
- * @see: https://codex.wordpress.org/Giving_WordPress_Its_Own_Directory
452
- * @return bool
453
- */
454
- protected function step6() {
455
-
456
- if( !$this->isSubDir() ) {
457
- $this->debugLog( "Preparing Data Step6: WP installation is not in a subdirectory! All good, skipping this step" );
458
- return true;
459
- }
460
-
461
- $path = $this->options->destinationDir . "index.php";
462
-
463
- if( false === ($content = file_get_contents( $path )) ) {
464
- $this->log( "Preparing Data Step6: Failed to reset {$path} for sub directory; can't read contents", Logger::TYPE_ERROR );
465
- return false;
466
- }
467
-
468
-
469
- if( !preg_match( "/(require(.*)wp-blog-header.php' \);)/", $content, $matches ) ) {
470
- $this->log(
471
- "Preparing Data Step6: Failed to reset index.php for sub directory. Can not find line 'require(.*)wp-blog-header.php' in index.php", Logger::TYPE_ERROR
472
- );
473
- return false;
474
- }
475
- $this->log( "Preparing Data: WP installation is in a subdirectory. Progressing..." );
476
-
477
- $pattern = "/require(.*) dirname(.*) __FILE__ (.*) \. '(.*)wp-blog-header.php'(.*);/";
478
-
479
- $replace = "require( dirname( __FILE__ ) . '/wp-blog-header.php' ); // " . $matches[0];
480
- $replace.= " // Changed by WP-Staging";
481
-
482
-
483
-
484
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
485
- $this->log( "Preparing Data: Failed to reset index.php for sub directory; replacement failed", Logger::TYPE_ERROR );
486
- return false;
487
- }
488
-
489
- if( false === @file_put_contents( $path, $content ) ) {
490
- $this->log( "Preparing Data: Failed to reset index.php for sub directory; can't save contents", Logger::TYPE_ERROR );
491
- return false;
492
- }
493
- $this->Log( "Preparing Data: Finished Step 6 successfully" );
494
- return true;
495
- }
496
-
497
- /**
498
- * Update wpstg_rmpermalinks_executed
499
- * @return bool
500
- */
501
- protected function step7() {
502
-
503
- $this->log( "Preparing Data Step7: Updating wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}" );
504
-
505
- // Skip - Table does not exist
506
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
507
- return true;
508
- }
509
-
510
- // Skip - Table is not selected or updated
511
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
512
- $this->log( "Preparing Data Step7: Skipping" );
513
- return true;
514
- }
515
-
516
- $result = $this->db->query(
517
- $this->db->prepare(
518
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_rmpermalinks_executed'", ' '
519
- )
520
- );
521
-
522
- // All good
523
- if( $result ) {
524
- $this->Log( "Preparing Data Step7: Finished Step 7 successfully" );
525
- return true;
526
- }
527
-
528
- $this->log( "Failed to update wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_WARNING );
529
- return true;
530
- }
531
-
532
- /**
533
- * Update permalink_structure
534
- * @return bool
535
- */
536
- protected function step8() {
537
-
538
- $this->log( "Preparing Data Step8: Updating permalink_structure in {$this->prefix}options {$this->db->last_error}" );
539
-
540
- // Skip - Table does not exist
541
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
542
- return true;
543
- }
544
-
545
- // Skip - Table is not selected or updated
546
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
547
- $this->log( "Preparing Data Step8: Skipping" );
548
- return true;
549
- }
550
-
551
- $result = $this->db->query(
552
- $this->db->prepare(
553
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'permalink_structure'", ' '
554
- )
555
- );
556
-
557
- // All good
558
- if( $result ) {
559
- $this->Log( "Preparing Data Step8: Finished Step 8 successfully" );
560
- return true;
561
- }
562
-
563
- $this->log( "Failed to update permalink_structure in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
564
- return true;
565
- }
566
-
567
- /**
568
- * Update blog_public option to not allow staging site to be indexed by search engines
569
- * @return bool
570
- */
571
- protected function step9() {
572
-
573
- $this->log( "Preparing Data Step9: Set staging site to noindex" );
574
-
575
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
576
- return true;
577
- }
578
-
579
- // Skip - Table is not selected or updated
580
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
581
- $this->log( "Preparing Data Step9: Skipping" );
582
- return true;
583
- }
584
-
585
- $result = $this->db->query(
586
- $this->db->prepare(
587
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'blog_public'", '0'
588
- )
589
- );
590
-
591
- // All good
592
- if( $result ) {
593
- $this->Log( "Preparing Data Step9: Finished Step 9 successfully" );
594
- return true;
595
- }
596
-
597
- $this->log( "Can not update staging site to noindex. Possible already done!", Logger::TYPE_WARNING );
598
- return true;
599
- }
600
-
601
- /**
602
- * Update WP_HOME in wp-config.php
603
- * @return bool
604
- */
605
- protected function step10() {
606
- $path = $this->options->destinationDir . "wp-config.php";
607
-
608
- $this->log( "Preparing Data Step10: Updating WP_HOME in wp-config.php to " . $this->getStagingSiteUrl() );
609
-
610
- if( false === ($content = file_get_contents( $path )) ) {
611
- $this->log( "Preparing Data Step10: Failed to update WP_HOME in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
612
- return false;
613
- }
614
-
615
-
616
- // Get WP_HOME from wp-config.php
617
- preg_match( "/define\s*\(\s*'WP_HOME'\s*,\s*(.*)\s*\);/", $content, $matches );
618
-
619
- if( !empty( $matches[1] ) ) {
620
- $matches[1];
621
-
622
- $pattern = "/define\s*\(\s*'WP_HOME'\s*,\s*(.*)\s*\);/";
623
-
624
- $replace = "define('WP_HOME','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
625
- $replace.= " // Changed by WP-Staging";
626
-
627
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
628
- $this->log( "Preparing Data: Failed to update WP_HOME", Logger::TYPE_ERROR );
629
- return false;
630
- }
631
- } else {
632
- $this->log( "Preparing Data Step10: WP_HOME not defined in wp-config.php. Skipping this step." );
633
- }
634
-
635
- if( false === @file_put_contents( $path, $content ) ) {
636
- $this->log( "Preparing Data Step10: Failed to update WP_HOME. Can't save contents", Logger::TYPE_ERROR );
637
- return false;
638
- }
639
- $this->Log( "Preparing Data: Finished Step 10 successfully" );
640
- return true;
641
- }
642
-
643
- /**
644
- * Update WP_SITEURL in wp-config.php
645
- * @return bool
646
- */
647
- protected function step11() {
648
- $path = $this->options->destinationDir . "wp-config.php";
649
-
650
- $this->log( "Preparing Data Step11: Updating WP_SITEURL in wp-config.php to " . $this->getStagingSiteUrl() );
651
-
652
- if( false === ($content = file_get_contents( $path )) ) {
653
- $this->log( "Preparing Data Step11: Failed to update WP_SITEURL in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
654
- return false;
655
- }
656
-
657
-
658
- // Get WP_SITEURL from wp-config.php
659
- preg_match( "/define\s*\(\s*'WP_SITEURL'\s*,\s*(.*)\s*\);/", $content, $matches );
660
-
661
- if( !empty( $matches[1] ) ) {
662
- $matches[1];
663
-
664
- $pattern = "/define\s*\(\s*'WP_SITEURL'\s*,\s*(.*)\s*\);/";
665
-
666
- $replace = "define('WP_SITEURL','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
667
- $replace.= " // Changed by WP-Staging";
668
-
669
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
670
- $this->log( "Preparing Data Step11: Failed to update WP_SITEURL", Logger::TYPE_ERROR );
671
- return false;
672
- }
673
- } else {
674
- $this->log( "Preparing Data Step11: WP_SITEURL not defined in wp-config.php. Skipping this step." );
675
- }
676
-
677
-
678
- if( false === @file_put_contents( $path, $content ) ) {
679
- $this->log( "Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR );
680
- return false;
681
- }
682
- $this->Log( "Preparing Data: Finished Step 11 successfully" );
683
- return true;
684
- }
685
-
686
- /**
687
- * Update WP_ALLOW_MULTISITE constant in wp-config.php
688
- * @return bool
689
- */
690
- protected function step12() {
691
- $path = $this->options->destinationDir . "wp-config.php";
692
-
693
- $this->log( "Preparing Data Step12: Updating WP_ALLOW_MULTISITE in wp-config.php to false" );
694
-
695
- if( false === ($content = file_get_contents( $path )) ) {
696
- $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
697
- return false;
698
- }
699
-
700
-
701
- // Get WP_SITEURL from wp-config.php
702
- preg_match( "/define\s*\(\s*'WP_ALLOW_MULTISITE'\s*,\s*(.*)\s*\);/", $content, $matches );
703
-
704
- if( !empty( $matches[1] ) ) {
705
- $matches[1];
706
-
707
- $pattern = "/define\s*\(\s*'WP_ALLOW_MULTISITE'\s*,\s*(.*)\s*\);/";
708
-
709
- $replace = "define('WP_ALLOW_MULTISITE',false); // " . $matches[1];
710
- $replace.= " // Changed by WP-Staging";
711
-
712
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
713
- $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE", Logger::TYPE_ERROR );
714
- return false;
715
- }
716
- } else {
717
- $this->log( "Preparing Data Step12: WP_ALLOW_MULTISITE not defined in wp-config.php. Skipping this step." );
718
- }
719
-
720
-
721
- if( false === @file_put_contents( $path, $content ) ) {
722
- $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE. Can't save contents", Logger::TYPE_ERROR );
723
- return false;
724
- }
725
- $this->Log( "Preparing Data: Finished Step 12 successfully" );
726
- return true;
727
- }
728
-
729
- /**
730
- * Update MULTISITE constant in wp-config.php
731
- * @return bool
732
- */
733
- protected function step13() {
734
- $path = $this->options->destinationDir . "wp-config.php";
735
-
736
- $this->log( "Preparing Data Step13: Updating MULTISITE in wp-config.php to false" );
737
-
738
- if( false === ($content = file_get_contents( $path )) ) {
739
- $this->log( "Preparing Data Step13: Failed to update MULTISITE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
740
- return false;
741
- }
742
-
743
-
744
- // Get WP_SITEURL from wp-config.php
745
- preg_match( "/define\s*\(\s*'MULTISITE'\s*,\s*(.*)\s*\);/", $content, $matches );
746
-
747
- if( !empty( $matches[1] ) ) {
748
- $matches[1];
749
-
750
- $pattern = "/define\s*\(\s*'MULTISITE'\s*,\s*(.*)\s*\);/";
751
-
752
- $replace = "define('MULTISITE',false); // " . $matches[1];
753
- $replace.= " // Changed by WP-Staging";
754
-
755
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
756
- $this->log( "Preparing Data Step13: Failed to update MULTISITE", Logger::TYPE_ERROR );
757
- return false;
758
- }
759
- } else {
760
- $this->log( "Preparing Data Step13: MULTISITE not defined in wp-config.php. Skipping this step." );
761
- }
762
-
763
-
764
- if( false === @file_put_contents( $path, $content ) ) {
765
- $this->log( "Preparing Data Step13: Failed to update MULTISITE. Can't save contents", Logger::TYPE_ERROR );
766
- return false;
767
- }
768
- $this->Log( "Preparing Data: Finished Step 13 successfully" );
769
- return true;
770
- }
771
-
772
- /**
773
- * Get active_sitewide_plugins from wp_sitemeta and active_plugins from subsite
774
- * Merge both arrays and copy them to the staging site into active_plugins
775
- */
776
- protected function step14() {
777
-
778
-
779
- $this->log( "Data Crunching Step 14: Updating active_plugins" );
780
-
781
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
782
- $this->log( 'Data Crunching Step 14: Fatal Error ' . $this->prefix . 'options does not exist' );
783
- $this->returnException( 'Data Crunching Step 14: Fatal Error ' . $this->prefix . 'options does not exist' );
784
- return false;
785
- }
786
-
787
- // Skip - Table is not selected or updated
788
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
789
- $this->log( "Preparing Data Step14: Skipping" );
790
- return true;
791
- }
792
-
793
- // Get active_plugins value from sub site options table
794
- $active_plugins = $this->productionDb->get_var( "SELECT option_value FROM {$this->productionDb->prefix}options WHERE option_name = 'active_plugins' " );
795
-
796
- if( !$active_plugins ) {
797
- $this->log( "Data Crunching Step 14: Option active_plugins are empty " );
798
- $active_plugins = array();
799
- }
800
- // Get active_sitewide_plugins value from main multisite wp_sitemeta table
801
- $active_sitewide_plugins = $this->productionDb->get_var( "SELECT meta_value FROM {$this->productionDb->base_prefix}sitemeta WHERE meta_key = 'active_sitewide_plugins' " );
802
-
803
- if( !$active_sitewide_plugins ) {
804
- $this->log( "Data Crunching Step 14: Options {$this->productionDb->base_prefix}active_sitewide_plugins is empty " );
805
- $active_sitewide_plugins = array();
806
- }
807
-
808
- $active_sitewide_plugins = unserialize( $active_sitewide_plugins );
809
- $active_plugins = unserialize( $active_plugins );
810
-
811
- $all_plugins = array_merge( $active_plugins, array_keys( $active_sitewide_plugins ) );
812
-
813
- sort( $all_plugins );
814
-
815
-
816
- // Update active_plugins
817
- $update = $this->db->query(
818
- "UPDATE {$this->prefix}options SET option_value = '" . serialize( $all_plugins ) . "' WHERE option_name = 'active_plugins'"
819
- );
820
-
821
- if( false === $update ) {
822
- $this->log( "Data Crunching Step 14: Can not update option active_plugins in {$this->prefix}options", Logger::TYPE_WARNING );
823
- return false;
824
- }
825
-
826
- $this->log( "Data Crunching Step 14: Successful!" );
827
- return true;
828
- }
829
-
830
- /**
831
- * Update Table Prefix in wp_options
832
- * @return bool
833
- */
834
- protected function step15() {
835
- $this->log( "Preparing Data Step15: Updating db prefix in {$this->prefix}options." );
836
-
837
- // Skip - Table does not exist
838
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
839
- return true;
840
- }
841
-
842
- // Skip - Table is not selected or updated
843
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
844
- $this->log( "Preparing Data Step4: Skipping" );
845
- return true;
846
- }
847
-
848
- // Skip, prefixes are identical. No change needed
849
- // if( $this->productionDb->prefix === $this->prefix ) {
850
- // $this->log( "Preparing Data Step4: Skipping" );
851
- // return true;
852
- // }
853
-
854
- $this->log( "Updating db option_names in {$this->prefix}options. " );
855
-
856
- // Filter the rows below. Do not update them!
857
- $filters = array(
858
- 'wp_mail_smtp',
859
- 'wp_mail_smtp_version',
860
- 'wp_mail_smtp_debug',
861
- );
862
-
863
- $filters = apply_filters( 'wpstg_data_excl_rows', $filters );
864
-
865
- $where = "";
866
- foreach ( $filters as $filter ) {
867
- $where .= " AND option_name <> '" . $filter . "'";
868
- }
869
-
870
- $updateOptions = $this->db->query(
871
- $this->db->prepare(
872
- "UPDATE IGNORE {$this->prefix}options SET option_name= replace(option_name, %s, %s) WHERE option_name LIKE %s" . $where, $this->productionDb->prefix, $this->prefix, $this->productionDb->prefix . "_%"
873
- )
874
- );
875
-
876
- if( !$updateOptions ) {
877
- $this->log( "Preparing Data Step15: Skip updating db option_names in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_WARNING );
878
- //$this->returnException( "Data Crunching Step 15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
879
- return true;
880
- }
881
-
882
-
883
- return true;
884
- }
885
-
886
- /**
887
- * Change upload_path in wp_options (if it is defined)
888
- * @return bool
889
- */
890
- protected function step16() {
891
- $this->log( "Preparing Data Step16: Updating upload_path {$this->prefix}options." );
892
-
893
- // Skip - Table does not exist
894
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
895
- return true;
896
- }
897
-
898
- $newUploadPath = $this->getNewUploadPath();
899
-
900
- if( false === $newUploadPath ) {
901
- $this->log( "Preparing Data Step16: Skipping" );
902
- return true;
903
- }
904
-
905
- // Skip - Table is not selected or updated
906
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
907
- $this->log( "Preparing Data Step16: Skipping" );
908
- return true;
909
- }
910
-
911
- $error = isset( $this->db->last_error ) ? 'Last error: ' . $this->db->last_error : '';
912
-
913
- $this->log( "Updating upload_path in {$this->prefix}options. {$error}" );
914
-
915
- $updateOptions = $this->db->query(
916
- $this->db->prepare(
917
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'upload_path'", $newUploadPath
918
- )
919
- );
920
-
921
- if( !$updateOptions ) {
922
- $this->log( "Preparing Data Step16: Failed to update upload_path in {$this->prefix}options. {$error}", Logger::TYPE_ERROR );
923
- return true;
924
- }
925
- $this->Log( "Preparing Data: Finished Step 16 successfully" );
926
- return true;
927
- }
928
-
929
- /**
930
- * Update WP_CACHE in wp-config.php
931
- * @return bool
932
- */
933
- protected function step17() {
934
- $path = $this->options->destinationDir . "wp-config.php";
935
-
936
- $this->log( "Preparing Data Step17: Set WP_CACHE in wp-config.php to false" );
937
-
938
- if( false === ($content = file_get_contents( $path )) ) {
939
- $this->log( "Preparing Data Step17: Failed to update WP_CACHE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
940
- return false;
941
- }
942
-
943
-
944
- // Get WP_CACHE from wp-config.php
945
- preg_match( "/define\s*\(\s*'WP_CACHE'\s*,\s*(.*)\s*\);/", $content, $matches );
946
-
947
- if( !empty( $matches[1] ) ) {
948
- $matches[1];
949
-
950
- $pattern = "/define\s*\(\s*'WP_CACHE'\s*,\s*(.*)\s*\);/";
951
-
952
- $replace = "define('WP_CACHE',false); // " . $matches[1];
953
- $replace.= " // Changed by WP-Staging";
954
-
955
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
956
- $this->log( "Preparing Data: Failed to change WP_CACHE", Logger::TYPE_ERROR );
957
- return false;
958
- }
959
- } else {
960
- $this->log( "Preparing Data Step17: WP_CACHE not defined in wp-config.php. Skipping this step." );
961
- }
962
-
963
- if( false === @file_put_contents( $path, $content ) ) {
964
- $this->log( "Preparing Data Step17: Failed to update WP_CACHE. Can't save contents", Logger::TYPE_ERROR );
965
- return false;
966
- }
967
- $this->Log( "Preparing Data: Finished Step 17 successfully" );
968
- return true;
969
- }
970
-
971
- /**
972
- * Update database credentials in wp-config.php
973
- * @return bool
974
- */
975
- protected function step18() {
976
- $path = $this->options->destinationDir . "wp-config.php";
977
-
978
- $this->log( "Preparing Data Step18: Change database credentials in wp-config.php" );
979
-
980
- if( false === ($content = file_get_contents( $path )) ) {
981
- $this->log( "Preparing Data Step18: Failed to update database credentials in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
982
- return false;
983
- }
984
-
985
-
986
- // Get DB_NAME from wp-config.php
987
- preg_match( "/define\s*\(\s*'DB_NAME'\s*,\s*(.*)\s*\);/", $content, $matches );
988
-
989
- if( !empty( $matches[1] ) ) {
990
- $matches[1];
991
-
992
- $pattern = "/define\s*\(\s*'DB_NAME'\s*,\s*(.*)\s*\);/";
993
-
994
- $replace = "define('DB_NAME','{$this->options->databaseDatabase}'); // " . $matches[1];
995
- $replace.= " // Changed by WP-Staging";
996
-
997
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
998
- $this->log( "Preparing Data: Failed to change DB_NAME", Logger::TYPE_ERROR );
999
- return false;
1000
- }
1001
- } else {
1002
- $this->log( "Preparing Data Step18: DB_NAME not defined in wp-config.php. Skipping this step." );
1003
- }
1004
- // Get DB_USER from wp-config.php
1005
- preg_match( "/define\s*\(\s*'DB_USER'\s*,\s*(.*)\s*\);/", $content, $matches );
1006
-
1007
- if( !empty( $matches[1] ) ) {
1008
- $matches[1];
1009
-
1010
- $pattern = "/define\s*\(\s*'DB_USER'\s*,\s*(.*)\s*\);/";
1011
-
1012
- $replace = "define('DB_USER','{$this->options->databaseUser}'); // " . $matches[1];
1013
- $replace.= " // Changed by WP-Staging";
1014
-
1015
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1016
- $this->log( "Preparing Data: Failed to change DB_USER", Logger::TYPE_ERROR );
1017
- return false;
1018
- }
1019
- } else {
1020
- $this->log( "Preparing Data Step18: DB_USER not defined in wp-config.php. Skipping this step." );
1021
- }
1022
- // Get DB_PASSWORD from wp-config.php
1023
- preg_match( "/define\s*\(\s*'DB_PASSWORD'\s*,\s*(.*)\s*\);/", $content, $matches );
1024
-
1025
- if( !empty( $matches[1] ) ) {
1026
- $matches[1];
1027
-
1028
- $pattern = "/define\s*\(\s*'DB_PASSWORD'\s*,\s*(.*)\s*\);/";
1029
-
1030
- $replace = "define('DB_PASSWORD','{$this->options->databasePassword}'); // " . $matches[1];
1031
- $replace.= " // Changed by WP-Staging";
1032
-
1033
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1034
- $this->log( "Preparing Data: Failed to change DB_PASSWORD", Logger::TYPE_ERROR );
1035
- return false;
1036
- }
1037
- } else {
1038
- $this->log( "Preparing Data Step18: DB_PASSWORD not defined in wp-config.php. Skipping this step." );
1039
- }
1040
- // Get DB_HOST from wp-config.php
1041
- preg_match( "/define\s*\(\s*'DB_HOST'\s*,\s*(.*)\s*\);/", $content, $matches );
1042
-
1043
- if( !empty( $matches[1] ) ) {
1044
- $matches[1];
1045
-
1046
- $pattern = "/define\s*\(\s*'DB_HOST'\s*,\s*(.*)\s*\);/";
1047
-
1048
- $replace = "define('DB_HOST','{$this->options->databaseServer}'); // " . $matches[1];
1049
- $replace.= " // Changed by WP-Staging";
1050
-
1051
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1052
- $this->log( "Preparing Data: Failed to change DB_HOST", Logger::TYPE_ERROR );
1053
- return false;
1054
- }
1055
- } else {
1056
- $this->log( "Preparing Data Step18: DB_HOST not defined in wp-config.php. Skipping this step." );
1057
- }
1058
-
1059
-
1060
- if( false === @file_put_contents( $path, $content ) ) {
1061
- $this->log( "Preparing Data Step18: Failed to update database credentials in wp-config.php. Can't save contents", Logger::TYPE_ERROR );
1062
- return false;
1063
- }
1064
- $this->Log( "Preparing Data: Finished Step 18 successfully" );
1065
- return true;
1066
- }
1067
-
1068
- /**
1069
- * Get upload path
1070
- * @return boolean|string
1071
- */
1072
- protected function getNewUploadPath() {
1073
- $uploadPath = get_option( 'upload_path' );
1074
-
1075
- if( !$uploadPath ) {
1076
- return false;
1077
- }
1078
-
1079
- $customSlug = str_replace( \WPStaging\WPStaging::getWPpath(), '', $uploadPath );
1080
-
1081
- $newUploadPath = \WPStaging\WPStaging::getWPpath() . $this->options->cloneDirectoryName . DIRECTORY_SEPARATOR . $customSlug;
1082
-
1083
- return $newUploadPath;
1084
- }
1085
-
1086
- /**
1087
- * Return URL to staging site
1088
- * @return string
1089
- */
1090
- protected function getStagingSiteUrl() {
1091
-
1092
- if( !empty( $this->options->cloneHostname ) ) {
1093
- return $this->options->cloneHostname;
1094
- }
1095
-
1096
- if( $this->isSubDir() ) {
1097
- return trailingslashit( $this->multisiteHomeDomain) . trailingslashit( $this->getSubDir() ) . $this->options->cloneDirectoryName;
1098
- }
1099
-
1100
- return trailingslashit( $this->multisiteHomeDomain) . $this->options->cloneDirectoryName;
1101
- }
1102
-
1103
- /**
1104
- * Check if WP is installed in subdir
1105
- * @return boolean
1106
- */
1107
- protected function isSubDir() {
1108
- // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
1109
- // This is happening much more often than you would expect
1110
- $siteurl = preg_replace( '#^https?://#', '', rtrim( get_option( 'siteurl' ), '/' ) );
1111
- $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
1112
-
1113
- if( $home !== $siteurl ) {
1114
- return true;
1115
- }
1116
- return false;
1117
- }
1118
-
1119
- /**
1120
- * Get the install sub directory if WP is installed in sub directory
1121
- * @return string
1122
- */
1123
- protected function getSubDir() {
1124
- $home = get_option( 'home' );
1125
- $siteurl = get_option( 'siteurl' );
1126
-
1127
- if( empty( $home ) || empty( $siteurl ) ) {
1128
- return '';
1129
- }
1130
-
1131
- $dir = str_replace( $home, '', $siteurl );
1132
- return str_replace( '/', '', $dir );
1133
- }
1134
-
1135
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 = $this->getStagingDB();
44
+ $this->productionDb = WPStaging::getInstance()->get( "wpdb" );
45
+ $this->prefix = $this->options->prefix;
46
+ $this->db->prefix = $this->options->databasePrefix;
47
+
48
+ $this->getTables();
49
+
50
+ // Fix current step
51
+ if( 0 == $this->options->currentStep ) {
52
+ $this->options->currentStep = 0;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Get database object to interact with
58
+ */
59
+ private function getStagingDB() {
60
+ return new \wpdb( $this->options->databaseUser, str_replace( "\\\\", "\\", $this->options->databasePassword ), $this->options->databaseDatabase, $this->options->databaseServer );
61
+ }
62
+
63
+ /**
64
+ * Get a list of tables to copy
65
+ */
66
+ private function getTables() {
67
+ $strings = new Strings();
68
+ $this->tables = array();
69
+ foreach ( $this->options->tables as $table ) {
70
+ $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->productionDb->prefix, null, $table );
71
+ }
72
+ // Add extra global tables from main multisite (wpstgx_users and wpstgx_usermeta)
73
+ $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->productionDb->prefix, null, 'users' );
74
+ $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->productionDb->prefix, null, 'usermeta' );
75
+ }
76
+
77
+ /**
78
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
79
+ * @return void
80
+ */
81
+ protected function calculateTotalSteps() {
82
+ $this->options->totalSteps = 22;
83
+ }
84
+
85
+ /**
86
+ * Start Module
87
+ * @return object
88
+ */
89
+ public function start() {
90
+ // Execute steps
91
+ $this->run();
92
+
93
+ // Save option, progress
94
+ $this->saveOptions();
95
+
96
+ return ( object ) $this->response;
97
+ }
98
+
99
+ /**
100
+ * Execute the Current Step
101
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
102
+ * @return bool
103
+ */
104
+ protected function execute() {
105
+ // Fatal error. Let this happen never and break here immediately
106
+ if( $this->isRoot() ) {
107
+ return false;
108
+ }
109
+
110
+ // Over limits threshold
111
+ if( $this->isOverThreshold() ) {
112
+ // Prepare response and save current progress
113
+ $this->prepareResponse( false, false );
114
+ $this->saveOptions();
115
+ return false;
116
+ }
117
+
118
+ // No more steps, finished
119
+ if( $this->isFinished() ) {
120
+ $this->prepareResponse( true, false );
121
+ return false;
122
+ }
123
+
124
+ // Execute step
125
+ $stepMethodName = "step" . $this->options->currentStep;
126
+ if( !$this->{$stepMethodName}() ) {
127
+ $this->prepareResponse( false, false );
128
+ return false;
129
+ }
130
+
131
+ // Prepare Response
132
+ $this->prepareResponse();
133
+
134
+ // Not finished
135
+ return true;
136
+ }
137
+
138
+ /**
139
+ * Checks Whether There is Any Job to Execute or Not
140
+ * @return bool
141
+ */
142
+ protected function isFinished() {
143
+ return (
144
+ !isset( $this->options->isRunning ) ||
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 domain === Staging domain
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 exist", Logger::TYPE_ERROR );
183
+ return false;
184
+ }
185
+ return true;
186
+ }
187
+
188
+ /**
189
+ * Copy wp-config.php from the staging site if it is located outside of root one level up or
190
+ * copy default wp-config.php if production site uses bedrock or any other boilerplate solution that stores wp default config data elsewhere.
191
+ * @return boolean
192
+ */
193
+ protected function step0() {
194
+ $this->log( "Preparing Data Step0: Copy wp-config.php file", Logger::TYPE_INFO );
195
+
196
+ $dir = trailingslashit( dirname( ABSPATH ) );
197
+
198
+ $source = $dir . 'wp-config.php';
199
+
200
+ $destination = $this->options->destinationDir . 'wp-config.php';
201
+
202
+ // Check if there is already a valid wp-config.php in root of staging site
203
+ if( $this->isValidWpConfig( $destination ) ) {
204
+ $this->log( "Preparing Data Step0: Found wp-config.php file in folder {$destination}", Logger::TYPE_INFO );
205
+ return true;
206
+ }
207
+
208
+ // Check if there is a valid wp-config.php outside root of wp production site
209
+ if( $this->isValidWpConfig( $source ) ) {
210
+ // Copy it to staging site
211
+ if( $this->copy( $source, $destination ) ) {
212
+ $this->log( "Preparing Data Step0: Copy wp-config.php file from source {$source} to {$destination}", Logger::TYPE_INFO );
213
+ return true;
214
+ }
215
+ }
216
+
217
+ // No valid wp-config.php found so let's copy wp stagings default wp-config.php to staging site
218
+ $source = WPSTG_PLUGIN_DIR . "apps/Backend/helpers/wp-config.php";
219
+
220
+ $this->log( "Preparing Data Step0: Copy default wp-config.php file from source {$source} to {$destination}", Logger::TYPE_INFO );
221
+
222
+ if( $this->copy( $source, $destination ) ) {
223
+ // add missing db credentials to wp-config.php
224
+ if( !$this->alterWpConfig( $destination ) ) {
225
+ $this->log( "Preparing Data Step0: Can not alter db credentials in wp-config.php", Logger::TYPE_INFO );
226
+ return false;
227
+ }
228
+ }
229
+
230
+ $this->log( "Preparing Data Step0: Successful", Logger::TYPE_INFO );
231
+ return true;
232
+ }
233
+
234
+ /**
235
+ * Copy files with symlink support
236
+ * @param type $source
237
+ * @param type $destination
238
+ * @return boolean
239
+ */
240
+ protected function copy( $source, $destination ) {
241
+ // Copy symbolic link
242
+ if( is_link( $source ) ) {
243
+ $this->log( "Preparing Data: Symbolic link found...", Logger::TYPE_INFO );
244
+ if( !@copy( readlink( $source ), $destination ) ) {
245
+ $errors = error_get_last();
246
+ $this->log( "Preparing Data: Failed to copy {$source} Error: {$errors['message']} {$source} -> {$destination}", Logger::TYPE_ERROR );
247
+ return false;
248
+ }
249
+ }
250
+
251
+ // Copy file
252
+ if( !@copy( $source, $destination ) ) {
253
+ $errors = error_get_last();
254
+ $this->log( "Preparing Data Step0: Failed to copy {$source}! Error: {$errors['message']} {$source} -> {$destination}", Logger::TYPE_ERROR );
255
+ return false;
256
+ }
257
+
258
+ return true;
259
+ }
260
+
261
+ /**
262
+ * Make sure wp-config.php contains correct db credentials
263
+ * @param type $source
264
+ * @return boolean
265
+ */
266
+ protected function alterWpConfig( $source ) {
267
+ $this->log( "Preparing Data: Alter wp-config.php", Logger::TYPE_INFO );
268
+
269
+ $content = file_get_contents( $source );
270
+
271
+ if( false === ($content = file_get_contents( $source )) ) {
272
+ return false;
273
+ }
274
+
275
+ $search = "// ** MySQL settings ** //";
276
+
277
+ $replace = "// ** MySQL settings ** //\r\n
278
+ define( 'DB_NAME', '" . DB_NAME . "' );\r\n
279
+ /** MySQL database username */\r\n
280
+ define( 'DB_USER', '" . DB_USER . "' );\r\n
281
+ /** MySQL database password */\r\n
282
+ define( 'DB_PASSWORD', '" . DB_PASSWORD . "' );\r\n
283
+ /** MySQL hostname */\r\n
284
+ define( 'DB_HOST', '" . DB_HOST . "' );\r\n
285
+ /** Database Charset to use in creating database tables. */\r\n
286
+ define( 'DB_CHARSET', '" . DB_CHARSET . "' );\r\n
287
+ /** The Database Collate type. Don't change this if in doubt. */\r\n
288
+ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
289
+
290
+ $content = str_replace( $search, $replace, $content );
291
+
292
+ if( false === @wpstg_put_contents( $source, $content ) ) {
293
+ $this->log( "Preparing Data: Can't save wp-config.php", Logger::TYPE_ERROR );
294
+ return false;
295
+ }
296
+
297
+ return true;
298
+ }
299
+
300
+ /**
301
+ * Check if wp-config.php contains important constants
302
+ * @param type $source
303
+ * @return boolean
304
+ */
305
+ protected function isValidWpConfig( $source ) {
306
+
307
+ if( !is_file( $source ) && !is_link( $source ) ) {
308
+ $this->log( "Preparing Data Step0: Can not find {$source}", Logger::TYPE_INFO );
309
+ return false;
310
+ }
311
+
312
+ $content = file_get_contents( $source );
313
+
314
+ if( false === ($content = file_get_contents( $source )) ) {
315
+ $this->log( "Preparing Data Step0: Can not read {$source}", Logger::TYPE_INFO );
316
+ return false;
317
+ }
318
+
319
+ // Get DB_NAME from wp-config.php
320
+ preg_match( "/define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
321
+
322
+ if( empty( $matches[1] ) ) {
323
+ $this->log( "Preparing Data Step0: Can not find DB_NAME in wp-config.php", Logger::TYPE_INFO );
324
+ return false;
325
+ }
326
+
327
+ // Get DB_USER from wp-config.php
328
+ preg_match( "/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
329
+
330
+ if( empty( $matches[1] ) ) {
331
+ $this->log( "Preparing Data Step0: Can not find DB_USER in wp-config.php", Logger::TYPE_INFO );
332
+ return false;
333
+ }
334
+
335
+ // Get DB_PASSWORD from wp-config.php
336
+ preg_match( "/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
337
+
338
+ if( empty( $matches[1] ) ) {
339
+ $this->log( "Preparing Data Step0: Can not find DB_PASSWORD in wp-config.php", Logger::TYPE_INFO );
340
+ return false;
341
+ }
342
+
343
+ // Get DB_HOST from wp-config.php
344
+ preg_match( "/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
345
+
346
+ if( empty( $matches[1] ) ) {
347
+ $this->log( "Preparing Data Step0: Can not find DB_HOST in wp-config.php", Logger::TYPE_INFO );
348
+ return false;
349
+ }
350
+ return true;
351
+ }
352
+
353
+ /**
354
+ * Replace "siteurl" and "home"
355
+ * @return bool
356
+ */
357
+ protected function step1() {
358
+ $this->log( "Preparing Data Step1: Updating siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_INFO );
359
+
360
+ // Skip - Table does not exist
361
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
362
+ return true;
363
+ }
364
+ // Skip - Table is not selected or updated
365
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
366
+ $this->log( "Preparing Data Step1: Skipping" );
367
+ return true;
368
+ }
369
+
370
+ $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . $this->getStagingSiteUrl() );
371
+ // Replace URLs
372
+ $result = $this->db->query(
373
+ $this->db->prepare(
374
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", $this->getStagingSiteUrl()
375
+ )
376
+ );
377
+
378
+
379
+
380
+ // All good
381
+ if( $result ) {
382
+ return true;
383
+ }
384
+
385
+ $this->log( "Preparing Data Step1: Skip updating siteurl and homeurl in {$this->prefix}options. Probably already did! {$this->db->last_error}", Logger::TYPE_WARNING );
386
+ return true;
387
+ }
388
+
389
+ /**
390
+ * Update "wpstg_is_staging_site"
391
+ * @return bool
392
+ */
393
+ protected function step2() {
394
+
395
+ $this->log( "Preparing Data Step2: Updating row wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}" );
396
+
397
+ // Skip - Table does not exist
398
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
399
+ $this->log( "Preparing Data Step2: Skipping" );
400
+ return true;
401
+ }
402
+ // Skip - Table is not selected or updated
403
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
404
+ $this->log( "Preparing Data Step2: Skipping" );
405
+ return true;
406
+ }
407
+
408
+ $result = $this->db->query(
409
+ $this->db->prepare(
410
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_is_staging_site'", "true"
411
+ )
412
+ );
413
+
414
+ // No errors but no option name such as wpstg_is_staging_site
415
+ if( '' === $this->db->last_error && 0 == $result ) {
416
+ $result = $this->db->query(
417
+ $this->db->prepare(
418
+ "INSERT INTO {$this->prefix}options (option_name,option_value) VALUES ('wpstg_is_staging_site',%s)", "true"
419
+ )
420
+ );
421
+ }
422
+
423
+ // All good
424
+ if( $result ) {
425
+ return true;
426
+ }
427
+
428
+ $this->log( "Preparing Data Step2: Failed to update wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
429
+ return false;
430
+ }
431
+
432
+ /**
433
+ * Update rewrite_rules
434
+ * @return bool
435
+ */
436
+ protected function step3() {
437
+
438
+ $this->log( "Preparing Data Step3: Updating rewrite_rules in {$this->prefix}options {$this->db->last_error}" );
439
+
440
+ // Keep Permalinks
441
+ if( isset( $this->settings->keepPermalinks ) && $this->settings->keepPermalinks === "1" ) {
442
+ $this->log( "Preparing Data Step3: Skipping" );
443
+ return true;
444
+ }
445
+
446
+ // Skip - Table does not exist
447
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
448
+ return true;
449
+ }
450
+
451
+ // Skip - Table is not selected or updated
452
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
453
+ $this->log( "Preparing Data Step3: Skipping" );
454
+ return true;
455
+ }
456
+
457
+ $result = $this->db->query(
458
+ $this->db->prepare(
459
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'rewrite_rules'", ' '
460
+ )
461
+ );
462
+
463
+ // All good
464
+ if( $result ) {
465
+ return true;
466
+ }
467
+
468
+ //$this->log( "Preparing Data Step3: Failed to update rewrite_rules in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
469
+ return true;
470
+ }
471
+
472
+ /**
473
+ * Update Table Prefix in wp_usermeta
474
+ * @return bool
475
+ */
476
+ protected function step4() {
477
+ $this->log( "Preparing Data Step4: Updating db prefix in {$this->prefix}usermeta. " );
478
+
479
+ // Skip - Table does not exist
480
+ if( false === $this->isTable( $this->prefix . 'usermeta' ) ) {
481
+ return true;
482
+ }
483
+
484
+ // Skip - Table is not selected or updated
485
+ if( !in_array( $this->prefix . 'usermeta', $this->tables ) ) {
486
+ $this->log( "Preparing Data Step4: Skipping" );
487
+ return true;
488
+ }
489
+
490
+ // Change the base table prefix of the main network site
491
+ $this->debugLog( "Preparing Data Step4: UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, {$this->productionDb->base_prefix}, {$this->prefix}) WHERE meta_key LIKE {$this->productionDb->base_prefix}_%" );
492
+ $update = $this->db->query(
493
+ $this->db->prepare(
494
+ "UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, %s, %s) WHERE meta_key LIKE %s", $this->productionDb->base_prefix, $this->prefix, $this->productionDb->base_prefix . "_%"
495
+ )
496
+ );
497
+
498
+ if( false === $update ) {
499
+ $this->log( "Preparing Data Step4: Skip updating {$this->prefix}usermeta meta_key database base_prefix; {$this->db->last_error}", Logger::TYPE_INFO );
500
+ }
501
+
502
+ // Change the table prefix of the child network site
503
+ $this->debugLog( "Preparing Data Step4: UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, {$this->db->prefix}, {$this->prefix}) WHERE meta_key LIKE {$this->db->prefix}_%" );
504
+ $update = $this->db->query(
505
+ $this->db->prepare(
506
+ "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 . "_%"
507
+ )
508
+ );
509
+
510
+ if( false === $update ) {
511
+ $this->log( "Preparing Data Step4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}", Logger::TYPE_ERROR );
512
+ $this->returnException( "Data Crunching Step 4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}" );
513
+ return false;
514
+ }
515
+ return true;
516
+ }
517
+
518
+ /**
519
+ * Update $table_prefix in wp-config.php
520
+ * @return bool
521
+ */
522
+ protected function step5() {
523
+ $path = $this->options->destinationDir . "wp-config.php";
524
+
525
+ $this->log( "Preparing Data Step5: Updating table_prefix in {$path} to " . $this->prefix );
526
+ if( false === ($content = file_get_contents( $path )) ) {
527
+ $this->log( "Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR );
528
+ return false;
529
+ }
530
+
531
+ // Replace table prefix
532
+ $pattern = '/\$table_prefix\s*=\s*(.*).*/';
533
+ $replacement = '$table_prefix = \'' . $this->prefix . '\'; // Changed by WP Staging';
534
+ $content = preg_replace( $pattern, $replacement, $content );
535
+
536
+ if( null === $content ) {
537
+ $this->log( "Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR );
538
+ return false;
539
+ }
540
+
541
+ // Replace URLs
542
+ $content = str_replace( $this->multisiteHomeDomain, $this->getStagingSiteUrl(), $content );
543
+
544
+ if( false === @wpstg_put_contents( $path, $content ) ) {
545
+ $this->log( "Preparing Data Step5: Failed to update $table_prefix in {$path} to " . $this->prefix . ". Can't save contents", Logger::TYPE_ERROR );
546
+ return false;
547
+ }
548
+
549
+ return true;
550
+ }
551
+
552
+ /**
553
+ * Reset index.php to original file
554
+ * This is needed if live site is located in subfolder
555
+ * Check first if main wordpress is used in subfolder and index.php in parent directory
556
+ * @see: https://codex.wordpress.org/Giving_WordPress_Its_Own_Directory
557
+ * @return bool
558
+ */
559
+ protected function step6() {
560
+
561
+ if( !$this->isSubDir() ) {
562
+ $this->debugLog( "Preparing Data Step6: WP installation is not in a subdirectory! All good, skipping this step" );
563
+ return true;
564
+ }
565
+
566
+ $path = $this->options->destinationDir . "index.php";
567
+
568
+ if( false === ($content = file_get_contents( $path )) ) {
569
+ $this->log( "Preparing Data Step6: Failed to reset {$path} for sub directory; can't read contents", Logger::TYPE_ERROR );
570
+ return false;
571
+ }
572
+
573
+
574
+ if( !preg_match( "/(require(.*)wp-blog-header.php' \);)/", $content, $matches ) ) {
575
+ $this->log(
576
+ "Preparing Data Step6: Failed to reset index.php for sub directory; wp-blog-header.php is missing", Logger::TYPE_ERROR
577
+ );
578
+ return false;
579
+ }
580
+ $this->log( "Preparing Data: WP installation is in a subdirectory. Progressing..." );
581
+
582
+ $pattern = "/require(.*) dirname(.*) __FILE__ (.*) \. '(.*)wp-blog-header.php'(.*);.*/";
583
+
584
+ $replace = "require( dirname( __FILE__ ) . '/wp-blog-header.php' ); // " . $matches[0];
585
+ //$replace.= " // Changed by WP-Staging";
586
+
587
+
588
+
589
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
590
+ $this->log( "Preparing Data: Failed to reset index.php for sub directory; replacement failed", Logger::TYPE_ERROR );
591
+ return false;
592
+ }
593
+
594
+ if( false === @wpstg_put_contents( $path, $content ) ) {
595
+ $this->log( "Preparing Data: Failed to reset index.php for sub directory; can't save contents", Logger::TYPE_ERROR );
596
+ return false;
597
+ }
598
+ $this->Log( "Preparing Data Step6: Finished successfully" );
599
+ return true;
600
+ }
601
+
602
+ /**
603
+ * Update wpstg_rmpermalinks_executed
604
+ * @return bool
605
+ */
606
+ protected function step7() {
607
+
608
+ $this->log( "Preparing Data Step7: Updating wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}" );
609
+
610
+ // Skip - Table does not exist
611
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
612
+ return true;
613
+ }
614
+
615
+ // Skip - Table is not selected or updated
616
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
617
+ $this->log( "Preparing Data Step7: Skipping" );
618
+ return true;
619
+ }
620
+
621
+ $result = $this->db->query(
622
+ $this->db->prepare(
623
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_rmpermalinks_executed'", ' '
624
+ )
625
+ );
626
+
627
+ $this->Log( "Preparing Data Step7: Finished successfully" );
628
+ return true;
629
+ }
630
+
631
+ /**
632
+ * Update permalink_structure
633
+ * @return bool
634
+ */
635
+ protected function step8() {
636
+
637
+ $this->log( "Preparing Data Step8: Updating permalink_structure in {$this->prefix}options {$this->db->last_error}" );
638
+
639
+ // Keep Permalinks
640
+ if( isset( $this->settings->keepPermalinks ) && $this->settings->keepPermalinks === "1" ) {
641
+ $this->log( "Preparing Data Step8: Skipping" );
642
+ return true;
643
+ }
644
+
645
+ // Skip - Table does not exist
646
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
647
+ return true;
648
+ }
649
+
650
+ // Skip - Table is not selected or updated
651
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
652
+ $this->log( "Preparing Data Step8: Skipping" );
653
+ return true;
654
+ }
655
+
656
+ $result = $this->db->query(
657
+ $this->db->prepare(
658
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'permalink_structure'", ' '
659
+ )
660
+ );
661
+
662
+ // All good
663
+ if( $result ) {
664
+ $this->Log( "Preparing Data Step8: Finished successfully" );
665
+ return true;
666
+ }
667
+
668
+ $this->log( "Failed to update permalink_structure in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
669
+ return true;
670
+ }
671
+
672
+ /**
673
+ * Update blog_public option to not allow staging site to be indexed by search engines
674
+ * @return bool
675
+ */
676
+ protected function step9() {
677
+
678
+ $this->log( "Preparing Data Step9: Set staging site to noindex" );
679
+
680
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
681
+ return true;
682
+ }
683
+
684
+ // Skip - Table is not selected or updated
685
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
686
+ $this->log( "Preparing Data Step9: Skipping" );
687
+ return true;
688
+ }
689
+
690
+ $result = $this->db->query(
691
+ $this->db->prepare(
692
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'blog_public'", '0'
693
+ )
694
+ );
695
+
696
+ // All good
697
+ if( $result ) {
698
+ $this->Log( "Preparing Data Step9: Finished successfully" );
699
+ return true;
700
+ }
701
+
702
+ $this->log( "Can not update staging site to noindex. Possible already done!", Logger::TYPE_WARNING );
703
+ return true;
704
+ }
705
+
706
+ /**
707
+ * Update WP_HOME in wp-config.php
708
+ * @return bool
709
+ */
710
+ protected function step10() {
711
+ $path = $this->options->destinationDir . "wp-config.php";
712
+
713
+ $this->log( "Preparing Data Step10: Updating WP_HOME in wp-config.php to " . $this->getStagingSiteUrl() );
714
+
715
+ if( false === ($content = file_get_contents( $path )) ) {
716
+ $this->log( "Preparing Data Step10: Failed to update WP_HOME in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
717
+ return false;
718
+ }
719
+
720
+
721
+ // Get WP_HOME from wp-config.php
722
+ preg_match( "/define\s*\(\s*['\"]WP_HOME['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
723
+
724
+ if( !empty( $matches[1] ) ) {
725
+ $matches[1];
726
+
727
+ $pattern = "/define\s*\(\s*['\"]WP_HOME['\"]\s*,\s*(.*)\s*\);.*/";
728
+
729
+ $replace = "define('WP_HOME','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
730
+ //$replace .= " // Changed by WP-Staging";
731
+
732
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
733
+ $this->log( "Preparing Data: Failed to update WP_HOME", Logger::TYPE_ERROR );
734
+ return false;
735
+ }
736
+ } else {
737
+ $this->log( "Preparing Data Step10: WP_HOME not defined in wp-config.php. Skipping this step." );
738
+ }
739
+
740
+ if( false === @wpstg_put_contents( $path, $content ) ) {
741
+ $this->log( "Preparing Data Step10: Failed to update WP_HOME. Can't save contents", Logger::TYPE_ERROR );
742
+ return false;
743
+ }
744
+ $this->Log( "Preparing Data Step 10: Finished successfully" );
745
+ return true;
746
+ }
747
+
748
+ /**
749
+ * Update WP_SITEURL in wp-config.php
750
+ * @return bool
751
+ */
752
+ protected function step11() {
753
+ $path = $this->options->destinationDir . "wp-config.php";
754
+
755
+ $this->log( "Preparing Data Step11: Updating WP_SITEURL in wp-config.php to " . $this->getStagingSiteUrl() );
756
+
757
+ if( false === ($content = file_get_contents( $path )) ) {
758
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
759
+ return false;
760
+ }
761
+
762
+
763
+ // Get WP_SITEURL from wp-config.php
764
+ preg_match( "/define\s*\(\s*['\"]WP_SITEURL['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
765
+
766
+ if( !empty( $matches[1] ) ) {
767
+ $matches[1];
768
+
769
+ $pattern = "/define\s*\(\s*['\"]WP_SITEURL['\"]\s*,\s*(.*)\s*\);.*/";
770
+
771
+ $replace = "define('WP_SITEURL','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
772
+ //$replace .= " // Changed by WP-Staging";
773
+
774
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
775
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL", Logger::TYPE_ERROR );
776
+ return false;
777
+ }
778
+ } else {
779
+ $this->log( "Preparing Data Step11: WP_SITEURL not defined in wp-config.php. Skipping this step." );
780
+ }
781
+
782
+
783
+ if( false === @wpstg_put_contents( $path, $content ) ) {
784
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR );
785
+ return false;
786
+ }
787
+ $this->Log( "Preparing Data Step 11: Finished successfully" );
788
+ return true;
789
+ }
790
+
791
+ /**
792
+ * Update WP_ALLOW_MULTISITE constant in wp-config.php
793
+ * @return bool
794
+ */
795
+ protected function step12() {
796
+ $path = $this->options->destinationDir . "wp-config.php";
797
+
798
+ $this->log( "Preparing Data Step12: Updating WP_ALLOW_MULTISITE in wp-config.php to false" );
799
+
800
+ if( false === ($content = file_get_contents( $path )) ) {
801
+ $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
802
+ return false;
803
+ }
804
+
805
+
806
+ // Get WP_SITEURL from wp-config.php
807
+ preg_match( "/define\s*\(\s*['\"]WP_ALLOW_MULTISITE['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
808
+
809
+ if( !empty( $matches[1] ) ) {
810
+ $matches[1];
811
+
812
+ $pattern = "/define\s*\(\s*['\"]WP_ALLOW_MULTISITE['\"]\s*,\s*(.*)\s*\);.*/";
813
+
814
+ $replace = "define('WP_ALLOW_MULTISITE',false); // " . $matches[1];
815
+ //$replace .= " // Changed by WP-Staging";
816
+
817
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
818
+ $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE", Logger::TYPE_ERROR );
819
+ return false;
820
+ }
821
+ } else {
822
+ $this->log( "Preparing Data Step12: WP_ALLOW_MULTISITE not defined in wp-config.php. Skipping this step." );
823
+ }
824
+
825
+
826
+ if( false === @wpstg_put_contents( $path, $content ) ) {
827
+ $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE. Can't save contents", Logger::TYPE_ERROR );
828
+ return false;
829
+ }
830
+ $this->Log( "Preparing Data: Finished Step 12 successfully" );
831
+ return true;
832
+ }
833
+
834
+ /**
835
+ * Update MULTISITE constant in wp-config.php
836
+ * @return bool
837
+ */
838
+ protected function step13() {
839
+ $path = $this->options->destinationDir . "wp-config.php";
840
+
841
+ $this->log( "Preparing Data Step13: Updating MULTISITE in wp-config.php to false" );
842
+
843
+ if( false === ($content = file_get_contents( $path )) ) {
844
+ $this->log( "Preparing Data Step13: Failed to update MULTISITE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
845
+ return false;
846
+ }
847
+
848
+
849
+ // Get WP_SITEURL from wp-config.php
850
+ preg_match( "/define\s*\(\s*['\"]MULTISITE['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
851
+
852
+ if( !empty( $matches[1] ) ) {
853
+ $matches[1];
854
+
855
+ $pattern = "/define\s*\(\s*['\"]MULTISITE['\"]\s*,\s*(.*)\s*\);.*/";
856
+
857
+ $replace = "define('MULTISITE',false); // " . $matches[1];
858
+ //$replace .= " // Changed by WP-Staging";
859
+
860
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
861
+ $this->log( "Preparing Data Step13: Failed to update MULTISITE", Logger::TYPE_ERROR );
862
+ return false;
863
+ }
864
+ } else {
865
+ $this->log( "Preparing Data Step13: MULTISITE not defined in wp-config.php. Skipping this step." );
866
+ }
867
+
868
+
869
+ if( false === @wpstg_put_contents( $path, $content ) ) {
870
+ $this->log( "Preparing Data Step13: Failed to update MULTISITE. Can't save contents", Logger::TYPE_ERROR );
871
+ return false;
872
+ }
873
+ $this->Log( "Preparing Data Step13: Finished successfully" );
874
+ return true;
875
+ }
876
+
877
+ /**
878
+ * Get active_sitewide_plugins from wp_sitemeta and active_plugins from subsite
879
+ * Merge both arrays and copy them to the staging site into active_plugins
880
+ */
881
+ protected function step14() {
882
+
883
+
884
+ $this->log( "Data Crunching Step14: Updating active_plugins" );
885
+
886
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
887
+ $this->log( 'Data Crunching Step14: Fatal Error ' . $this->prefix . 'options does not exist' );
888
+ $this->returnException( 'Data Crunching Step14: Fatal Error ' . $this->prefix . 'options does not exist' );
889
+ return false;
890
+ }
891
+
892
+ // Skip - Table is not selected or updated
893
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
894
+ $this->log( "Preparing Data Step14: Skipping" );
895
+ return true;
896
+ }
897
+
898
+ // Get active_plugins value from sub site options table
899
+ $active_plugins = $this->productionDb->get_var( "SELECT option_value FROM {$this->productionDb->prefix}options WHERE option_name = 'active_plugins' " );
900
+
901
+ if( !$active_plugins ) {
902
+ $this->log( "Data Crunching Step14: Option active_plugins are empty " );
903
+ $active_plugins = array();
904
+ }
905
+ // Get active_sitewide_plugins value from main multisite wp_sitemeta table
906
+ $active_sitewide_plugins = $this->productionDb->get_var( "SELECT meta_value FROM {$this->productionDb->base_prefix}sitemeta WHERE meta_key = 'active_sitewide_plugins' " );
907
+
908
+ if( !$active_sitewide_plugins ) {
909
+ $this->log( "Data Crunching Step14: Options {$this->productionDb->base_prefix}active_sitewide_plugins is empty " );
910
+ $active_sitewide_plugins = array();
911
+ }
912
+
913
+ $active_sitewide_plugins = unserialize( $active_sitewide_plugins );
914
+ $active_plugins = unserialize( $active_plugins );
915
+
916
+ $all_plugins = array_merge( $active_plugins, array_keys( $active_sitewide_plugins ) );
917
+
918
+ sort( $all_plugins );
919
+
920
+
921
+ // Update active_plugins
922
+ $update = $this->db->query(
923
+ "UPDATE {$this->prefix}options SET option_value = '" . serialize( $all_plugins ) . "' WHERE option_name = 'active_plugins'"
924
+ );
925
+
926
+ if( false === $update ) {
927
+ $this->log( "Data Crunching Step14: Can not update option active_plugins in {$this->prefix}options", Logger::TYPE_WARNING );
928
+ return false;
929
+ }
930
+
931
+ $this->log( "Data Crunching Step14: Successful!" );
932
+ return true;
933
+ }
934
+
935
+ /**
936
+ * Update Table Prefix in wp_options
937
+ * @return bool
938
+ */
939
+ protected function step15() {
940
+ $this->log( "Preparing Data Step15: Updating db prefix in {$this->prefix}options." );
941
+
942
+ // Skip - Table does not exist
943
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
944
+ return true;
945
+ }
946
+
947
+ // Skip - Table is not selected or updated
948
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
949
+ $this->log( "Preparing Data Step4: Skipping" );
950
+ return true;
951
+ }
952
+
953
+
954
+ $this->log( "Updating db option_names in {$this->prefix}options. " );
955
+
956
+ // Filter the rows below. Do not update them!
957
+ $filters = array(
958
+ 'wp_mail_smtp',
959
+ 'wp_mail_smtp_version',
960
+ 'wp_mail_smtp_debug',
961
+ );
962
+
963
+ $filters = apply_filters( 'wpstg_data_excl_rows', $filters );
964
+
965
+ $where = "";
966
+ foreach ( $filters as $filter ) {
967
+ $where .= " AND option_name <> '" . $filter . "'";
968
+ }
969
+
970
+ $updateOptions = $this->db->query(
971
+ $this->db->prepare(
972
+ "UPDATE IGNORE {$this->prefix}options SET option_name= replace(option_name, %s, %s) WHERE option_name LIKE %s" . $where, $this->productionDb->prefix, $this->prefix, $this->productionDb->prefix . "_%"
973
+ )
974
+ );
975
+
976
+ if( false === $updateOptions ) {
977
+ $this->log( "Preparing Data Step15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_WARNING );
978
+ //$this->returnException( "Data Crunching Step 15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
979
+ return true;
980
+ }
981
+
982
+
983
+ return true;
984
+ }
985
+
986
+ /**
987
+ * Change upload_path in wp_options (if it is defined)
988
+ * @return bool
989
+ */
990
+ protected function step16() {
991
+ $this->log( "Preparing Data Step16: Updating upload_path {$this->prefix}options." );
992
+
993
+ // Skip - Table does not exist
994
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
995
+ return true;
996
+ }
997
+
998
+ $newUploadPath = $this->getNewUploadPath();
999
+
1000
+ if( false === $newUploadPath ) {
1001
+ $this->log( "Preparing Data Step16: Skipping" );
1002
+ return true;
1003
+ }
1004
+
1005
+ // Skip - Table is not selected or updated
1006
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
1007
+ $this->log( "Preparing Data Step16: Skipping" );
1008
+ return true;
1009
+ }
1010
+
1011
+ $error = isset( $this->db->last_error ) ? 'Last error: ' . $this->db->last_error : '';
1012
+
1013
+ $this->log( "Updating upload_path in {$this->prefix}options. {$error}" );
1014
+
1015
+ $updateOptions = $this->db->query(
1016
+ $this->db->prepare(
1017
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'upload_path'", $newUploadPath
1018
+ )
1019
+ );
1020
+
1021
+ if( false === $updateOptions ) {
1022
+ $this->log( "Preparing Data Step16: Failed to update upload_path in {$this->prefix}options. {$error}", Logger::TYPE_ERROR );
1023
+ return true;
1024
+ }
1025
+ $this->Log( "Preparing Data: Finished Step 16 successfully" );
1026
+ return true;
1027
+ }
1028
+
1029
+ /**
1030
+ * Update WP_CACHE in wp-config.php
1031
+ * @return bool
1032
+ */
1033
+ protected function step17() {
1034
+ $path = $this->options->destinationDir . "wp-config.php";
1035
+
1036
+ $this->log( "Preparing Data Step17: Set WP_CACHE in wp-config.php to false" );
1037
+
1038
+ if( false === ($content = file_get_contents( $path )) ) {
1039
+ $this->log( "Preparing Data Step17: Failed to update WP_CACHE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
1040
+ return false;
1041
+ }
1042
+
1043
+
1044
+ // Get WP_CACHE from wp-config.php
1045
+ preg_match( "/define\s*\(\s*['\"]WP_CACHE['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
1046
+
1047
+ if( !empty( $matches[1] ) ) {
1048
+ $matches[1];
1049
+
1050
+ $pattern = "/define\s*\(\s*['\"]WP_CACHE['\"]\s*,\s*(.*)\s*\);.*/";
1051
+
1052
+ $replace = "define('WP_CACHE',false); // " . $matches[1];
1053
+ //$replace.= " // Changed by WP-Staging";
1054
+
1055
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1056
+ $this->log( "Preparing Data: Failed to change WP_CACHE", Logger::TYPE_ERROR );
1057
+ return false;
1058
+ }
1059
+ } else {
1060
+ $this->log( "Preparing Data Step17: WP_CACHE not defined in wp-config.php. Skipping this step." );
1061
+ }
1062
+
1063
+ if( false === @wpstg_put_contents( $path, $content ) ) {
1064
+ $this->log( "Preparing Data Step17: Failed to update WP_CACHE. Can't save contents", Logger::TYPE_ERROR );
1065
+ return false;
1066
+ }
1067
+ $this->Log( "Preparing Data: Finished Step 17 successfully" );
1068
+ return true;
1069
+ }
1070
+
1071
+ /**
1072
+ * Add UPLOADS constant in wp-config.php or change it to correct destination (multisite type /sites/2/)
1073
+ * @return bool
1074
+ */
1075
+ protected function step18() {
1076
+ $path = $this->options->destinationDir . "wp-config.php";
1077
+ $this->log( "Preparing Data Step18: Update UPLOADS constant in wp-config.php" );
1078
+ if( false === ($content = file_get_contents( $path )) ) {
1079
+ $this->log( "Preparing Data Step18: Failed to get UPLOADS in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
1080
+ return false;
1081
+ }
1082
+ // Get UPLOADS from wp-config.php if there is already one
1083
+ preg_match( "/define\s*\(\s*['\"]UPLOADS['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
1084
+ $uploadFolder = $this->getMultisiteUploadFolder();
1085
+ if( !empty( $matches[0] ) ) {
1086
+ $pattern = "/define\s*\(\s*'UPLOADS'\s*,\s*(.*)\s*\);/";
1087
+
1088
+ $replace = "define('UPLOADS', '" . $uploadFolder . "');";
1089
+ $this->log( "Preparing Data Step18: Change UPLOADS constant in wp-config.php to {$uploadFolder}." );
1090
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1091
+ $this->log( "Preparing Data Step 18: Failed to change UPLOADS", Logger::TYPE_ERROR );
1092
+ return false;
1093
+ }
1094
+ } else {
1095
+ $this->log( "Preparing Data Step18: UPLOADS not defined in wp-config.php. Creating new entry." );
1096
+ // Find ABSPATH and add UPLOAD constant above
1097
+ preg_match( "/if\s*\(\s*\s*!\s*defined\s*\(\s*['\"]ABSPATH['\"]\s*(.*)\s*\)\s*\)/", $content, $matches );
1098
+ if( !empty( $matches[0] ) ) {
1099
+ $matches[0];
1100
+ $pattern = "/if\s*\(\s*\s*!\s*defined\s*\(\s*['\"]ABSPATH['\"]\s*(.*)\s*\)\s*\)/";
1101
+ $replace = "define('UPLOADS', '" . $uploadFolder . "'); \n" .
1102
+ "if ( ! defined( 'ABSPATH' ) )";
1103
+ $this->log( "Preparing Data Step18: Change UPLOADS constant in wp-config.php to {$uploadFolder}." );
1104
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1105
+ $this->log( "Preparing Data Step 18: Failed to change UPLOADS", Logger::TYPE_ERROR );
1106
+ return false;
1107
+ }
1108
+ } else {
1109
+ $this->log( "Preparing Data Step 18: Can not add UPLOAD constant to wp-config.php. Can not find free position to add it.", Logger::TYPE_ERROR );
1110
+ }
1111
+ }
1112
+ if( false === @wpstg_put_contents( $path, $content ) ) {
1113
+ $this->log( "Preparing Data Step18: Failed to update UPLOADS. Can't save contents", Logger::TYPE_ERROR );
1114
+ return false;
1115
+ }
1116
+ $this->Log( "Preparing Data Step18: Finished successfully" );
1117
+ return true;
1118
+ }
1119
+
1120
+ /**
1121
+ * Update database credentials in wp-config.php
1122
+ * @return bool
1123
+ */
1124
+ protected function step19() {
1125
+ $path = $this->options->destinationDir . "wp-config.php";
1126
+
1127
+ $this->log( "Preparing Data Step19: Change database credentials in wp-config.php" );
1128
+
1129
+ if( false === ($content = file_get_contents( $path )) ) {
1130
+ $this->log( "Preparing Data Step19: Failed to update database credentials in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
1131
+ return false;
1132
+ }
1133
+
1134
+
1135
+ // Get DB_NAME from wp-config.php
1136
+ preg_match( "/define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
1137
+
1138
+ if( !empty( $matches[1] ) ) {
1139
+ $matches[1];
1140
+
1141
+ $pattern = "/define\s*\(\s*'DB_NAME'\s*,\s*(.*)\s*\);.*/";
1142
+
1143
+ $replace = "define('DB_NAME','{$this->options->databaseDatabase}'); // " . $matches[1];
1144
+ //$replace.= " // Changed by WP-Staging";
1145
+
1146
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1147
+ $this->log( "Preparing Data: Failed to change DB_NAME", Logger::TYPE_ERROR );
1148
+ return false;
1149
+ }
1150
+ } else {
1151
+ $this->log( "Preparing Data Step19: DB_NAME not defined in wp-config.php. Skipping this step." );
1152
+ }
1153
+ // Get DB_USER from wp-config.php
1154
+ preg_match( "/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
1155
+
1156
+ if( !empty( $matches[1] ) ) {
1157
+ $matches[1];
1158
+
1159
+ $pattern = "/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*(.*)\s*\);.*/";
1160
+
1161
+ $replace = "define('DB_USER','{$this->options->databaseUser}'); // " . $matches[1];
1162
+ //$replace.= " // Changed by WP-Staging";
1163
+
1164
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1165
+ $this->log( "Preparing Data: Failed to change DB_USER", Logger::TYPE_ERROR );
1166
+ return false;
1167
+ }
1168
+ } else {
1169
+ $this->log( "Preparing Data Step19: DB_USER not defined in wp-config.php. Skipping this step." );
1170
+ }
1171
+ // Get DB_PASSWORD from wp-config.php
1172
+ preg_match( "/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
1173
+
1174
+ if( !empty( $matches[1] ) ) {
1175
+ $matches[1];
1176
+
1177
+ $pattern = "/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*(.*)\s*\);.*/";
1178
+
1179
+ $replace = "define('DB_PASSWORD','{$this->options->databasePassword}'); // Changed by WP Staging";
1180
+ //$replace.= " // Changed by WP-Staging";
1181
+
1182
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1183
+ $this->log( "Preparing Data: Failed to change DB_PASSWORD", Logger::TYPE_ERROR );
1184
+ return false;
1185
+ }
1186
+ } else {
1187
+ $this->log( "Preparing Data Step19: DB_PASSWORD not defined in wp-config.php. Skipping this step." );
1188
+ }
1189
+ // Get DB_HOST from wp-config.php
1190
+ preg_match( "/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
1191
+
1192
+ if( !empty( $matches[1] ) ) {
1193
+ $matches[1];
1194
+
1195
+ $pattern = "/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*(.*)\s*\);.*/";
1196
+
1197
+ $replace = "define('DB_HOST','{$this->options->databaseServer}'); // " . $matches[1];
1198
+ //$replace.= " // Changed by WP-Staging";
1199
+
1200
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1201
+ $this->log( "Preparing Data: Failed to change DB_HOST", Logger::TYPE_ERROR );
1202
+ return false;
1203
+ }
1204
+ } else {
1205
+ $this->log( "Preparing Data Step19: DB_HOST not defined in wp-config.php. Skipping this step." );
1206
+ }
1207
+
1208
+
1209
+ if( false === @wpstg_put_contents( $path, $content ) ) {
1210
+ $this->log( "Preparing Data Step19: Failed to update database credentials in wp-config.php. Can't save contents", Logger::TYPE_ERROR );
1211
+ return false;
1212
+ }
1213
+ $this->Log( "Preparing Data Step 19: Finished successfully" );
1214
+ return true;
1215
+ }
1216
+
1217
+ /**
1218
+ * Save hostname of parent production site in option_name wpstg_connection
1219
+ * @return boolean
1220
+ */
1221
+ protected function step20() {
1222
+
1223
+ $table = $this->prefix . 'options';
1224
+
1225
+ $siteurl = get_site_url();
1226
+
1227
+ $connection = json_encode( array('prodHostname' => $siteurl) );
1228
+
1229
+ $data = array(
1230
+ 'option_name' => 'wpstg_connection',
1231
+ 'option_value' => $connection
1232
+ );
1233
+
1234
+ $format = array('%s', '%s');
1235
+
1236
+ $result = $this->db->replace( $table, $data, $format );
1237
+
1238
+ if( false === $result ) {
1239
+ $this->Log( "Preparing Data Step20: Could not save {$siteurl} in {$table}", Logger::TYPE_ERROR );
1240
+ }
1241
+ return true;
1242
+ }
1243
+
1244
+ /**
1245
+ * Add option_name wpstg_execute and set it to true
1246
+ * This option is used to determine if the staging website has not been loaded initiall for executing certain custom actions from \WPStaging\initActions()
1247
+ * @return boolean
1248
+ */
1249
+ protected function step21() {
1250
+
1251
+ $table = $this->prefix . 'options';
1252
+
1253
+ // Skip - Table does not exist
1254
+ if( false === $this->isTable( $table ) ) {
1255
+ return true;
1256
+ }
1257
+
1258
+
1259
+ $result = $this->db->query(
1260
+ $this->db->prepare(
1261
+ "INSERT INTO {$this->prefix}options (option_name,option_value) VALUES ('wpstg_execute',%s) ON DUPLICATE KEY UPDATE option_value = %s", "true", "true"
1262
+ )
1263
+ );
1264
+
1265
+ if( false === $result ) {
1266
+ $this->Log( "Preparing Data Step21: Could not save wpstg_execute in {$table}", Logger::TYPE_ERROR );
1267
+ }
1268
+ return true;
1269
+ }
1270
+
1271
+ /**
1272
+ * Preserve data and prevents data in wp_options from beeing cloned to staging site
1273
+ * @return bool
1274
+ */
1275
+ protected function step22() {
1276
+ $this->log( "Preparing Data Step22: Preserve Data in " . $this->prefix . "options" );
1277
+
1278
+ // Skip - Table does not exist
1279
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
1280
+ return true;
1281
+ }
1282
+
1283
+ // Skip - Table is not selected or updated
1284
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
1285
+ $this->log( "Preparing Data Step22: Skipped" );
1286
+ return true;
1287
+ }
1288
+
1289
+ $sql = '';
1290
+
1291
+ $preserved_option_names = array('wpstg_existing_clones_beta');
1292
+
1293
+ $preserved_option_names = apply_filters( 'wpstg_preserved_options_cloning', $preserved_option_names );
1294
+ $preserved_options_escaped = esc_sql( $preserved_option_names );
1295
+
1296
+ $preserved_options_data = array();
1297
+
1298
+ // Get preserved data in wp_options tables
1299
+ $table = $this->db->prefix . 'options';
1300
+ $preserved_options_data[$this->prefix . 'options'] = $this->db->get_results(
1301
+ sprintf(
1302
+ "SELECT * FROM `{$table}` WHERE `option_name` IN ('%s')", implode( "','", $preserved_options_escaped )
1303
+ ), ARRAY_A
1304
+ );
1305
+
1306
+ // Create preserved data queries for options tables
1307
+ foreach ( $preserved_options_data as $key => $value ) {
1308
+ if( false === empty( $value ) ) {
1309
+ foreach ( $value as $option ) {
1310
+ $sql .= $this->db->prepare(
1311
+ "DELETE FROM `{$key}` WHERE `option_name` = %s;\n", $option['option_name']
1312
+ );
1313
+
1314
+ $sql .= $this->db->prepare(
1315
+ "INSERT INTO `{$key}` ( `option_id`, `option_name`, `option_value`, `autoload` ) VALUES ( NULL , %s, %s, %s );\n", $option['option_name'], $option['option_value'], $option['autoload']
1316
+ );
1317
+ }
1318
+ }
1319
+ }
1320
+
1321
+ $this->debugLog( "Preparing Data Step22: Preserve values " . json_encode( $preserved_options_data ), Logger::TYPE_INFO );
1322
+
1323
+ $this->executeSql( $sql );
1324
+
1325
+ $this->log( "Preparing Data Step22: Successful!" );
1326
+ return true;
1327
+ }
1328
+
1329
+ /**
1330
+ * Execute a batch of sql queries
1331
+ * @param string $sqlbatch
1332
+ */
1333
+ private function executeSql( $sqlbatch ) {
1334
+ $queries = array_filter( explode( ";\n", $sqlbatch ) );
1335
+
1336
+ foreach ( $queries as $query ) {
1337
+ if( false === $this->db->query( $query ) ) {
1338
+ $this->log( "Data Crunching Warning: Can not execute query {$query}", Logger::TYPE_WARNING );
1339
+ }
1340
+ }
1341
+ return true;
1342
+ }
1343
+
1344
+ /**
1345
+ * Get relative path to the uploads media folder of multisite e.g.
1346
+ * wp-content/uploads/sites/SITEID or old wordpress structure wp-content/blogs.dir/SITEID/files
1347
+ * @return boolean
1348
+ */
1349
+ protected function getMultisiteUploadFolder() {
1350
+ $strings = new Strings();
1351
+ // Get absolute path to uploads folder
1352
+ $uploads = wp_upload_dir();
1353
+ $basedir = $uploads['basedir'];
1354
+ // Get relative upload path
1355
+ $relDir = str_replace( wpstg_replace_windows_directory_separator( ABSPATH ), null, wpstg_replace_windows_directory_separator( $basedir ) );
1356
+ return $relDir;
1357
+ }
1358
+
1359
+ /**
1360
+ * Get Upload Path to staging site
1361
+ * @return boolean|string
1362
+ */
1363
+ protected function getNewUploadPath() {
1364
+ $uploadPath = get_option( 'upload_path' );
1365
+
1366
+ if( !$uploadPath ) {
1367
+ return false;
1368
+ }
1369
+
1370
+ $customSlug = str_replace( wpstg_replace_windows_directory_separator( \WPStaging\WPStaging::getWPpath() ), '', wpstg_replace_windows_directory_separator( $uploadPath ) );
1371
+
1372
+ $newUploadPath = wpstg_replace_windows_directory_separator( \WPStaging\WPStaging::getWPpath() ) . $this->options->cloneDirectoryName . '/' . $customSlug;
1373
+
1374
+ return $newUploadPath;
1375
+ }
1376
+
1377
+ /**
1378
+ * Return URL to staging site
1379
+ * @return string
1380
+ */
1381
+ protected function getStagingSiteUrl() {
1382
+
1383
+ if( !empty( $this->options->cloneHostname ) ) {
1384
+ return $this->options->cloneHostname;
1385
+ }
1386
+
1387
+ if( $this->isSubDir() ) {
1388
+ return trailingslashit( $this->multisiteHomeDomain ) . trailingslashit( $this->getSubDir() ) . $this->options->cloneDirectoryName;
1389
+ }
1390
+
1391
+ // Get the path to the main multisite without appending and trailingslash e.g. wordpress
1392
+ $multisitePath = defined( 'PATH_CURRENT_SITE' ) ? PATH_CURRENT_SITE : '/';
1393
+ $url = rtrim( $this->multisiteHomeDomain, '/\\' ) . $multisitePath . $this->options->cloneDirectoryName;
1394
+
1395
+ return $url;
1396
+ }
1397
+
1398
+ /**
1399
+ * Check if WP is installed in subdir
1400
+ * @return boolean
1401
+ */
1402
+ protected function isSubDir() {
1403
+ // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
1404
+ // This is happening much more often than you would expect
1405
+ $siteurl = preg_replace( '#^https?://#', '', rtrim( get_option( 'siteurl' ), '/' ) );
1406
+ $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
1407
+
1408
+ if( $home !== $siteurl ) {
1409
+ return true;
1410
+ }
1411
+ return false;
1412
+ }
1413
+
1414
+ /**
1415
+ * Get the install sub directory if WP is installed in sub directory
1416
+ * @return string
1417
+ */
1418
+ protected function getSubDir() {
1419
+ $home = get_option( 'home' );
1420
+ $siteurl = get_option( 'siteurl' );
1421
+
1422
+ if( empty( $home ) || empty( $siteurl ) ) {
1423
+ return '';
1424
+ }
1425
+
1426
+ $dir = str_replace( $home, '', $siteurl );
1427
+ return str_replace( '/', '', $dir );
1428
+ }
1429
+
1430
+ }
apps/Backend/Modules/Jobs/Multisite/Database.php CHANGED
@@ -1,363 +1,366 @@
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
- $this->db = WPStaging::getInstance()->get( "wpdb" );
35
- $this->getTables();
36
- // Add wp_users and wp_usermeta to the tables object because they are not available in MU installation but we need them on the staging site
37
- $this->total = count( $this->options->tables );
38
- $this->isFatalError();
39
- }
40
-
41
- private function getTables() {
42
- // Add wp_users and wp_usermeta to the tables if they do not exist
43
- // because they are not available in MU installation but we need them on the staging site
44
-
45
- if( !in_array( $this->db->prefix . 'users', $this->options->tables ) ) {
46
- array_push( $this->options->tables, $this->db->prefix . 'users' );
47
- $this->saveOptions();
48
- }
49
- if( !in_array( $this->db->prefix . 'usermeta', $this->options->tables ) ) {
50
- array_push( $this->options->tables, $this->db->prefix . 'usermeta' );
51
- $this->saveOptions();
52
- }
53
- }
54
-
55
- /**
56
- * Return fatal error and stops here if subfolder already exists
57
- * and mainJob is not updating the clone
58
- * @return boolean
59
- */
60
- private function isFatalError() {
61
- $path = trailingslashit( get_home_path() ) . $this->options->cloneDirectoryName;
62
- if( isset( $this->options->mainJob ) && $this->options->mainJob !== 'updating' && is_dir( $path ) ) {
63
- $this->returnException( " Can not continue! Change the name of the clone or delete existing folder. Then try again. Folder already exists: " . $path );
64
- }
65
- return false;
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 === 0) ? 1 : $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
-
83
-
84
- // Over limits threshold
85
- if( $this->isOverThreshold() ) {
86
- // Prepare response and save current progress
87
- $this->prepareResponse( false, false );
88
- $this->saveOptions();
89
- return false;
90
- }
91
-
92
- // No more steps, finished
93
- if( !isset( $this->options->isRunning ) || $this->options->currentStep > $this->total ) {
94
- $this->prepareResponse( true, false );
95
- return false;
96
- }
97
-
98
- // Copy table
99
- if( isset( $this->options->tables[$this->options->currentStep] ) && !$this->copyTable( $this->options->tables[$this->options->currentStep] ) ) {
100
- // Prepare Response
101
- $this->prepareResponse( false, false );
102
-
103
- // Not finished
104
- return true;
105
- }
106
-
107
- // if( isset( $this->options->tables[$this->options->currentStep] ) && $this->db->prefix . 'users' === $this->options->tables[$this->options->currentStep] ) {
108
- // $this->copyWpUsers();
109
- // }
110
- // if( isset( $this->options->tables[$this->options->currentStep] ) && $this->db->prefix . 'usermeta' === $this->options->tables[$this->options->currentStep] ) {
111
- // $this->copyWpUsermeta();
112
- // }
113
- // Prepare Response
114
- $this->prepareResponse();
115
-
116
- // Not finished
117
- return true;
118
- }
119
-
120
- /**
121
- * Get new prefix for the staging site
122
- * @return string
123
- */
124
- private function getStagingPrefix() {
125
- $stagingPrefix = $this->options->prefix;
126
- // Make sure prefix of staging site is NEVER identical to prefix of live site!
127
- if( $stagingPrefix == $this->db->prefix ) {
128
- $error = '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';
129
- $this->returnException($error);
130
- wp_die( $error );
131
-
132
- }
133
- return $stagingPrefix;
134
- }
135
-
136
- /**
137
- * No worries, SQL queries don't eat from PHP execution time!
138
- * @param string $tableName
139
- * @return bool
140
- */
141
- private function copyTable( $tableName ) {
142
-
143
- $strings = new Strings();
144
- $tableName = is_object( $tableName ) ? $tableName->name : $tableName;
145
- $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->prefix, null, $tableName );
146
-
147
- // Get wp_users from main site
148
- if( 'users' === $strings->str_replace_first( $this->db->prefix, null, $tableName ) ) {
149
- $tableName = $this->db->base_prefix . 'users';
150
- }
151
- // Get wp_usermeta from main site
152
- if( 'usermeta' === $strings->str_replace_first( $this->db->prefix, null, $tableName ) ) {
153
- $tableName = $this->db->base_prefix . 'usermeta';
154
- }
155
-
156
- // Drop table if necessary
157
- $this->dropTable( $newTableName );
158
-
159
- // Save current job
160
- $this->setJob( $newTableName );
161
-
162
- // Beginning of the job
163
- if( !$this->startJob( $newTableName, $tableName ) ) {
164
- return true;
165
- }
166
-
167
- // Copy data
168
- $this->copyData( $newTableName, $tableName );
169
-
170
- // Finish the step
171
- return $this->finishStep();
172
- }
173
-
174
- /**
175
- * Copy multisite global user table wp_users to wpstgX_users
176
- * @return bool
177
- */
178
- // private function copyWpUsers() {
179
- //// $strings = new Strings();
180
- //// $tableName = $this->db->base_prefix . 'users';
181
- //// $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->base_prefix, null, $tableName );
182
- //
183
- // $tableName = $this->db->base_prefix . 'users';
184
- // $newTableName = $this->getStagingPrefix() . 'users';
185
- //
186
- // $this->log( "DB Copy: Try to create table {$newTableName}" );
187
- //
188
- // // Drop table if necessary
189
- // $this->dropTable( $newTableName );
190
- //
191
- // // Save current job
192
- // $this->setJob( $newTableName );
193
- //
194
- // // Beginning of the job
195
- // if( !$this->startJob( $newTableName, $tableName ) ) {
196
- // return true;
197
- // }
198
- //
199
- // // Copy data
200
- // $this->copyData( $newTableName, $tableName );
201
- //
202
- // // Finish the step
203
- // return $this->finishStep();
204
- // }
205
-
206
- /**
207
- * Copy multisite global user table wp_usermeta to wpstgX_users
208
- * @return bool
209
- */
210
- // private function copyWpUsermeta() {
211
- //// $strings = new Strings();
212
- //// $tableName = $this->db->base_prefix . 'usermeta';
213
- //// $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->base_prefix, null, $tableName );
214
- // $tableName = $this->db->base_prefix . 'usermeta';
215
- // $newTableName = $this->getStagingPrefix() . 'usermeta';
216
- //
217
- // $this->log( "DB Copy: Try to create table {$newTableName}" );
218
- //
219
- //
220
- // // Drop table if necessary
221
- // $this->dropTable( $newTableName );
222
- //
223
- // // Save current job
224
- // $this->setJob( $newTableName );
225
- //
226
- // // Beginning of the job
227
- // if( !$this->startJob( $newTableName, $tableName ) ) {
228
- // return true;
229
- // }
230
- // // Copy data
231
- // $this->copyData( $newTableName, $tableName );
232
- //
233
- // // Finish the step
234
- // return $this->finishStep();
235
- // }
236
-
237
- /**
238
- * Copy data from old table to new table
239
- * @param string $new
240
- * @param string $old
241
- */
242
- private function copyData( $new, $old ) {
243
- $rows = $this->options->job->start + $this->settings->queryLimit;
244
- $this->log(
245
- "DB Copy: {$old} as {$new} from {$this->options->job->start} to {$rows} records"
246
- );
247
-
248
- $limitation = '';
249
-
250
- if( 0 < ( int ) $this->settings->queryLimit ) {
251
- $limitation = " LIMIT {$this->settings->queryLimit} OFFSET {$this->options->job->start}";
252
- }
253
-
254
- $this->db->query(
255
- "INSERT INTO {$new} SELECT * FROM {$old} {$limitation}"
256
- );
257
-
258
- // Set new offset
259
- $this->options->job->start += $this->settings->queryLimit;
260
- }
261
-
262
- /**
263
- * Set the job
264
- * @param string $table
265
- */
266
- private function setJob( $table ) {
267
- if( isset( $this->options->job->current ) ) {
268
- return;
269
- }
270
-
271
- $this->options->job->current = $table;
272
- $this->options->job->start = 0;
273
- }
274
-
275
- /**
276
- * Start Job
277
- * @param string $new
278
- * @param string $old
279
- * @return bool
280
- */
281
- private function startJob( $new, $old ) {
282
-
283
- if( 0 != $this->options->job->start ) {
284
- return true;
285
- }
286
-
287
- // Table does not exist
288
- $result = $this->db->query( "SHOW TABLES LIKE '{$old}'" );
289
- if( !$result || 0 === $result ) {
290
- return true;
291
- }
292
-
293
- $this->log( "DB Copy: Creating table {$new}" );
294
-
295
- $this->db->query( "CREATE TABLE {$new} LIKE {$old}" );
296
-
297
- $this->options->job->total = 0;
298
- $this->options->job->total = ( int ) $this->db->get_var( "SELECT COUNT(1) FROM {$old}" );
299
-
300
- if( 0 == $this->options->job->total ) {
301
- $this->finishStep();
302
- return false;
303
- }
304
-
305
- return true;
306
- }
307
-
308
- /**
309
- * Finish the step
310
- */
311
- private function finishStep() {
312
- // This job is not finished yet
313
- if( $this->options->job->total > $this->options->job->start ) {
314
- return false;
315
- }
316
-
317
- // Add it to cloned tables listing
318
- $this->options->clonedTables[] = isset( $this->options->tables[$this->options->currentStep] ) ? $this->options->tables[$this->options->currentStep] : false;
319
-
320
- // Reset job
321
- $this->options->job = new \stdClass();
322
-
323
- return true;
324
- }
325
-
326
- /**
327
- * Drop table if necessary
328
- * @param string $new
329
- */
330
- private function dropTable( $new ) {
331
-
332
- $old = $this->db->get_var( $this->db->prepare( "SHOW TABLES LIKE %s", $new ) );
333
-
334
- if( !$this->shouldDropTable( $new, $old ) ) {
335
- return;
336
- }
337
-
338
- $this->log( "DB Copy: {$new} already exists, dropping it first" );
339
- $this->db->query( "DROP TABLE {$new}" );
340
- }
341
-
342
- /**
343
- * Check if table needs to be dropped
344
- * @param string $new
345
- * @param string $old
346
- * @return bool
347
- */
348
- private function shouldDropTable( $new, $old ) {
349
-
350
-
351
-
352
- if( $old === $new &&
353
- (
354
- !isset( $this->options->job->current ) ||
355
- !isset( $this->options->job->start ) ||
356
- 0 == $this->options->job->start
357
- ) ) {
358
- return true;
359
- }
360
- return false;
361
- }
362
-
363
- }
 
 
 
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
+ $this->db = WPStaging::getInstance()->get( "wpdb" );
35
+ $this->getTables();
36
+ // Add wp_users and wp_usermeta to the tables object because they are not available in MU installation but we need them on the staging site
37
+ $this->total = count( $this->options->tables );
38
+ $this->isFatalError();
39
+ }
40
+
41
+ private function getTables() {
42
+ // Add wp_users and wp_usermeta to the tables if they do not exist
43
+ // because they are not available in MU installation but we need them on the staging site
44
+
45
+ if( !in_array( $this->db->prefix . 'users', $this->options->tables ) ) {
46
+ array_push( $this->options->tables, $this->db->prefix . 'users' );
47
+ $this->saveOptions();
48
+ }
49
+ if( !in_array( $this->db->prefix . 'usermeta', $this->options->tables ) ) {
50
+ array_push( $this->options->tables, $this->db->prefix . 'usermeta' );
51
+ $this->saveOptions();
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Return fatal error and stops here if subfolder already exists
57
+ * and mainJob is not updating the clone
58
+ * @return boolean
59
+ */
60
+ private function isFatalError() {
61
+ //$path = trailingslashit( get_home_path() ) . $this->options->cloneDirectoryName;
62
+ $path = trailingslashit($this->options->cloneDir);
63
+ if( isset( $this->options->mainJob ) && $this->options->mainJob !== 'updating' && is_dir( $path ) && !wpstg_is_empty_dir( $path) ) {
64
+ $this->returnException( " Can not continue! Change the name of the clone or delete existing folder. Then try again. Folder already exists and is not empty: " . $path );
65
+ }
66
+ return false;
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 = ($this->total === 0) ? 1 : $this->total;
75
+ }
76
+
77
+ /**
78
+ * Execute the Current Step
79
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
80
+ * @return bool
81
+ */
82
+ protected function execute() {
83
+
84
+
85
+ // Over limits threshold
86
+ if( $this->isOverThreshold() ) {
87
+ // Prepare response and save current progress
88
+ $this->prepareResponse( false, false );
89
+ $this->saveOptions();
90
+ return false;
91
+ }
92
+
93
+ // No more steps, finished
94
+ if( !isset( $this->options->isRunning ) || $this->options->currentStep > $this->total ) {
95
+ $this->prepareResponse( true, false );
96
+ return false;
97
+ }
98
+
99
+ // Copy table
100
+ if( isset( $this->options->tables[$this->options->currentStep] ) && !$this->copyTable( $this->options->tables[$this->options->currentStep] ) ) {
101
+ // Prepare Response
102
+ $this->prepareResponse( false, false );
103
+
104
+ // Not finished
105
+ return true;
106
+ }
107
+
108
+ // if( isset( $this->options->tables[$this->options->currentStep] ) && $this->db->prefix . 'users' === $this->options->tables[$this->options->currentStep] ) {
109
+ // $this->copyWpUsers();
110
+ // }
111
+ // if( isset( $this->options->tables[$this->options->currentStep] ) && $this->db->prefix . 'usermeta' === $this->options->tables[$this->options->currentStep] ) {
112
+ // $this->copyWpUsermeta();
113
+ // }
114
+ // Prepare Response
115
+ $this->prepareResponse();
116
+
117
+ // Not finished
118
+ return true;
119
+ }
120
+
121
+ /**
122
+ * Get new prefix for the staging site
123
+ * @return string
124
+ */
125
+ private function getStagingPrefix() {
126
+ $stagingPrefix = $this->options->prefix;
127
+ // Make sure prefix of staging site is NEVER identical to prefix of live site!
128
+ if( $stagingPrefix == $this->db->prefix ) {
129
+ $error = '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';
130
+ $this->returnException($error);
131
+ wp_die( $error );
132
+
133
+ }
134
+ return $stagingPrefix;
135
+ }
136
+
137
+ /**
138
+ * No worries, SQL queries don't eat from PHP execution time!
139
+ * @param string $tableName
140
+ * @return bool
141
+ */
142
+ private function copyTable( $tableName ) {
143
+
144
+ $strings = new Strings();
145
+ $tableName = is_object( $tableName ) ? $tableName->name : $tableName;
146
+ $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->prefix, null, $tableName );
147
+
148
+ // Get wp_users from main site
149
+ if( 'users' === $strings->str_replace_first( $this->db->prefix, null, $tableName ) ) {
150
+ $tableName = $this->db->base_prefix . 'users';
151
+ }
152
+ // Get wp_usermeta from main site
153
+ if( 'usermeta' === $strings->str_replace_first( $this->db->prefix, null, $tableName ) ) {
154
+ $tableName = $this->db->base_prefix . 'usermeta';
155
+ }
156
+
157
+ // Drop table if necessary
158
+ $this->dropTable( $newTableName );
159
+
160
+ // Save current job
161
+ $this->setJob( $newTableName );
162
+
163
+ // Beginning of the job
164
+ if( !$this->startJob( $newTableName, $tableName ) ) {
165
+ return true;
166
+ }
167
+
168
+ // Copy data
169
+ $this->copyData( $newTableName, $tableName );
170
+
171
+ // Finish the step
172
+ return $this->finishStep();
173
+ }
174
+
175
+ /**
176
+ * Copy multisite global user table wp_users to wpstgX_users
177
+ * @return bool
178
+ */
179
+ // private function copyWpUsers() {
180
+ //// $strings = new Strings();
181
+ //// $tableName = $this->db->base_prefix . 'users';
182
+ //// $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->base_prefix, null, $tableName );
183
+ //
184
+ // $tableName = $this->db->base_prefix . 'users';
185
+ // $newTableName = $this->getStagingPrefix() . 'users';
186
+ //
187
+ // $this->log( "DB Copy: Try to create table {$newTableName}" );
188
+ //
189
+ // // Drop table if necessary
190
+ // $this->dropTable( $newTableName );
191
+ //
192
+ // // Save current job
193
+ // $this->setJob( $newTableName );
194
+ //
195
+ // // Beginning of the job
196
+ // if( !$this->startJob( $newTableName, $tableName ) ) {
197
+ // return true;
198
+ // }
199
+ //
200
+ // // Copy data
201
+ // $this->copyData( $newTableName, $tableName );
202
+ //
203
+ // // Finish the step
204
+ // return $this->finishStep();
205
+ // }
206
+
207
+ /**
208
+ * Copy multisite global user table wp_usermeta to wpstgX_users
209
+ * @return bool
210
+ */
211
+ // private function copyWpUsermeta() {
212
+ //// $strings = new Strings();
213
+ //// $tableName = $this->db->base_prefix . 'usermeta';
214
+ //// $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->base_prefix, null, $tableName );
215
+ // $tableName = $this->db->base_prefix . 'usermeta';
216
+ // $newTableName = $this->getStagingPrefix() . 'usermeta';
217
+ //
218
+ // $this->log( "DB Copy: Try to create table {$newTableName}" );
219
+ //
220
+ //
221
+ // // Drop table if necessary
222
+ // $this->dropTable( $newTableName );
223
+ //
224
+ // // Save current job
225
+ // $this->setJob( $newTableName );
226
+ //
227
+ // // Beginning of the job
228
+ // if( !$this->startJob( $newTableName, $tableName ) ) {
229
+ // return true;
230
+ // }
231
+ // // Copy data
232
+ // $this->copyData( $newTableName, $tableName );
233
+ //
234
+ // // Finish the step
235
+ // return $this->finishStep();
236
+ // }
237
+
238
+ /**
239
+ * Copy data from old table to new table
240
+ * @param string $new
241
+ * @param string $old
242
+ */
243
+ private function copyData( $new, $old ) {
244
+ $rows = $this->options->job->start + $this->settings->queryLimit;
245
+ $this->log(
246
+ "DB Copy: {$old} as {$new} from {$this->options->job->start} to {$rows} records"
247
+ );
248
+
249
+ $limitation = '';
250
+
251
+ if( 0 < ( int ) $this->settings->queryLimit ) {
252
+ $limitation = " LIMIT {$this->settings->queryLimit} OFFSET {$this->options->job->start}";
253
+ }
254
+
255
+ $this->db->query(
256
+ "INSERT INTO {$new} SELECT * FROM {$old} {$limitation}"
257
+ );
258
+
259
+ // Set new offset
260
+ $this->options->job->start += $this->settings->queryLimit;
261
+ }
262
+
263
+ /**
264
+ * Set the job
265
+ * @param string $table
266
+ */
267
+ private function setJob( $table ) {
268
+ if( isset( $this->options->job->current ) ) {
269
+ return;
270
+ }
271
+
272
+ $this->options->job->current = $table;
273
+ $this->options->job->start = 0;
274
+ }
275
+
276
+ /**
277
+ * Start Job
278
+ * @param string $new
279
+ * @param string $old
280
+ * @return bool
281
+ */
282
+ private function startJob( $new, $old ) {
283
+
284
+ if( 0 != $this->options->job->start ) {
285
+ return true;
286
+ }
287
+
288
+ // Table does not exist
289
+ $result = $this->db->query( "SHOW TABLES LIKE '{$old}'" );
290
+ if( !$result || 0 === $result ) {
291
+ return true;
292
+ }
293
+
294
+ $this->log( "DB Copy: Creating table {$new}" );
295
+
296
+ $this->db->query( "CREATE TABLE {$new} LIKE {$old}" );
297
+
298
+ $this->options->job->total = 0;
299
+ $this->options->job->total = ( int ) $this->db->get_var( "SELECT COUNT(1) FROM {$old}" );
300
+
301
+ if( 0 == $this->options->job->total ) {
302
+ $this->finishStep();
303
+ return false;
304
+ }
305
+
306
+ return true;
307
+ }
308
+
309
+ /**
310
+ * Finish the step
311
+ */
312
+ private function finishStep() {
313
+ // This job is not finished yet
314
+ if( $this->options->job->total > $this->options->job->start ) {
315
+ return false;
316
+ }
317
+
318
+ // Add it to cloned tables listing
319
+ $this->options->clonedTables[] = isset( $this->options->tables[$this->options->currentStep] ) ? $this->options->tables[$this->options->currentStep] : false;
320
+
321
+ // Reset job
322
+ $this->options->job = new \stdClass();
323
+
324
+ return true;
325
+ }
326
+
327
+ /**
328
+ * Drop table if necessary
329
+ * @param string $new
330
+ */
331
+ private function dropTable( $new ) {
332
+
333
+ $old = $this->db->get_var( $this->db->prepare( "SHOW TABLES LIKE %s", $new ) );
334
+
335
+ if( !$this->shouldDropTable( $new, $old ) ) {
336
+ return;
337
+ }
338
+
339
+ $this->log( "DB Copy: {$new} already exists, dropping it first" );
340
+ $this->db->query( "SET FOREIGN_KEY_CHECKS=0" );
341
+ $this->db->query( "DROP TABLE {$new}" );
342
+ $this->db->query( "SET FOREIGN_KEY_CHECKS=1" );
343
+ }
344
+
345
+ /**
346
+ * Check if table needs to be dropped
347
+ * @param string $new
348
+ * @param string $old
349
+ * @return bool
350
+ */
351
+ private function shouldDropTable( $new, $old ) {
352
+
353
+
354
+
355
+ if( $old === $new &&
356
+ (
357
+ !isset( $this->options->job->current ) ||
358
+ !isset( $this->options->job->start ) ||
359
+ 0 == $this->options->job->start
360
+ ) ) {
361
+ return true;
362
+ }
363
+ return false;
364
+ }
365
+
366
+ }
apps/Backend/Modules/Jobs/Multisite/DatabaseExternal.php CHANGED
@@ -1,411 +1,446 @@
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
- $this->db = WPStaging::getInstance()->get( "wpdb" );
41
- $this->stagingDb = $this->getExternalDBConnection();
42
- $this->getTables();
43
- // Add 2 to total table count because we need to copy two more tables from the main multisite installation wp_users and wp_usermeta
44
- $this->total = count( $this->options->tables ) + 2;
45
- $this->isFatalError();
46
- }
47
-
48
- /**
49
- * Get external db object
50
- * @return mixed db | false
51
- */
52
- private function getExternalDBConnection() {
53
-
54
- $db = new \wpdb( $this->options->databaseUser, $this->options->databasePassword, $this->options->databaseDatabase, $this->options->databaseServer );
55
-
56
- // Can not connect to mysql
57
- if( !empty( $db->error->errors['db_connect_fail']['0'] ) ) {
58
- $this->returnException( "Can not connect to external database {$this->options->databaseDatabase}" );
59
- return false;
60
- }
61
-
62
- // Can not connect to database
63
- $sql = "SHOW DATABASES LIKE '{$this->options->databaseDatabase}';";
64
- $results = $db->query( $sql );
65
- if( empty( $results ) ) {
66
- $this->returnException( "Can not connect to external database {$this->options->databaseDatabase}" );
67
- return false;
68
- }
69
- return $db;
70
- }
71
-
72
- private function getTables() {
73
- // Add wp_users and wp_usermeta to the tables if they do not exist
74
- // because they are not available in MU installation but we need them on the staging site
75
-
76
- if( !in_array( $this->db->prefix . 'users', $this->options->tables ) ) {
77
- array_push( $this->options->tables, $this->db->prefix . 'users' );
78
- $this->saveOptions();
79
- }
80
- if( !in_array( $this->db->prefix . 'usermeta', $this->options->tables ) ) {
81
- array_push( $this->options->tables, $this->db->prefix . 'usermeta' );
82
- $this->saveOptions();
83
- }
84
- }
85
-
86
- /**
87
- * Return fatal error and stops here if subfolder already exists
88
- * and mainJob is not updating the clone
89
- * @return boolean
90
- */
91
- private function isFatalError() {
92
- $path = trailingslashit( get_home_path() ) . $this->options->cloneDirectoryName;
93
- if( isset( $this->options->mainJob ) && $this->options->mainJob !== 'updating' && is_dir( $path ) ) {
94
- $this->returnException( " Can not continue! Change the name of the clone or delete existing folder. Then try again. Folder already exists: " . $path );
95
- }
96
- return false;
97
- }
98
-
99
- /**
100
- * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
101
- * @return void
102
- */
103
- protected function calculateTotalSteps() {
104
- $this->options->totalSteps = ($this->total === 0) ? 1 : $this->total;
105
- }
106
-
107
- /**
108
- * Execute the Current Step
109
- * Returns false when over threshold limits are hit or when the job is done, true otherwise
110
- * @return bool
111
- */
112
- protected function execute() {
113
-
114
-
115
- // Over limits threshold
116
- if( $this->isOverThreshold() ) {
117
- // Prepare response and save current progress
118
- $this->prepareResponse( false, false );
119
- $this->saveOptions();
120
- return false;
121
- }
122
-
123
- // No more steps, finished
124
- if( $this->options->currentStep > $this->total ) {
125
- $this->prepareResponse( true, false );
126
- return false;
127
- }
128
-
129
- // Copy table
130
- if( isset( $this->options->tables[$this->options->currentStep] ) && !$this->copyTable( $this->options->tables[$this->options->currentStep] ) ) {
131
- // Prepare Response
132
- $this->prepareResponse( false, false );
133
-
134
- // Not finished
135
- return true;
136
- }
137
-
138
- // $this->copyWpUsers();
139
- //
140
- // $this->copyWpUsermeta();
141
- // Prepare Response
142
- $this->prepareResponse();
143
-
144
- // Not finished
145
- return true;
146
- }
147
-
148
- /**
149
- * Get new prefix for the staging site
150
- * @return string
151
- */
152
- private function getStagingPrefix() {
153
-
154
- $this->options->prefix = !empty( $this->options->databasePrefix ) ? $this->options->databasePrefix : $this->db->prefix;
155
-
156
- return $this->options->prefix;
157
- }
158
-
159
- /**
160
- * No worries, SQL queries don't eat from PHP execution time!
161
- * @param string $tableName
162
- * @return bool
163
- */
164
- private function copyTable( $tableName ) {
165
-
166
- $strings = new Strings();
167
- $tableName = is_object( $tableName ) ? $tableName->name : $tableName;
168
- $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->prefix, null, $tableName );
169
-
170
- // Get wp_users from main site
171
- if( 'users' === $strings->str_replace_first( $this->db->prefix, null, $tableName ) ) {
172
- $tableName = $this->db->base_prefix . 'users';
173
- }
174
- // Get wp_usermeta from main site
175
- if( 'usermeta' === $strings->str_replace_first( $this->db->prefix, null, $tableName ) ) {
176
- $tableName = $this->db->base_prefix . 'usermeta';
177
- }
178
-
179
- // Drop table if necessary
180
- $this->dropTable( $newTableName );
181
-
182
- // Save current job
183
- $this->setJob( $newTableName );
184
-
185
- // Beginning of the job
186
- if( !$this->startJob( $newTableName, $tableName ) ) {
187
- return true;
188
- }
189
-
190
- // Copy data
191
- $this->copyData( $newTableName, $tableName );
192
-
193
- // Finish the step
194
- return $this->finishStep();
195
- }
196
-
197
- /**
198
- * Copy multisite global user table wp_users to wpstgX_users
199
- * @return bool
200
- */
201
- // private function copyWpUsers() {
202
- // $strings = new Strings();
203
- // $tableName = $this->db->base_prefix . 'users';
204
- // $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->base_prefix, null, $tableName );
205
- //
206
- // // Drop table if necessary
207
- // $this->dropTable( $newTableName );
208
- //
209
- // // Save current job
210
- // $this->setJob( $newTableName );
211
- //
212
- // // Beginning of the job
213
- // if( !$this->startJob( $newTableName, $tableName ) ) {
214
- // return true;
215
- // }
216
- //
217
- // // Copy data
218
- // $this->copyData( $newTableName, $tableName );
219
- //
220
- // // Finish the step
221
- // return $this->finishStep();
222
- // }
223
-
224
- /**
225
- * Copy multisite global user table wp_usermeta to wpstgX_users
226
- * @return bool
227
- */
228
- // private function copyWpUsermeta() {
229
- // $strings = new Strings();
230
- // $tableName = $this->db->base_prefix . 'usermeta';
231
- // $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->base_prefix, null, $tableName );
232
- //
233
- // // Drop table if necessary
234
- // $this->dropTable( $newTableName );
235
- //
236
- // // Save current job
237
- // $this->setJob( $newTableName );
238
- //
239
- // // Beginning of the job
240
- // if( !$this->startJob( $newTableName, $tableName ) ) {
241
- // return true;
242
- // }
243
- // // Copy data
244
- // $this->copyData( $newTableName, $tableName );
245
- //
246
- // // Finish the step
247
- // return $this->finishStep();
248
- // }
249
-
250
- /**
251
- * Copy data from old table to new table
252
- * @param string $new
253
- * @param string $old
254
- */
255
- private function copyData( $new, $old ) {
256
- $old = $this->db->dbname . '.' . $old;
257
- $new = $this->options->databaseDatabase . '.' . $new;
258
-
259
-
260
- $rows = $this->options->job->start + $this->settings->queryLimit;
261
- $this->log(
262
- "DB Copy: {$old} as {$new} from {$this->options->job->start} to {$rows} records"
263
- );
264
-
265
- $limitation = '';
266
-
267
- if( 0 < ( int ) $this->settings->queryLimit ) {
268
- $limitation = " LIMIT {$this->settings->queryLimit} OFFSET {$this->options->job->start}";
269
- }
270
-
271
- $this->stagingDb->query(
272
- "INSERT INTO {$new} SELECT * FROM {$old} {$limitation}"
273
- );
274
-
275
- // Set new offset
276
- $this->options->job->start += $this->settings->queryLimit;
277
- }
278
-
279
- /**
280
- * Set the job
281
- * @param string $table
282
- */
283
- private function setJob( $table ) {
284
- if( isset( $this->options->job->current ) ) {
285
- return;
286
- }
287
-
288
- $this->options->job->current = $table;
289
- $this->options->job->start = 0;
290
- }
291
-
292
- /**
293
- * Start Job
294
- * @param string $new
295
- * @param string $old
296
- * @return bool
297
- */
298
- private function startJob( $new, $old ) {
299
-
300
- if( $this->isExcludedTable( $new ) ) {
301
- return false;
302
- }
303
-
304
- $this->options->job->total = 0;
305
-
306
- $new = $this->options->databaseDatabase . '.' . $new;
307
- $old = $this->db->dbname . '.' . $old;
308
-
309
- if( 0 != $this->options->job->start ) {
310
- return true;
311
- }
312
-
313
- // Table does not exist
314
- $table = str_replace( $this->db->dbname . '.', null, $old );
315
- $result = $this->db->query( "SHOW TABLES LIKE '{$table}'" );
316
- if( !$result || 0 === $result ) {
317
- return true;
318
- }
319
-
320
- $this->log( "DB Copy: Creating table {$new}" );
321
-
322
- $this->stagingDb->query( "CREATE TABLE {$new} LIKE {$old}" );
323
-
324
- $this->options->job->total = ( int ) $this->stagingDb->get_var( "SELECT COUNT(1) FROM {$old}" );
325
-
326
- if( 0 == $this->options->job->total ) {
327
- $this->finishStep();
328
- return false;
329
- }
330
-
331
- return true;
332
- }
333
-
334
- /**
335
- * Is table excluded from search replace processing?
336
- * @param string $table
337
- * @return boolean
338
- */
339
- private function isExcludedTable( $table ) {
340
-
341
- $customTables = apply_filters( 'wpstg_clone_database_tables_exclude', array() );
342
- $defaultTables = array('blogs', 'blog_versions');
343
-
344
- $tables = array_merge( $customTables, $defaultTables );
345
-
346
- $excludedTables = array();
347
- foreach ( $tables as $key => $value ) {
348
- $excludedTables[] = $this->options->prefix . $value;
349
- }
350
-
351
- if( in_array( $table, $excludedTables ) ) {
352
- return true;
353
- }
354
- return false;
355
- }
356
-
357
- /**
358
- * Finish the step
359
- */
360
- private function finishStep() {
361
- // This job is not finished yet
362
- if( $this->options->job->total > $this->options->job->start ) {
363
- return false;
364
- }
365
-
366
- // Add it to cloned tables listing
367
- $this->options->clonedTables[] = isset( $this->options->tables[$this->options->currentStep] ) ? $this->options->tables[$this->options->currentStep] : false;
368
-
369
- // Reset job
370
- $this->options->job = new \stdClass();
371
-
372
- return true;
373
- }
374
-
375
- /**
376
- * Drop table if necessary
377
- * @param string $new
378
- */
379
- private function dropTable( $new ) {
380
- $old = $this->stagingDb->get_var( $this->stagingDb->prepare( "SHOW TABLES LIKE %s", $new ) );
381
-
382
- if( !$this->shouldDropTable( $new, $old ) ) {
383
- return;
384
- }
385
-
386
- $this->log( "DB Copy: {$new} already exists, dropping it first" );
387
- $this->stagingDb->query( "DROP TABLE {$new}" );
388
- }
389
-
390
- /**
391
- * Check if table needs to be dropped
392
- * @param string $new
393
- * @param string $old
394
- * @return bool
395
- */
396
- private function shouldDropTable( $new, $old ) {
397
-
398
-
399
-
400
- if( $old === $new &&
401
- (
402
- !isset( $this->options->job->current ) ||
403
- !isset( $this->options->job->start ) ||
404
- 0 == $this->options->job->start
405
- ) ) {
406
- return true;
407
- }
408
- return false;
409
- }
410
-
411
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * Helper class for string methods
38
+ * @var object
39
+ */
40
+ private $strings;
41
+
42
+ /**
43
+ * Initialize
44
+ */
45
+ public function initialize() {
46
+ $this->db = WPStaging::getInstance()->get( "wpdb" );
47
+ $this->stagingDb = $this->getExternalDBConnection();
48
+ $this->getTables();
49
+ // Add 2 more tables to total count because we need to copy two more tables from the main multisite installation wp_users and wp_usermeta
50
+ $this->total = count( $this->options->tables ) + 2;
51
+ $this->isFatalError();
52
+ $this->strings = new Strings();
53
+ }
54
+
55
+ /**
56
+ * Get external db object
57
+ * @return mixed db | false
58
+ */
59
+ private function getExternalDBConnection() {
60
+
61
+ $db = new \wpdb( $this->options->databaseUser, str_replace( "\\\\", "\\", $this->options->databasePassword ), $this->options->databaseDatabase, $this->options->databaseServer );
62
+
63
+ // Can not connect to mysql
64
+ if( !empty( $db->error->errors['db_connect_fail']['0'] ) ) {
65
+ $this->returnException( "Can not connect to external database {$this->options->databaseDatabase}" );
66
+ return false;
67
+ }
68
+
69
+ // Can not connect to database
70
+ $db->select( $this->options->databaseDatabase );
71
+ if( !$db->ready ) {
72
+ $error = isset( $db->error->errors['db_select_fail'] ) ? $db->error->errors['db_select_fail'] : "Error: Can't select {database} Either it does not exist or you don't have privileges to access it.";
73
+ $this->returnException( $error );
74
+ exit;
75
+ }
76
+ return $db;
77
+ }
78
+
79
+ /**
80
+ * Add wp_users and wp_usermeta to the tables if they do not exist
81
+ * because they are not available in MU installation but we need them on the staging site
82
+ *
83
+ * return void
84
+ */
85
+ private function getTables() {
86
+
87
+ if( !in_array( $this->db->prefix . 'users', $this->options->tables ) ) {
88
+ array_push( $this->options->tables, $this->db->prefix . 'users' );
89
+ $this->saveOptions();
90
+ }
91
+ if( !in_array( $this->db->prefix . 'usermeta', $this->options->tables ) ) {
92
+ array_push( $this->options->tables, $this->db->prefix . 'usermeta' );
93
+ $this->saveOptions();
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Return fatal error and stops here if subfolder already exists & is not empty
99
+ * and mainJob is not updating the clone
100
+ * @return boolean
101
+ */
102
+ private function isFatalError() {
103
+ //$path = trailingslashit( get_home_path() ) . $this->options->cloneDirectoryName;
104
+ $path = trailingslashit($this->options->cloneDir);
105
+ if( isset( $this->options->mainJob ) && $this->options->mainJob !== 'updating' && (is_dir( $path ) && !wpstg_is_empty_dir( $path ) ) ) {
106
+ $this->returnException( " Can not continue! Change the name of the clone or delete existing folder. Then try again. Folder already exists and is not empty: " . $path );
107
+ }
108
+ return false;
109
+ }
110
+
111
+ /**
112
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
113
+ * @return void
114
+ */
115
+ protected function calculateTotalSteps() {
116
+ $this->options->totalSteps = ($this->total === 0) ? 1 : $this->total;
117
+ }
118
+
119
+ /**
120
+ * Execute the Current Step
121
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
122
+ * @return bool
123
+ */
124
+ protected function execute() {
125
+
126
+
127
+ // Over limits threshold
128
+ if( $this->isOverThreshold() ) {
129
+ // Prepare response and save current progress
130
+ $this->prepareResponse( false, false );
131
+ $this->saveOptions();
132
+ return false;
133
+ }
134
+
135
+ // No more steps, finished
136
+ if( !isset( $this->options->isRunning ) || $this->options->currentStep > $this->total ) {
137
+ $this->prepareResponse( true, false );
138
+ return false;
139
+ }
140
+
141
+ // Copy table
142
+ if( isset( $this->options->tables[$this->options->currentStep] ) && !$this->copyTable( $this->options->tables[$this->options->currentStep] ) ) {
143
+ // Prepare Response
144
+ $this->prepareResponse( false, false );
145
+
146
+ // Not finished
147
+ return true;
148
+ }
149
+
150
+ // Prepare Response
151
+ $this->prepareResponse();
152
+
153
+ // Not finished
154
+ return true;
155
+ }
156
+
157
+ /**
158
+ * Get new prefix for the staging site
159
+ * @return string
160
+ */
161
+ private function getStagingPrefix() {
162
+
163
+ $this->options->prefix = !empty( $this->options->databasePrefix ) ? $this->options->databasePrefix : $this->db->prefix;
164
+
165
+ return $this->options->prefix;
166
+ }
167
+
168
+ /**
169
+ * No worries, SQL queries don't eat from PHP execution time!
170
+ * @param string $tableName
171
+ * @return bool
172
+ */
173
+ private function copyTable( $tableName ) {
174
+
175
+ $tableName = is_object( $tableName ) ? $tableName->name : $tableName;
176
+ $newTableName = $this->getStagingPrefix() . $this->strings->str_replace_first( $this->db->prefix, null, $tableName );
177
+
178
+ // Get wp_users from main site
179
+ if( 'users' === $this->strings->str_replace_first( $this->db->prefix, null, $tableName ) ) {
180
+ $tableName = $this->db->base_prefix . 'users';
181
+ }
182
+ // Get wp_usermeta from main site
183
+ if( 'usermeta' === $this->strings->str_replace_first( $this->db->prefix, null, $tableName ) ) {
184
+ $tableName = $this->db->base_prefix . 'usermeta';
185
+ }
186
+
187
+ // Drop table if necessary
188
+ $this->dropTable( $newTableName );
189
+
190
+ // Save current job
191
+ $this->setJob( $newTableName );
192
+
193
+ // Beginning of the job
194
+ if( !$this->startJob( $newTableName, $tableName ) ) {
195
+ return true;
196
+ }
197
+
198
+ // Copy data
199
+ $this->copyData( $newTableName, $tableName );
200
+
201
+ // Finish the step
202
+ return $this->finishStep();
203
+ }
204
+
205
+ /**
206
+ * Set the job
207
+ * @param string $table
208
+ */
209
+ private function setJob( $table ) {
210
+ if( isset( $this->options->job->current ) ) {
211
+ return;
212
+ }
213
+
214
+ $this->options->job->current = $table;
215
+ $this->options->job->start = 0;
216
+ }
217
+
218
+ /**
219
+ * Start Job and create tables
220
+ * @param string $new
221
+ * @param string $old
222
+ * @return bool
223
+ */
224
+ private function startJob( $new, $old ) {
225
+
226
+ if( $this->isExcludedTable( $new ) ) {
227
+ return false;
228
+ }
229
+
230
+ if( 0 != $this->options->job->start ) {
231
+ return true;
232
+ }
233
+
234
+ // Table does not exist
235
+ $result = $this->db->query( "SHOW TABLES LIKE '{$old}'" );
236
+ if( false === $result || 0 === $result ) {
237
+ $this->log( "DB External Copy: Table {$this->options->databaseDatabase}.{$old} does not exists. Skip!" );
238
+ return true;
239
+ }
240
+
241
+ $this->log( "DB External Copy: CREATE table {$this->options->databaseDatabase}.{$new}" );
242
+ $sql = $this->getCreateStatement( $old );
243
+
244
+ $search = '';
245
+ // Get table 'wp_users' from main site
246
+ if( 'users' === $this->strings->str_replace_first( $this->db->base_prefix, null, $old ) ) {
247
+ $search = $this->db->prefix . 'users';
248
+ }
249
+ // Get table 'wp_usermeta' from main site
250
+ if( 'usermeta' === $this->strings->str_replace_first( $this->db->base_prefix, null, $old ) ) {
251
+ $search = $this->db->prefix . 'usermeta';
252
+ }
253
+
254
+ // Replace table prefix to the new one
255
+ $sql = str_replace( "CREATE TABLE `{$search}`", "CREATE TABLE `{$new}`", $sql );
256
+
257
+ $this->stagingDb->query('SET FOREIGN_KEY_CHECKS=0;');
258
+
259
+ if( false === $this->stagingDb->query( $sql ) ) {
260
+ $this->returnException( "DB External Copy - Fatal Error: {$this->stagingDb->last_error} Query: {$sql}" );
261
+ }
262
+
263
+
264
+ // Count amount of rows to insert with next step
265
+ $this->options->job->total = 0;
266
+ $this->options->job->total = ( int ) $this->db->get_var( "SELECT COUNT(1) FROM `{$this->db->dbname}`.`{$old}`" );
267
+
268
+ $this->log( "DB External Copy: Table {$old} contains {$this->options->job->total} rows " );
269
+
270
+
271
+ if( 0 == $this->options->job->total ) {
272
+ $this->finishStep();
273
+ return false;
274
+ }
275
+
276
+ return true;
277
+ }
278
+
279
+ /**
280
+ * Get MySQL query create table
281
+ *
282
+ * @param string $table_name Table name
283
+ * @return array
284
+ */
285
+ private function getCreateStatement( $tableName ) {
286
+
287
+ // Query: Default
288
+ $row = $this->db->get_results( "SHOW CREATE TABLE `{$tableName}`", ARRAY_A );
289
+
290
+ // Query: Get wp_users from main site
291
+ if( 'users' === $this->strings->str_replace_first( $this->db->base_prefix, null, $tableName ) ) {
292
+ //$tableName = $this->db->base_prefix . 'users';
293
+ //$row = $this->db->get_results( "SHOW CREATE TABLE `{$tableName}`", ARRAY_A );
294
+ $row[0] = str_replace( $tableName, $this->db->prefix . 'users', $row[0] );
295
+ }
296
+ // Query: Get wp_usermeta from main site
297
+ if( 'usermeta' === $this->strings->str_replace_first( $this->db->base_prefix, null, $tableName ) ) {
298
+ //$tableName = $this->db->base_prefix . 'usermeta';
299
+ //$row = $this->db->get_results( "SHOW CREATE TABLE `{$tableName}`", ARRAY_A );
300
+ $row[0] = str_replace( $tableName, $this->db->prefix . 'usermeta', $row[0] );
301
+ }
302
+
303
+ // Get create table
304
+ if( isset( $row[0]['Create Table'] ) ) {
305
+ return $row[0]['Create Table'];
306
+ }
307
+ return array();
308
+ }
309
+
310
+ /**
311
+ * Copy data from old table to new table
312
+ * @param string $new
313
+ * @param string $old
314
+ */
315
+ private function copyData( $new, $old ) {
316
+
317
+ $rows = $this->options->job->start + $this->settings->queryLimit;
318
+ $this->log(
319
+ "DB External Copy: INSERT {$this->db->dbname}.{$old} as {$this->options->databaseDatabase}.{$new} from {$this->options->job->start} to {$rows} records"
320
+ );
321
+
322
+ $limitation = '';
323
+
324
+ if( 0 < ( int ) $this->settings->queryLimit ) {
325
+ $limitation = " LIMIT {$this->settings->queryLimit} OFFSET {$this->options->job->start}";
326
+ }
327
+
328
+ // Get data from production site
329
+ $rows = $this->db->get_results( "SELECT * FROM `{$old}` {$limitation}", ARRAY_A );
330
+
331
+ // Start transaction
332
+ $this->stagingDb->query( 'SET autocommit=0;' );
333
+ $this->stagingDb->query('SET FOREIGN_KEY_CHECKS=0;');
334
+ $this->stagingDb->query( 'START TRANSACTION;' );
335
+
336
+ // Copy into staging site
337
+ foreach ( $rows as $row ) {
338
+ $escaped_values = $this->mysqlEscapeMimic( array_values( $row ) );
339
+ $values = implode( "', '", $escaped_values );
340
+ $this->stagingDb->query( "INSERT INTO `{$new}` VALUES ('{$values}')" );
341
+ }
342
+ // Commit transaction
343
+ $this->stagingDb->query( 'COMMIT;' );
344
+ $this->stagingDb->query( 'SET autocommit=1;' );
345
+
346
+
347
+ // Set new offset
348
+ $this->options->job->start += $this->settings->queryLimit;
349
+ }
350
+
351
+ /**
352
+ * Mimics the mysql_real_escape_string function. Adapted from a post by 'feedr' on php.net.
353
+ * @link http://php.net/manual/en/function.mysql-real-escape-string.php#101248
354
+ * @access public
355
+ * @param string $input The string to escape.
356
+ * @return string
357
+ */
358
+ private function mysqlEscapeMimic( $input ) {
359
+ if( is_array( $input ) ) {
360
+ return array_map( __METHOD__, $input );
361
+ }
362
+ if( !empty( $input ) && is_string( $input ) ) {
363
+ return str_replace( array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $input );
364
+ }
365
+
366
+ return $input;
367
+ }
368
+
369
+ /**
370
+ * Is table excluded from search replace processing?
371
+ * @param string $table
372
+ * @return boolean
373
+ */
374
+ private function isExcludedTable( $table ) {
375
+
376
+ $customTables = apply_filters( 'wpstg_clone_database_tables_exclude', array() );
377
+ $defaultTables = array('blogs', 'blog_versions');
378
+
379
+ $tables = array_merge( $customTables, $defaultTables );
380
+
381
+ $excludedTables = array();
382
+ foreach ( $tables as $key => $value ) {
383
+ $excludedTables[] = $this->options->prefix . $value;
384
+ }
385
+
386
+ if( in_array( $table, $excludedTables ) ) {
387
+ return true;
388
+ }
389
+ return false;
390
+ }
391
+
392
+ /**
393
+ * Finish the step
394
+ */
395
+ private function finishStep() {
396
+ // This job is not finished yet
397
+ if( $this->options->job->total > $this->options->job->start ) {
398
+ return false;
399
+ }
400
+
401
+ // Add it to cloned tables listing
402
+ $this->options->clonedTables[] = isset( $this->options->tables[$this->options->currentStep] ) ? $this->options->tables[$this->options->currentStep] : false;
403
+
404
+ // Reset job
405
+ $this->options->job = new \stdClass();
406
+
407
+ return true;
408
+ }
409
+
410
+ /**
411
+ * Drop table if necessary
412
+ * @param string $new
413
+ */
414
+ private function dropTable( $new ) {
415
+
416
+ $old = $this->stagingDb->get_var( $this->stagingDb->prepare( "SHOW TABLES LIKE %s", $new ) );
417
+
418
+ if( !$this->shouldDropTable( $new, $old ) ) {
419
+ return;
420
+ }
421
+
422
+ $this->log( "DB External Copy: {$new} already exists, dropping it first" );
423
+ $this->stagingDb->query( "SET FOREIGN_KEY_CHECKS=0" );
424
+ $this->stagingDb->query( "DROP TABLE {$new}" );
425
+ $this->stagingDb->query( "SET FOREIGN_KEY_CHECKS=1" );
426
+ }
427
+
428
+ /**
429
+ * Check if table needs to be dropped
430
+ * @param string $new
431
+ * @param string $old
432
+ * @return bool
433
+ */
434
+ private function shouldDropTable( $new, $old ) {
435
+ if( $old === $new &&
436
+ (
437
+ !isset( $this->options->job->current ) ||
438
+ !isset( $this->options->job->start ) ||
439
+ 0 == $this->options->job->start
440
+ ) ) {
441
+ return true;
442
+ }
443
+ return false;
444
+ }
445
+
446
+ }
apps/Backend/Modules/Jobs/Multisite/Directories.php CHANGED
@@ -1,632 +1,609 @@
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\RecursiveFilterExclude;
15
- use WPStaging\Backend\Modules\Jobs\JobExecutable;
16
-
17
- /**
18
- * Class Files
19
- * @package WPStaging\Backend\Modules\Directories
20
- */
21
- class Directories extends JobExecutable {
22
-
23
- /**
24
- * @var array
25
- */
26
- private $files = array();
27
-
28
- /**
29
- * Total steps to do
30
- * @var int
31
- */
32
- private $total = 5;
33
-
34
- /**
35
- * path to the cache file
36
- * @var string
37
- */
38
- private $filename;
39
-
40
- /**
41
- * Initialize
42
- */
43
- public function initialize() {
44
- $this->filename = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
45
- }
46
-
47
- /**
48
- * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
49
- * @return void
50
- */
51
- protected function calculateTotalSteps() {
52
-
53
- $this->options->totalSteps = $this->total + count( $this->options->extraDirectories );
54
- }
55
-
56
- /**
57
- * Start Module
58
- * @return object
59
- */
60
- public function start() {
61
-
62
- // Execute steps
63
- $this->run();
64
-
65
- // Save option, progress
66
- $this->saveProgress();
67
-
68
- return ( object ) $this->response;
69
- }
70
-
71
- /**
72
- * Step 0
73
- * Get WP Root files
74
- * Does not collect any sub folders
75
- */
76
- private function getWpRootFiles() {
77
-
78
- // open file handle
79
- $files = $this->open( $this->filename, 'a' );
80
-
81
-
82
- try {
83
-
84
- // Iterate over wp root directory
85
- $iterator = new \DirectoryIterator( \WPStaging\WPStaging::getWPpath() );
86
-
87
- $this->log( "Scanning / for files" );
88
-
89
- // Write path line
90
- foreach ( $iterator as $item ) {
91
- if( !$item->isDot() && $item->isFile() ) {
92
- if( $this->write( $files, $iterator->getFilename() . PHP_EOL ) ) {
93
- $this->options->totalFiles++;
94
-
95
- // Add current file size
96
- $this->options->totalFileSize += $iterator->getSize();
97
- }
98
- }
99
- }
100
- } catch ( \Exception $e ) {
101
- $this->returnException( 'Error: ' . $e->getMessage() );
102
- } catch ( \Exception $e ) {
103
- // Skip bad file permissions
104
- }
105
-
106
- $this->close( $files );
107
- return true;
108
- }
109
-
110
- /**
111
- * Step 2
112
- * Get WP Content Files without multisite folder wp-content/uploads/sites
113
- */
114
- private function getWpContentFiles() {
115
-
116
- // Skip it
117
- if( $this->isDirectoryExcluded( WP_CONTENT_DIR ) ) {
118
- $this->log( "Skip " . \WPStaging\WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR );
119
- return true;
120
- }
121
- // open file handle
122
- $files = $this->open( $this->filename, 'a' );
123
-
124
- /**
125
- * Excluded folders relative to the folder to iterate
126
- */
127
- $excludePaths = array(
128
- 'cache',
129
- 'plugins/wps-hide-login',
130
- 'uploads/sites'
131
- );
132
-
133
- /**
134
- * Get user excluded folders
135
- */
136
- $directory = array();
137
- foreach ( $this->options->excludedDirectories as $dir ) {
138
- // Windows compatibility fix
139
- $dir = wpstg_replace_windows_directory_separator($dir);
140
- $wpContentDir = wpstg_replace_windows_directory_separator(WP_CONTENT_DIR);
141
-
142
- if( strpos( $dir, $wpContentDir ) !== false ) {
143
- $directory[] = ltrim( str_replace( $wpContentDir, '', $dir ), '/\\' );
144
- }
145
- }
146
-
147
- $excludePaths = array_merge( $excludePaths, $directory );
148
-
149
- try {
150
-
151
- // Iterate over content directory
152
- $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( WP_CONTENT_DIR );
153
-
154
- // Exclude sites, uploads, plugins or themes
155
- $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, apply_filters( 'wpstg_clone_mu_excl_folders', $excludePaths ) );
156
-
157
- // Recursively iterate over content directory
158
- $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
159
-
160
- $this->log( "Scanning /wp-content for its sub-directories and files" );
161
-
162
- // Write path line
163
- foreach ( $iterator as $item ) {
164
- if( $item->isFile() ) {
165
- $file = 'wp-content' . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL;
166
- if( $this->write( $files, $file ) ) {
167
- $this->options->totalFiles++;
168
-
169
- // Add current file size
170
- $this->options->totalFileSize += $iterator->getSize();
171
- }
172
- }
173
- }
174
- } catch ( \Exception $e ) {
175
- $this->returnException( 'Error: ' . $e->getMessage() );
176
- //throw new \Exception( 'Error: ' . $e->getMessage() );
177
- } catch ( \Exception $e ) {
178
- // Skip bad file permissions
179
- }
180
-
181
- // close the file handler
182
- $this->close( $files );
183
- return true;
184
- }
185
-
186
- /**
187
- * Step 2
188
- * @return boolean
189
- * @throws \Exception
190
- */
191
- private function getWpIncludesFiles() {
192
-
193
- // Skip it
194
- if( $this->isDirectoryExcluded( \WPStaging\WPStaging::getWPpath() . 'wp-includes' . DIRECTORY_SEPARATOR ) ) {
195
- $this->log( "Skip " . \WPStaging\WPStaging::getWPpath() . 'wp-includes' . DIRECTORY_SEPARATOR );
196
- return true;
197
- }
198
-
199
- // open file handle and attach data to end of file
200
- $files = $this->open( $this->filename, 'a' );
201
-
202
- try {
203
-
204
- // Iterate over wp-admin directory
205
- $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( \WPStaging\WPStaging::getWPpath() . 'wp-includes' . DIRECTORY_SEPARATOR );
206
-
207
- // Recursively iterate over wp-includes directory
208
- $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
209
-
210
- $this->log( "Scanning /wp-includes for its sub-directories and files" );
211
-
212
- // Write files
213
- foreach ( $iterator as $item ) {
214
- if( $item->isFile() ) {
215
- if( $this->write( $files, 'wp-includes' . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
216
- $this->options->totalFiles++;
217
-
218
- // Add current file size
219
- $this->options->totalFileSize += $iterator->getSize();
220
- }
221
- }
222
- }
223
- } catch ( \Exception $e ) {
224
- //$this->returnException('Out of disk space.');
225
- throw new \Exception( 'Error: ' . $e->getMessage() );
226
- } catch ( \Exception $e ) {
227
- // Skip bad file permissions
228
- }
229
-
230
- // close the file handler
231
- $this->close( $files );
232
- return true;
233
- }
234
-
235
- /**
236
- * Step 3
237
- * @return boolean
238
- * @throws \Exception
239
- */
240
- private function getWpAdminFiles() {
241
-
242
- // Skip it
243
- if( $this->isDirectoryExcluded( \WPStaging\WPStaging::getWPpath() . 'wp-admin' . DIRECTORY_SEPARATOR ) ) {
244
- $this->log( "Skip " . \WPStaging\WPStaging::getWPpath() . 'wp-admin' . DIRECTORY_SEPARATOR );
245
- return true;
246
- }
247
-
248
- // open file handle and attach data to end of file
249
- $files = $this->open( $this->filename, 'a' );
250
-
251
- try {
252
-
253
- // Iterate over wp-admin directory
254
- $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( \WPStaging\WPStaging::getWPpath() . 'wp-admin' . DIRECTORY_SEPARATOR );
255
-
256
- // Recursively iterate over content directory
257
- $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
258
-
259
- $this->log( "Scanning /wp-admin for its sub-directories and files" );
260
-
261
- // Write path line
262
- foreach ( $iterator as $item ) {
263
- if( $item->isFile() ) {
264
- if( $this->write( $files, 'wp-admin' . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
265
- $this->options->totalFiles++;
266
- // Add current file size
267
- $this->options->totalFileSize += $iterator->getSize();
268
- }
269
- }
270
- }
271
- } catch ( \Exception $e ) {
272
- $this->returnException( 'Error: ' . $e->getMessage() );
273
- //throw new \Exception('Error: ' . $e->getMessage());
274
- } catch ( \Exception $e ) {
275
- // Skip bad file permissions
276
- }
277
-
278
- // close the file handler
279
- $this->close( $files );
280
- return true;
281
- }
282
-
283
- /**
284
- * Step 4
285
- * Get WP Content Uploads Files multisite folder wp-content/uploads/sites or wp-content/blogs.dir/ID/files
286
- */
287
- private function getWpContentUploadsSites() {
288
-
289
- // Skip if main site is cloned
290
- if( is_main_site() ) {
291
- return true;
292
- }
293
-
294
- $blogId = get_current_blog_id();
295
-
296
- // Absolute path to uploads folder
297
- //$path = WP_CONTENT_DIR . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . $blogId . DIRECTORY_SEPARATOR;
298
- $path = $this->getAbsUploadPath();
299
-
300
- // Skip it
301
- if( !is_dir( $path ) ) {
302
- return true;
303
- }
304
-
305
- // Skip it
306
- if( $this->isDirectoryExcluded( $path ) ) {
307
- return true;
308
- }
309
-
310
-
311
- // open file handle
312
- $files = $this->open( $this->filename, 'a' );
313
-
314
- /**
315
- * Excluded folders relative to the folder to iterate
316
- */
317
- $excludePaths = array(
318
- 'cache',
319
- 'plugins/wps-hide-login',
320
- 'uploads/sites'
321
- );
322
-
323
- /**
324
- * Get user excluded folders
325
- */
326
- $directory = array();
327
- foreach ( $this->options->excludedDirectories as $dir ) {
328
- $path = wpstg_replace_windows_directory_separator($path);
329
- $dir = wpstg_replace_windows_directory_separator($dir);
330
- if( strpos( $dir, $path ) !== false ) {
331
- $directory[] = ltrim( str_replace( $path, '', $dir ), '/' );
332
- }
333
- }
334
-
335
- $excludePaths = array_merge( $excludePaths, $directory );
336
-
337
- try {
338
-
339
- // Iterate over content directory
340
- $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( $path );
341
-
342
- // Exclude sites, uploads, plugins or themes
343
- $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, $excludePaths );
344
-
345
- // Recursively iterate over content directory
346
- $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
347
- $this->log( "Scanning /wp-content/uploads/sites/{$blogId} for its sub-directories and files" );
348
-
349
- // Write path line
350
- foreach ( $iterator as $item ) {
351
- if( $item->isFile() ) {
352
- //$file = 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . $blogId . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL;
353
- $file = $this->getRelUploadPath() . $iterator->getSubPathName() . PHP_EOL;
354
- if( $this->write( $files, $file ) ) {
355
- $this->options->totalFiles++;
356
-
357
- // Add current file size
358
- $this->options->totalFileSize += $iterator->getSize();
359
- }
360
- }
361
- }
362
- } catch ( \Exception $e ) {
363
- $this->returnException( 'Error: ' . $e->getMessage() );
364
- //throw new \Exception( 'Error: ' . $e->getMessage() );
365
- } catch ( \Exception $e ) {
366
- // Skip bad file permissions
367
- }
368
-
369
- // close the file handler
370
- $this->close( $files );
371
- return true;
372
- }
373
-
374
- /**
375
- * 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/
376
- * @return type
377
- */
378
- private function getAbsUploadPath() {
379
- // Check first which method is used
380
- $uploads = wp_upload_dir();
381
- $basedir = $uploads['basedir'];
382
-
383
- return trailingslashit( $basedir );
384
-
385
- // if( false === strpos( $basedir, 'blogs.dir' ) ) {
386
- // // Since WP 3.5
387
- // return 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . get_current_blog_id();
388
- // } else {
389
- // // old blog structure
390
- // return 'wp-content' . DIRECTORY_SEPARATOR . 'blogs.dir' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR . 'files';
391
- // }
392
- }
393
-
394
- /**
395
- * Get relative path to the upload folder like wp-content/uploads or wp-content/blogs.dir/2/files
396
- * @return string
397
- */
398
- private function getRelUploadPath() {
399
- $uploads = wp_upload_dir();
400
- $basedir = $uploads['basedir'];
401
-
402
- return trailingslashit( str_replace( \WPStaging\WPStaging::getWPpath(), '', $basedir ) );
403
- }
404
-
405
- /**
406
- * Step 5 - x
407
- * Get extra folders of the wp root level
408
- * Does not collect wp-includes, wp-admin and wp-content folder
409
- */
410
- private function getExtraFiles( $folder ) {
411
-
412
- if( !is_dir( $folder ) ) {
413
- return true;
414
- }
415
-
416
- // open file handle and attach data to end of file
417
- $files = $this->open( $this->filename, 'a' );
418
-
419
- try {
420
-
421
- // Iterate over extra directory
422
- $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( $folder );
423
-
424
- $exclude = array();
425
-
426
- $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, $exclude );
427
- // Recursively iterate over content directory
428
- $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
429
-
430
- $strings = new Strings();
431
- $this->log( "Scanning {$strings->getLastElemAfterString( '/', $folder )} for its sub-directories and files" );
432
-
433
- // Write path line
434
- foreach ( $iterator as $item ) {
435
- if( $item->isFile() ) {
436
- //if( $this->write( $files, $strings->getLastElemAfterString( '/', $folder ) . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
437
- if( $this->write( $files, str_replace( \WPStaging\WPStaging::getWPpath(), '', $folder ) . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
438
- $this->options->totalFiles++;
439
- // Add current file size
440
- $this->options->totalFileSize += $iterator->getSize();
441
- }
442
- }
443
- }
444
- } catch ( \Exception $e ) {
445
- $this->returnException( 'Error: ' . $e->getMessage() );
446
- } catch ( \Exception $e ) {
447
- // Skip bad file permissions
448
- }
449
-
450
- // close the file handler
451
- $this->close( $files );
452
- return true;
453
- }
454
-
455
- /**
456
- * Closes a file handle
457
- *
458
- * @param resource $handle File handle to close
459
- * @return boolean
460
- */
461
- public function close( $handle ) {
462
- return @fclose( $handle );
463
- }
464
-
465
- /**
466
- * Opens a file in specified mode
467
- *
468
- * @param string $file Path to the file to open
469
- * @param string $mode Mode in which to open the file
470
- * @return resource
471
- * @throws Exception
472
- */
473
- public function open( $file, $mode ) {
474
-
475
- $file_handle = @fopen( $file, $mode );
476
- if( false === $file_handle ) {
477
- $this->returnException( sprintf( __( 'Unable to open %s with mode %s', 'wp-staging' ), $file, $mode ) );
478
- //throw new Exception(sprintf(__('Unable to open %s with mode %s', 'wp-staging'), $file, $mode));
479
- }
480
-
481
- return $file_handle;
482
- }
483
-
484
- /**
485
- * Write contents to a file
486
- *
487
- * @param resource $handle File handle to write to
488
- * @param string $content Contents to write to the file
489
- * @return integer
490
- * @throws Exception
491
- * @throws Exception
492
- */
493
- public function write( $handle, $content ) {
494
- $write_result = @fwrite( $handle, $content );
495
- if( false === $write_result ) {
496
- if( ( $meta = \stream_get_meta_data( $handle ) ) ) {
497
- //$this->returnException(sprintf(__('Unable to write to: %s', 'wp-staging'), $meta['uri']));
498
- throw new \Exception( sprintf( __( 'Unable to write to: %s', 'wp-staging' ), $meta['uri'] ) );
499
- }
500
- } elseif( strlen( $content ) !== $write_result ) {
501
- //$this->returnException(__('Out of disk space.', 'wp-staging'));
502
- throw new \Exception( __( 'Out of disk space.', 'wp-staging' ) );
503
- }
504
-
505
- return $write_result;
506
- }
507
-
508
- /**
509
- * Execute the Current Step
510
- * Returns false when over threshold limits are hit or when the job is done, true otherwise
511
- * @return bool
512
- */
513
- protected function execute() {
514
-
515
- // No job left to execute
516
- if( $this->isFinished() ) {
517
- $this->prepareResponse( true, false );
518
- return false;
519
- }
520
-
521
-
522
- if( $this->options->currentStep == 0 ) {
523
- $this->getWpRootFiles();
524
- $this->prepareResponse( false, true );
525
- return false;
526
- }
527
-
528
- if( $this->options->currentStep == 1 ) {
529
- $this->getWpContentFiles();
530
- $this->prepareResponse( false, true );
531
- return false;
532
- }
533
-
534
- if( $this->options->currentStep == 2 ) {
535
- $this->getWpIncludesFiles();
536
- $this->prepareResponse( false, true );
537
- return false;
538
- }
539
-
540
- if( $this->options->currentStep == 3 ) {
541
- $this->getWpAdminFiles();
542
- $this->prepareResponse( false, true );
543
- return false;
544
- }
545
-
546
- if( $this->options->currentStep == 4 ) {
547
- $this->getWpContentUploadsSites();
548
- $this->prepareResponse( false, true );
549
- return false;
550
- }
551
-
552
- if( isset( $this->options->extraDirectories[$this->options->currentStep - $this->total] ) ) {
553
- $this->getExtraFiles( $this->options->extraDirectories[$this->options->currentStep - $this->total] );
554
- $this->prepareResponse( false, true );
555
- return false;
556
- }
557
-
558
-
559
- // Prepare response
560
- $this->prepareResponse( false, true );
561
- // Not finished
562
- return true;
563
- }
564
-
565
- /**
566
- * Checks Whether There is Any Job to Execute or Not
567
- * @return bool
568
- */
569
- protected function isFinished() {
570
- if( $this->options->currentStep >= $this->options->totalSteps ) {
571
- return true;
572
- }
573
-
574
- // return (
575
- // //$this->options->currentStep > $this->total ||
576
- // $this->options->currentStep >= $this->options->totalSteps
577
- // );
578
- }
579
-
580
- /**
581
- * Save files
582
- * @return bool
583
- */
584
- protected function saveProgress() {
585
- return $this->saveOptions();
586
- }
587
-
588
- /**
589
- * Get files
590
- * @return void
591
- */
592
- protected function getFiles() {
593
- $fileName = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
594
-
595
- if( false === ($this->files = @file_get_contents( $fileName )) ) {
596
- $this->files = array();
597
- return;
598
- }
599
-
600
- $this->files = explode( PHP_EOL, $this->files );
601
- }
602
-
603
- /**
604
- * Replace forward slash with current directory separator
605
- *
606
- * @param string $path Path
607
- *
608
- * @return string
609
- */
610
- private function sanitizeDirectorySeparator( $path ) {
611
- $string = str_replace( "/", "\\", $path );
612
- return str_replace( '\\\\', '\\', $string );
613
- }
614
-
615
- /**
616
- * Check if directory is excluded
617
- * @param string $directory
618
- * @return bool
619
- */
620
- protected function isDirectoryExcluded( $directory ) {
621
- $directory = $this->sanitizeDirectorySeparator( $directory );
622
- foreach ( $this->options->excludedDirectories as $excludedDirectory ) {
623
- $excludedDirectory = $this->sanitizeDirectorySeparator( $excludedDirectory );
624
- if( strpos( trailingslashit( $directory ), trailingslashit( $excludedDirectory ) ) === 0 ) {
625
- return true;
626
- }
627
- }
628
-
629
- return false;
630
- }
631
-
632
- }
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\RecursiveFilterExclude;
15
+ use WPStaging\Backend\Modules\Jobs\JobExecutable;
16
+
17
+ /**
18
+ * Class Files
19
+ * @package WPStaging\Backend\Modules\Directories
20
+ */
21
+ class Directories extends JobExecutable {
22
+
23
+ /**
24
+ * @var array
25
+ */
26
+ private $files = array();
27
+
28
+ /**
29
+ * Total steps to do
30
+ * @var int
31
+ */
32
+ private $total = 5;
33
+
34
+ /**
35
+ * path to the cache file
36
+ * @var string
37
+ */
38
+ private $filename;
39
+
40
+ /**
41
+ * Initialize
42
+ */
43
+ public function initialize() {
44
+ $this->filename = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
45
+ }
46
+
47
+ /**
48
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
49
+ * @return void
50
+ */
51
+ protected function calculateTotalSteps() {
52
+
53
+ $this->options->totalSteps = $this->total + count( $this->options->extraDirectories );
54
+ }
55
+
56
+ /**
57
+ * Start Module
58
+ * @return object
59
+ */
60
+ public function start() {
61
+
62
+ // Execute steps
63
+ $this->run();
64
+
65
+ // Save option, progress
66
+ $this->saveProgress();
67
+
68
+ return ( object ) $this->response;
69
+ }
70
+
71
+ /**
72
+ * Step 0
73
+ * Get WP Root files
74
+ * Does not collect any sub folders
75
+ */
76
+ private function getWpRootFiles() {
77
+
78
+ // Skip it
79
+ if( $this->isDirectoryExcluded( \WPStaging\WPStaging::getWPpath() . 'wp-admin' . DIRECTORY_SEPARATOR ) ) {
80
+ $this->log( "Skip " . \WPStaging\WPStaging::getWPpath() . 'wp-admin' . DIRECTORY_SEPARATOR );
81
+ return true;
82
+ }
83
+
84
+ // open file handle
85
+ $files = $this->open( $this->filename, 'a' );
86
+
87
+ try {
88
+
89
+ // Iterate over wp root directory
90
+ $iterator = new \DirectoryIterator( \WPStaging\WPStaging::getWPpath() );
91
+
92
+ $this->log( "Scanning / for files" );
93
+
94
+ // Write path line
95
+ foreach ( $iterator as $item ) {
96
+ if( !$item->isDot() && $item->isFile() ) {
97
+ if( $this->write( $files, $iterator->getFilename() . PHP_EOL ) ) {
98
+ $this->options->totalFiles++;
99
+
100
+ // Too much cpu time
101
+ //$this->options->totalFileSize += $iterator->getSize();
102
+ }
103
+ }
104
+ }
105
+ } catch ( \Exception $e ) {
106
+ $this->returnException( 'Error: ' . $e->getMessage() );
107
+ }
108
+
109
+ $this->close( $files );
110
+ return true;
111
+ }
112
+
113
+ /**
114
+ * Step 2
115
+ * Get WP Content Files without multisite folder wp-content/uploads/sites or wp-content/blogs.dir/
116
+ */
117
+ private function getWpContentFiles() {
118
+
119
+ // Skip it
120
+ if( $this->isDirectoryExcluded( WP_CONTENT_DIR ) ) {
121
+ $this->log( "Skip " . WP_CONTENT_DIR );
122
+ return true;
123
+ }
124
+ // open file handle
125
+ $files = $this->open( $this->filename, 'a' );
126
+
127
+ /**
128
+ * Excluded folders relative to the folder to iterate
129
+ */
130
+ $excludePaths = array(
131
+ 'cache',
132
+ 'plugins/wps-hide-login',
133
+ 'uploads/sites',
134
+ 'blogs.dir'
135
+ );
136
+
137
+ /**
138
+ * Get user excluded folders
139
+ */
140
+ $directory = array();
141
+ foreach ( $this->options->excludedDirectories as $dir ) {
142
+ // Windows compatibility fix
143
+ $dir = wpstg_replace_windows_directory_separator( $dir );
144
+ $wpContentDir = wpstg_replace_windows_directory_separator( WP_CONTENT_DIR );
145
+
146
+ if( strpos( $dir, $wpContentDir ) !== false ) {
147
+ $directory[] = ltrim( str_replace( $wpContentDir, '', $dir ), '/\\' );
148
+ }
149
+ }
150
+
151
+ $excludePaths = array_merge( $excludePaths, $directory );
152
+
153
+ try {
154
+
155
+ // Iterate over content directory
156
+ $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( WP_CONTENT_DIR );
157
+
158
+ // Exclude sites, uploads, plugins or themes
159
+ $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, apply_filters( 'wpstg_clone_mu_excl_folders', $excludePaths ) );
160
+
161
+ // Recursively iterate over content directory
162
+ $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
163
+
164
+ $this->log( "Scanning wp-content folder " . WP_CONTENT_DIR );
165
+
166
+ // Write path line
167
+ foreach ( $iterator as $item ) {
168
+ if( $item->isFile() ) {
169
+ $file = 'wp-content/' . $iterator->getSubPathName() . PHP_EOL;
170
+ if( $this->write( $files, $file ) ) {
171
+ $this->options->totalFiles++;
172
+
173
+ // Add current file size
174
+ $this->options->totalFileSize += $iterator->getSize();
175
+ }
176
+ }
177
+ }
178
+ } catch ( \Exception $e ) {
179
+ $this->returnException( 'Error: ' . $e->getMessage() );
180
+ //throw new \Exception( 'Error: ' . $e->getMessage() );
181
+ }
182
+
183
+ // close the file handler
184
+ $this->close( $files );
185
+ return true;
186
+ }
187
+
188
+ /**
189
+ * Step 2
190
+ * @return boolean
191
+ * @throws \Exception
192
+ */
193
+ private function getWpIncludesFiles() {
194
+
195
+ // Skip it
196
+ if( $this->isDirectoryExcluded( \WPStaging\WPStaging::getWPpath() . 'wp-includes' ) ) {
197
+ $this->log( "Skip " . \WPStaging\WPStaging::getWPpath() . 'wp-includes' );
198
+ return true;
199
+ }
200
+
201
+ // open file handle and attach data to end of file
202
+ $files = $this->open( $this->filename, 'a' );
203
+
204
+ try {
205
+
206
+ // Iterate over wp-admin directory
207
+ $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( \WPStaging\WPStaging::getWPpath() . 'wp-includes/' );
208
+
209
+ // Recursively iterate over wp-includes directory
210
+ $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
211
+
212
+ $this->log( "Scanning /wp-includes for its sub-directories and files" );
213
+
214
+ // Write files
215
+ foreach ( $iterator as $item ) {
216
+ if( $item->isFile() ) {
217
+ if( $this->write( $files, 'wp-includes/' . $iterator->getSubPathName() . PHP_EOL ) ) {
218
+ $this->options->totalFiles++;
219
+
220
+ // Add current file size
221
+ $this->options->totalFileSize += $iterator->getSize();
222
+ }
223
+ }
224
+ }
225
+ } catch ( \Exception $e ) {
226
+ $this->returnException( 'Error: ' . $e->getMessage() );
227
+ }
228
+
229
+ // close the file handler
230
+ $this->close( $files );
231
+ return true;
232
+ }
233
+
234
+ /**
235
+ * Step 3
236
+ * @return boolean
237
+ * @throws \Exception
238
+ */
239
+ private function getWpAdminFiles() {
240
+
241
+ // Skip it
242
+ if( $this->isDirectoryExcluded( \WPStaging\WPStaging::getWPpath() . 'wp-admin/' ) ) {
243
+ $this->log( "Skip " . \WPStaging\WPStaging::getWPpath() . 'wp-admin/' );
244
+ return true;
245
+ }
246
+
247
+ // open file handle and attach data to end of file
248
+ $files = $this->open( $this->filename, 'a' );
249
+
250
+ try {
251
+
252
+ // Iterate over wp-admin directory
253
+ $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( \WPStaging\WPStaging::getWPpath() . 'wp-admin/' );
254
+
255
+ // Recursively iterate over content directory
256
+ $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
257
+
258
+ $this->log( "Scanning /wp-admin for its sub-directories and files" );
259
+
260
+ // Write path line
261
+ foreach ( $iterator as $item ) {
262
+ if( $item->isFile() ) {
263
+ if( $this->write( $files, 'wp-admin/' . $iterator->getSubPathName() . PHP_EOL ) ) {
264
+ $this->options->totalFiles++;
265
+ // Too much cpu time
266
+ //$this->options->totalFileSize += $iterator->getSize();
267
+ }
268
+ }
269
+ }
270
+ } catch ( \Exception $e ) {
271
+ $this->returnException( 'Error: ' . $e->getMessage() );
272
+ }
273
+
274
+ // close the file handler
275
+ $this->close( $files );
276
+ return true;
277
+ }
278
+
279
+ /**
280
+ * Step 4
281
+ * Get WP Content Uploads Files multisite folder wp-content/uploads/sites or wp-content/blogs.dir/ID/files
282
+ */
283
+ private function getWpContentUploadsSites() {
284
+
285
+ // Skip if main site is cloned
286
+ if( is_main_site() ) {
287
+ return true;
288
+ }
289
+
290
+ $blogId = get_current_blog_id();
291
+
292
+ // Absolute path to uploads folder
293
+ //$path = WP_CONTENT_DIR . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . $blogId . DIRECTORY_SEPARATOR;
294
+ $path = $this->getAbsUploadPath();
295
+
296
+ // Skip it
297
+ if( !is_dir( $path ) ) {
298
+ $this->log( "Skipping: {$path} does not exist." );
299
+ return true;
300
+ }
301
+
302
+ // Skip it
303
+ if( $this->isDirectoryExcluded( $path ) ) {
304
+ $this->log( "Skipping: {$path}" );
305
+ return true;
306
+ }
307
+
308
+
309
+ // open file handle
310
+ $files = $this->open( $this->filename, 'a' );
311
+
312
+ /**
313
+ * Excluded folders relative to the folder to iterate
314
+ */
315
+ $excludePaths = array(
316
+ 'cache',
317
+ 'plugins/wps-hide-login',
318
+ 'uploads/sites'
319
+ );
320
+
321
+ /**
322
+ * Get user excluded folders
323
+ */
324
+ $directory = array();
325
+ foreach ( $this->options->excludedDirectories as $dir ) {
326
+ $path = wpstg_replace_windows_directory_separator( $path );
327
+ $dir = wpstg_replace_windows_directory_separator( $dir );
328
+ if( strpos( $dir, $path ) !== false ) {
329
+ $directory[] = ltrim( str_replace( $path, '', $dir ), '/' );
330
+ }
331
+ }
332
+
333
+ $excludePaths = array_merge( $excludePaths, $directory );
334
+
335
+ try {
336
+
337
+ // Iterate over content directory
338
+ $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( $path );
339
+
340
+ // Exclude sites, uploads, plugins or themes
341
+ $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, $excludePaths );
342
+
343
+ // Recursively iterate over content directory
344
+ $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
345
+ $uploadDir = wpstg_get_upload_dir();
346
+ $this->log( "Scanning {$uploadDir} for its sub-directories and files..." );
347
+
348
+ // Write path line
349
+ foreach ( $iterator as $item ) {
350
+ if( $item->isFile() ) {
351
+ $file = $this->getRelUploadPath() . $iterator->getSubPathName() . PHP_EOL;
352
+ if( $this->write( $files, $file ) ) {
353
+ $this->options->totalFiles++;
354
+
355
+ // Add current file size
356
+ $this->options->totalFileSize += $iterator->getSize();
357
+ }
358
+ }
359
+ }
360
+ } catch ( \Exception $e ) {
361
+ $this->returnException( 'Error: ' . $e->getMessage() );
362
+ }
363
+
364
+ // close the file handler
365
+ $this->close( $files );
366
+ return true;
367
+ }
368
+
369
+ /**
370
+ * 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/
371
+ * @return type
372
+ */
373
+ private function getAbsUploadPath() {
374
+ // Check first which method is used
375
+ $uploads = wp_upload_dir();
376
+ $basedir = $uploads['basedir'];
377
+
378
+ return trailingslashit( $basedir );
379
+ }
380
+
381
+ /**
382
+ * Get relative path to the upload folder like wp-content/uploads or wp-content/blogs.dir/2/files
383
+ * @return string
384
+ */
385
+ private function getRelUploadPath() {
386
+ $uploads = wp_upload_dir();
387
+ $basedir = $uploads['basedir'];
388
+
389
+ return trailingslashit( str_replace( wpstg_replace_windows_directory_separator(\WPStaging\WPStaging::getWPpath()), null, wpstg_replace_windows_directory_separator($basedir) ) );
390
+ }
391
+
392
+ /**
393
+ * Step 5 - x
394
+ * Get extra folders of the wp root level
395
+ * Does not collect wp-includes, wp-admin and wp-content folder
396
+ */
397
+ private function getExtraFiles( $folder ) {
398
+
399
+ if( !is_dir( $folder ) ) {
400
+ return true;
401
+ }
402
+
403
+ // open file handle and attach data to end of file
404
+ $files = $this->open( $this->filename, 'a' );
405
+
406
+ try {
407
+
408
+ // Iterate over extra directory
409
+ $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( $folder );
410
+
411
+ $exclude = array();
412
+
413
+ $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, $exclude );
414
+ // Recursively iterate over content directory
415
+ $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
416
+
417
+ $strings = new Strings();
418
+ $this->log( "Scanning {$strings->getLastElemAfterString( '/', $folder )} for its sub-directories and files" );
419
+
420
+ // Write path line
421
+ foreach ( $iterator as $item ) {
422
+ if( $item->isFile() ) {
423
+ $path = str_replace( wpstg_replace_windows_directory_separator(\WPStaging\WPStaging::getWPpath()), '', wpstg_replace_windows_directory_separator($folder) ) . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL;
424
+ if( $this->write( $files, $path ) ) {
425
+ $this->options->totalFiles++;
426
+ // Add current file size
427
+ $this->options->totalFileSize += $iterator->getSize();
428
+ }
429
+ }
430
+ }
431
+ } catch ( \Exception $e ) {
432
+ $this->returnException( 'Error: ' . $e->getMessage() );
433
+ }
434
+
435
+ // close the file handler
436
+ $this->close( $files );
437
+ return true;
438
+ }
439
+
440
+ /**
441
+ * Closes a file handle
442
+ *
443
+ * @param resource $handle File handle to close
444
+ * @return boolean
445
+ */
446
+ public function close( $handle ) {
447
+ return @fclose( $handle );
448
+ }
449
+
450
+ /**
451
+ * Opens a file in specified mode
452
+ *
453
+ * @param string $file Path to the file to open
454
+ * @param string $mode Mode in which to open the file
455
+ * @return resource
456
+ * @throws Exception
457
+ */
458
+ public function open( $file, $mode ) {
459
+
460
+ $file_handle = @fopen( $file, $mode );
461
+ if( false === $file_handle ) {
462
+ $this->returnException( sprintf( __( 'Unable to open %s with mode %s', 'wp-staging' ), $file, $mode ) );
463
+ }
464
+
465
+ return $file_handle;
466
+ }
467
+
468
+ /**
469
+ * Write contents to a file
470
+ *
471
+ * @param resource $handle File handle to write to
472
+ * @param string $content Contents to write to the file
473
+ * @return integer
474
+ * @throws Exception
475
+ * @throws Exception
476
+ */
477
+ public function write( $handle, $content ) {
478
+ $write_result = @fwrite( $handle, $content );
479
+ if( false === $write_result ) {
480
+ if( ( $meta = \stream_get_meta_data( $handle ) ) ) {
481
+ throw new \Exception( sprintf( __( 'Unable to write to: %s', 'wp-staging' ), $meta['uri'] ) );
482
+ }
483
+ } elseif( strlen( $content ) !== $write_result ) {
484
+ throw new \Exception( __( 'Out of disk space.', 'wp-staging' ) );
485
+ }
486
+
487
+ return $write_result;
488
+ }
489
+
490
+ /**
491
+ * Execute the Current Step
492
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
493
+ * @return bool
494
+ */
495
+ protected function execute() {
496
+
497
+ // No job left to execute
498
+ if( $this->isFinished() ) {
499
+ $this->prepareResponse( true, false );
500
+ return false;
501
+ }
502
+
503
+
504
+ if( $this->options->currentStep == 0 ) {
505
+ $this->getWpRootFiles();
506
+ $this->prepareResponse( false, true );
507
+ return false;
508
+ }
509
+
510
+ if( $this->options->currentStep == 1 ) {
511
+ $this->getWpContentFiles();
512
+ $this->prepareResponse( false, true );
513
+ return false;
514
+ }
515
+
516
+ if( $this->options->currentStep == 2 ) {
517
+ $this->getWpIncludesFiles();
518
+ $this->prepareResponse( false, true );
519
+ return false;
520
+ }
521
+
522
+ if( $this->options->currentStep == 3 ) {
523
+ $this->getWpAdminFiles();
524
+ $this->prepareResponse( false, true );
525
+ return false;
526
+ }
527
+
528
+ if( $this->options->currentStep == 4 ) {
529
+ $this->getWpContentUploadsSites();
530
+ $this->prepareResponse( false, true );
531
+ return false;
532
+ }
533
+
534
+ if( isset( $this->options->extraDirectories[$this->options->currentStep - $this->total] ) ) {
535
+ $this->getExtraFiles( $this->options->extraDirectories[$this->options->currentStep - $this->total] );
536
+ $this->prepareResponse( false, true );
537
+ return false;
538
+ }
539
+
540
+
541
+ // Prepare response
542
+ $this->prepareResponse( false, true );
543
+ // Not finished
544
+ return true;
545
+ }
546
+
547
+ /**
548
+ * Checks Whether There is Any Job to Execute or Not
549
+ * @return bool
550
+ */
551
+ protected function isFinished() {
552
+ if( $this->options->currentStep >= $this->options->totalSteps ) {
553
+ return true;
554
+ }
555
+ }
556
+
557
+ /**
558
+ * Save files
559
+ * @return bool
560
+ */
561
+ protected function saveProgress() {
562
+ return $this->saveOptions();
563
+ }
564
+
565
+ /**
566
+ * Get files
567
+ * @return void
568
+ */
569
+ protected function getFiles() {
570
+ $fileName = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
571
+
572
+ if( false === ($this->files = @file_get_contents( $fileName )) ) {
573
+ $this->files = array();
574
+ return;
575
+ }
576
+
577
+ $this->files = explode( PHP_EOL, $this->files );
578
+ }
579
+
580
+ /**
581
+ * Replace forward slash with backslash directory separator
582
+ *
583
+ * @param string $path Path
584
+ *
585
+ * @return string
586
+ */
587
+ // private function sanitizeDirectorySeparator( $path ) {
588
+ // $string = str_replace( "/", "\\", $path );
589
+ // return str_replace( '\\\\', '\\', $string );
590
+ // }
591
+
592
+ /**
593
+ * Check if directory is excluded
594
+ * @param string $directory
595
+ * @return bool
596
+ */
597
+ protected function isDirectoryExcluded( $directory ) {
598
+ $directory = wpstg_replace_windows_directory_separator( $directory );
599
+ foreach ( $this->options->excludedDirectories as $excludedDirectory ) {
600
+ $excludedDirectory = wpstg_replace_windows_directory_separator( $excludedDirectory );
601
+ if( strpos( trailingslashit( $directory ), trailingslashit( $excludedDirectory ) ) === 0 ) {
602
+ return true;
603
+ }
604
+ }
605
+
606
+ return false;
607
+ }
608
+
609
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
apps/Backend/Modules/Jobs/Multisite/Files.php CHANGED
@@ -1,431 +1,481 @@
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 = $this->options->destinationDir;
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
- //$this->maxFilesPerRun = ($this->settings->cpuLoad === 'low') ? 50 : 1;
55
- }
56
-
57
- /**
58
- * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
59
- * @return void
60
- */
61
- protected function calculateTotalSteps() {
62
- $this->options->totalSteps = ceil( $this->options->totalFiles / $this->maxFilesPerRun );
63
- }
64
-
65
- /**
66
- * Execute the Current Step
67
- * Returns false when over threshold limits are hit or when the job is done, true otherwise
68
- * @return bool
69
- */
70
- protected function execute() {
71
- // Finished
72
- if( $this->isFinished() ) {
73
- $this->log( "Copying files finished" );
74
- $this->prepareResponse( true, false );
75
- return false;
76
- }
77
-
78
- // Get files and copy'em
79
- if( !$this->getFilesAndCopy() ) {
80
- $this->prepareResponse( false, false );
81
- return false;
82
- }
83
-
84
- // Prepare and return response
85
- $this->prepareResponse();
86
-
87
- // Not finished
88
- return true;
89
- }
90
-
91
- /**
92
- * Get files and copy
93
- * @return bool
94
- */
95
- private function getFilesAndCopy() {
96
- // Over limits threshold
97
- if( $this->isOverThreshold() ) {
98
- // Prepare response and save current progress
99
- $this->prepareResponse( false, false );
100
- $this->saveOptions();
101
- return false;
102
- }
103
-
104
- // Go to last copied line and than to next one
105
- //if ($this->options->copiedFiles != 0) {
106
- if( isset( $this->options->copiedFiles ) && $this->options->copiedFiles != 0 ) {
107
- $this->file->seek( $this->options->copiedFiles - 1 );
108
- }
109
-
110
- $this->file->setFlags( \SplFileObject::SKIP_EMPTY | \SplFileObject::READ_AHEAD );
111
-
112
- // Start time
113
- //$start = microtime( true );
114
- // Loop x files at a time
115
- //$this->maxFilesPerRun = 300;
116
- for ( $i = 0; $i < $this->maxFilesPerRun; $i++ ) {
117
-
118
- // Reached timeout
119
- // if( ( $timeout = apply_filters( 'wpstg_job_timeout', 10 ) ) ) {
120
- // if( ( \microtime( true ) - $start ) > $timeout ) {
121
- // // Prepare response and save current progress
122
- // $this->prepareResponse( false, true );
123
- // $this->saveOptions();
124
- // return false;
125
- // }
126
- // }
127
- // Increment copied files
128
- // Do this anytime to make sure to not stuck in the same step / files
129
- $this->options->copiedFiles++;
130
-
131
- // End of file
132
- if( $this->file->eof() ) {
133
- break;
134
- }
135
-
136
- $file = $this->file->fgets();
137
-
138
- // if( false !== strpos( $file, 'index.php' ) ) {
139
- // $test = $file;
140
- // }
141
- $this->copyFile( $file );
142
- }
143
-
144
-
145
-
146
- $totalFiles = $this->options->copiedFiles;
147
- // Log this only every 50 entries to keep the log small and to not block the rendering browser
148
- if( $this->options->copiedFiles % 50 == 0 ) {
149
- $this->log( "Total {$totalFiles} files processed" );
150
- }
151
-
152
- return true;
153
- }
154
-
155
- /**
156
- * Checks Whether There is Any Job to Execute or Not
157
- * @return bool
158
- */
159
- private function isFinished() {
160
- return (
161
- $this->options->currentStep > $this->options->totalSteps ||
162
- $this->options->copiedFiles >= $this->options->totalFiles
163
- );
164
- }
165
-
166
- /**
167
- * @param string $file
168
- * @return bool
169
- */
170
- private function copyFile( $file ) {
171
-
172
- $file = trim( \WPStaging\WPStaging::getWPpath() . $file );
173
-
174
- $directory = dirname( $file );
175
-
176
- // Directory is excluded
177
- if( $this->isDirectoryExcluded( $directory ) ) {
178
- $this->debugLog( "Skipping directory by rule: {$file}", Logger::TYPE_INFO );
179
- return false;
180
- }
181
-
182
- // File is excluded
183
- if( $this->isFileExcluded( $file ) ) {
184
- $this->debugLog( "Skipping file by rule: {$file}", Logger::TYPE_INFO );
185
- return false;
186
- }
187
- // Path + File is excluded
188
- if( $this->isFileExcludedFullPath( $file ) ) {
189
- $this->debugLog( "Skipping file by rule: {$file}", Logger::TYPE_INFO );
190
- return false;
191
- }
192
-
193
- // Invalid file, skipping it as if succeeded
194
- if( !is_file( $file ) ) {
195
- $this->log( "File doesn't exist {$file}", Logger::TYPE_WARNING );
196
- return true;
197
- }
198
- // Invalid file, skipping it as if succeeded
199
- if( !is_readable( $file ) ) {
200
- $this->log( "Can't read file {$file}", Logger::TYPE_WARNING );
201
- return true;
202
- }
203
-
204
-
205
- // Get file size
206
- $fileSize = filesize( $file );
207
-
208
- // File is over maximum allowed file size (8MB)
209
- if( $fileSize >= $this->settings->maxFileSize * 1000000 ) {
210
- $this->log( "Skipping big file: {$file}", Logger::TYPE_INFO );
211
- return false;
212
- }
213
-
214
- // Failed to get destination
215
- if( false === ($destination = $this->getDestination( $file )) ) {
216
- $this->log( "Can't get the destination of {$file}", Logger::TYPE_WARNING );
217
- return false;
218
- }
219
-
220
- // File is over batch size
221
- if( $fileSize >= $this->settings->batchSize ) {
222
- $this->log( "Trying to copy big file: {$file} -> {$destination}", Logger::TYPE_INFO );
223
- return $this->copyBig( $file, $destination, $this->settings->batchSize );
224
- }
225
-
226
- // Attempt to copy
227
- if( !@copy( $file, $destination ) ) {
228
- $errors = error_get_last();
229
- $this->log( "Files: Failed to copy file to destination. Error: {$errors['message']} {$file} -> {$destination}", Logger::TYPE_ERROR );
230
- return false;
231
- }
232
-
233
- // Set file permissions
234
- @chmod( $file, wpstg_get_permissions_for_file() );
235
-
236
- $this->setDirPermissions( $file );
237
-
238
- return true;
239
- }
240
-
241
- /**
242
- * Set directory permissions
243
- * @param type $file
244
- * @return boolean
245
- */
246
- private function setDirPermissions( $file ) {
247
- $dir = dirname( $file );
248
- if( is_dir( $dir ) ) {
249
- @chmod( $dir, wpstg_get_permissions_for_directory() );
250
- }
251
- return false;
252
- }
253
-
254
- /**
255
- * Gets destination file and checks if the directory exists, if it does not attempts to create it.
256
- * If creating destination directory fails, it returns false, gives destination full path otherwise
257
- * @param string $file
258
- * @return bool|string
259
- */
260
- private function getDestination( $file ) {
261
- $file = $this->getMultisiteUploadFolder( $file );
262
- $relativePath = str_replace( \WPStaging\WPStaging::getWPpath(), null, $file );
263
- $destinationPath = $this->destination . $relativePath;
264
- $destinationDirectory = dirname( $destinationPath );
265
-
266
- if( !is_dir( $destinationDirectory ) && !@mkdir( $destinationDirectory, wpstg_get_permissions_for_directory(), true ) ) {
267
- $this->log( "Files: Can not create directory {$destinationDirectory}", Logger::TYPE_ERROR );
268
- return false;
269
- }
270
-
271
- return $this->sanitizeDirectorySeparator( $destinationPath );
272
- }
273
-
274
- /**
275
- * Replace relative path of file if its located in multisite upload folder
276
- * wp-content/uploads/sites/SITEID or old wordpress structure wp-content/blogs.dir/SITEID/files
277
- * @return boolean
278
- */
279
- private function getMultisiteUploadFolder( $file ) {
280
- // Check first which method is used
281
- $uploads = wp_upload_dir();
282
- $basedir = $uploads['basedir'];
283
-
284
- if( false === strpos( $basedir, 'blogs.dir' ) ) {
285
- // Since WP 3.5
286
- $search = 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . get_current_blog_id();
287
- $replace = 'wp-content' . DIRECTORY_SEPARATOR . 'uploads';
288
- $uploadsFolder = str_replace( $search, $replace, $file );
289
- } else {
290
- // old blog structure
291
- $search = 'wp-content' . DIRECTORY_SEPARATOR . 'blogs.dir' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR . 'files';
292
- $replace = 'wp-content' . DIRECTORY_SEPARATOR . 'uploads';
293
- $uploadsFolder = str_replace( $search, $replace, $file );
294
- }
295
-
296
- return $uploadsFolder;
297
- }
298
-
299
- /**
300
- * Copy bigger files than $this->settings->batchSize
301
- * @param string $src
302
- * @param string $dst
303
- * @param int $buffersize
304
- * @return boolean
305
- */
306
- private function copyBig( $src, $dst, $buffersize ) {
307
- $src = fopen( $src, 'r' );
308
- $dest = fopen( $dst, 'w' );
309
-
310
- // Try first method:
311
- while ( !feof( $src ) ) {
312
- if( false === fwrite( $dest, fread( $src, $buffersize ) ) ) {
313
- $error = true;
314
- }
315
- }
316
- // Try second method if first one failed
317
- if( isset( $error ) && ($error === true) ) {
318
- while ( !feof( $src ) ) {
319
- if( false === stream_copy_to_stream( $src, $dest, 1024 ) ) {
320
- $this->log( "Can not copy file; {$src} -> {$dest}" );
321
- fclose( $src );
322
- fclose( $dest );
323
- return false;
324
- }
325
- }
326
- }
327
- // Close any open handler
328
- fclose( $src );
329
- fclose( $dest );
330
- return true;
331
- }
332
-
333
- /**
334
- * Check if certain file is excluded from copying process
335
- *
336
- * @param string $file filename including ending without full path
337
- * @return boolean
338
- */
339
- private function isFileExcluded( $file ) {
340
- // If file name exists
341
- if( in_array( basename( $file ), $this->options->excludedFiles ) ) {
342
- return true;
343
- }
344
-
345
- // Do not copy wp-config.php if the clone gets updated. This is for security purposes,
346
- // because if the updating process fails, the staging site would not be accessable any longer
347
- if( isset( $this->options->mainJob ) && $this->options->mainJob == "updating" && stripos( strrev( $file ), strrev( "wp-config.php" ) ) === 0 ) {
348
- return true;
349
- }
350
-
351
-
352
- return false;
353
- }
354
-
355
- /**
356
- * Check if certain file is excluded from copying process
357
- *
358
- * @param string $file filename including ending + (part) path e.g wp-content/db.php
359
- * @return boolean
360
- */
361
- private function isFileExcludedFullPath( $file ) {
362
- // If path + file exists
363
- foreach ( $this->options->excludedFilesFullPath as $excludedFile ) {
364
- if( false !== strpos( $file, $excludedFile ) ) {
365
- return true;
366
- }
367
- }
368
-
369
- return false;
370
- }
371
-
372
- /**
373
- * Replace backward slash with forward slash directory separator
374
- * Escape Windows Backward Slash - Compatibility Fix
375
-
376
- * @param string $path Path
377
- *
378
- * @return string
379
- */
380
- private function sanitizeDirectorySeparator( $path ) {
381
- //$string = str_replace( '\\', '/', $path );
382
- //$string = str_replace( "/", "\\", $path );
383
- //return str_replace( '\\\\', '\\', $string );
384
- //return preg_replace( '/[\\\\]+/', '\\\\\\\\', $string );
385
- return preg_replace( '/[\\\\]+/', '/', $path );
386
- }
387
-
388
- /**
389
- * Check if directory is excluded from copying
390
- * @param string $directory
391
- * @return bool
392
- */
393
- private function isDirectoryExcluded( $directory ) {
394
- // Make sure that wp-staging-pro directory / plugin is never excluded
395
- if( false !== strpos( $directory, 'wp-staging' ) || false !== strpos( $directory, 'wp-staging-pro' ) ) {
396
- return false;
397
- }
398
- // $directory = wpstg_replace_windows_directory_separator( $directory );
399
- // $directory = trailingslashit( $directory );
400
- $directory = trailingslashit( $this->sanitizeDirectorySeparator( $directory ) );
401
-
402
- foreach ( $this->options->excludedDirectories as $excludedDirectory ) {
403
- // $excludedDirectory = wpstg_replace_windows_directory_separator( $excludedDirectory );
404
- // $excludedDirectory = trailingslashit( $excludedDirectory );
405
- $excludedDirectory = trailingslashit( $this->sanitizeDirectorySeparator( $excludedDirectory ) );
406
- if( strpos( $directory, $excludedDirectory ) === 0 && !$this->isExtraDirectory( $directory ) ) {
407
- return true;
408
- }
409
- }
410
-
411
- return false;
412
- }
413
-
414
- /**
415
- * Check if directory is an extra directory and should be copied
416
- * @param string $directory
417
- * @return boolean
418
- */
419
- private function isExtraDirectory( $directory ) {
420
- $directory = $this->sanitizeDirectorySeparator( $directory );
421
-
422
- foreach ( $this->options->extraDirectories as $extraDirectory ) {
423
- if( strpos( $directory, $this->sanitizeDirectorySeparator( $extraDirectory ) ) === 0 ) {
424
- return true;
425
- }
426
- }
427
-
428
- return false;
429
- }
430
-
431
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 = $this->options->destinationDir;
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
+ //$this->maxFilesPerRun = ($this->settings->cpuLoad === 'low') ? 50 : 1;
55
+ }
56
+
57
+ /**
58
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
59
+ * @return void
60
+ */
61
+ protected function calculateTotalSteps() {
62
+ $this->options->totalSteps = ceil( $this->options->totalFiles / $this->maxFilesPerRun );
63
+ }
64
+
65
+ /**
66
+ * Execute the Current Step
67
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
68
+ * @return bool
69
+ */
70
+ protected function execute() {
71
+ // Finished
72
+ if( $this->isFinished() ) {
73
+ $this->log( "Copying files finished" );
74
+ $this->prepareResponse( true, false );
75
+ return false;
76
+ }
77
+
78
+ // Get files and copy'em
79
+ if( !$this->getFilesAndCopy() ) {
80
+ $this->prepareResponse( false, false );
81
+ return false;
82
+ }
83
+
84
+ // Prepare and return response
85
+ $this->prepareResponse();
86
+
87
+ // Not finished
88
+ return true;
89
+ }
90
+
91
+ /**
92
+ * Get files and copy
93
+ * @return bool
94
+ */
95
+ private function getFilesAndCopy() {
96
+ // Over limits threshold
97
+ if( $this->isOverThreshold() ) {
98
+ // Prepare response and save current progress
99
+ $this->prepareResponse( false, false );
100
+ $this->saveOptions();
101
+ return false;
102
+ }
103
+
104
+ // Go to last copied line and than to next one
105
+ //if ($this->options->copiedFiles != 0) {
106
+ if( isset( $this->options->copiedFiles ) && $this->options->copiedFiles != 0 ) {
107
+ $this->file->seek( $this->options->copiedFiles - 1 );
108
+ }
109
+
110
+ $this->file->setFlags( \SplFileObject::SKIP_EMPTY | \SplFileObject::READ_AHEAD );
111
+
112
+ // Start time
113
+ //$start = microtime( true );
114
+ // Loop x files at a time
115
+ //$this->maxFilesPerRun = 300;
116
+ for ( $i = 0; $i < $this->maxFilesPerRun; $i++ ) {
117
+
118
+ // Reached timeout
119
+ // if( ( $timeout = apply_filters( 'wpstg_job_timeout', 10 ) ) ) {
120
+ // if( ( \microtime( true ) - $start ) > $timeout ) {
121
+ // // Prepare response and save current progress
122
+ // $this->prepareResponse( false, true );
123
+ // $this->saveOptions();
124
+ // return false;
125
+ // }
126
+ // }
127
+ // Increment copied files
128
+ // Do this anytime to make sure to not stuck in the same step / files
129
+ $this->options->copiedFiles++;
130
+
131
+ // End of file
132
+ if( $this->file->eof() ) {
133
+ break;
134
+ }
135
+
136
+ $file = $this->file->fgets();
137
+
138
+
139
+ $this->copyFile( $file );
140
+ }
141
+
142
+
143
+
144
+ $totalFiles = $this->options->copiedFiles;
145
+ // Log this only every 50 entries to keep the log small and to not block the rendering browser
146
+ if( $this->options->copiedFiles % 50 == 0 ) {
147
+ $this->log( "Total {$totalFiles} files processed" );
148
+ }
149
+
150
+ return true;
151
+ }
152
+
153
+ /**
154
+ * Checks Whether There is Any Job to Execute or Not
155
+ * @return bool
156
+ */
157
+ private function isFinished() {
158
+ return (
159
+ !isset( $this->options->isRunning ) ||
160
+ $this->options->currentStep > $this->options->totalSteps ||
161
+ $this->options->copiedFiles >= $this->options->totalFiles
162
+ );
163
+ }
164
+
165
+ /**
166
+ * @param string $file
167
+ * @return bool
168
+ */
169
+ private function copyFile( $file ) {
170
+
171
+ $file = trim( \WPStaging\WPStaging::getWPpath() . $file );
172
+
173
+ $file = wpstg_replace_windows_directory_separator($file);
174
+
175
+ $directory = dirname( $file );
176
+
177
+ // Directory is excluded
178
+ if( $this->isDirectoryExcluded( $directory ) ) {
179
+ $this->debugLog( "Skipping directory by rule: {$file}", Logger::TYPE_INFO );
180
+ return false;
181
+ }
182
+
183
+ // File is excluded
184
+ if( $this->isFileExcluded( $file ) ) {
185
+ $this->debugLog( "Skipping file by rule: {$file}", Logger::TYPE_INFO );
186
+ return false;
187
+ }
188
+ // Path + File is excluded
189
+ if( $this->isFileExcludedFullPath( $file ) ) {
190
+ $this->debugLog( "Skipping file by rule: {$file}", Logger::TYPE_INFO );
191
+ return false;
192
+ }
193
+
194
+ // Invalid file, skipping it as if succeeded
195
+ if( !is_file( $file ) ) {
196
+ $this->log( "File doesn't exist {$file}", Logger::TYPE_WARNING );
197
+ return true;
198
+ }
199
+ // Invalid file, skipping it as if succeeded
200
+ if( !is_readable( $file ) ) {
201
+ $this->log( "Can't read file {$file}", Logger::TYPE_WARNING );
202
+ return true;
203
+ }
204
+
205
+
206
+ // Get file size
207
+ $fileSize = filesize( $file );
208
+
209
+ // File is over maximum allowed file size (8MB)
210
+ if( $fileSize >= $this->settings->maxFileSize * 1000000 ) {
211
+ $this->log( "Skipping big file: {$file}", Logger::TYPE_INFO );
212
+ return false;
213
+ }
214
+
215
+ // Failed to get destination
216
+ if( false === ($destination = $this->getDestination( $file )) ) {
217
+ $this->log( "Can't get the destination of {$file}", Logger::TYPE_WARNING );
218
+ return false;
219
+ }
220
+
221
+ // File is over batch size
222
+ if( $fileSize >= $this->settings->batchSize ) {
223
+ $this->log( "Trying to copy big file: {$file} -> {$destination}", Logger::TYPE_INFO );
224
+ return $this->copyBig( $file, $destination, $this->settings->batchSize );
225
+ }
226
+
227
+ // Attempt to copy
228
+ if( !@copy( $file, $destination ) ) {
229
+ $errors = error_get_last();
230
+ $this->log( "Files: Failed to copy file to destination. Error: {$errors['message']} {$file} -> {$destination}", Logger::TYPE_ERROR );
231
+ return false;
232
+ }
233
+
234
+ // Set file permissions
235
+ @chmod( $destination, wpstg_get_permissions_for_file() );
236
+
237
+ $this->setDirPermissions( $destination );
238
+
239
+ return true;
240
+ }
241
+
242
+ /**
243
+ * Set directory permissions
244
+ * @param type $file
245
+ * @return boolean
246
+ */
247
+ private function setDirPermissions( $file ) {
248
+ $dir = dirname( $file );
249
+ if( is_dir( $dir ) ) {
250
+ @chmod( $dir, wpstg_get_permissions_for_directory() );
251
+ }
252
+ return false;
253
+ }
254
+
255
+ /**
256
+ * Gets destination file and checks if the directory exists, if it does not attempts to create it.
257
+ * If creating destination directory fails, it returns false, gives destination full path otherwise
258
+ * @param string $file
259
+ * @return bool|string
260
+ */
261
+ private function getDestination( $file ) {
262
+ //$file = $this->getMultisiteUploadFolder( $file );
263
+ $file = wpstg_replace_windows_directory_separator($file);
264
+ $rootPath = wpstg_replace_windows_directory_separator(\WPStaging\WPStaging::getWPpath());
265
+ $relativePath = str_replace( $rootPath, null, $file );
266
+ $destinationPath = $this->destination . $relativePath;
267
+ $destinationDirectory = dirname( $destinationPath );
268
+
269
+ if( !is_dir( $destinationDirectory ) && !@mkdir( $destinationDirectory, wpstg_get_permissions_for_directory(), true ) ) {
270
+ $this->log( "Files: Can not create directory {$destinationDirectory}", Logger::TYPE_ERROR );
271
+ return false;
272
+ }
273
+
274
+ return $this->sanitizeDirectorySeparator( $destinationPath );
275
+ }
276
+
277
+ /**
278
+ * Replace relative path of file if its located in multisite upload folder
279
+ * wp-content/uploads/sites/SITEID or old wordpress structure wp-content/blogs.dir/SITEID/files
280
+ * @return boolean
281
+ */
282
+ private function getMultisiteUploadFolder( $file ) {
283
+ // Check first which method is used
284
+ $uploads = wp_upload_dir();
285
+ $basedir = $uploads['basedir'];
286
+
287
+ if( false === strpos( $basedir, 'blogs.dir' ) ) {
288
+ // Since WP 3.5
289
+ $search = 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . get_current_blog_id();
290
+ $replace = 'wp-content' . DIRECTORY_SEPARATOR . 'uploads';
291
+ $uploadsFolder = str_replace( $search, $replace, $file );
292
+ } else {
293
+ // old blog structure
294
+ $search = 'wp-content' . DIRECTORY_SEPARATOR . 'blogs.dir' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR . 'files';
295
+ $replace = 'wp-content' . DIRECTORY_SEPARATOR . 'uploads';
296
+ $uploadsFolder = str_replace( $search, $replace, $file );
297
+ }
298
+
299
+ return $uploadsFolder;
300
+ }
301
+
302
+ /**
303
+ * Copy bigger files than $this->settings->batchSize
304
+ * @param string $src
305
+ * @param string $dst
306
+ * @param int $buffersize
307
+ * @return boolean
308
+ */
309
+ private function copyBig( $src, $dst, $buffersize ) {
310
+ $src = fopen( $src, 'r' );
311
+ $dest = fopen( $dst, 'w' );
312
+
313
+ // Try first method:
314
+ while ( !feof( $src ) ) {
315
+ if( false === fwrite( $dest, fread( $src, $buffersize ) ) ) {
316
+ $error = true;
317
+ }
318
+ }
319
+ // Try second method if first one failed
320
+ if( isset( $error ) && ($error === true) ) {
321
+ while ( !feof( $src ) ) {
322
+ if( false === stream_copy_to_stream( $src, $dest, 1024 ) ) {
323
+ $this->log( "Can not copy file; {$src} -> {$dest}" );
324
+ fclose( $src );
325
+ fclose( $dest );
326
+ return false;
327
+ }
328
+ }
329
+ }
330
+ // Close any open handler
331
+ fclose( $src );
332
+ fclose( $dest );
333
+ return true;
334
+ }
335
+
336
+ /**
337
+ * Check if certain file is excluded from copying process
338
+ *
339
+ * @param string $file filename including ending without full path
340
+ * @return boolean
341
+ */
342
+ private function isFileExcluded( $file ) {
343
+
344
+ $excludedFiles = ( array ) $this->options->excludedFiles;
345
+
346
+ $basenameFile = basename( $file );
347
+
348
+
349
+ // Remove .htaccess and web.config from array excludedFiles if staging site is copied to a subdomain
350
+ //if( $this->isCustomDirectory() ) {
351
+ if( false === $this->isIdenticalHostname() ) {
352
+ $excludedFiles = \array_diff( $excludedFiles, array("web.config", ".htaccess") );
353
+ }
354
+
355
+
356
+ // If file name exists
357
+ if( in_array( $basenameFile, $excludedFiles ) ) {
358
+ return true;
359
+ }
360
+
361
+ // Check for wildcards
362
+ foreach ($excludedFiles as $pattern){
363
+ if(fnmatch($pattern, $basenameFile)){
364
+ return true;
365
+ }
366
+ }
367
+
368
+ // Do not copy wp-config.php if the clone gets updated. This is for security purposes,
369
+ // because if the updating process fails, the staging site would not be accessable any longer
370
+ if( isset( $this->options->mainJob ) && $this->options->mainJob == "updating" && stripos( strrev( $file ), strrev( "wp-config.php" ) ) === 0 ) {
371
+ return true;
372
+ }
373
+
374
+
375
+ return false;
376
+ }
377
+
378
+ /**
379
+ * Check if custom target directory is used
380
+ * @return boolean
381
+ */
382
+ // private function isCustomDirectory() {
383
+ //
384
+ // if( empty( $this->options->cloneDir ) ) {
385
+ // return false;
386
+ // }
387
+ // return true;
388
+ // }
389
+
390
+ /**
391
+ * Check if production and staging hostname are identical
392
+ * If they are not identical we assume website is cloned to a subdomain and not into a subfolder
393
+ * @return boolean
394
+ */
395
+ private function isIdenticalHostname() {
396
+ // hostname of production site without scheme
397
+ $siteurl = get_site_url();
398
+ $url = parse_url( $siteurl );
399
+ $productionHostname = $url['host'];
400
+
401
+ // hostname of staging site without scheme
402
+ $cloneUrl = empty( $this->options->cloneHostname ) ? $url : parse_url( $this->options->cloneHostname );
403
+ $targetHostname = $cloneUrl['host'];
404
+
405
+ // Check if target hostname beginns with the production hostname
406
+ // Only compare the hostname without path
407
+ if( wpstg_starts_with( $productionHostname, $targetHostname ) ) {
408
+ return true;
409
+ }
410
+ return false;
411
+ }
412
+
413
+ /**
414
+ * Check if certain file is excluded from copying process
415
+ *
416
+ * @param string $file filename including ending + (part) path e.g wp-content/db.php
417
+ * @return boolean
418
+ */
419
+ private function isFileExcludedFullPath( $file ) {
420
+ // If path + file exists
421
+ foreach ( $this->options->excludedFilesFullPath as $excludedFile ) {
422
+ if( false !== strpos( $file, $excludedFile ) ) {
423
+ return true;
424
+ }
425
+ }
426
+
427
+ return false;
428
+ }
429
+
430
+ /**
431
+ * Replace backward slash with forward slash directory separator
432
+ * Windows Compatibility Fix
433
+ *
434
+ * @param string $path Path
435
+ * @return string
436
+ */
437
+ private function sanitizeDirectorySeparator( $path ) {
438
+ return preg_replace( '/[\\\\]+/', '/', $path );
439
+ }
440
+
441
+ /**
442
+ * Check if directory is excluded from copying
443
+ * @param string $directory
444
+ * @return bool
445
+ */
446
+ private function isDirectoryExcluded( $directory ) {
447
+ // Make sure that wp-staging-pro directory / plugin is never excluded
448
+ if( false !== strpos( $directory, 'wp-staging' ) || false !== strpos( $directory, 'wp-staging-pro' ) ) {
449
+ return false;
450
+ }
451
+
452
+ $directory = trailingslashit( $this->sanitizeDirectorySeparator( $directory ) );
453
+
454
+ foreach ( $this->options->excludedDirectories as $excludedDirectory ) {
455
+ $excludedDirectory = trailingslashit( $this->sanitizeDirectorySeparator( $excludedDirectory ) );
456
+ if( strpos( $directory, $excludedDirectory ) === 0 && !$this->isExtraDirectory( $directory ) ) {
457
+ return true;
458
+ }
459
+ }
460
+
461
+ return false;
462
+ }
463
+
464
+ /**
465
+ * Check if directory is an extra directory and should be copied
466
+ * @param string $directory
467
+ * @return boolean
468
+ */
469
+ private function isExtraDirectory( $directory ) {
470
+ $directory = $this->sanitizeDirectorySeparator( $directory );
471
+
472
+ foreach ( $this->options->extraDirectories as $extraDirectory ) {
473
+ if( strpos( $directory, $this->sanitizeDirectorySeparator( $extraDirectory ) ) === 0 ) {
474
+ return true;
475
+ }
476
+ }
477
+
478
+ return false;
479
+ }
480
+
481
+ }
apps/Backend/Modules/Jobs/Multisite/Finish.php CHANGED
@@ -1,139 +1,141 @@
1
- <?php
2
-
3
- namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
-
5
- use WPStaging\WPStaging;
6
- use WPStaging\Backend\Modules\Jobs\Job;
7
- use WPStaging\Utils\Multisite;
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
- * Start Module
23
- * @return object
24
- */
25
- public function start() {
26
- // sanitize the clone name before saving
27
- $this->clone = preg_replace( "#\W+#", '-', strtolower( $this->options->clone ) );
28
-
29
- // Delete Cache Files
30
- $this->deleteCacheFiles();
31
-
32
- // Prepare clone records & save scanned directories for delete job later
33
- $this->prepareCloneDataRecords();
34
-
35
- $this->options->isRunning = false;
36
-
37
- $multisite = new Multisite;
38
-
39
-
40
-
41
- $return = array(
42
- "directoryName" => $this->options->cloneDirectoryName,
43
- "path" => $this->options->destinationDir,
44
- "url" => $this->getDestinationUrl(),
45
- "number" => $this->options->cloneNumber,
46
- "version" => \WPStaging\WPStaging::VERSION,
47
- "status" => 'finished',
48
- "prefix" => $this->options->prefix,
49
- "last_msg" => $this->logger->getLastLogMsg(),
50
- "job" => $this->options->currentJob,
51
- "percentage" => 100
52
- );
53
-
54
- //$this->flush();
55
-
56
- return ( object ) $return;
57
- }
58
-
59
- /**
60
- * Delete Cache Files
61
- */
62
- protected function deleteCacheFiles() {
63
- $this->log( "Finish: Deleting clone job's cache files..." );
64
-
65
- // Clean cache files
66
- $this->cache->delete( "clone_options" );
67
- $this->cache->delete( "files_to_copy" );
68
-
69
- $this->log( "Finish: Clone job's cache files have been deleted!" );
70
- }
71
-
72
- /**
73
- * Prepare clone records
74
- * @return bool
75
- */
76
- protected function prepareCloneDataRecords() {
77
- // Check if clones still exist
78
- $this->log( "Finish: Verifying existing clones..." );
79
-
80
- // Clone data already exists
81
- if( isset( $this->options->existingClones[$this->options->clone] ) ) {
82
- $this->options->existingClones[$this->options->clone]['datetime'] = time();
83
- $this->options->existingClones[$this->options->clone]['url'] = $this->getDestinationUrl();
84
- $this->options->existingClones[$this->options->clone]['status'] = 'finished';
85
- update_option( "wpstg_existing_clones_beta", $this->options->existingClones );
86
- $this->log( "Finish: The job finished!" );
87
- return true;
88
- }
89
-
90
- // Save new clone data
91
- $this->log( "Finish: {$this->options->clone}'s clone job's data is not in database, generating data" );
92
-
93
- // sanitize the clone name before saving
94
- //$clone = preg_replace("#\W+#", '-', strtolower($this->options->clone));
95
-
96
- $this->options->existingClones[$this->clone] = array(
97
- "directoryName" => $this->options->cloneDirectoryName,
98
- "path" => $this->options->destinationDir,
99
- "url" => $this->getDestinationUrl(),
100
- "number" => $this->options->cloneNumber,
101
- "version" => \WPStaging\WPStaging::VERSION,
102
- "status" => "finished",
103
- "prefix" => $this->options->prefix,
104
- "datetime" => time(),
105
- "databaseUser" => $this->options->databaseUser,
106
- "databasePassword" => $this->options->databasePassword,
107
- "databaseDatabase" => $this->options->databaseDatabase,
108
- "databaseServer" => $this->options->databaseServer,
109
- "databasePrefix" => $this->options->databasePrefix,
110
- );
111
-
112
- if( false === update_option( "wpstg_existing_clones_beta", $this->options->existingClones ) ) {
113
- $this->log( "Finish: Failed to save {$this->options->clone}'s clone job data to database'" );
114
- return false;
115
- }
116
-
117
- return true;
118
- }
119
-
120
- /**
121
- * Get destination Hostname depending on wheather WP has been installed in sub dir or not
122
- * @return type
123
- */
124
- private function getDestinationUrl() {
125
-
126
- if( !empty( $this->options->cloneHostname ) ) {
127
- return $this->options->cloneHostname;
128
- }
129
-
130
- //return trailingslashit( $this->multisiteHomeDomain ) . $this->options->cloneDirectoryName;
131
- // Get the path to the main multisite without appending and trailingslash e.g. wordpress
132
- $multisitePath = defined( 'PATH_CURRENT_SITE') ? PATH_CURRENT_SITE : '/';
133
- return rtrim( $this->multisiteHomeDomain, '/\\' ) . $multisitePath . $this->options->cloneDirectoryName;
134
- //$multisitePath = defined( 'PATH_CURRENT_SITE' ) ? str_replace( '/', '', PATH_CURRENT_SITE ) : '';
135
-
136
- //return trailingslashit( $this->multisiteHomeDomain ) . $multisitePath . '/' . $this->options->cloneDirectoryName;
137
- }
138
-
139
- }
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
+
5
+ use WPStaging\WPStaging;
6
+ use WPStaging\Backend\Modules\Jobs\Job;
7
+ use WPStaging\Utils\Multisite;
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
+ * Start Module
23
+ * @return object
24
+ */
25
+ public function start() {
26
+ // sanitize the clone name before saving
27
+ $this->clone = preg_replace( "#\W+#", '-', strtolower( $this->options->clone ) );
28
+
29
+ // Delete Cache Files
30
+ $this->deleteCacheFiles();
31
+
32
+ // Prepare clone records & save scanned directories for delete job later
33
+ $this->prepareCloneDataRecords();
34
+
35
+ $this->options->isRunning = false;
36
+
37
+ $multisite = new Multisite;
38
+
39
+
40
+
41
+ $return = array(
42
+ "directoryName" => $this->options->cloneDirectoryName,
43
+ "path" => trailingslashit( $this->options->destinationDir ),
44
+ "url" => $this->getDestinationUrl(),
45
+ "number" => $this->options->cloneNumber,
46
+ "version" => \WPStaging\WPStaging::VERSION,
47
+ "status" => 'finished',
48
+ "prefix" => $this->options->prefix,
49
+ "last_msg" => $this->logger->getLastLogMsg(),
50
+ "job" => $this->options->currentJob,
51
+ "percentage" => 100
52
+ );
53
+
54
+ //$this->flush();
55
+ do_action( 'wpstg_cloning_complete', $this->options );
56
+
57
+
58
+ return ( object ) $return;
59
+ }
60
+
61
+ /**
62
+ * Delete Cache Files
63
+ */
64
+ protected function deleteCacheFiles() {
65
+ $this->log( "Finish: Deleting clone job's cache files..." );
66
+
67
+ // Clean cache files
68
+ $this->cache->delete( "clone_options" );
69
+ $this->cache->delete( "files_to_copy" );
70
+
71
+ $this->log( "Finish: Clone job's cache files have been deleted!" );
72
+ }
73
+
74
+ /**
75
+ * Prepare clone records
76
+ * @return bool
77
+ */
78
+ protected function prepareCloneDataRecords() {
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
+ $this->options->existingClones[$this->options->clone]['datetime'] = time();
85
+ $this->options->existingClones[$this->options->clone]['url'] = $this->getDestinationUrl();
86
+ $this->options->existingClones[$this->options->clone]['status'] = 'finished';
87
+ $this->options->existingClones[$this->options->clone]['prefix'] = $this->options->prefix;
88
+ update_option( "wpstg_existing_clones_beta", $this->options->existingClones );
89
+ $this->log( "Finish: The job finished!" );
90
+ return true;
91
+ }
92
+
93
+ // Save new clone data
94
+ $this->log( "Finish: {$this->options->clone}'s clone job's data is not in database, generating data" );
95
+
96
+ // sanitize the clone name before saving
97
+ //$clone = preg_replace("#\W+#", '-', strtolower($this->options->clone));
98
+
99
+ $this->options->existingClones[$this->clone] = array(
100
+ "directoryName" => $this->options->cloneDirectoryName,
101
+ "path" => trailingslashit( $this->options->destinationDir ),
102
+ "url" => $this->getDestinationUrl(),
103
+ "number" => $this->options->cloneNumber,
104
+ "version" => \WPStaging\WPStaging::VERSION,
105
+ "status" => "finished",
106
+ "prefix" => $this->options->prefix,
107
+ "datetime" => time(),
108
+ "databaseUser" => $this->options->databaseUser,
109
+ "databasePassword" => $this->options->databasePassword,
110
+ "databaseDatabase" => $this->options->databaseDatabase,
111
+ "databaseServer" => $this->options->databaseServer,
112
+ "databasePrefix" => $this->options->databasePrefix,
113
+ );
114
+
115
+ if( false === update_option( "wpstg_existing_clones_beta", $this->options->existingClones ) ) {
116
+ $this->log( "Finish: Failed to save {$this->options->clone}'s clone job data to database'" );
117
+ return false;
118
+ }
119
+
120
+ return true;
121
+ }
122
+
123
+ /**
124
+ * Get destination Hostname depending on wheather WP has been installed in sub dir or not
125
+ * @return type
126
+ */
127
+ private function getDestinationUrl() {
128
+
129
+ if( !empty( $this->options->cloneHostname ) ) {
130
+ return $this->options->cloneHostname;
131
+ }
132
+
133
+ //return trailingslashit( $this->multisiteHomeDomain ) . $this->options->cloneDirectoryName;
134
+ // Get the path to the main multisite without appending and trailingslash e.g. wordpress
135
+ $multisitePath = defined( 'PATH_CURRENT_SITE' ) ? PATH_CURRENT_SITE : '/';
136
+ return rtrim( $this->multisiteHomeDomain, '/\\' ) . $multisitePath . $this->options->cloneDirectoryName;
137
+ //$multisitePath = defined( 'PATH_CURRENT_SITE' ) ? str_replace( '/', '', PATH_CURRENT_SITE ) : '';
138
+ //return trailingslashit( $this->multisiteHomeDomain ) . $multisitePath . '/' . $this->options->cloneDirectoryName;
139
+ }
140
+
141
+ }
apps/Backend/Modules/Jobs/Multisite/SearchReplace.php CHANGED
@@ -1,816 +1,839 @@
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
- *
34
- * @var Obj
35
- */
36
- private $strings;
37
-
38
- /**
39
- *
40
- * @var string
41
- */
42
- private $destinationHostname;
43
-
44
- /**
45
- *
46
- * @var string
47
- */
48
- private $sourceHostname;
49
-
50
- /**
51
- *
52
- * @var string
53
- */
54
- //private $targetDir;
55
-
56
- /**
57
- * The prefix of the new database tables which are used for the live site after updating tables
58
- * @var string
59
- */
60
- public $tmpPrefix;
61
-
62
- /**
63
- * Initialize
64
- */
65
- public function initialize() {
66
- $this->total = count( $this->options->tables );
67
- $this->db = WPStaging::getInstance()->get( "wpdb" );
68
- $this->tmpPrefix = $this->options->prefix;
69
- $this->strings = new Strings();
70
- $this->sourceHostname = $this->getSourceHostname();
71
- $this->destinationHostname = $this->getDestinationHostname();
72
- }
73
-
74
- public function start() {
75
- // Skip job. Nothing to do
76
- if( $this->options->totalSteps === 0 ) {
77
- $this->prepareResponse( true, false );
78
- }
79
-
80
- $this->run();
81
-
82
- // Save option, progress
83
- $this->saveOptions();
84
-
85
- return ( object ) $this->response;
86
- }
87
-
88
- /**
89
- * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
90
- * @return void
91
- */
92
- protected function calculateTotalSteps() {
93
- $this->options->totalSteps = $this->total;
94
- }
95
-
96
- /**
97
- * Execute the Current Step
98
- * Returns false when over threshold limits are hit or when the job is done, true otherwise
99
- * @return bool
100
- */
101
- protected function execute() {
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->options->currentStep > $this->total || !isset( $this->options->tables[$this->options->currentStep] ) ) {
112
- $this->prepareResponse( true, false );
113
- return false;
114
- }
115
-
116
- // Table is excluded
117
- if( in_array( $this->options->tables[$this->options->currentStep], $this->options->excludedTables ) ) {
118
- $this->prepareResponse();
119
- return true;
120
- }
121
-
122
- // Search & Replace
123
- if( !$this->stopExecution() && !$this->updateTable( $this->options->tables[$this->options->currentStep] ) ) {
124
- // Prepare Response
125
- $this->prepareResponse( false, false );
126
-
127
- // Not finished
128
- return true;
129
- }
130
-
131
-
132
- // Prepare Response
133
- $this->prepareResponse();
134
-
135
- // Not finished
136
- return true;
137
- }
138
-
139
- /**
140
- * Stop Execution immediately
141
- * return mixed bool | json
142
- */
143
- private function stopExecution() {
144
- if( $this->db->prefix == $this->tmpPrefix ) {
145
- $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.' );
146
- }
147
- return false;
148
- }
149
-
150
- /**
151
- * Copy Tables
152
- * @param string $tableName
153
- * @return bool
154
- */
155
- private function updateTable( $tableName ) {
156
- $strings = new Strings();
157
- $table = $strings->str_replace_first( $this->db->prefix, '', $tableName );
158
- $newTableName = $this->tmpPrefix . $table;
159
-
160
- // Save current job
161
- $this->setJob( $newTableName );
162
-
163
- // Beginning of the job
164
- if( !$this->startJob( $newTableName, $tableName ) ) {
165
- return true;
166
- }
167
- // Copy data
168
- $this->startReplace( $newTableName );
169
-
170
- // Finish the step
171
- return $this->finishStep();
172
- }
173
-
174
- /**
175
- * Get source Hostname depending on wheather WP has been installed in sub dir or not
176
- * @return type
177
- */
178
- public function getSourceHostname() {
179
-
180
- if( $this->isSubDir() ) {
181
- return trailingslashit( $this->multisiteHomeUrlWithoutScheme ) . '/' . $this->getSubDir();
182
- }
183
- return $this->multisiteHomeUrlWithoutScheme;
184
- }
185
-
186
- /**
187
- * Get destination Hostname depending on wheather WP has been installed in sub dir or not
188
- * @return type
189
- */
190
- public function getDestinationHostname() {
191
-
192
- if( !empty( $this->options->cloneHostname ) ) {
193
- return $this->strings->getUrlWithoutScheme( $this->options->cloneHostname );
194
- }
195
-
196
- if( $this->isSubDir() ) {
197
- return trailingslashit( $this->strings->getUrlWithoutScheme( $this->multisiteDomainWithoutScheme ) ) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName;
198
- }
199
-
200
- // Get the path to the main multisite without appending and trailingslash e.g. wordpress
201
- $multisitePath = defined( 'PATH_CURRENT_SITE') ? PATH_CURRENT_SITE : '/';
202
- $url = rtrim( $this->strings->getUrlWithoutScheme( $this->multisiteDomainWithoutScheme ), '/\\' ) . $multisitePath . $this->options->cloneDirectoryName;
203
- //$multisitePath = defined( 'PATH_CURRENT_SITE' ) ? str_replace( '/', '', PATH_CURRENT_SITE ) : '';
204
- //$url = trailingslashit( $this->strings->getUrlWithoutScheme( $this->multisiteDomainWithoutScheme ) ) . $multisitePath . '/' . $this->options->cloneDirectoryName;
205
- return $url;
206
- }
207
-
208
- /**
209
- * Get the install sub directory if WP is installed in sub directory
210
- * @return string
211
- */
212
- private function getSubDir() {
213
- $home = get_option( 'home' );
214
- $siteurl = get_option( 'siteurl' );
215
-
216
- if( empty( $home ) || empty( $siteurl ) ) {
217
- return '';
218
- }
219
-
220
- $dir = str_replace( $home, '', $siteurl );
221
- return str_replace( '/', '', $dir );
222
- }
223
-
224
- /**
225
- * Start search replace job
226
- * @param string $new
227
- * @param string $old
228
- */
229
- private function startReplace( $table ) {
230
- $rows = $this->options->job->start + $this->settings->querySRLimit;
231
- $this->log(
232
- "DB Processing: Table {$table} {$this->options->job->start} to {$rows} records"
233
- );
234
-
235
- // Search & Replace
236
- $this->searchReplace( $table, $rows, array() );
237
-
238
- // Set new offset
239
- $this->options->job->start += $this->settings->querySRLimit;
240
- }
241
-
242
- /**
243
- * Returns the number of pages in a table.
244
- * @access public
245
- * @return int
246
- */
247
- private function get_pages_in_table( $table ) {
248
-
249
- // Table does not exist
250
- $result = $this->db->query( "SHOW TABLES LIKE '{$table}'" );
251
- if( !$result || 0 === $result ) {
252
- return 0;
253
- }
254
-
255
- $table = esc_sql( $table );
256
- $rows = $this->db->get_var( "SELECT COUNT(*) FROM $table" );
257
- $pages = ceil( $rows / $this->settings->querySRLimit );
258
- return absint( $pages );
259
- }
260
-
261
- /**
262
- * Gets the columns in a table.
263
- * @access public
264
- * @param string $table The table to check.
265
- * @return array
266
- */
267
- private function get_columns( $table ) {
268
- $primary_key = null;
269
- $columns = array();
270
- $fields = $this->db->get_results( 'DESCRIBE ' . $table );
271
- if( is_array( $fields ) ) {
272
- foreach ( $fields as $column ) {
273
- $columns[] = $column->Field;
274
- if( $column->Key == 'PRI' ) {
275
- $primary_key = $column->Field;
276
- }
277
- }
278
- }
279
- return array($primary_key, $columns);
280
- }
281
-
282
- /**
283
- * Return absolute destination path
284
- * @return string
285
- */
286
- private function getAbsDestination() {
287
- if( empty( $this->options->cloneDir ) ) {
288
- return \WPStaging\WPStaging::getWPpath();
289
- }
290
- return trailingslashit( $this->options->cloneDir );
291
- }
292
-
293
- /**
294
- * Adapated from interconnect/it's search/replace script, adapted from Better Search Replace
295
- *
296
- * Modified to use WordPress wpdb functions instead of PHP's native mysql/pdo functions,
297
- * and to be compatible with batch processing.
298
- *
299
- * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
300
- *
301
- * @access public
302
- * @param string $table The table to run the replacement on.
303
- * @param int $page The page/block to begin the query on.
304
- * @param array $args An associative array containing arguments for this run.
305
- * @return array
306
- */
307
- private function searchReplace( $table, $page, $args ) {
308
-
309
- if( $this->thirdParty->isSearchReplaceExcluded( $table ) ) {
310
- $this->log( "DB Processing: Skip {$table}", \WPStaging\Utils\Logger::TYPE_INFO );
311
- return true;
312
- }
313
-
314
- // Load up the default settings for this chunk.
315
- $table = esc_sql( $table );
316
- $current_page = $this->options->job->start + $this->settings->querySRLimit;
317
- $pages = $this->get_pages_in_table( $table );
318
-
319
-
320
- // Search URL example.com/staging and root path to staging site /var/www/htdocs/staging
321
- $args['search_for'] = array(
322
- '//' . $this->getSourceHostname(),
323
- ABSPATH,
324
- '\/\/' . str_replace( '/', '\/', $this->getSourceHostname() ), // Used by revslider and several visual editors
325
- '%2F%2F' . str_replace( '/', '%2F', $this->getSourceHostname() ), // HTML entitity for WP Backery Page Builder Plugin
326
- //$this->getImagePathLive()
327
- );
328
-
329
-
330
- $args['replace_with'] = array(
331
- '//' . $this->getDestinationHostname(),
332
- $this->options->destinationDir,
333
- '\/\/' . str_replace( '/', '\/', $this->getDestinationHostname() ), // Used by revslider and several visual editors
334
- '%2F%2F' . str_replace( '/', '%2F', $this->getDestinationHostname() ), // HTML entitity for WP Backery Page Builder Plugin
335
- //$this->getImagePathStaging()
336
- );
337
-
338
- $this->debugLog( "DB Processing: Search: {$args['search_for'][0]}", \WPStaging\Utils\Logger::TYPE_INFO );
339
- $this->debugLog( "DB Processing: Replace: {$args['replace_with'][0]}", \WPStaging\Utils\Logger::TYPE_INFO );
340
-
341
-
342
-
343
- $args['replace_guids'] = 'off';
344
- $args['dry_run'] = 'off';
345
- $args['case_insensitive'] = false;
346
- //$args['replace_mails'] = 'off';
347
- $args['skip_transients'] = 'on';
348
-
349
-
350
- // Allow filtering of search & replace parameters
351
- $args = apply_filters( 'wpstg_clone_searchreplace_params', $args );
352
-
353
- // Get a list of columns in this table.
354
- list( $primary_key, $columns ) = $this->get_columns( $table );
355
-
356
- // Bail out early if there isn't a primary key.
357
- // We commented this to search & replace through tables which have no primary keys like wp_revslider_slides
358
- // @todo test this carefully. If it causes (performance) issues we need to activate it again!
359
- // @since 2.4.4
360
- // if( null === $primary_key ) {
361
- // return false;
362
- // }
363
-
364
- $current_row = 0;
365
- $start = $this->options->job->start;
366
- $end = $this->settings->querySRLimit;
367
-
368
- // Grab the content of the table.
369
- $data = $this->db->get_results( "SELECT * FROM $table LIMIT $start, $end", ARRAY_A );
370
-
371
- // Filter certain rows option_name in wpstg_options
372
- $filter = array(
373
- 'Admin_custome_login_Slidshow',
374
- 'Admin_custome_login_Social',
375
- 'Admin_custome_login_logo',
376
- 'Admin_custome_login_text',
377
- 'Admin_custome_login_login',
378
- 'Admin_custome_login_top',
379
- 'Admin_custome_login_dashboard',
380
- 'Admin_custome_login_Version',
381
- 'upload_path',
382
- );
383
-
384
- $filter = apply_filters( 'wpstg_clone_searchreplace_excl_rows', $filter );
385
-
386
- // Loop through the data.
387
- foreach ( $data as $row ) {
388
- $current_row++;
389
- $update_sql = array();
390
- $where_sql = array();
391
- $upd = false;
392
-
393
- // Skip rows below
394
- if( isset( $row['option_name'] ) && in_array( $row['option_name'], $filter ) ) {
395
- continue;
396
- }
397
-
398
- // Skip rows with transients (They can store huge data and we need to save memory)
399
- if( isset( $row['option_name'] ) && 'on' === $args['skip_transients'] && false !== strpos( $row['option_name'], '_transient' ) ) {
400
- continue;
401
- }
402
- // Skip rows with more than 5MB to save memory
403
- if( isset( $row['option_value'] ) && strlen( $row['option_value'] ) >= 5000000 ) {
404
- continue;
405
- }
406
-
407
-
408
- foreach ( $columns as $column ) {
409
-
410
- $dataRow = $row[$column];
411
-
412
- // Skip rows larger than 5MB
413
- $size = strlen( $dataRow );
414
- if( $size >= 5000000 ) {
415
- continue;
416
- }
417
-
418
- // Skip Primary key
419
- if( $column == $primary_key ) {
420
- $where_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
421
- continue;
422
- }
423
-
424
- // Skip GUIDs by default.
425
- if( 'on' !== $args['replace_guids'] && 'guid' == $column ) {
426
- continue;
427
- }
428
-
429
- // Skip mail addresses
430
- // if( 'off' === $args['replace_mails'] && false !== strpos( $dataRow, '@' . $this->multisiteDomainWithoutScheme ) ) {
431
- // continue;
432
- // }
433
- // Check options table
434
- if( $this->options->prefix . 'options' === $table ) {
435
-
436
- // Skip certain options
437
- // if( isset( $should_skip ) && true === $should_skip ) {
438
- // $should_skip = false;
439
- // continue;
440
- // }
441
- // Skip this row
442
- if( 'wpstg_existing_clones_beta' === $dataRow ||
443
- 'wpstg_existing_clones' === $dataRow ||
444
- 'wpstg_settings' === $dataRow ||
445
- 'wpstg_license_status' === $dataRow ||
446
- 'siteurl' === $dataRow ||
447
- 'home' === $dataRow
448
- ) {
449
- //$should_skip = true;
450
- continue;
451
- }
452
- }
453
-
454
- // Check the path delimiter for / or \/ and remove one of those which prevents from resulting in wrong syntax like domain.com/staging\/.
455
- // 1. local.wordpress.test -> local.wordpress.test/staging
456
- // 2. local.wordpress.test\/ -> local.wordpress.test\/staging\/
457
- $tmp = $args;
458
- if( false === strpos( $dataRow, $tmp['search_for'][0] ) ) {
459
- array_shift( $tmp['search_for'] ); // rtrim( $this->homeUrl, '/' ),
460
- array_shift( $tmp['replace_with'] ); // rtrim( $this->homeUrl, '/' ) . '/' . $this->options->cloneDirectoryName,
461
- } else {
462
- unset( $tmp['search_for'][1] );
463
- unset( $tmp['replace_with'][1] );
464
- // recount array
465
- $tmp['search_for'] = array_values( $tmp['search_for'] );
466
- $tmp['replace_with'] = array_values( $tmp['replace_with'] );
467
- }
468
-
469
- // Run a search replace on the data row and respect the serialisation.
470
- $i = 0;
471
- foreach ( $tmp['search_for'] as $replace ) {
472
- $dataRow = $this->recursive_unserialize_replace( $tmp['search_for'][$i], $tmp['replace_with'][$i], $dataRow, false, $args['case_insensitive'] );
473
- $i++;
474
- }
475
- unset( $replace );
476
- unset( $i );
477
- unset( $tmp );
478
-
479
- // Something was changed
480
- if( $row[$column] != $dataRow ) {
481
- $update_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
482
- $upd = true;
483
- }
484
- }
485
-
486
- // Determine what to do with updates.
487
- if( $args['dry_run'] === 'on' ) {
488
- // Don't do anything if a dry run
489
- } elseif( $upd && !empty( $where_sql ) ) {
490
- // If there are changes to make, run the query.
491
- $sql = 'UPDATE ' . $table . ' SET ' . implode( ', ', $update_sql ) . ' WHERE ' . implode( ' AND ', array_filter( $where_sql ) );
492
- $result = $this->db->query( $sql );
493
-
494
- if( !$result ) {
495
- $this->log( "Error updating row {$current_row} SQL: {$sql}", \WPStaging\Utils\Logger::TYPE_ERROR );
496
- }
497
- }
498
- } // end row loop
499
- unset( $row );
500
- unset( $update_sql );
501
- unset( $where_sql );
502
- unset( $sql );
503
-
504
-
505
- // DB Flush
506
- $this->db->flush();
507
- return true;
508
- }
509
-
510
- /**
511
- * Get path to multisite image folder e.g. wp-content/blogs.dir/ID/files or wp-content/uploads/sites/ID
512
- * @return string
513
- */
514
- private function getImagePathLive() {
515
- // Check first which structure is used
516
- $uploads = wp_upload_dir();
517
- $basedir = $uploads['basedir'];
518
- $blogId = get_current_blog_id();
519
-
520
- if( false === strpos( $basedir, 'blogs.dir' ) ) {
521
- // Since WP 3.5
522
- $path = $blogId > 1 ?
523
- 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR :
524
- 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
525
- } else {
526
- // old blog structure
527
- $path = $blogId > 1 ?
528
- 'wp-content' . DIRECTORY_SEPARATOR . 'blogs.dir' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR . 'files' . DIRECTORY_SEPARATOR :
529
- 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
530
- }
531
- return $path;
532
- }
533
-
534
- /**
535
- * Get path to staging site image path wp-content/uploads
536
- * @return string
537
- */
538
- private function getImagePathStaging() {
539
- return 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
540
- }
541
-
542
- /**
543
- * Adapted from interconnect/it's search/replace script.
544
- *
545
- * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
546
- *
547
- * Take a serialised array and unserialise it replacing elements as needed and
548
- * unserialising any subordinate arrays and performing the replace on those too.
549
- *
550
- * @access private
551
- * @param string $from String we're looking to replace.
552
- * @param string $to What we want it to be replaced with
553
- * @param array $data Used to pass any subordinate arrays back to in.
554
- * @param boolean $serialized Does the array passed via $data need serialising.
555
- * @param sting|boolean $case_insensitive Set to 'on' if we should ignore case, false otherwise.
556
- *
557
- * @return string|array The original array with all elements replaced as needed.
558
- */
559
- private function recursive_unserialize_replace( $from = '', $to = '', $data = '', $serialized = false, $case_insensitive = false ) {
560
- try {
561
- // Some unserialized data cannot be re-serialized eg. SimpleXMLElements
562
- if( is_serialized( $data ) && ( $unserialized = @unserialize( $data ) ) !== false ) {
563
- $data = $this->recursive_unserialize_replace( $from, $to, $unserialized, true, $case_insensitive );
564
- } elseif( is_array( $data ) ) {
565
- $tmp = array();
566
- foreach ( $data as $key => $value ) {
567
- $tmp[$key] = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
568
- }
569
-
570
- $data = $tmp;
571
- unset( $tmp );
572
- } elseif( is_object( $data ) ) {
573
- $tmp = $data;
574
- $props = get_object_vars( $data );
575
-
576
- // Do not continue if class contains __PHP_Incomplete_Class_Name
577
- if( !empty( $props['__PHP_Incomplete_Class_Name'] ) ) {
578
- return $data;
579
-
580
- }
581
-
582
- // Do a search & replace
583
- foreach ( $props as $key => $value ) {
584
- if( $key === '' || ord( $key[0] ) === 0 ) {
585
- continue;
586
- }
587
- $tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
588
- }
589
-
590
- $data = $tmp;
591
- unset( $tmp );
592
- unset( $props );
593
- } else {
594
- if( is_string( $data ) ) {
595
- if( !empty( $from ) && !empty( $to ) ) {
596
- $data = $this->str_replace( $from, $to, $data, $case_insensitive );
597
- }
598
- }
599
- }
600
-
601
- if( $serialized ) {
602
- return serialize( $data );
603
- }
604
- } catch ( Exception $error ) {
605
-
606
- }
607
-
608
- return $data;
609
- }
610
-
611
- /**
612
- * Check if the object is a valid one and not __PHP_Incomplete_Class_Name
613
- * Can not use is_object alone because in php 7.2 it's returning true even though object is __PHP_Incomplete_Class_Name
614
- * @return boolean
615
- */
616
- // private function isValidObject( $data ) {
617
- // if( !is_object( $data ) || gettype( $data ) != 'object' ) {
618
- // return false;
619
- // }
620
- //
621
- // $invalid_class_props = get_object_vars( $data );
622
- //
623
- // if (!isset($invalid_class_props['__PHP_Incomplete_Class_Name'])){
624
- // // Assume it must be an valid object
625
- // return true;
626
- // }
627
- //
628
- // $invalid_object_class = $invalid_class_props['__PHP_Incomplete_Class_Name'];
629
- //
630
- // if( !empty( $invalid_object_class ) ) {
631
- // return false;
632
- // }
633
- //
634
- // // Assume it must be an valid object
635
- // return true;
636
- // }
637
-
638
- /**
639
- * Mimics the mysql_real_escape_string function. Adapted from a post by 'feedr' on php.net.
640
- * @link http://php.net/manual/en/function.mysql-real-escape-string.php#101248
641
- * @access public
642
- * @param string $input The string to escape.
643
- * @return string
644
- */
645
- private function mysql_escape_mimic( $input ) {
646
- if( is_array( $input ) ) {
647
- return array_map( __METHOD__, $input );
648
- }
649
- if( !empty( $input ) && is_string( $input ) ) {
650
- return str_replace( array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $input );
651
- }
652
-
653
- return $input;
654
- }
655
-
656
- /**
657
- * Return unserialized object or array
658
- *
659
- * @param string $serialized_string Serialized string.
660
- * @param string $method The name of the caller method.
661
- *
662
- * @return mixed, false on failure
663
- */
664
- private static function unserialize( $serialized_string ) {
665
- if( !is_serialized( $serialized_string ) ) {
666
- return false;
667
- }
668
-
669
- $serialized_string = trim( $serialized_string );
670
- $unserialized_string = @unserialize( $serialized_string );
671
-
672
- return $unserialized_string;
673
- }
674
-
675
- /**
676
- * Wrapper for str_replace
677
- *
678
- * @param string $from
679
- * @param string $to
680
- * @param string $data
681
- * @param string|bool $case_insensitive
682
- *
683
- * @return string
684
- */
685
- private function str_replace( $from, $to, $data, $case_insensitive = false ) {
686
-
687
- // Add filter
688
- $excludes = apply_filters( 'wpstg_clone_searchreplace_excl', array() );
689
-
690
- // Build pattern
691
- $regexExclude = '';
692
- foreach ( $excludes as $exclude ) {
693
- $regexExclude .= $exclude . '(*SKIP)(FAIL)|';
694
- }
695
-
696
- if( 'on' === $case_insensitive ) {
697
- //$data = str_ireplace( $from, $to, $data );
698
- $data = preg_replace( '#' . $regexExclude . preg_quote( $from ) . '#i', $to, $data );
699
- } else {
700
- //$data = str_replace( $from, $to, $data );
701
- $data = preg_replace( '#' . $regexExclude . preg_quote( $from ) . '#', $to, $data );
702
- }
703
-
704
- return $data;
705
- }
706
-
707
- /**
708
- * Set the job
709
- * @param string $table
710
- */
711
- private function setJob( $table ) {
712
- if( !empty( $this->options->job->current ) ) {
713
- return;
714
- }
715
-
716
- $this->options->job->current = $table;
717
- $this->options->job->start = 0;
718
- }
719
-
720
- /**
721
- * Start Job
722
- * @param string $new
723
- * @param string $old
724
- * @return bool
725
- */
726
- private function startJob( $new, $old ) {
727
-
728
- $this->options->job->total = 0;
729
-
730
- if( 0 != $this->options->job->start ) {
731
- return true;
732
- }
733
-
734
- // Table does not exist
735
- $result = $this->db->query( "SHOW TABLES LIKE '{$old}'" );
736
- if( !$result || 0 === $result ) {
737
- return false;
738
- }
739
-
740
- $this->options->job->total = ( int ) $this->db->get_var( "SELECT COUNT(1) FROM {$old}" );
741
-
742
- if( 0 == $this->options->job->total ) {
743
- $this->finishStep();
744
- return false;
745
- }
746
-
747
- return true;
748
- }
749
-
750
- /**
751
- * Finish the step
752
- */
753
- private function finishStep() {
754
- // This job is not finished yet
755
- if( $this->options->job->total > $this->options->job->start ) {
756
- return false;
757
- }
758
-
759
- // Add it to cloned tables listing
760
- $this->options->clonedTables[] = $this->options->tables[$this->options->currentStep];
761
-
762
- // Reset job
763
- $this->options->job = new \stdClass();
764
-
765
- return true;
766
- }
767
-
768
- /**
769
- * Drop table if necessary
770
- * @param string $new
771
- */
772
- private function dropTable( $new ) {
773
- $old = $this->db->get_var( $this->db->prepare( "SHOW TABLES LIKE %s", $new ) );
774
-
775
- if( !$this->shouldDropTable( $new, $old ) ) {
776
- return;
777
- }
778
-
779
- $this->log( "DB Processing: {$new} already exists, dropping it first" );
780
- $this->db->query( "DROP TABLE {$new}" );
781
- }
782
-
783
- /**
784
- * Check if table needs to be dropped
785
- * @param string $new
786
- * @param string $old
787
- * @return bool
788
- */
789
- private function shouldDropTable( $new, $old ) {
790
- return (
791
- $old == $new &&
792
- (
793
- !isset( $this->options->job->current ) ||
794
- !isset( $this->options->job->start ) ||
795
- 0 == $this->options->job->start
796
- )
797
- );
798
- }
799
-
800
- /**
801
- * Check if WP is installed in subdir
802
- * @return boolean
803
- */
804
- private function isSubDir() {
805
- // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
806
- // This is happening much more often than you would expect
807
- $siteurl = preg_replace( '#^https?://#', '', rtrim( get_option( 'siteurl' ), '/' ) );
808
- $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
809
-
810
- if( $home !== $siteurl ) {
811
- return true;
812
- }
813
- return false;
814
- }
815
-
816
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ *
34
+ * @var Obj
35
+ */
36
+ private $strings;
37
+
38
+ /**
39
+ *
40
+ * @var string
41
+ */
42
+ private $destinationHostname;
43
+
44
+ /**
45
+ *
46
+ * @var string
47
+ */
48
+ private $sourceHostname;
49
+
50
+ /**
51
+ *
52
+ * @var string
53
+ */
54
+ //private $targetDir;
55
+
56
+ /**
57
+ * The prefix of the new database tables which are used for the live site after updating tables
58
+ * @var string
59
+ */
60
+ public $tmpPrefix;
61
+
62
+ /**
63
+ * Initialize
64
+ */
65
+ public function initialize() {
66
+ $this->total = count( $this->options->tables );
67
+ $this->db = WPStaging::getInstance()->get( "wpdb" );
68
+ $this->tmpPrefix = $this->options->prefix;
69
+ $this->strings = new Strings();
70
+ $this->sourceHostname = $this->getSourceHostname();
71
+ $this->destinationHostname = $this->getDestinationHostname();
72
+ }
73
+
74
+ public function start() {
75
+ // Skip job. Nothing to do
76
+ if( $this->options->totalSteps === 0 ) {
77
+ $this->prepareResponse( true, false );
78
+ }
79
+
80
+ $this->run();
81
+
82
+ // Save option, progress
83
+ $this->saveOptions();
84
+
85
+ return ( object ) $this->response;
86
+ }
87
+
88
+ /**
89
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
90
+ * @return void
91
+ */
92
+ protected function calculateTotalSteps() {
93
+ $this->options->totalSteps = $this->total;
94
+ }
95
+
96
+ /**
97
+ * Execute the Current Step
98
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
99
+ * @return bool
100
+ */
101
+ protected function execute() {
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->options->currentStep > $this->total || !isset( $this->options->tables[$this->options->currentStep] ) ) {
112
+ $this->prepareResponse( true, false );
113
+ return false;
114
+ }
115
+
116
+ // Table is excluded
117
+ if( in_array( $this->options->tables[$this->options->currentStep], $this->options->excludedTables ) ) {
118
+ $this->prepareResponse();
119
+ return true;
120
+ }
121
+
122
+ // Search & Replace
123
+ if( !$this->stopExecution() && !$this->updateTable( $this->options->tables[$this->options->currentStep] ) ) {
124
+ // Prepare Response
125
+ $this->prepareResponse( false, false );
126
+
127
+ // Not finished
128
+ return true;
129
+ }
130
+
131
+
132
+ // Prepare Response
133
+ $this->prepareResponse();
134
+
135
+ // Not finished
136
+ return true;
137
+ }
138
+
139
+ /**
140
+ * Stop Execution immediately
141
+ * return mixed bool | json
142
+ */
143
+ private function stopExecution() {
144
+ if( $this->db->prefix == $this->tmpPrefix ) {
145
+ $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.' );
146
+ }
147
+ return false;
148
+ }
149
+
150
+ /**
151
+ * Copy Tables
152
+ * @param string $tableName
153
+ * @return bool
154
+ */
155
+ private function updateTable( $tableName ) {
156
+ $strings = new Strings();
157
+ $table = $strings->str_replace_first( $this->db->prefix, '', $tableName );
158
+ $newTableName = $this->tmpPrefix . $table;
159
+
160
+ // Save current job
161
+ $this->setJob( $newTableName );
162
+
163
+ // Beginning of the job
164
+ if( !$this->startJob( $newTableName, $tableName ) ) {
165
+ return true;
166
+ }
167
+ // Copy data
168
+ $this->startReplace( $newTableName );
169
+
170
+ // Finish the step
171
+ return $this->finishStep();
172
+ }
173
+
174
+ /**
175
+ * Get source Hostname depending on wheather WP has been installed in sub dir or not
176
+ * @return type
177
+ */
178
+ public function getSourceHostname() {
179
+
180
+ if( $this->isSubDir() ) {
181
+ return trailingslashit( $this->multisiteHomeUrlWithoutScheme ) . '/' . $this->getSubDir();
182
+ }
183
+ return $this->multisiteHomeUrlWithoutScheme;
184
+ }
185
+
186
+ /**
187
+ * Get destination Hostname depending on wheather WP has been installed in sub dir or not
188
+ * @return type
189
+ */
190
+ public function getDestinationHostname() {
191
+
192
+ if( !empty( $this->options->cloneHostname ) ) {
193
+ return $this->strings->getUrlWithoutScheme( $this->options->cloneHostname );
194
+ }
195
+
196
+ if( $this->isSubDir() ) {
197
+ return trailingslashit( $this->strings->getUrlWithoutScheme( $this->multisiteDomainWithoutScheme ) ) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName;
198
+ }
199
+
200
+ // Get the path to the main multisite without appending and trailingslash e.g. wordpress
201
+ $multisitePath = defined( 'PATH_CURRENT_SITE' ) ? PATH_CURRENT_SITE : '/';
202
+ $url = rtrim( $this->strings->getUrlWithoutScheme( $this->multisiteDomainWithoutScheme ), '/\\' ) . $multisitePath . $this->options->cloneDirectoryName;
203
+ //$multisitePath = defined( 'PATH_CURRENT_SITE' ) ? str_replace( '/', '', PATH_CURRENT_SITE ) : '';
204
+ //$url = trailingslashit( $this->strings->getUrlWithoutScheme( $this->multisiteDomainWithoutScheme ) ) . $multisitePath . '/' . $this->options->cloneDirectoryName;
205
+ return $url;
206
+ }
207
+
208
+ /**
209
+ * Get the install sub directory if WP is installed in sub directory
210
+ * @return string
211
+ */
212
+ private function getSubDir() {
213
+ $home = get_option( 'home' );
214
+ $siteurl = get_option( 'siteurl' );
215
+
216
+ if( empty( $home ) || empty( $siteurl ) ) {
217
+ return '';
218
+ }
219
+
220
+ $dir = str_replace( $home, '', $siteurl );
221
+ return str_replace( '/', '', $dir );
222
+ }
223
+
224
+ /**
225
+ * Start search replace job
226
+ * @param string $new
227
+ * @param string $old
228
+ */
229
+ private function startReplace( $table ) {
230
+ $rows = $this->options->job->start + $this->settings->querySRLimit;
231
+ $this->log(
232
+ "DB Search & Replace: Table {$table} {$this->options->job->start} to {$rows} records"
233
+ );
234
+
235
+ // Search & Replace
236
+ $this->searchReplace( $table, $rows, array() );
237
+
238
+ // Set new offset
239
+ $this->options->job->start += $this->settings->querySRLimit;
240
+ }
241
+
242
+ /**
243
+ * Returns the number of pages in a table.
244
+ * @access public
245
+ * @return int
246
+ */
247
+ // private function get_pages_in_table( $table ) {
248
+ //
249
+ // // Table does not exist
250
+ // $result = $this->db->query( "SHOW TABLES LIKE '{$table}'" );
251
+ // if( !$result || 0 === $result ) {
252
+ // return 0;
253
+ // }
254
+ //
255
+ // $table = esc_sql( $table );
256
+ // $rows = $this->db->get_var( "SELECT COUNT(*) FROM $table" );
257
+ // $pages = ceil( $rows / $this->settings->querySRLimit );
258
+ // return absint( $pages );
259
+ // }
260
+
261
+ /**
262
+ * Gets the columns in a table.
263
+ * @access public
264
+ * @param string $table The table to check.
265
+ * @return array
266
+ */
267
+ private function get_columns( $table ) {
268
+ $primary_key = null;
269
+ $columns = array();
270
+ $fields = $this->db->get_results( 'DESCRIBE ' . $table );
271
+ if( is_array( $fields ) ) {
272
+ foreach ( $fields as $column ) {
273
+ $columns[] = $column->Field;
274
+ if( $column->Key == 'PRI' ) {
275
+ $primary_key = $column->Field;
276
+ }
277
+ }
278
+ }
279
+ return array($primary_key, $columns);
280
+ }
281
+
282
+ /**
283
+ * Return absolute destination path
284
+ * @return string
285
+ */
286
+ private function getAbsDestination() {
287
+ if( empty( $this->options->cloneDir ) ) {
288
+ return \WPStaging\WPStaging::getWPpath();
289
+ }
290
+ return trailingslashit( $this->options->cloneDir );
291
+ }
292
+
293
+ /**
294
+ * Adapated from interconnect/it's search/replace script, adapted from Better Search Replace
295
+ *
296
+ * Modified to use WordPress wpdb functions instead of PHP's native mysql/pdo functions,
297
+ * and to be compatible with batch processing.
298
+ *
299
+ * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
300
+ *
301
+ * @access public
302
+ * @param string $table The table to run the replacement on.
303
+ * @param int $page The page/block to begin the query on.
304
+ * @param array $args An associative array containing arguments for this run.
305
+ * @return array
306
+ */
307
+ private function searchReplace( $table, $page, $args ) {
308
+
309
+ if( $this->thirdParty->isSearchReplaceExcluded( $table ) ) {
310
+ $this->log( "DB Search & Replace: Skip {$table}", \WPStaging\Utils\Logger::TYPE_INFO );
311
+ return true;
312
+ }
313
+
314
+ // Load up the default settings for this chunk.
315
+ $table = esc_sql( $table );
316
+ //$current_page = $this->options->job->start + $this->settings->querySRLimit;
317
+ //$pages = $this->get_pages_in_table( $table );
318
+ // Search URL example.com/staging and root path to staging site /var/www/htdocs/staging
319
+ $args['search_for'] = array(
320
+ '//' . $this->getSourceHostname(),
321
+ ABSPATH,
322
+ '\/\/' . str_replace( '/', '\/', $this->getSourceHostname() ), // Used by revslider and several visual editors
323
+ '%2F%2F' . str_replace( '/', '%2F', $this->getSourceHostname() ), // HTML entitity for WP Backery Page Builder Plugin
324
+ //$this->getImagePathLive()
325
+ );
326
+
327
+
328
+ $args['replace_with'] = array(
329
+ '//' . $this->getDestinationHostname(),
330
+ $this->options->destinationDir,
331
+ '\/\/' . str_replace( '/', '\/', $this->getDestinationHostname() ), // Used by revslider and several visual editors
332
+ '%2F%2F' . str_replace( '/', '%2F', $this->getDestinationHostname() ), // HTML entitity for WP Backery Page Builder Plugin
333
+ //$this->getImagePathStaging()
334
+ );
335
+
336
+ $this->debugLog( "DB Search & Replace: Search: {$args['search_for'][0]}", \WPStaging\Utils\Logger::TYPE_INFO );
337
+ $this->debugLog( "DB Search & Replace: Replace: {$args['replace_with'][0]}", \WPStaging\Utils\Logger::TYPE_INFO );
338
+
339
+
340
+
341
+ $args['replace_guids'] = 'off';
342
+ $args['dry_run'] = 'off';
343
+ $args['case_insensitive'] = false;
344
+ //$args['replace_mails'] = 'off';
345
+ $args['skip_transients'] = 'on';
346
+
347
+
348
+ // Allow filtering of search & replace parameters
349
+ $args = apply_filters( 'wpstg_clone_searchreplace_params', $args );
350
+
351
+ // Get a list of columns in this table.
352
+ list( $primary_key, $columns ) = $this->get_columns( $table );
353
+
354
+ // Bail out early if there isn't a primary key.
355
+ // We commented this to search & replace through tables which have no primary keys like wp_revslider_slides
356
+ // @todo test this carefully. If it causes (performance) issues we need to activate it again!
357
+ // @since 2.4.4
358
+ // if( null === $primary_key ) {
359
+ // return false;
360
+ // }
361
+
362
+ $current_row = 0;
363
+ $start = $this->options->job->start;
364
+ $end = $this->settings->querySRLimit;
365
+
366
+ // Grab the content of the table.
367
+ $data = $this->db->get_results( "SELECT * FROM $table LIMIT $start, $end", ARRAY_A );
368
+
369
+ // Filter certain rows option_name in wpstg_options
370
+ $filter = array(
371
+ 'Admin_custome_login_Slidshow',
372
+ 'Admin_custome_login_Social',
373
+ 'Admin_custome_login_logo',
374
+ 'Admin_custome_login_text',
375
+ 'Admin_custome_login_login',
376
+ 'Admin_custome_login_top',
377
+ 'Admin_custome_login_dashboard',
378
+ 'Admin_custome_login_Version',
379
+ 'upload_path'
380
+ );
381
+
382
+ $filter = apply_filters( 'wpstg_clone_searchreplace_excl_rows', $filter );
383
+
384
+ // Loop through the data.
385
+ foreach ( $data as $row ) {
386
+ $current_row++;
387
+ $update_sql = array();
388
+ $where_sql = array();
389
+ $upd = false;
390
+
391
+ // Skip rows below
392
+ if( isset( $row['option_name'] ) && in_array( $row['option_name'], $filter ) ) {
393
+ continue;
394
+ }
395
+
396
+ // Skip rows with transients (They can store huge data and we need to save memory)
397
+ if( isset( $row['option_name'] ) && 'on' === $args['skip_transients'] && false !== strpos( $row['option_name'], '_transient' ) ) {
398
+ continue;
399
+ }
400
+ // Skip rows with more than 5MB to save memory
401
+ if( isset( $row['option_value'] ) && strlen( $row['option_value'] ) >= 5000000 ) {
402
+ continue;
403
+ }
404
+
405
+
406
+ foreach ( $columns as $column ) {
407
+
408
+ $dataRow = $row[$column];
409
+
410
+ // Skip rows larger than 5MB
411
+ $size = strlen( $dataRow );
412
+ if( $size >= 5000000 ) {
413
+ continue;
414
+ }
415
+
416
+ // Skip Primary key
417
+ if( $column == $primary_key ) {
418
+ $where_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
419
+ continue;
420
+ }
421
+
422
+ // Skip GUIDs by default.
423
+ if( 'on' !== $args['replace_guids'] && 'guid' == $column ) {
424
+ continue;
425
+ }
426
+
427
+ // Skip mail addresses
428
+ // if( 'off' === $args['replace_mails'] && false !== strpos( $dataRow, '@' . $this->multisiteDomainWithoutScheme ) ) {
429
+ // continue;
430
+ // }
431
+ // Check options table
432
+ if( $this->options->prefix . 'options' === $table ) {
433
+
434
+ // Skip certain options
435
+ // if( isset( $should_skip ) && true === $should_skip ) {
436
+ // $should_skip = false;
437
+ // continue;
438
+ // }
439
+ // Skip this row
440
+ if( 'wpstg_existing_clones_beta' === $dataRow ||
441
+ 'wpstg_existing_clones' === $dataRow ||
442
+ 'wpstg_settings' === $dataRow ||
443
+ 'wpstg_license_status' === $dataRow ||
444
+ 'siteurl' === $dataRow ||
445
+ 'home' === $dataRow
446
+ ) {
447
+ //$should_skip = true;
448
+ continue;
449
+ }
450
+ }
451
+
452
+ // Check the path delimiter for / or \/ and remove one of those which prevents from resulting in wrong syntax like domain.com/staging\/.
453
+ // 1. local.wordpress.test -> local.wordpress.test/staging
454
+ // 2. local.wordpress.test\/ -> local.wordpress.test\/staging\/
455
+ $tmp = $args;
456
+ if( false === strpos( $dataRow, $tmp['search_for'][0] ) ) {
457
+ array_shift( $tmp['search_for'] ); // rtrim( $this->homeUrl, '/' ),
458
+ array_shift( $tmp['replace_with'] ); // rtrim( $this->homeUrl, '/' ) . '/' . $this->options->cloneDirectoryName,
459
+ } else {
460
+ unset( $tmp['search_for'][1] );
461
+ unset( $tmp['replace_with'][1] );
462
+ // recount array
463
+ $tmp['search_for'] = array_values( $tmp['search_for'] );
464
+ $tmp['replace_with'] = array_values( $tmp['replace_with'] );
465
+ }
466
+
467
+ // Run a search replace on the data row and respect the serialisation.
468
+ $i = 0;
469
+ foreach ( $tmp['search_for'] as $replace ) {
470
+ $dataRow = $this->recursive_unserialize_replace( $tmp['search_for'][$i], $tmp['replace_with'][$i], $dataRow, false, $args['case_insensitive'] );
471
+ $i++;
472
+ }
473
+ unset( $replace );
474
+ unset( $i );
475
+ unset( $tmp );
476
+
477
+ // Something was changed
478
+ if( $row[$column] != $dataRow ) {
479
+ $update_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
480
+ $upd = true;
481
+ }
482
+ }
483
+
484
+ // Determine what to do with updates.
485
+ if( $args['dry_run'] === 'on' ) {
486
+ // Don't do anything if a dry run
487
+ } elseif( $upd && !empty( $where_sql ) ) {
488
+ // If there are changes to make, run the query.
489
+ $sql = 'UPDATE ' . $table . ' SET ' . implode( ', ', $update_sql ) . ' WHERE ' . implode( ' AND ', array_filter( $where_sql ) );
490
+ $result = $this->db->query( $sql );
491
+
492
+ if( !$result ) {
493
+ $this->log( "Error updating row {$current_row} SQL: {$sql}", \WPStaging\Utils\Logger::TYPE_ERROR );
494
+ }
495
+ }
496
+ } // end row loop
497
+ unset( $row );
498
+ unset( $update_sql );
499
+ unset( $where_sql );
500
+ unset( $sql );
501
+ unset( $current_row );
502
+
503
+
504
+ // DB Flush
505
+ $this->db->flush();
506
+ return true;
507
+ }
508
+
509
+ /**
510
+ * Get path to multisite image folder e.g. wp-content/blogs.dir/ID/files or wp-content/uploads/sites/ID
511
+ * @return string
512
+ */
513
+ private function getImagePathLive() {
514
+ // Check first which structure is used
515
+ $uploads = wp_upload_dir();
516
+ $basedir = $uploads['basedir'];
517
+ $blogId = get_current_blog_id();
518
+
519
+ if( false === strpos( $basedir, 'blogs.dir' ) ) {
520
+ // Since WP 3.5
521
+ $path = $blogId > 1 ?
522
+ 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR :
523
+ 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
524
+ } else {
525
+ // old blog structure
526
+ $path = $blogId > 1 ?
527
+ 'wp-content' . DIRECTORY_SEPARATOR . 'blogs.dir' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR . 'files' . DIRECTORY_SEPARATOR :
528
+ 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
529
+ }
530
+ return $path;
531
+ }
532
+
533
+ /**
534
+ * Get path to staging site image path wp-content/uploads
535
+ * @return string
536
+ */
537
+ private function getImagePathStaging() {
538
+ return 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
539
+ }
540
+
541
+ /**
542
+ * Adapted from interconnect/it's search/replace script.
543
+ *
544
+ * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
545
+ *
546
+ * Take a serialised array and unserialise it replacing elements as needed and
547
+ * unserialising any subordinate arrays and performing the replace on those too.
548
+ *
549
+ * @access private
550
+ * @param string $from String we're looking to replace.
551
+ * @param string $to What we want it to be replaced with
552
+ * @param array $data Used to pass any subordinate arrays back to in.
553
+ * @param boolean $serialized Does the array passed via $data need serialising.
554
+ * @param sting|boolean $case_insensitive Set to 'on' if we should ignore case, false otherwise.
555
+ *
556
+ * @return string|array The original array with all elements replaced as needed.
557
+ */
558
+ private function recursive_unserialize_replace( $from = '', $to = '', $data = '', $serialized = false, $case_insensitive = false ) {
559
+ try {
560
+ // PDO instances can not be serialized or unserialized
561
+ if( is_serialized( $data ) && strpos( $data, 'O:3:"PDO":0:' ) !== false ) {
562
+ return $data;
563
+ }
564
+ // Some unserialized data cannot be re-serialized eg. SimpleXMLElements
565
+ if( is_serialized( $data ) && ( $unserialized = @unserialize( $data ) ) !== false ) {
566
+ $data = $this->recursive_unserialize_replace( $from, $to, $unserialized, true, $case_insensitive );
567
+ } elseif( is_array( $data ) ) {
568
+ $tmp = array();
569
+ foreach ( $data as $key => $value ) {
570
+ $tmp[$key] = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
571
+ }
572
+
573
+ $data = $tmp;
574
+ unset( $tmp );
575
+ } elseif( is_object( $data ) ) {
576
+ $props = get_object_vars( $data );
577
+
578
+ // Do a search & replace
579
+ if( empty( $props['__PHP_Incomplete_Class_Name'] ) ) {
580
+ $tmp = $data;
581
+ foreach ( $props as $key => $value ) {
582
+ if( $key === '' || ord( $key[0] ) === 0 ) {
583
+ continue;
584
+ }
585
+ $tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
586
+ }
587
+ $data = $tmp;
588
+ $tmp = '';
589
+ $props = '';
590
+ unset( $tmp );
591
+ unset( $props );
592
+ }
593
+ } else {
594
+ if( is_string( $data ) ) {
595
+ if( !empty( $from ) && !empty( $to ) ) {
596
+ $data = $this->str_replace( $from, $to, $data, $case_insensitive );
597
+ }
598
+ }
599
+ }
600
+
601
+ if( $serialized ) {
602
+ return serialize( $data );
603
+ }
604
+ } catch ( Exception $error ) {
605
+
606
+ }
607
+
608
+ return $data;
609
+ }
610
+
611
+ /**
612
+ * Check if the object is a valid one and not __PHP_Incomplete_Class_Name
613
+ * Can not use is_object alone because in php 7.2 it's returning true even though object is __PHP_Incomplete_Class_Name
614
+ * @return boolean
615
+ */
616
+ // private function isValidObject( $data ) {
617
+ // if( !is_object( $data ) || gettype( $data ) != 'object' ) {
618
+ // return false;
619
+ // }
620
+ //
621
+ // $invalid_class_props = get_object_vars( $data );
622
+ //
623
+ // if (!isset($invalid_class_props['__PHP_Incomplete_Class_Name'])){
624
+ // // Assume it must be an valid object
625
+ // return true;
626
+ // }
627
+ //
628
+ // $invalid_object_class = $invalid_class_props['__PHP_Incomplete_Class_Name'];
629
+ //
630
+ // if( !empty( $invalid_object_class ) ) {
631
+ // return false;
632
+ // }
633
+ //
634
+ // // Assume it must be an valid object
635
+ // return true;
636
+ // }
637
+
638
+ /**
639
+ * Mimics the mysql_real_escape_string function. Adapted from a post by 'feedr' on php.net.
640
+ * @link http://php.net/manual/en/function.mysql-real-escape-string.php#101248
641
+ * @access public
642
+ * @param string $input The string to escape.
643
+ * @return string
644
+ */
645
+ private function mysql_escape_mimic( $input ) {
646
+ if( is_array( $input ) ) {
647
+ return array_map( __METHOD__, $input );
648
+ }
649
+ if( !empty( $input ) && is_string( $input ) ) {
650
+ return str_replace( array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $input );
651
+ }
652
+
653
+ return $input;
654
+ }
655
+
656
+ /**
657
+ * Return unserialized object or array
658
+ *
659
+ * @param string $serialized_string Serialized string.
660
+ * @param string $method The name of the caller method.
661
+ *
662
+ * @return mixed, false on failure
663
+ */
664
+ private static function unserialize( $serialized_string ) {
665
+ if( !is_serialized( $serialized_string ) ) {
666
+ return false;
667
+ }
668
+
669
+ $serialized_string = trim( $serialized_string );
670
+ $unserialized_string = @unserialize( $serialized_string );
671
+
672
+ return $unserialized_string;
673
+ }
674
+
675
+ /**
676
+ * Wrapper for str_replace
677
+ *
678
+ * @param string $from
679
+ * @param string $to
680
+ * @param string $data
681
+ * @param string|bool $case_insensitive
682
+ *
683
+ * @return string
684
+ */
685
+ private function str_replace( $from, $to, $data, $case_insensitive = false ) {
686
+
687
+ // Add filter
688
+ $excludes = apply_filters( 'wpstg_clone_searchreplace_excl', array() );
689
+
690
+ // Build pattern
691
+ $regexExclude = '';
692
+ foreach ( $excludes as $exclude ) {
693
+ $regexExclude .= $exclude . '(*SKIP)(FAIL)|';
694
+ }
695
+
696
+ if( 'on' === $case_insensitive ) {
697
+ //$data = str_ireplace( $from, $to, $data );
698
+ $data = preg_replace( '#' . $regexExclude . preg_quote( $from ) . '#i', $to, $data );
699
+ } else {
700
+ //$data = str_replace( $from, $to, $data );
701
+ $data = preg_replace( '#' . $regexExclude . preg_quote( $from ) . '#', $to, $data );
702
+ }
703
+
704
+ return $data;
705
+ }
706
+
707
+ /**
708
+ * Set the job
709
+ * @param string $table
710
+ */
711
+ private function setJob( $table ) {
712
+ if( !empty( $this->options->job->current ) ) {
713
+ return;
714
+ }
715
+
716
+ $this->options->job->current = $table;
717
+ $this->options->job->start = 0;
718
+ }
719
+
720
+ /**
721
+ * Start Job
722
+ * @param string $new
723
+ * @param string $old
724
+ * @return bool
725
+ */
726
+ private function startJob( $new, $old ) {
727
+
728
+ $this->options->job->total = 0;
729
+
730
+ if( 0 != $this->options->job->start ) {
731
+ return true;
732
+ }
733
+
734
+ // Table does not exist
735
+ $result = $this->db->query( "SHOW TABLES LIKE '{$old}'" );
736
+ if( !$result || 0 === $result ) {
737
+ return false;
738
+ }
739
+
740
+ $this->options->job->total = ( int ) $this->db->get_var( "SELECT COUNT(1) FROM {$old}" );
741
+
742
+ if( 0 == $this->options->job->total ) {
743
+ $this->finishStep();
744
+ return false;
745
+ }
746
+
747
+ return true;
748
+ }
749
+
750
+ /**
751
+ * Is table excluded from search replace processing?
752
+ * @param string $table
753
+ * @return boolean
754
+ */
755
+ private function isExcludedTable( $table ) {
756
+
757
+ $customTables = apply_filters( 'wpstg_clone_searchreplace_tables_exclude', array() );
758
+ $defaultTables = array('blogs');
759
+
760
+ $tables = array_merge( $customTables, $defaultTables );
761
+
762
+ $excludedTables = array();
763
+ foreach ( $tables as $key => $value ) {
764
+ $excludedTables[] = $this->options->prefix . $value;
765
+ }
766
+
767
+ if( in_array( $table, $excludedTables ) ) {
768
+ return true;
769
+ }
770
+ return false;
771
+ }
772
+
773
+ /**
774
+ * Finish the step
775
+ */
776
+ private function finishStep() {
777
+ // This job is not finished yet
778
+ if( $this->options->job->total > $this->options->job->start ) {
779
+ return false;
780
+ }
781
+
782
+ // Add it to cloned tables listing
783
+ $this->options->clonedTables[] = $this->options->tables[$this->options->currentStep];
784
+
785
+ // Reset job
786
+ $this->options->job = new \stdClass();
787
+
788
+ return true;
789
+ }
790
+
791
+ /**
792
+ * Drop table if necessary
793
+ * @param string $new
794
+ */
795
+ private function dropTable( $new ) {
796
+ $old = $this->db->get_var( $this->db->prepare( "SHOW TABLES LIKE %s", $new ) );
797
+
798
+ if( !$this->shouldDropTable( $new, $old ) ) {
799
+ return;
800
+ }
801
+
802
+ $this->log( "DB Search & Replace: {$new} already exists, dropping it first" );
803
+ $this->db->query( "DROP TABLE {$new}" );
804
+ }
805
+
806
+ /**
807
+ * Check if table needs to be dropped
808
+ * @param string $new
809
+ * @param string $old
810
+ * @return bool
811
+ */
812
+ private function shouldDropTable( $new, $old ) {
813
+ return (
814
+ $old == $new &&
815
+ (
816
+ !isset( $this->options->job->current ) ||
817
+ !isset( $this->options->job->start ) ||
818
+ 0 == $this->options->job->start
819
+ )
820
+ );
821
+ }
822
+
823
+ /**
824
+ * Check if WP is installed in subdir
825
+ * @return boolean
826
+ */
827
+ private function isSubDir() {
828
+ // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
829
+ // This is happening much more often than you would expect
830
+ $siteurl = preg_replace( '#^https?://#', '', rtrim( get_option( 'siteurl' ), '/' ) );
831
+ $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
832
+
833
+ if( $home !== $siteurl ) {
834
+ return true;
835
+ }
836
+ return false;
837
+ }
838
+
839
+ }
apps/Backend/Modules/Jobs/Multisite/SearchReplaceExternal.php CHANGED
@@ -1,840 +1,833 @@
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 SearchReplaceExternal extends JobExecutable {
19
-
20
- /**
21
- * @var int
22
- */
23
- private $total = 0;
24
-
25
- /**
26
- * Staging Site DB
27
- * @var \WPDB
28
- */
29
- private $stagingDb;
30
-
31
- /**
32
- * Production Site DB
33
- * @var \WPDB
34
- */
35
- private $productionDb;
36
-
37
- /**
38
- *
39
- * @var string
40
- */
41
- private $sourceHostname;
42
-
43
- /**
44
- *
45
- * @var string
46
- */
47
- private $destinationHostname;
48
-
49
- /**
50
- *
51
- * @var Obj
52
- */
53
- private $strings;
54
-
55
- /**
56
- * The prefix of the new database tables which are used for the live site after updating tables
57
- * @var string
58
- */
59
- public $tmpPrefix;
60
-
61
- /**
62
- * Initialize
63
- */
64
- public function initialize() {
65
- $this->total = count( $this->options->tables );
66
- $this->stagingDb = $this->getStagingDB();
67
- $this->productionDb = WPStaging::getInstance()->get( "wpdb" );
68
- $this->tmpPrefix = $this->options->prefix;
69
- $this->strings = new Strings();
70
- $this->sourceHostname = $this->getSourceHostname();
71
- $this->destinationHostname = $this->getDestinationHostname();
72
- }
73
-
74
- /**
75
- * Get database object to interact with
76
- */
77
- private function getStagingDB() {
78
- return new \wpdb( $this->options->databaseUser, $this->options->databasePassword, $this->options->databaseDatabase, $this->options->databaseServer );
79
- }
80
-
81
- public function start() {
82
- // Skip job. Nothing to do
83
- if( $this->options->totalSteps === 0 ) {
84
- $this->prepareResponse( true, false );
85
- }
86
-
87
- $this->run();
88
-
89
- // Save option, progress
90
- $this->saveOptions();
91
-
92
- return ( object ) $this->response;
93
- }
94
-
95
- /**
96
- * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
97
- * @return void
98
- */
99
- protected function calculateTotalSteps() {
100
- $this->options->totalSteps = $this->total;
101
- }
102
-
103
- /**
104
- * Execute the Current Step
105
- * Returns false when over threshold limits are hit or when the job is done, true otherwise
106
- * @return bool
107
- */
108
- protected function execute() {
109
- // Over limits threshold
110
- if( $this->isOverThreshold() ) {
111
- // Prepare response and save current progress
112
- $this->prepareResponse( false, false );
113
- $this->saveOptions();
114
- return false;
115
- }
116
-
117
- // No more steps, finished
118
- if( $this->options->currentStep > $this->total || !isset( $this->options->tables[$this->options->currentStep] ) ) {
119
- $this->prepareResponse( true, false );
120
- return false;
121
- }
122
-
123
- // Table is excluded
124
- if( in_array( $this->options->tables[$this->options->currentStep], $this->options->excludedTables ) ) {
125
- $this->prepareResponse();
126
- return true;
127
- }
128
-
129
- // Search & Replace
130
- if( !$this->stopExecution() && !$this->updateTable( $this->options->tables[$this->options->currentStep] ) ) {
131
- // Prepare Response
132
- $this->prepareResponse( false, false );
133
-
134
- // Not finished
135
- return true;
136
- }
137
-
138
-
139
- // Prepare Response
140
- $this->prepareResponse();
141
-
142
- // Not finished
143
- return true;
144
- }
145
-
146
- /**
147
- * Stop Execution immediately
148
- * return mixed bool | json
149
- */
150
- private function stopExecution() {
151
- // if( $this->stagingDb->prefix == $this->tmpPrefix ) {
152
- // $this->returnException( 'Fatal Error 9: Prefix ' . $this->stagingDb->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.' );
153
- // }
154
- return false;
155
- }
156
-
157
- /**
158
- * Copy Tables
159
- * @param string $tableName
160
- * @return bool
161
- */
162
- private function updateTable( $tableName ) {
163
- $strings = new Strings();
164
- $table = $strings->str_replace_first( $this->productionDb->prefix, '', $tableName );
165
- $newTableName = $this->tmpPrefix . $table;
166
-
167
- // Save current job
168
- $this->setJob( $newTableName );
169
-
170
- // Beginning of the job
171
- if( !$this->startJob( $newTableName, $tableName ) ) {
172
- return true;
173
- }
174
- // Copy data
175
- $this->startReplace( $newTableName );
176
-
177
- // Finish the step
178
- return $this->finishStep();
179
- }
180
-
181
- /**
182
- * Get source Hostname depending on wheather WP has been installed in sub dir or not
183
- * @return type
184
- */
185
- private function getSourceHostname() {
186
-
187
- if( $this->isSubDir() ) {
188
- return trailingslashit( $this->multisiteHomeUrlWithoutScheme ) . '/' . $this->getSubDir();
189
- }
190
- return $this->multisiteHomeUrlWithoutScheme;
191
- }
192
-
193
- /**
194
- * Get destination Hostname depending on wheather WP has been installed in sub dir or not
195
- * Retun host name without scheme
196
- * @return type
197
- */
198
- private function getDestinationHostname() {
199
-
200
- if( !empty( $this->options->cloneHostname ) ) {
201
- return $this->strings->getUrlWithoutScheme( $this->options->cloneHostname );
202
- }
203
-
204
- if( $this->isSubDir() ) {
205
- return trailingslashit( $this->strings->getUrlWithoutScheme( $this->multisiteDomainWithoutScheme ) ) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName;
206
- }
207
- return trailingslashit( $this->strings->getUrlWithoutScheme( $this->multisiteDomainWithoutScheme ) ) . $this->options->cloneDirectoryName;
208
- }
209
-
210
- /**
211
- * Get the install sub directory if WP is installed in sub directory
212
- * @return string
213
- */
214
- private function getSubDir() {
215
- $home = get_option( 'home' );
216
- $siteurl = get_option( 'siteurl' );
217
-
218
- if( empty( $home ) || empty( $siteurl ) ) {
219
- return '';
220
- }
221
-
222
- $dir = str_replace( $home, '', $siteurl );
223
- return str_replace( '/', '', $dir );
224
- }
225
-
226
- /**
227
- * Start search replace job
228
- * @param string $new
229
- * @param string $old
230
- */
231
- private function startReplace( $table ) {
232
- $rows = $this->options->job->start + $this->settings->querySRLimit;
233
- $this->log(
234
- "DB Processing: Table {$table} {$this->options->job->start} to {$rows} records"
235
- );
236
-
237
- // Search & Replace
238
- $this->searchReplace( $table, $rows, array() );
239
-
240
- // Set new offset
241
- $this->options->job->start += $this->settings->querySRLimit;
242
- }
243
-
244
- /**
245
- * Returns the number of pages in a table.
246
- * @access public
247
- * @return int
248
- */
249
- private function get_pages_in_table( $table ) {
250
-
251
- // Table does not exist
252
- $table = str_replace( $this->options->prefix . '.', null, $table );
253
- $result = $this->productionDb->query( "SHOW TABLES LIKE '{$table}'" );
254
- if( !$result || 0 === $result ) {
255
- return 0;
256
- }
257
-
258
- $table = esc_sql( $table );
259
- $rows = $this->productionDb->get_var( "SELECT COUNT(*) FROM $table" );
260
- $pages = ceil( $rows / $this->settings->querySRLimit );
261
- return absint( $pages );
262
- }
263
-
264
- /**
265
- * Gets the columns in a table.
266
- * @access public
267
- * @param string $table The table to check.
268
- * @return array
269
- */
270
- private function get_columns( $table ) {
271
- $primary_key = null;
272
- $columns = array();
273
- $fields = $this->stagingDb->get_results( 'DESCRIBE ' . $table );
274
- if( is_array( $fields ) ) {
275
- foreach ( $fields as $column ) {
276
- $columns[] = $column->Field;
277
- if( $column->Key == 'PRI' ) {
278
- $primary_key = $column->Field;
279
- }
280
- }
281
- }
282
- return array($primary_key, $columns);
283
- }
284
-
285
- /**
286
- * Return absolute destination path
287
- * @return string
288
- */
289
- // private function getAbsDestination() {
290
- // if( empty( $this->options->cloneDir ) ) {
291
- // return \WPStaging\WPStaging::getWPpath();
292
- // }
293
- // return trailingslashit( $this->options->cloneDir );
294
- // }
295
-
296
- /**
297
- * Adapated from interconnect/it's search/replace script, adapted from Better Search Replace
298
- *
299
- * Modified to use WordPress wpdb functions instead of PHP's native mysql/pdo functions,
300
- * and to be compatible with batch processing.
301
- *
302
- * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
303
- *
304
- * @access public
305
- * @param string $table The table to run the replacement on.
306
- * @param int $page The page/block to begin the query on.
307
- * @param array $args An associative array containing arguments for this run.
308
- * @return array
309
- */
310
- private function searchReplace( $table, $page, $args ) {
311
-
312
- if( $this->thirdParty->isSearchReplaceExcluded( $table ) ) {
313
- $this->log( "DB Processing: Skip {$table}", \WPStaging\Utils\Logger::TYPE_INFO );
314
- return true;
315
- }
316
-
317
- // Load up the default settings for this chunk.
318
- $table = esc_sql( $table );
319
- $current_page = $this->options->job->start + $this->settings->querySRLimit;
320
- $pages = $this->get_pages_in_table( $table );
321
-
322
-
323
- // Search URL example.com/staging and root path to staging site /var/www/htdocs/staging
324
- $args['search_for'] = array(
325
- '//' . $this->sourceHostname,
326
- ABSPATH,
327
- '\/\/' . str_replace( '/', '\/', $this->sourceHostname ), // Used by revslider and several visual editors
328
- '%2F%2F' . str_replace( '/', '%2F', $this->sourceHostname ), // HTML entitity for WP Backery Page Builder Plugin
329
- //$this->getImagePathLive()
330
- );
331
-
332
-
333
- $args['replace_with'] = array(
334
- '//' . $this->destinationHostname,
335
- $this->options->destinationDir,
336
- '\/\/' . str_replace( '/', '\/', $this->destinationHostname ), // Used by revslider and several visual editors
337
- '%2F%2F' . str_replace( '/', '%2F', $this->destinationHostname ), // HTML entitity for WP Backery Page Builder Plugin
338
- //$this->getImagePathStaging()
339
- );
340
-
341
-
342
- $args['replace_guids'] = 'off';
343
- $args['dry_run'] = 'off';
344
- $args['case_insensitive'] = false;
345
- //$args['replace_mails'] = 'off';
346
- $args['skip_transients'] = 'on';
347
-
348
-
349
- // Allow filtering of search & replace parameters
350
- $args = apply_filters( 'wpstg_clone_searchreplace_params', $args );
351
-
352
- // Get a list of columns in this table.
353
- list( $primary_key, $columns ) = $this->get_columns( $table );
354
-
355
- // Bail out early if there isn't a primary key.
356
- // We commented this to search & replace through tables which have no primary keys like wp_revslider_slides
357
- // @todo test this carefully. If it causes (performance) issues we need to activate it again!
358
- // @since 2.4.4
359
- // if( null === $primary_key ) {
360
- // return false;
361
- // }
362
-
363
- $current_row = 0;
364
- $start = $this->options->job->start;
365
- $end = $this->settings->querySRLimit;
366
-
367
- // Grab the content of the table.
368
- $data = $this->stagingDb->get_results( "SELECT * FROM $table LIMIT $start, $end", ARRAY_A );
369
-
370
- // Filter certain rows (of other plugins)
371
- $filter = array(
372
- 'Admin_custome_login_Slidshow',
373
- 'Admin_custome_login_Social',
374
- 'Admin_custome_login_logo',
375
- 'Admin_custome_login_text',
376
- 'Admin_custome_login_login',
377
- 'Admin_custome_login_top',
378
- 'Admin_custome_login_dashboard',
379
- 'Admin_custome_login_Version',
380
- 'upload_path',
381
- );
382
-
383
- $filter = apply_filters( 'wpstg_clone_searchreplace_excl_rows', $filter );
384
-
385
- // Loop through the data.
386
- foreach ( $data as $row ) {
387
- $current_row++;
388
- $update_sql = array();
389
- $where_sql = array();
390
- $upd = false;
391
-
392
- // Skip rows below
393
- if( isset( $row['option_name'] ) && in_array( $row['option_name'], $filter ) ) {
394
- continue;
395
- }
396
-
397
- // Skip rows with transients (They can store huge data and we need to save memory)
398
- if( isset( $row['option_name'] ) && 'on' === $args['skip_transients'] && false !== strpos( $row['option_name'], '_transient' ) ) {
399
- continue;
400
- }
401
- // Skip rows with more than 5MB to save memory
402
- if( isset( $row['option_value'] ) && strlen( $row['option_value'] ) >= 5000000 ) {
403
- continue;
404
- }
405
-
406
-
407
- foreach ( $columns as $column ) {
408
-
409
- $dataRow = $row[$column];
410
-
411
- // Skip rows larger than 10MB
412
- $size = strlen( $dataRow );
413
- if( $size >= 5000000 ) {
414
- continue;
415
- }
416
-
417
- // Skip Primary key
418
- if( $column == $primary_key ) {
419
- $where_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
420
- continue;
421
- }
422
-
423
- // Skip GUIDs by default.
424
- if( 'on' !== $args['replace_guids'] && 'guid' == $column ) {
425
- continue;
426
- }
427
-
428
- // Skip mail addresses
429
- // if( 'off' === $args['replace_mails'] && false !== strpos( $dataRow, '@' . $this->multisiteDomainWithoutScheme ) ) {
430
- // continue;
431
- // }
432
-
433
- // Check options table
434
- if( $this->options->prefix . 'options' === $table ) {
435
-
436
- // Skip certain options
437
- if( isset( $should_skip ) && true === $should_skip ) {
438
- $should_skip = false;
439
- continue;
440
- }
441
-
442
- // Skip this row
443
- if( 'wpstg_existing_clones_beta' === $dataRow ||
444
- 'wpstg_existing_clones' === $dataRow ||
445
- 'wpstg_settings' === $dataRow ||
446
- 'wpstg_license_status' === $dataRow ||
447
- 'siteurl' === $dataRow ||
448
- 'home' === $dataRow
449
- ) {
450
- $should_skip = true;
451
- }
452
- }
453
-
454
- // Check the path delimiter for / or \/ and remove one of those which prevents from resulting in wrong syntax like domain.com/staging\/.
455
- // 1. local.wordpress.test -> local.wordpress.test/staging
456
- // 2. local.wordpress.test\/ -> local.wordpress.test\/staging\/
457
- $tmp = $args;
458
- if( false === strpos( $dataRow, $tmp['search_for'][0] ) ) {
459
- array_shift( $tmp['search_for'] ); // rtrim( $this->homeUrl, '/' ),
460
- array_shift( $tmp['replace_with'] ); // rtrim( $this->homeUrl, '/' ) . '/' . $this->options->cloneDirectoryName,
461
- } else {
462
- unset( $tmp['search_for'][1] );
463
- unset( $tmp['replace_with'][1] );
464
- // recount array
465
- $tmp['search_for'] = array_values( $tmp['search_for'] );
466
- $tmp['replace_with'] = array_values( $tmp['replace_with'] );
467
- }
468
-
469
- // Run a search replace on the data row and respect the serialisation.
470
- $i = 0;
471
- foreach ( $tmp['search_for'] as $replace ) {
472
- $dataRow = $this->recursive_unserialize_replace( $tmp['search_for'][$i], $tmp['replace_with'][$i], $dataRow, false, $args['case_insensitive'] );
473
- $i++;
474
- }
475
- unset( $replace );
476
- unset( $i );
477
- unset( $tmp );
478
-
479
- // Something was changed
480
- if( $row[$column] != $dataRow ) {
481
- $update_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
482
- $upd = true;
483
- }
484
- }
485
-
486
- // Determine what to do with updates.
487
- if( $args['dry_run'] === 'on' ) {
488
- // Don't do anything if a dry run
489
- } elseif( $upd && !empty( $where_sql ) ) {
490
- // If there are changes to make, run the query.
491
- $sql = 'UPDATE ' . $table . ' SET ' . implode( ', ', $update_sql ) . ' WHERE ' . implode( ' AND ', array_filter( $where_sql ) );
492
- $result = $this->stagingDb->query( $sql );
493
-
494
- if( !$result ) {
495
- $this->log( "Error updating row {$current_row} SQL: {$sql}", \WPStaging\Utils\Logger::TYPE_ERROR );
496
- }
497
- }
498
- } // end row loop
499
- unset( $row );
500
- unset( $update_sql );
501
- unset( $where_sql );
502
- unset( $sql );
503
-
504
-
505
- // DB Flush
506
- $this->stagingDb->flush();
507
- return true;
508
- }
509
-
510
- /**
511
- * Get path to multisite image folder e.g. wp-content/blogs.dir/ID/files or wp-content/uploads/sites/ID
512
- * @return string
513
- */
514
- private function getImagePathLive() {
515
- // Check first which structure is used
516
- $uploads = wp_upload_dir();
517
- $basedir = $uploads['basedir'];
518
- $blogId = get_current_blog_id();
519
-
520
- if( false === strpos( $basedir, 'blogs.dir' ) ) {
521
- // Since WP 3.5
522
- $path = $blogId > 1 ?
523
- 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR :
524
- 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
525
- } else {
526
- // old blog structure
527
- $path = $blogId > 1 ?
528
- 'wp-content' . DIRECTORY_SEPARATOR . 'blogs.dir' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR . 'files' . DIRECTORY_SEPARATOR :
529
- 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
530
- }
531
- return $path;
532
- }
533
-
534
- /**
535
- * Get path to staging site image path wp-content/uploads
536
- * @return string
537
- */
538
- private function getImagePathStaging() {
539
- return 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
540
- }
541
-
542
- /**
543
- * Adapted from interconnect/it's search/replace script.
544
- *
545
- * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
546
- *
547
- * Take a serialised array and unserialise it replacing elements as needed and
548
- * unserialising any subordinate arrays and performing the replace on those too.
549
- *
550
- * @access private
551
- * @param string $from String we're looking to replace.
552
- * @param string $to What we want it to be replaced with
553
- * @param array $data Used to pass any subordinate arrays back to in.
554
- * @param boolean $serialized Does the array passed via $data need serialising.
555
- * @param sting|boolean $case_insensitive Set to 'on' if we should ignore case, false otherwise.
556
- *
557
- * @return string|array The original array with all elements replaced as needed.
558
- */
559
- private function recursive_unserialize_replace( $from = '', $to = '', $data = '', $serialized = false, $case_insensitive = false ) {
560
- try {
561
- // Some unserialized data cannot be re-serialized eg. SimpleXMLElements
562
- if( is_serialized( $data ) && ( $unserialized = @unserialize( $data ) ) !== false ) {
563
- $data = $this->recursive_unserialize_replace( $from, $to, $unserialized, true, $case_insensitive );
564
- } elseif( is_array( $data ) ) {
565
- $tmp = array();
566
- foreach ( $data as $key => $value ) {
567
- $tmp[$key] = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
568
- }
569
-
570
- $data = $tmp;
571
- unset( $tmp );
572
- } elseif( is_object( $data ) ) {
573
- $tmp = $data;
574
- $props = get_object_vars( $data );
575
-
576
- // Do not continue if class contains __PHP_Incomplete_Class_Name
577
- if( !empty( $props['__PHP_Incomplete_Class_Name'] ) ) {
578
- return $data;
579
- }
580
-
581
- // Do a search & replace
582
- foreach ( $props as $key => $value ) {
583
- if( $key === '' || ord( $key[0] ) === 0 ) {
584
- continue;
585
- }
586
- $tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
587
- }
588
-
589
- $data = $tmp;
590
- unset( $tmp );
591
- unset( $props );
592
- } else {
593
- if( is_string( $data ) ) {
594
- if( !empty( $from ) && !empty( $to ) ) {
595
- $data = $this->str_replace( $from, $to, $data, $case_insensitive );
596
- }
597
- }
598
- }
599
-
600
- if( $serialized ) {
601
- return serialize( $data );
602
- }
603
- } catch ( Exception $error ) {
604
-
605
- }
606
-
607
- return $data;
608
- }
609
-
610
- /**
611
- * Check if the object is a valid one and not __PHP_Incomplete_Class_Name
612
- * Can not use is_object alone because in php 7.2 it's returning true even though object is __PHP_Incomplete_Class_Name
613
- * @return boolean
614
- */
615
- // private function isValidObject( $data ) {
616
- // if( !is_object( $data ) || gettype( $data ) != 'object' ) {
617
- // return false;
618
- // }
619
- //
620
- // $invalid_class_props = get_object_vars( $data );
621
- //
622
- // if (!isset($invalid_class_props['__PHP_Incomplete_Class_Name'])){
623
- // // Assume it must be an valid object
624
- // return true;
625
- // }
626
- //
627
- // $invalid_object_class = $invalid_class_props['__PHP_Incomplete_Class_Name'];
628
- //
629
- // if( !empty( $invalid_object_class ) ) {
630
- // return false;
631
- // }
632
- //
633
- // // Assume it must be an valid object
634
- // return true;
635
- // }
636
-
637
- /**
638
- * Mimics the mysql_real_escape_string function. Adapted from a post by 'feedr' on php.net.
639
- * @link http://php.net/manual/en/function.mysql-real-escape-string.php#101248
640
- * @access public
641
- * @param string $input The string to escape.
642
- * @return string
643
- */
644
- private function mysql_escape_mimic( $input ) {
645
- if( is_array( $input ) ) {
646
- return array_map( __METHOD__, $input );
647
- }
648
- if( !empty( $input ) && is_string( $input ) ) {
649
- return str_replace( array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $input );
650
- }
651
-
652
- return $input;
653
- }
654
-
655
- /**
656
- * Return unserialized object or array
657
- *
658
- * @param string $serialized_string Serialized string.
659
- * @param string $method The name of the caller method.
660
- *
661
- * @return mixed, false on failure
662
- */
663
- private static function unserialize( $serialized_string ) {
664
- if( !is_serialized( $serialized_string ) ) {
665
- return false;
666
- }
667
-
668
- $serialized_string = trim( $serialized_string );
669
- $unserialized_string = @unserialize( $serialized_string );
670
-
671
- return $unserialized_string;
672
- }
673
-
674
- /**
675
- * Wrapper for str_replace
676
- *
677
- * @param string $from
678
- * @param string $to
679
- * @param string $data
680
- * @param string|bool $case_insensitive
681
- *
682
- * @return string
683
- */
684
- private function str_replace( $from, $to, $data, $case_insensitive = false ) {
685
-
686
- // Add filter
687
- $excludes = apply_filters( 'wpstg_clone_searchreplace_excl', array() );
688
-
689
- // Build pattern
690
- $regexExclude = '';
691
- foreach ( $excludes as $exclude ) {
692
- $regexExclude .= $exclude . '(*SKIP)(FAIL)|';
693
- }
694
-
695
- if( 'on' === $case_insensitive ) {
696
- //$data = str_ireplace( $from, $to, $data );
697
- $data = preg_replace( '#' . $regexExclude . preg_quote( $from ) . '#i', $to, $data );
698
- } else {
699
- //$data = str_replace( $from, $to, $data );
700
- $data = preg_replace( '#' . $regexExclude . preg_quote( $from ) . '#', $to, $data );
701
- }
702
-
703
- return $data;
704
- }
705
-
706
- /**
707
- * Set the job
708
- * @param string $table
709
- */
710
- private function setJob( $table ) {
711
- if( !empty( $this->options->job->current ) ) {
712
- return;
713
- }
714
-
715
- $this->options->job->current = $table;
716
- $this->options->job->start = 0;
717
- }
718
-
719
- /**
720
- * Start Job
721
- * @param string $new
722
- * @param string $old
723
- * @return bool
724
- */
725
- private function startJob( $new, $old ) {
726
-
727
- if( $this->isExcludedTable( $new ) ) {
728
- return false;
729
- }
730
-
731
- // Table does not exist
732
- $result = $this->productionDb->query( "SHOW TABLES LIKE '{$old}'" );
733
- if( !$result || 0 === $result ) {
734
- return false;
735
- }
736
-
737
- if( 0 != $this->options->job->start ) {
738
- return true;
739
- }
740
-
741
- $this->options->job->total = ( int ) $this->productionDb->get_var( "SELECT COUNT(1) FROM {$old}" );
742
-
743
- if( 0 == $this->options->job->total ) {
744
- $this->finishStep();
745
- return false;
746
- }
747
-
748
- return true;
749
- }
750
-
751
- /**
752
- * Is table excluded from search replace processing?
753
- * @param string $table
754
- * @return boolean
755
- */
756
- private function isExcludedTable( $table ) {
757
-
758
- $customTables = apply_filters( 'wpstg_clone_searchreplace_tables_exclude', array() );
759
- $defaultTables = array('blogs');
760
-
761
- $tables = array_merge( $customTables, $defaultTables );
762
-
763
- $excludedTables = array();
764
- foreach ( $tables as $key => $value ) {
765
- $excludedTables[] = $this->options->prefix . $value;
766
- }
767
-
768
- if( in_array( $table, $excludedTables ) ) {
769
- return true;
770
- }
771
- return false;
772
- }
773
-
774
- /**
775
- * Finish the step
776
- */
777
- private function finishStep() {
778
- // This job is not finished yet
779
- if( $this->options->job->total > $this->options->job->start ) {
780
- return false;
781
- }
782
-
783
- // Add it to cloned tables listing
784
- $this->options->clonedTables[] = $this->options->tables[$this->options->currentStep];
785
-
786
- // Reset job
787
- $this->options->job = new \stdClass();
788
-
789
- return true;
790
- }
791
-
792
- /**
793
- * Drop table if necessary
794
- * @param string $new
795
- */
796
- private function dropTable( $new ) {
797
- $old = $this->stagingDb->get_var( $this->stagingDb->prepare( "SHOW TABLES LIKE %s", $new ) );
798
-
799
- if( !$this->shouldDropTable( $new, $old ) ) {
800
- return;
801
- }
802
-
803
- $this->log( "DB Processing: {$new} already exists, dropping it first" );
804
- $this->stagingDb->query( "DROP TABLE {$new}" );
805
- }
806
-
807
- /**
808
- * Check if table needs to be dropped
809
- * @param string $new
810
- * @param string $old
811
- * @return bool
812
- */
813
- private function shouldDropTable( $new, $old ) {
814
- return (
815
- $old == $new &&
816
- (
817
- !isset( $this->options->job->current ) ||
818
- !isset( $this->options->job->start ) ||
819
- 0 == $this->options->job->start
820
- )
821
- );
822
- }
823
-
824
- /**
825
- * Check if WP is installed in subdir
826
- * @return boolean
827
- */
828
- private function isSubDir() {
829
- // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
830
- // This is happening much more often than you would expect
831
- $siteurl = preg_replace( '#^https?://#', '', rtrim( get_option( 'siteurl' ), '/' ) );
832
- $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
833
-
834
- if( $home !== $siteurl ) {
835
- return true;
836
- }
837
- return false;
838
- }
839
-
840
- }
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 SearchReplaceExternal extends JobExecutable {
19
+
20
+ /**
21
+ * @var int
22
+ */
23
+ private $total = 0;
24
+
25
+ /**
26
+ * Staging Site DB
27
+ * @var \WPDB
28
+ */
29
+ private $stagingDb;
30
+
31
+ /**
32
+ * Production Site DB
33
+ * @var \WPDB
34
+ */
35
+ private $productionDb;
36
+
37
+ /**
38
+ *
39
+ * @var string
40
+ */
41
+ private $sourceHostname;
42
+
43
+ /**
44
+ *
45
+ * @var string
46
+ */
47
+ private $destinationHostname;
48
+
49
+ /**
50
+ *
51
+ * @var Obj
52
+ */
53
+ private $strings;
54
+
55
+ /**
56
+ * The prefix of the new database tables which are used for the live site after updating tables
57
+ * @var string
58
+ */
59
+ public $tmpPrefix;
60
+
61
+ /**
62
+ * Initialize
63
+ */
64
+ public function initialize() {
65
+ $this->total = count( $this->options->tables );
66
+ $this->stagingDb = $this->getStagingDB();
67
+ $this->productionDb = WPStaging::getInstance()->get( "wpdb" );
68
+ $this->tmpPrefix = $this->options->prefix;
69
+ $this->strings = new Strings();
70
+ $this->sourceHostname = $this->getSourceHostname();
71
+ $this->destinationHostname = $this->getDestinationHostname();
72
+ }
73
+
74
+ /**
75
+ * Get database object to interact with
76
+ */
77
+ private function getStagingDB() {
78
+ return new \wpdb( $this->options->databaseUser, $this->options->databasePassword, $this->options->databaseDatabase, $this->options->databaseServer );
79
+ }
80
+
81
+ public function start() {
82
+ // Skip job. Nothing to do
83
+ if( $this->options->totalSteps === 0 ) {
84
+ $this->prepareResponse( true, false );
85
+ }
86
+
87
+ $this->run();
88
+
89
+ // Save option, progress
90
+ $this->saveOptions();
91
+
92
+ return ( object ) $this->response;
93
+ }
94
+
95
+ /**
96
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
97
+ * @return void
98
+ */
99
+ protected function calculateTotalSteps() {
100
+ $this->options->totalSteps = $this->total;
101
+ }
102
+
103
+ /**
104
+ * Execute the Current Step
105
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
106
+ * @return bool
107
+ */
108
+ protected function execute() {
109
+ // Over limits threshold
110
+ if( $this->isOverThreshold() ) {
111
+ // Prepare response and save current progress
112
+ $this->prepareResponse( false, false );
113
+ $this->saveOptions();
114
+ return false;
115
+ }
116
+
117
+ // No more steps, finished
118
+ if( $this->options->currentStep > $this->total || !isset( $this->options->tables[$this->options->currentStep] ) ) {
119
+ $this->prepareResponse( true, false );
120
+ return false;
121
+ }
122
+
123
+ // Table is excluded
124
+ if( in_array( $this->options->tables[$this->options->currentStep], $this->options->excludedTables ) ) {
125
+ $this->prepareResponse();
126
+ return true;
127
+ }
128
+
129
+ // Search & Replace
130
+ if( !$this->stopExecution() && !$this->updateTable( $this->options->tables[$this->options->currentStep] ) ) {
131
+ // Prepare Response
132
+ $this->prepareResponse( false, false );
133
+
134
+ // Not finished
135
+ return true;
136
+ }
137
+
138
+
139
+ // Prepare Response
140
+ $this->prepareResponse();
141
+
142
+ // Not finished
143
+ return true;
144
+ }
145
+
146
+ /**
147
+ * Stop Execution immediately
148
+ * return mixed bool | json
149
+ */
150
+ private function stopExecution() {
151
+ // if( $this->stagingDb->prefix == $this->tmpPrefix ) {
152
+ // $this->returnException( 'Fatal Error 9: Prefix ' . $this->stagingDb->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.' );
153
+ // }
154
+ return false;
155
+ }
156
+
157
+ /**
158
+ * Copy Tables
159
+ * @param string $tableName
160
+ * @return bool
161
+ */
162
+ private function updateTable( $tableName ) {
163
+ $strings = new Strings();
164
+ $table = $strings->str_replace_first( $this->productionDb->prefix, '', $tableName );
165
+ $newTableName = $this->tmpPrefix . $table;
166
+
167
+ // Save current job
168
+ $this->setJob( $newTableName );
169
+
170
+ // Beginning of the job
171
+ if( !$this->startJob( $newTableName, $tableName ) ) {
172
+ return true;
173
+ }
174
+ // Copy data
175
+ $this->startReplace( $newTableName );
176
+
177
+ // Finish the step
178
+ return $this->finishStep();
179
+ }
180
+
181
+ /**
182
+ * Get source Hostname depending on wheather WP has been installed in sub dir or not
183
+ * @return type
184
+ */
185
+ private function getSourceHostname() {
186
+
187
+ if( $this->isSubDir() ) {
188
+ return trailingslashit( $this->multisiteHomeUrlWithoutScheme ) . '/' . $this->getSubDir();
189
+ }
190
+ return $this->multisiteHomeUrlWithoutScheme;
191
+ }
192
+
193
+ /**
194
+ * Get destination Hostname depending on wheather WP has been installed in sub dir or not
195
+ * Retun host name without scheme
196
+ * @return type
197
+ */
198
+ private function getDestinationHostname() {
199
+
200
+ if( !empty( $this->options->cloneHostname ) ) {
201
+ return $this->strings->getUrlWithoutScheme( $this->options->cloneHostname );
202
+ }
203
+
204
+ if( $this->isSubDir() ) {
205
+ return trailingslashit( $this->strings->getUrlWithoutScheme( $this->multisiteDomainWithoutScheme ) ) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName;
206
+ }
207
+ return trailingslashit( $this->strings->getUrlWithoutScheme( $this->multisiteDomainWithoutScheme ) ) . $this->options->cloneDirectoryName;
208
+ }
209
+
210
+ /**
211
+ * Get the install sub directory if WP is installed in sub directory
212
+ * @return string
213
+ */
214
+ private function getSubDir() {
215
+ $home = get_option( 'home' );
216
+ $siteurl = get_option( 'siteurl' );
217
+
218
+ if( empty( $home ) || empty( $siteurl ) ) {
219
+ return '';
220
+ }
221
+
222
+ $dir = str_replace( $home, '', $siteurl );
223
+ return str_replace( '/', '', $dir );
224
+ }
225
+
226
+ /**
227
+ * Start search replace job
228
+ * @param string $new
229
+ * @param string $old
230
+ */
231
+ private function startReplace( $table ) {
232
+ $rows = $this->options->job->start + $this->settings->querySRLimit;
233
+ $this->log(
234
+ "DB Search & Replace: Table {$table} {$this->options->job->start} to {$rows} records"
235
+ );
236
+
237
+ // Search & Replace
238
+ $this->searchReplace( $table, $rows, array() );
239
+
240
+ // Set new offset
241
+ $this->options->job->start += $this->settings->querySRLimit;
242
+ }
243
+
244
+ /**
245
+ * Returns the number of pages in a table.
246
+ * @access public
247
+ * @return int
248
+ */
249
+ // private function get_pages_in_table( $table ) {
250
+ //
251
+ // // Table does not exist
252
+ // $table = str_replace( $this->options->prefix . '.', null, $table );
253
+ // $result = $this->productionDb->query( "SHOW TABLES LIKE '{$table}'" );
254
+ // if( !$result || 0 === $result ) {
255
+ // return 0;
256
+ // }
257
+ //
258
+ // $table = esc_sql( $table );
259
+ // $rows = $this->productionDb->get_var( "SELECT COUNT(*) FROM $table" );
260
+ // $pages = ceil( $rows / $this->settings->querySRLimit );
261
+ // return absint( $pages );
262
+ // }
263
+
264
+ /**
265
+ * Gets the columns in a table.
266
+ * @access public
267
+ * @param string $table The table to check.
268
+ * @return array
269
+ */
270
+ private function get_columns( $table ) {
271
+ $primary_key = null;
272
+ $columns = array();
273
+ $fields = $this->stagingDb->get_results( 'DESCRIBE ' . $table );
274
+ if( is_array( $fields ) ) {
275
+ foreach ( $fields as $column ) {
276
+ $columns[] = $column->Field;
277
+ if( $column->Key == 'PRI' ) {
278
+ $primary_key = $column->Field;
279
+ }
280
+ }
281
+ }
282
+ return array($primary_key, $columns);
283
+ }
284
+
285
+ /**
286
+ * Adapated from interconnect/it's search/replace script, adapted from Better Search Replace
287
+ *
288
+ * Modified to use WordPress wpdb functions instead of PHP's native mysql/pdo functions,
289
+ * and to be compatible with batch processing.
290
+ *
291
+ * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
292
+ *
293
+ * @access public
294
+ * @param string $table The table to run the replacement on.
295
+ * @param int $page The page/block to begin the query on.
296
+ * @param array $args An associative array containing arguments for this run.
297
+ * @return array
298
+ */
299
+ private function searchReplace( $table, $page, $args ) {
300
+
301
+ if( $this->thirdParty->isSearchReplaceExcluded( $table ) ) {
302
+ $this->log( "DB Search & Replace: Skip {$table}", \WPStaging\Utils\Logger::TYPE_INFO );
303
+ return true;
304
+ }
305
+
306
+ // Load up the default settings for this chunk.
307
+ $table = esc_sql( $table );
308
+ //$current_page = $this->options->job->start + $this->settings->querySRLimit;
309
+ //$pages = $this->get_pages_in_table( $table );
310
+ // Search URL example.com/staging and root path to staging site /var/www/htdocs/staging
311
+ $args['search_for'] = array(
312
+ '//' . $this->sourceHostname,
313
+ ABSPATH,
314
+ '\/\/' . str_replace( '/', '\/', $this->sourceHostname ), // Used by revslider and several visual editors
315
+ '%2F%2F' . str_replace( '/', '%2F', $this->sourceHostname ), // HTML entitity for WP Backery Page Builder Plugin
316
+ //$this->getImagePathLive()
317
+ );
318
+
319
+
320
+ $args['replace_with'] = array(
321
+ '//' . $this->destinationHostname,
322
+ $this->options->destinationDir,
323
+ '\/\/' . str_replace( '/', '\/', $this->destinationHostname ), // Used by revslider and several visual editors
324
+ '%2F%2F' . str_replace( '/', '%2F', $this->destinationHostname ), // HTML entitity for WP Backery Page Builder Plugin
325
+ //$this->getImagePathStaging()
326
+ );
327
+
328
+ $this->debugLog( "DB Search & Replace: Search: {$args['search_for'][0]}", \WPStaging\Utils\Logger::TYPE_INFO );
329
+ $this->debugLog( "DB Search & Replace: Replace: {$args['replace_with'][0]}", \WPStaging\Utils\Logger::TYPE_INFO );
330
+
331
+
332
+ $args['replace_guids'] = 'off';
333
+ $args['dry_run'] = 'off';
334
+ $args['case_insensitive'] = false;
335
+ //$args['replace_mails'] = 'off';
336
+ $args['skip_transients'] = 'on';
337
+
338
+
339
+ // Allow filtering of search & replace parameters
340
+ $args = apply_filters( 'wpstg_clone_searchreplace_params', $args );
341
+
342
+ // Get a list of columns in this table.
343
+ list( $primary_key, $columns ) = $this->get_columns( $table );
344
+
345
+ // Bail out early if there isn't a primary key.
346
+ // We commented this to search & replace through tables which have no primary keys like wp_revslider_slides
347
+ // @todo test this carefully. If it causes (performance) issues we need to activate it again!
348
+ // @since 2.4.4
349
+ // if( null === $primary_key ) {
350
+ // return false;
351
+ // }
352
+
353
+ $current_row = 0;
354
+ $start = $this->options->job->start;
355
+ $end = $this->settings->querySRLimit;
356
+
357
+ // Grab the content of the table.
358
+ $data = $this->stagingDb->get_results( "SELECT * FROM $table LIMIT $start, $end", ARRAY_A );
359
+
360
+ // Filter certain rows (of other plugins)
361
+ $filter = array(
362
+ 'Admin_custome_login_Slidshow',
363
+ 'Admin_custome_login_Social',
364
+ 'Admin_custome_login_logo',
365
+ 'Admin_custome_login_text',
366
+ 'Admin_custome_login_login',
367
+ 'Admin_custome_login_top',
368
+ 'Admin_custome_login_dashboard',
369
+ 'Admin_custome_login_Version',
370
+ 'upload_path'
371
+ );
372
+
373
+ $filter = apply_filters( 'wpstg_clone_searchreplace_excl_rows', $filter );
374
+
375
+ // Loop through the data.
376
+ foreach ( $data as $row ) {
377
+ $current_row++;
378
+ $update_sql = array();
379
+ $where_sql = array();
380
+ $upd = false;
381
+
382
+ // Skip rows below
383
+ if( isset( $row['option_name'] ) && in_array( $row['option_name'], $filter ) ) {
384
+ continue;
385
+ }
386
+
387
+ // Skip rows with transients (They can store huge data and we need to save memory)
388
+ if( isset( $row['option_name'] ) && 'on' === $args['skip_transients'] && false !== strpos( $row['option_name'], '_transient' ) ) {
389
+ continue;
390
+ }
391
+ // Skip rows with more than 5MB to save memory
392
+ if( isset( $row['option_value'] ) && strlen( $row['option_value'] ) >= 5000000 ) {
393
+ continue;
394
+ }
395
+
396
+
397
+ foreach ( $columns as $column ) {
398
+
399
+ $dataRow = $row[$column];
400
+
401
+
402
+ // Skip rows larger than 5MB
403
+ $size = strlen( $dataRow );
404
+ if( $size >= 5000000 ) {
405
+ continue;
406
+ }
407
+
408
+ // Skip Primary key
409
+ if( $column == $primary_key ) {
410
+ $where_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
411
+ continue;
412
+ }
413
+
414
+ // Skip GUIDs by default.
415
+ if( 'on' !== $args['replace_guids'] && 'guid' == $column ) {
416
+ continue;
417
+ }
418
+
419
+ // Skip mail addresses
420
+ // if( 'off' === $args['replace_mails'] && false !== strpos( $dataRow, '@' . $this->multisiteDomainWithoutScheme ) ) {
421
+ // continue;
422
+ // }
423
+ // Check options table
424
+ if( $this->options->prefix . 'options' === $table ) {
425
+
426
+ // Skip certain options
427
+ // if( isset( $should_skip ) && true === $should_skip ) {
428
+ // $should_skip = false;
429
+ // continue;
430
+ // }
431
+ // Skip this row
432
+ if( 'wpstg_existing_clones_beta' === $dataRow ||
433
+ 'wpstg_existing_clones' === $dataRow ||
434
+ 'wpstg_settings' === $dataRow ||
435
+ 'wpstg_license_status' === $dataRow ||
436
+ 'siteurl' === $dataRow ||
437
+ 'home' === $dataRow
438
+ ) {
439
+ //$should_skip = true;
440
+ continue;
441
+ }
442
+ }
443
+
444
+ // Check the path delimiter for / or \/ and remove one of those which prevents from resulting in wrong syntax like domain.com/staging\/.
445
+ // 1. local.wordpress.test -> local.wordpress.test/staging
446
+ // 2. local.wordpress.test\/ -> local.wordpress.test\/staging\/
447
+ $tmp = $args;
448
+ if( false === strpos( $dataRow, $tmp['search_for'][0] ) ) {
449
+ array_shift( $tmp['search_for'] ); // rtrim( $this->homeUrl, '/' ),
450
+ array_shift( $tmp['replace_with'] ); // rtrim( $this->homeUrl, '/' ) . '/' . $this->options->cloneDirectoryName,
451
+ } else {
452
+ unset( $tmp['search_for'][1] );
453
+ unset( $tmp['replace_with'][1] );
454
+ // recount array
455
+ $tmp['search_for'] = array_values( $tmp['search_for'] );
456
+ $tmp['replace_with'] = array_values( $tmp['replace_with'] );
457
+ }
458
+
459
+ // Run a search replace on the data row and respect the serialisation.
460
+ $i = 0;
461
+ foreach ( $tmp['search_for'] as $replace ) {
462
+ $dataRow = $this->recursive_unserialize_replace( $tmp['search_for'][$i], $tmp['replace_with'][$i], $dataRow, false, $args['case_insensitive'] );
463
+ $i++;
464
+ }
465
+ unset( $replace );
466
+ unset( $i );
467
+ unset( $tmp );
468
+
469
+ // Something was changed
470
+ if( $row[$column] != $dataRow ) {
471
+ $update_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
472
+ $upd = true;
473
+ }
474
+ }
475
+
476
+ // Determine what to do with updates.
477
+ if( $args['dry_run'] === 'on' ) {
478
+ // Don't do anything if a dry run
479
+ } elseif( $upd && !empty( $where_sql ) ) {
480
+ // If there are changes to make, run the query.
481
+ $sql = 'UPDATE ' . $table . ' SET ' . implode( ', ', $update_sql ) . ' WHERE ' . implode( ' AND ', array_filter( $where_sql ) );
482
+ $result = $this->stagingDb->query( $sql );
483
+
484
+ if( !$result ) {
485
+ $this->log( "Error updating row {$current_row} SQL: {$sql}", \WPStaging\Utils\Logger::TYPE_ERROR );
486
+ }
487
+ }
488
+ } // end row loop
489
+ unset( $row );
490
+ unset( $update_sql );
491
+ unset( $where_sql );
492
+ unset( $sql );
493
+ unset( $current_row );
494
+
495
+
496
+ // DB Flush
497
+ $this->stagingDb->flush();
498
+ return true;
499
+ }
500
+
501
+ /**
502
+ * Get path to multisite image folder e.g. wp-content/blogs.dir/ID/files or wp-content/uploads/sites/ID
503
+ * @return string
504
+ */
505
+ private function getImagePathLive() {
506
+ // Check first which structure is used
507
+ $uploads = wp_upload_dir();
508
+ $basedir = $uploads['basedir'];
509
+ $blogId = get_current_blog_id();
510
+
511
+ if( false === strpos( $basedir, 'blogs.dir' ) ) {
512
+ // Since WP 3.5
513
+ $path = $blogId > 1 ?
514
+ 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR :
515
+ 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
516
+ } else {
517
+ // old blog structure
518
+ $path = $blogId > 1 ?
519
+ 'wp-content' . DIRECTORY_SEPARATOR . 'blogs.dir' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR . 'files' . DIRECTORY_SEPARATOR :
520
+ 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
521
+ }
522
+ return $path;
523
+ }
524
+
525
+ /**
526
+ * Get path to staging site image path wp-content/uploads
527
+ * @return string
528
+ */
529
+ private function getImagePathStaging() {
530
+ return 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
531
+ }
532
+
533
+ /**
534
+ * Adapted from interconnect/it's search/replace script.
535
+ *
536
+ * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
537
+ *
538
+ * Take a serialised array and unserialise it replacing elements as needed and
539
+ * unserialising any subordinate arrays and performing the replace on those too.
540
+ *
541
+ * @access private
542
+ * @param string $from String we're looking to replace.
543
+ * @param string $to What we want it to be replaced with
544
+ * @param array $data Used to pass any subordinate arrays back to in.
545
+ * @param boolean $serialized Does the array passed via $data need serialising.
546
+ * @param sting|boolean $case_insensitive Set to 'on' if we should ignore case, false otherwise.
547
+ *
548
+ * @return string|array The original array with all elements replaced as needed.
549
+ */
550
+ private function recursive_unserialize_replace( $from = '', $to = '', $data = '', $serialized = false, $case_insensitive = false ) {
551
+ try {
552
+ // PDO instances can not be serialized or unserialized
553
+ if( is_serialized( $data ) && strpos( $data, 'O:3:"PDO":0:' ) !== false ) {
554
+ return $data;
555
+ }
556
+ // Some unserialized data cannot be re-serialized eg. SimpleXMLElements
557
+ if( is_serialized( $data ) && ( $unserialized = @unserialize( $data ) ) !== false ) {
558
+ $data = $this->recursive_unserialize_replace( $from, $to, $unserialized, true, $case_insensitive );
559
+ } elseif( is_array( $data ) ) {
560
+ $tmp = array();
561
+ foreach ( $data as $key => $value ) {
562
+ $tmp[$key] = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
563
+ }
564
+
565
+ $data = $tmp;
566
+ unset( $tmp );
567
+ } elseif( is_object( $data ) ) {
568
+ $props = get_object_vars( $data );
569
+
570
+ // Do a search & replace
571
+ if( empty( $props['__PHP_Incomplete_Class_Name'] ) ) {
572
+ $tmp = $data;
573
+ foreach ( $props as $key => $value ) {
574
+ if( $key === '' || ord( $key[0] ) === 0 ) {
575
+ continue;
576
+ }
577
+ $tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
578
+ }
579
+ $data = $tmp;
580
+ $tmp = '';
581
+ $props = '';
582
+ unset( $tmp );
583
+ unset( $props );
584
+ }
585
+ } else {
586
+ if( is_string( $data ) ) {
587
+ if( !empty( $from ) && !empty( $to ) ) {
588
+ $data = $this->str_replace( $from, $to, $data, $case_insensitive );
589
+ }
590
+ }
591
+ }
592
+
593
+ if( $serialized ) {
594
+ return serialize( $data );
595
+ }
596
+ } catch ( Exception $error ) {
597
+
598
+ }
599
+
600
+ return $data;
601
+ }
602
+
603
+ /**
604
+ * Check if the object is a valid one and not __PHP_Incomplete_Class_Name
605
+ * Can not use is_object alone because in php 7.2 it's returning true even though object is __PHP_Incomplete_Class_Name
606
+ * @return boolean
607
+ */
608
+ // private function isValidObject( $data ) {
609
+ // if( !is_object( $data ) || gettype( $data ) != 'object' ) {
610
+ // return false;
611
+ // }
612
+ //
613
+ // $invalid_class_props = get_object_vars( $data );
614
+ //
615
+ // if (!isset($invalid_class_props['__PHP_Incomplete_Class_Name'])){
616
+ // // Assume it must be an valid object
617
+ // return true;
618
+ // }
619
+ //
620
+ // $invalid_object_class = $invalid_class_props['__PHP_Incomplete_Class_Name'];
621
+ //
622
+ // if( !empty( $invalid_object_class ) ) {
623
+ // return false;
624
+ // }
625
+ //
626
+ // // Assume it must be an valid object
627
+ // return true;
628
+ // }
629
+
630
+ /**
631
+ * Mimics the mysql_real_escape_string function. Adapted from a post by 'feedr' on php.net.
632
+ * @link http://php.net/manual/en/function.mysql-real-escape-string.php#101248
633
+ * @access public
634
+ * @param string $input The string to escape.
635
+ * @return string
636
+ */
637
+ private function mysql_escape_mimic( $input ) {
638
+ if( is_array( $input ) ) {
639
+ return array_map( __METHOD__, $input );
640
+ }
641
+ if( !empty( $input ) && is_string( $input ) ) {
642
+ return str_replace( array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $input );
643
+ }
644
+
645
+ return $input;
646
+ }
647
+
648
+ /**
649
+ * Return unserialized object or array
650
+ *
651
+ * @param string $serialized_string Serialized string.
652
+ * @param string $method The name of the caller method.
653
+ *
654
+ * @return mixed, false on failure
655
+ */
656
+ private static function unserialize( $serialized_string ) {
657
+ if( !is_serialized( $serialized_string ) ) {
658
+ return false;
659
+ }
660
+
661
+ $serialized_string = trim( $serialized_string );
662
+ $unserialized_string = @unserialize( $serialized_string );
663
+
664
+ return $unserialized_string;
665
+ }
666
+
667
+ /**
668
+ * Wrapper for str_replace
669
+ *
670
+ * @param string $from
671
+ * @param string $to
672
+ * @param string $data
673
+ * @param string|bool $case_insensitive
674
+ *
675
+ * @return string
676
+ */
677
+ private function str_replace( $from, $to, $data, $case_insensitive = false ) {
678
+
679
+ // Add filter
680
+ $excludes = apply_filters( 'wpstg_clone_searchreplace_excl', array() );
681
+
682
+ // Build pattern
683
+ $regexExclude = '';
684
+ foreach ( $excludes as $exclude ) {
685
+ $regexExclude .= $exclude . '(*SKIP)(FAIL)|';
686
+ }
687
+
688
+ if( 'on' === $case_insensitive ) {
689
+ //$data = str_ireplace( $from, $to, $data );
690
+ $data = preg_replace( '#' . $regexExclude . preg_quote( $from ) . '#i', $to, $data );
691
+ } else {
692
+ //$data = str_replace( $from, $to, $data );
693
+ $data = preg_replace( '#' . $regexExclude . preg_quote( $from ) . '#', $to, $data );
694
+ }
695
+
696
+ return $data;
697
+ }
698
+
699
+ /**
700
+ * Set the job
701
+ * @param string $table
702
+ */
703
+ private function setJob( $table ) {
704
+ if( !empty( $this->options->job->current ) ) {
705
+ return;
706
+ }
707
+
708
+ $this->options->job->current = $table;
709
+ $this->options->job->start = 0;
710
+ }
711
+
712
+ /**
713
+ * Start Job
714
+ * @param string $new
715
+ * @param string $old
716
+ * @return bool
717
+ */
718
+ private function startJob( $new, $old ) {
719
+
720
+ if( $this->isExcludedTable( $new ) ) {
721
+ return false;
722
+ }
723
+
724
+ // Table does not exist
725
+ $result = $this->productionDb->query( "SHOW TABLES LIKE '{$old}'" );
726
+ if( !$result || 0 === $result ) {
727
+ return false;
728
+ }
729
+
730
+ if( 0 != $this->options->job->start ) {
731
+ return true;
732
+ }
733
+
734
+ $this->options->job->total = ( int ) $this->productionDb->get_var( "SELECT COUNT(1) FROM {$old}" );
735
+
736
+ if( 0 == $this->options->job->total ) {
737
+ $this->finishStep();
738
+ return false;
739
+ }
740
+
741
+ return true;
742
+ }
743
+
744
+ /**
745
+ * Is table excluded from search replace processing?
746
+ * @param string $table
747
+ * @return boolean
748
+ */
749
+ private function isExcludedTable( $table ) {
750
+
751
+ $customTables = apply_filters( 'wpstg_clone_searchreplace_tables_exclude', array() );
752
+ $defaultTables = array('blogs');
753
+
754
+ $tables = array_merge( $customTables, $defaultTables );
755
+
756
+ $excludedTables = array();
757
+ foreach ( $tables as $key => $value ) {
758
+ $excludedTables[] = $this->options->prefix . $value;
759
+ }
760
+
761
+ if( in_array( $table, $excludedTables ) ) {
762
+ return true;
763
+ }
764
+ return false;
765
+ }
766
+
767
+ /**
768
+ * Finish the step
769
+ */
770
+ private function finishStep() {
771
+ // This job is not finished yet
772
+ if( $this->options->job->total > $this->options->job->start ) {
773
+ return false;
774
+ }
775
+
776
+ // Add it to cloned tables listing
777
+ $this->options->clonedTables[] = $this->options->tables[$this->options->currentStep];
778
+
779
+ // Reset job
780
+ $this->options->job = new \stdClass();
781
+
782
+ return true;
783
+ }
784
+
785
+ /**
786
+ * Drop table if necessary
787
+ * @param string $new
788
+ */
789
+ private function dropTable( $new ) {
790
+ $old = $this->stagingDb->get_var( $this->stagingDb->prepare( "SHOW TABLES LIKE %s", $new ) );
791
+
792
+ if( !$this->shouldDropTable( $new, $old ) ) {
793
+ return;
794
+ }
795
+
796
+ $this->log( "DB Search & Replace: {$new} already exists, dropping it first" );
797
+ $this->stagingDb->query( "DROP TABLE {$new}" );
798
+ }
799
+
800
+ /**
801
+ * Check if table needs to be dropped
802
+ * @param string $new
803
+ * @param string $old
804
+ * @return bool
805
+ */
806
+ private function shouldDropTable( $new, $old ) {
807
+ return (
808
+ $old == $new &&
809
+ (
810
+ !isset( $this->options->job->current ) ||
811
+ !isset( $this->options->job->start ) ||
812
+ 0 == $this->options->job->start
813
+ )
814
+ );
815
+ }
816
+
817
+ /**
818
+ * Check if WP is installed in subdir
819
+ * @return boolean
820
+ */
821
+ private function isSubDir() {
822
+ // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
823
+ // This is happening much more often than you would expect
824
+ $siteurl = preg_replace( '#^https?://#', '', rtrim( get_option( 'siteurl' ), '/' ) );
825
+ $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
826
+
827
+ if( $home !== $siteurl ) {
828
+ return true;
829
+ }
830
+ return false;
831
+ }
832
+
833
+ }
 
 
 
 
 
 
 
apps/Backend/Modules/Jobs/Scan.php CHANGED
@@ -161,15 +161,15 @@ class Scan extends Job {
161
  $dataSize = isset( $data["size"] ) ? $data["size"] : '';
162
 
163
 
164
- // Include wp core folders and their sub dirs.
165
- // Exclude all other folders (default setting)
166
  $isDisabled = ($name !== 'wp-admin' &&
167
  $name !== 'wp-includes' &&
168
  $name !== 'wp-content' &&
169
  $name !== 'sites') &&
170
- false === strpos( strrev( $dataPath ), strrev( "wp-admin" ) ) &&
171
- false === strpos( strrev( $dataPath ), strrev( "wp-includes" ) ) &&
172
- false === strpos( strrev( $dataPath ), strrev( "wp-content" ) ) ? true : false;
173
 
174
  // Extra class to differentiate between wp core and non core folders
175
  $class = !$isDisabled ? 'wpstg-root' : 'wpstg-extra';
@@ -190,6 +190,7 @@ class Scan extends Job {
190
  $output .= "'>{$name}";
191
  $output .= "</a>";
192
  $output .= "<span class='wpstg-size-info'>{$this->formatSize( $dataSize )}</span>";
 
193
 
194
  if( !empty( $directory ) ) {
195
  $output .= "<div class='wpstg-dir wpstg-subdir'>";
@@ -303,10 +304,11 @@ class Scan extends Job {
303
  // Gather Themes
304
  $this->getSubDirectories( WP_CONTENT_DIR . DIRECTORY_SEPARATOR . "themes" );
305
 
306
- // Gather Default Uploads Folder
307
- //$this->getSubDirectories(WP_CONTENT_DIR . DIRECTORY_SEPARATOR . "uploads");
308
  // Gather Custom Uploads Folder if there is one
309
  $this->getSubDirectories( $this->getUploadDir() );
 
 
 
310
  }
311
 
312
  /**
@@ -318,6 +320,16 @@ class Scan extends Job {
318
  return false;
319
  }
320
 
 
 
 
 
 
 
 
 
 
 
321
  $directories = new \DirectoryIterator( $path );
322
 
323
  foreach ( $directories as $directory ) {
@@ -420,12 +432,63 @@ class Scan extends Job {
420
  }
421
 
422
  /**
423
- * Get absolute WP uploads path e.g. /var/www/htdocs/example.com/wp-content/uploads/sites/1
 
 
424
  * @return string
425
  */
426
  protected function getUploadDir() {
427
- $uploads = wp_upload_dir();
428
- return $uploads['basedir'];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
429
  }
430
 
431
  }
161
  $dataSize = isset( $data["size"] ) ? $data["size"] : '';
162
 
163
 
164
+ // Select all wp core folders and their sub dirs.
165
+ // Unselect all other folders (default setting)
166
  $isDisabled = ($name !== 'wp-admin' &&
167
  $name !== 'wp-includes' &&
168
  $name !== 'wp-content' &&
169
  $name !== 'sites') &&
170
+ false === strpos( strrev( wpstg_replace_windows_directory_separator( $dataPath ) ), strrev( wpstg_replace_windows_directory_separator( ABSPATH . "wp-admin" ) ) ) &&
171
+ false === strpos( strrev( wpstg_replace_windows_directory_separator( $dataPath ) ), strrev( wpstg_replace_windows_directory_separator( ABSPATH . "wp-includes" ) ) ) &&
172
+ false === strpos( strrev( wpstg_replace_windows_directory_separator( $dataPath ) ), strrev( wpstg_replace_windows_directory_separator( ABSPATH . "wp-content" ) ) ) ? true : false;
173
 
174
  // Extra class to differentiate between wp core and non core folders
175
  $class = !$isDisabled ? 'wpstg-root' : 'wpstg-extra';
190
  $output .= "'>{$name}";
191
  $output .= "</a>";
192
  $output .= "<span class='wpstg-size-info'>{$this->formatSize( $dataSize )}</span>";
193
+ $output .= isset( $this->settings->debugMode ) ? "<span class='wpstg-size-info'> {$dataPath}</span>" : "";
194
 
195
  if( !empty( $directory ) ) {
196
  $output .= "<div class='wpstg-dir wpstg-subdir'>";
304
  // Gather Themes
305
  $this->getSubDirectories( WP_CONTENT_DIR . DIRECTORY_SEPARATOR . "themes" );
306
 
 
 
307
  // Gather Custom Uploads Folder if there is one
308
  $this->getSubDirectories( $this->getUploadDir() );
309
+
310
+ // Gather /sites/ or /blogs.dir/ folder if there is one (for multisites)
311
+ $this->getSubDirectories( $this->getMuUploadSitesDir() );
312
  }
313
 
314
  /**
320
  return false;
321
  }
322
 
323
+ if( !is_dir( $path ) ) {
324
+ return false;
325
+ }
326
+
327
+ // IMPORTANT: If this is not used and a folder belongs to another user
328
+ // DirectoryIterator() will throw a fatal error which can not be catched with is_readable()
329
+ if( !opendir( $path ) ) {
330
+ return false;
331
+ }
332
+
333
  $directories = new \DirectoryIterator( $path );
334
 
335
  foreach ( $directories as $directory ) {
432
  }
433
 
434
  /**
435
+ * Get absolute WP uploads path e.g.
436
+ * Multisites: /var/www/htdocs/example.com/wp-content/uploads/sites/1 or /var/www/htdocs/example.com/wp-content/blogs.dir/1/files
437
+ * Single sites: /var/www/htdocs/example.com/wp-content/uploads
438
  * @return string
439
  */
440
  protected function getUploadDir() {
441
+ $uploads = wp_upload_dir( null, false );
442
+
443
+ $baseDir = wpstg_replace_windows_directory_separator( $uploads['basedir'] );
444
+
445
+ // If multisite (and if not the main site in a post-MU network)
446
+ if( is_multisite() && !( is_main_network() && is_main_site() && defined( 'MULTISITE' ) ) ) {
447
+ // blogs.dir is used on WP 3.5 and lower
448
+ if( false !== strpos( $baseDir, 'blogs.dir' ) ) {
449
+ // remove this piece from the basedir: /blogs.dir/2/files
450
+ $uploadDir = wpstg_replace_first_match( '/blogs.dir/' . get_current_blog_id() . '/files', null, $baseDir );
451
+ $dir = wpstg_replace_windows_directory_separator( $uploadDir . '/blogs.dir' );
452
+ } else {
453
+ // remove this piece from the basedir: /sites/2
454
+ $uploadDir = wpstg_replace_first_match( '/sites/' . get_current_blog_id(), null, $baseDir );
455
+ $dir = wpstg_replace_windows_directory_separator( $uploadDir . '/sites' );
456
+ }
457
+
458
+
459
+ return $dir;
460
+ }
461
+ return $baseDir;
462
+ }
463
+
464
+ /**
465
+ * Get absolute WP uploads path e.g.
466
+ * Multisites: /var/www/htdocs/example.com/wp-content/uploads/sites/1 or /var/www/htdocs/example.com/wp-content/blogs.dir/1/files
467
+ * Single sites: /var/www/htdocs/example.com/wp-content/uploads
468
+ * @return string
469
+ */
470
+ protected function getMuUploadSitesDir() {
471
+ $uploads = wp_upload_dir( null, false );
472
+
473
+ $baseDir = wpstg_replace_windows_directory_separator( $uploads['basedir'] );
474
+
475
+ // If multisite (and if not the main site in a post-MU network)
476
+ if( is_multisite() && !( is_main_network() && is_main_site() && defined( 'MULTISITE' ) ) ) {
477
+ // blogs.dir is used on WP 3.5 and lower
478
+ if( false !== strpos( $baseDir, 'blogs.dir' ) ) {
479
+ // remove this piece from the basedir: /blogs.dir/2/files
480
+ $uploadDir = wpstg_replace_first_match( '/blogs.dir/' . get_current_blog_id() . '/files', null, $baseDir );
481
+ $dir = wpstg_replace_windows_directory_separator( $uploadDir . '/blogs.dir' );
482
+ } else {
483
+ // remove this piece from the basedir: /sites/2
484
+ $uploadDir = wpstg_replace_first_match( '/sites/' . get_current_blog_id(), null, $baseDir );
485
+ $dir = wpstg_replace_windows_directory_separator( $uploadDir . '/sites' );
486
+ }
487
+
488
+
489
+ return $dir;
490
+ }
491
+ return $baseDir;
492
  }
493
 
494
  }
apps/Backend/Modules/Jobs/SearchReplace.php CHANGED
@@ -220,7 +220,7 @@ class SearchReplace extends JobExecutable {
220
  private function startReplace( $table ) {
221
  $rows = $this->options->job->start + $this->settings->querySRLimit;
222
  $this->log(
223
- "DB Processing: Table {$table} {$this->options->job->start} to {$rows} records"
224
  );
225
 
226
  // Search & Replace
@@ -288,7 +288,7 @@ class SearchReplace extends JobExecutable {
288
  private function searchReplace( $table, $page, $args ) {
289
 
290
  if( $this->thirdParty->isSearchReplaceExcluded( $table ) ) {
291
- $this->log( "DB Processing: Skip {$table}", \WPStaging\Utils\Logger::TYPE_INFO );
292
  return true;
293
  }
294
 
@@ -302,16 +302,21 @@ class SearchReplace extends JobExecutable {
302
  '%2F%2F' . str_replace( '/', '%2F', $this->sourceHostname ), // HTML entitity for WP Backery Page Builder Plugin
303
  '\/\/' . str_replace( '/', '\/', $this->sourceHostname ), // Escaped \/ used by revslider and several visual editors
304
  '//' . $this->sourceHostname,
305
- rtrim( ABSPATH, '/' ),
 
306
  );
307
 
308
  $args['replace_with'] = array(
309
  '%2F%2F' . str_replace( '/', '%2F', $this->destinationHostname ),
310
  '\/\/' . str_replace( '/', '\/', $this->destinationHostname ),
311
  '//' . $this->destinationHostname,
312
- rtrim( $this->options->destinationDir, '/' ),
 
313
  );
314
 
 
 
 
315
 
316
  $args['replace_guids'] = 'off';
317
  $args['dry_run'] = 'off';
@@ -772,7 +777,7 @@ class SearchReplace extends JobExecutable {
772
  return;
773
  }
774
 
775
- $this->log( "DB Processing: {$new} already exists, dropping it first" );
776
  $this->db->query( "DROP TABLE {$new}" );
777
  }
778
 
220
  private function startReplace( $table ) {
221
  $rows = $this->options->job->start + $this->settings->querySRLimit;
222
  $this->log(
223
+ "DB Search & Replace: Table {$table} {$this->options->job->start} to {$rows} records"
224
  );
225
 
226
  // Search & Replace
288
  private function searchReplace( $table, $page, $args ) {
289
 
290
  if( $this->thirdParty->isSearchReplaceExcluded( $table ) ) {
291
+ $this->log( "DB Search & Replace: Skip {$table}", \WPStaging\Utils\Logger::TYPE_INFO );
292
  return true;
293
  }
294
 
302
  '%2F%2F' . str_replace( '/', '%2F', $this->sourceHostname ), // HTML entitity for WP Backery Page Builder Plugin
303
  '\/\/' . str_replace( '/', '\/', $this->sourceHostname ), // Escaped \/ used by revslider and several visual editors
304
  '//' . $this->sourceHostname,
305
+ //rtrim( ABSPATH, '/' ), // This lead to errors if ABSPATH is /www/ because this would break external links like https://www.domain.com to https://www/replace-string/domain.com
306
+ ABSPATH
307
  );
308
 
309
  $args['replace_with'] = array(
310
  '%2F%2F' . str_replace( '/', '%2F', $this->destinationHostname ),
311
  '\/\/' . str_replace( '/', '\/', $this->destinationHostname ),
312
  '//' . $this->destinationHostname,
313
+ //rtrim( $this->options->destinationDir, '/' ),
314
+ $this->options->destinationDir
315
  );
316
 
317
+ $this->debugLog( "DB Search & Replace: Search: {$args['search_for'][0]}", \WPStaging\Utils\Logger::TYPE_INFO );
318
+ $this->debugLog( "DB Search & Replace: Replace: {$args['replace_with'][0]}", \WPStaging\Utils\Logger::TYPE_INFO );
319
+
320
 
321
  $args['replace_guids'] = 'off';
322
  $args['dry_run'] = 'off';
777
  return;
778
  }
779
 
780
+ $this->log( "DB Search & Replace: {$new} already exists, dropping it first" );
781
  $this->db->query( "DROP TABLE {$new}" );
782
  }
783
 
apps/Core/Utils/Cache.php CHANGED
@@ -4,7 +4,7 @@ namespace WPStaging\Utils;
4
 
5
  // No Direct Access
6
  if( !defined( "WPINC" ) ) {
7
- die;
8
  }
9
 
10
  use WPStaging\WPStaging;
@@ -15,187 +15,188 @@ use WPStaging\WPStaging;
15
  */
16
  class Cache {
17
 
18
- /**
19
- * Cache directory (full path)
20
- * @var string
21
- */
22
- private $cacheDir;
23
-
24
- /**
25
- * Cache file extension
26
- * @var string
27
- */
28
- private $cacheExtension = "cache";
29
-
30
- /**
31
- * Lifetime of cache files in seconds
32
- * @var int
33
- */
34
- private $lifetime = 2592000; // 30 days
35
-
36
- /**
37
- * Cache constructor.
38
- * @param null|int $lifetime
39
- * @param null|string $cacheDir
40
- * @param null|string $cacheExtension
41
- * @throws \Exception
42
- */
43
-
44
- public function __construct( $lifetime = null, $cacheDir = null, $cacheExtension = null ) {
45
- // Set lifetime
46
- $lifetime = ( int ) $lifetime;
47
- if( $lifetime > 0 ) {
48
- $this->lifetime = $lifetime;
49
- }
50
-
51
- // Set cache directory
52
- if( !empty( $cacheDir ) && is_dir( $cacheDir ) ) {
53
- $this->cacheDir = $cacheDir;
54
- }
55
- // Set default
56
- else {
57
-
58
- $this->cacheDir = \WPStaging\WPStaging::getContentDir();
59
- }
60
-
61
- // Set cache extension
62
- if( !empty( $cacheExtension ) ) {
63
- $this->cacheExtension = $cacheExtension;
64
- }
65
-
66
- // If cache directory doesn't exists, create it
67
- if( !is_dir( $this->cacheDir ) && !@mkdir( $this->cacheDir, 0775, true ) ) {
68
- throw new \Exception( "Failed to create cache directory " . $this->cacheDir . '! Make sure folder permission is 755 and owner is correct. Should be www-data or similar.' );
69
- }
70
- }
71
-
72
- /**
73
- * Get cache
74
- * @param string $cacheFileName
75
- * @param mixed $defaultValue
76
- * @param null|int $lifetime
77
- * @return mixed|null
78
- */
79
- public function get( $cacheFileName, $defaultValue = null, $lifetime = null ) {
80
- // Check if file is valid
81
- if( false === ($cacheFile = $this->isValid( $cacheFileName, true, $lifetime )) ) {
82
- return $defaultValue;
83
- }
84
-
85
- return @unserialize( file_get_contents( $cacheFile ) );
86
- }
87
-
88
- /**
89
- * Saves value to given cache file
90
- * @param string $cacheFileName
91
- * @param mixed $value
92
- * @return bool
93
- * @throws \Exception
94
- */
95
- public function save( $cacheFileName, $value ) {
96
- $cacheFile = $this->cacheDir . $cacheFileName . '.' . $this->cacheExtension;
97
-
98
- // Attempt to delete cache file if it exists
99
- if( is_file( $cacheFile ) && !@unlink( $cacheFile ) ) {
100
- $this->returnException( "Can't delete existing cache file" );
101
- throw new \Exception( "Can't delete existing cache file" );
102
- }
103
-
104
- try {
105
- // Save it to file
106
- if( !file_put_contents( $cacheFile, @serialize( $value ) ) ) {
107
- $this->returnException( " Can't save data to: " . $cacheFile . " Disk quota exceeded or not enough free disk space left" );
108
- return false;
109
- }
110
- } catch ( Exception $e ) {
111
- $this->returnException( " Can't save data to: " . $cacheFile . " Error: " . $e );
112
- return false;
113
- }
114
- return true;
115
- }
116
-
117
- /**
118
- * Checks if file is valid or not
119
- * @param $cacheFileName
120
- * @param bool $deleteFileIfInvalid
121
- * @param null|int $lifetime
122
- * @return string|bool
123
- * @throws \Exception
124
- */
125
- public function isValid( $cacheFileName, $deleteFileIfInvalid = false, $lifetime = null ) {
126
- // Lifetime
127
- $lifetime = ( int ) $lifetime;
128
- if( -1 > $lifetime || 0 == $lifetime ) {
129
- $lifetime = $this->lifetime;
130
- }
131
-
132
- // Get full path of the given cache file
133
- $cacheFile = $this->cacheDir . $cacheFileName . '.' . $this->cacheExtension;
134
-
135
- // File doesn't exist
136
- if( !is_file( $cacheFile ) ) {
137
- return false;
138
- }
139
-
140
- // As long as file exists, don't check lifetime
141
- if( -1 == $lifetime ) {
142
- return $cacheFile;
143
- }
144
-
145
- // Time is up, file is invalid
146
- if( time() - filemtime( $cacheFile ) >= $lifetime ) {
147
-
148
- // Attempt to delete the file
149
- if( $deleteFileIfInvalid === true && !@unlink( $cacheFile ) ) {
150
- throw new \Exception( "Attempting to delete invalid cache file has failed!" );
151
- }
152
-
153
- // No need to delete the file, return
154
- return false;
155
- }
156
-
157
- return $cacheFile;
158
- }
159
-
160
- /**
161
- * Delete a cache file
162
- * @param string $cacheFileName
163
- * @return bool
164
- * @throws \Exception
165
- */
166
- public function delete( $cacheFileName ) {
167
- if( false !== ($cacheFile = $this->isValid( $cacheFileName, true )) && false === @unlink( $cacheFile ) ) {
168
- throw new \Exception( "Couldn't delete cache: {$cacheFileName}. Full Path: {$cacheFile}" );
169
- }
170
-
171
- return true;
172
- }
173
-
174
- /**
175
- * @return string
176
- */
177
- public function getCacheDir() {
178
- return $this->cacheDir;
179
- }
180
-
181
- /**
182
- * @return string
183
- */
184
- public function getCacheExtension() {
185
- return $this->cacheExtension;
186
- }
187
-
188
- /**
189
- * Throw a errror message via json and stop further execution
190
- * @param string $message
191
- */
192
- protected function returnException( $message = '' ) {
193
- wp_die( json_encode( array(
194
- 'job' => isset( $this->options->currentJob ) ? $this->options->currentJob : '',
195
- 'status' => false,
196
- 'message' => $message,
197
- 'error' => true
198
- ) ) );
199
- }
 
200
 
201
  }
4
 
5
  // No Direct Access
6
  if( !defined( "WPINC" ) ) {
7
+ die;
8
  }
9
 
10
  use WPStaging\WPStaging;
15
  */
16
  class Cache {
17
 
18
+ /**
19
+ * Cache directory (full path)
20
+ * @var string
21
+ */
22
+ private $cacheDir;
23
+
24
+ /**
25
+ * Cache file extension
26
+ * @var string
27
+ */
28
+ private $cacheExtension = "cache";
29
+
30
+ /**
31
+ * Lifetime of cache files in seconds
32
+ * @var int
33
+ */
34
+ private $lifetime = 2592000; // 30 days
35
+
36
+ /**
37
+ * Cache constructor.
38
+ * @param null|int $lifetime
39
+ * @param null|string $cacheDir
40
+ * @param null|string $cacheExtension
41
+ * @throws \Exception
42
+ */
43
+
44
+ public function __construct( $lifetime = null, $cacheDir = null, $cacheExtension = null ) {
45
+ // Set lifetime
46
+ $lifetime = ( int ) $lifetime;
47
+ if( $lifetime > 0 ) {
48
+ $this->lifetime = $lifetime;
49
+ }
50
+
51
+ // Set cache directory
52
+ if( !empty( $cacheDir ) && is_dir( $cacheDir ) ) {
53
+ $this->cacheDir = $cacheDir;
54
+ }
55
+ // Set default
56
+ else {
57
+
58
+ $this->cacheDir = \WPStaging\WPStaging::getContentDir();
59
+ }
60
+
61
+ // Set cache extension
62
+ if( !empty( $cacheExtension ) ) {
63
+ $this->cacheExtension = $cacheExtension;
64
+ }
65
+
66
+ // If cache directory doesn't exists, create it
67
+ if( !is_dir( $this->cacheDir ) && !@mkdir( $this->cacheDir, 0775, true ) ) {
68
+ throw new \Exception( "Failed to create cache directory " . $this->cacheDir . '! Make sure folder permission is 755 and owner is correct. Should be www-data or similar.' );
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Get cache
74
+ * @param string $cacheFileName
75
+ * @param mixed $defaultValue
76
+ * @param null|int $lifetime
77
+ * @return mixed|null
78
+ */
79
+ public function get( $cacheFileName, $defaultValue = null, $lifetime = null ) {
80
+ // Check if file is valid
81
+ if( false === ($cacheFile = $this->isValid( $cacheFileName, true, $lifetime )) ) {
82
+ return $defaultValue;
83
+ }
84
+
85
+ return @unserialize( file_get_contents( $cacheFile ) );
86
+ }
87
+
88
+ /**
89
+ * Saves value to given cache file
90
+ * @param string $cacheFileName
91
+ * @param mixed $value
92
+ * @return bool
93
+ * @throws \Exception
94
+ */
95
+ public function save( $cacheFileName, $value ) {
96
+ $cacheFile = $this->cacheDir . $cacheFileName . '.' . $this->cacheExtension;
97
+
98
+ // Attempt to delete cache file if it exists
99
+ if( is_file( $cacheFile ) && !@unlink( $cacheFile ) ) {
100
+ $this->returnException( "Can't delete existing cache file" );
101
+ throw new \Exception( "Can't delete existing cache file" );
102
+ }
103
+
104
+ try {
105
+
106
+ // Save it to file
107
+ if( !wpstg_put_contents( $cacheFile, @serialize( $value ) ) ) {
108
+ $this->returnException( " Can't save data to: " . $cacheFile . " Disk quota exceeded or not enough free disk space left" );
109
+ return false;
110
+ }
111
+ } catch ( Exception $e ) {
112
+ $this->returnException( " Can't save data to: " . $cacheFile . " Error: " . $e );
113
+ return false;
114
+ }
115
+ return true;
116
+ }
117
+
118
+ /**
119
+ * Checks if file is valid or not
120
+ * @param $cacheFileName
121
+ * @param bool $deleteFileIfInvalid
122
+ * @param null|int $lifetime
123
+ * @return string|bool
124
+ * @throws \Exception
125
+ */
126
+ public function isValid( $cacheFileName, $deleteFileIfInvalid = false, $lifetime = null ) {
127
+ // Lifetime
128
+ $lifetime = ( int ) $lifetime;
129
+ if( -1 > $lifetime || 0 == $lifetime ) {
130
+ $lifetime = $this->lifetime;
131
+ }
132
+
133
+ // Get full path of the given cache file
134
+ $cacheFile = $this->cacheDir . $cacheFileName . '.' . $this->cacheExtension;
135
+
136
+ // File doesn't exist
137
+ if( !is_file( $cacheFile ) ) {
138
+ return false;
139
+ }
140
+
141
+ // As long as file exists, don't check lifetime
142
+ if( -1 == $lifetime ) {
143
+ return $cacheFile;
144
+ }
145
+
146
+ // Time is up, file is invalid
147
+ if( time() - filemtime( $cacheFile ) >= $lifetime ) {
148
+
149
+ // Attempt to delete the file
150
+ if( $deleteFileIfInvalid === true && !@unlink( $cacheFile ) ) {
151
+ throw new \Exception( "Attempting to delete invalid cache file has failed!" );
152
+ }
153
+
154
+ // No need to delete the file, return
155
+ return false;
156
+ }
157
+
158
+ return $cacheFile;
159
+ }
160
+
161
+ /**
162
+ * Delete a cache file
163
+ * @param string $cacheFileName
164
+ * @return bool
165
+ * @throws \Exception
166
+ */
167
+ public function delete( $cacheFileName ) {
168
+ if( false !== ($cacheFile = $this->isValid( $cacheFileName, true )) && false === @unlink( $cacheFile ) ) {
169
+ throw new \Exception( "Couldn't delete cache: {$cacheFileName}. Full Path: {$cacheFile}" );
170
+ }
171
+
172
+ return true;
173
+ }
174
+
175
+ /**
176
+ * @return string
177
+ */
178
+ public function getCacheDir() {
179
+ return $this->cacheDir;
180
+ }
181
+
182
+ /**
183
+ * @return string
184
+ */
185
+ public function getCacheExtension() {
186
+ return $this->cacheExtension;
187
+ }
188
+
189
+ /**
190
+ * Throw a errror message via json and stop further execution
191
+ * @param string $message
192
+ */
193
+ protected function returnException( $message = '' ) {
194
+ wp_die( json_encode( array(
195
+ 'job' => isset( $this->options->currentJob ) ? $this->options->currentJob : '',
196
+ 'status' => false,
197
+ 'message' => $message,
198
+ 'error' => true
199
+ ) ) );
200
+ }
201
 
202
  }
apps/Core/Utils/Logger.php CHANGED
@@ -101,6 +101,7 @@ class Logger
101
  */
102
  public function add($message, $type = self::TYPE_ERROR)
103
  {
 
104
  $this->messages[] = array(
105
  "type" => $type,
106
  "date" => date("Y/m/d H:i:s"),
@@ -145,7 +146,8 @@ class Logger
145
  return true;
146
  }
147
 
148
- return (@file_put_contents($this->getLogFile(), $messageString, FILE_APPEND | LOCK_EX));
 
149
  }
150
 
151
  /**
101
  */
102
  public function add($message, $type = self::TYPE_ERROR)
103
  {
104
+
105
  $this->messages[] = array(
106
  "type" => $type,
107
  "date" => date("Y/m/d H:i:s"),
146
  return true;
147
  }
148
 
149
+ //return (@file_put_contents($this->getLogFile(), $messageString, FILE_APPEND | LOCK_EX));
150
+ return (@file_put_contents($this->getLogFile(), $messageString, FILE_APPEND));
151
  }
152
 
153
  /**
apps/Core/Utils/functions.php CHANGED
@@ -166,6 +166,12 @@ function wpstg_urldecode( $data ) {
166
  * @return bool
167
  */
168
  function wpstg_is_stagingsite() {
 
 
 
 
 
 
169
  if( "true" === get_option( "wpstg_is_staging_site" ) ) {
170
  return true;
171
  }
@@ -254,3 +260,199 @@ function wpstg_get_abs_upload_dir() {
254
 
255
  return $uploadsAbsPath;
256
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  * @return bool
167
  */
168
  function wpstg_is_stagingsite() {
169
+
170
+ if( file_exists( ABSPATH . '.wp-staging-cloneable' ) ) {
171
+ return false;
172
+ }
173
+
174
+
175
  if( "true" === get_option( "wpstg_is_staging_site" ) ) {
176
  return true;
177
  }
260
 
261
  return $uploadsAbsPath;
262
  }
263
+
264
+ /**
265
+ * Get hostname of production site including scheme
266
+ * @return string
267
+ */
268
+ function wpstg_get_production_hostname() {
269
+
270
+ $connection = get_option( 'wpstg_connection' );
271
+
272
+ // Get the stored hostname
273
+ if( !empty( $connection['prodHostname'] ) ) {
274
+ return $connection['prodHostname'];
275
+ }
276
+
277
+ // Default. Try to get the hostname from the main domain (Workaround for WP Staging Pro older < 2.9.1)
278
+ $siteurl = get_site_url();
279
+ $result = parse_url( $siteurl );
280
+ return $result['scheme'] . "://" . $result['host'];
281
+ }
282
+
283
+ /**
284
+ * Check if string starts with specific string
285
+ * @param string $haystack
286
+ * @param string $needle
287
+ * @return bool
288
+ */
289
+ function wpstg_starts_with( $haystack, $needle ) {
290
+ $length = strlen( $needle );
291
+ return (substr( $haystack, 0, $length ) === $needle);
292
+ }
293
+
294
+ /**
295
+ * Check if folder is empty
296
+ * @param type $dir
297
+ * @return boolean
298
+ */
299
+ function wpstg_is_empty_dir( $dir ) {
300
+ if( !is_dir( $dir ) ) {
301
+ return true;
302
+ }
303
+ $iterator = new \FilesystemIterator( $dir );
304
+ if( $iterator->valid() ) {
305
+ return false;
306
+ }
307
+ return true;
308
+ }
309
+
310
+ /**
311
+ * Get absolute WP uploads path e.g.
312
+ * Multisites: /var/www/htdocs/example.com/wp-content/uploads/sites/1 or /var/www/htdocs/example.com/wp-content/blogs.dir/1/files
313
+ * Single sites: /var/www/htdocs/example.com/wp-content/uploads
314
+ * @return string
315
+ */
316
+ function wpstg_get_upload_dir() {
317
+ $uploads = wp_upload_dir( null, false );
318
+
319
+ $baseDir = wpstg_replace_windows_directory_separator( $uploads['basedir'] );
320
+
321
+ // If multisite (and if not the main site in a post-MU network)
322
+ if( is_multisite() && !( is_main_network() && is_main_site() && defined( 'MULTISITE' ) ) ) {
323
+ // blogs.dir is used on WP 3.5 and earlier
324
+ if( false !== strpos( $baseDir, 'blogs.dir' ) ) {
325
+ // remove this piece from the basedir: /blogs.dir/2/files
326
+ $uploadDir = wpstg_replace_first_match( '/blogs.dir/' . get_current_blog_id() . '/files', null, $baseDir );
327
+ $dir = wpstg_replace_windows_directory_separator( $uploadDir . '/blogs.dir' );
328
+ } else {
329
+ // remove this piece from the basedir: /sites/2
330
+ $uploadDir = wpstg_replace_first_match( '/sites/' . get_current_blog_id(), null, $baseDir );
331
+ $dir = wpstg_replace_windows_directory_separator( $uploadDir . '/sites' );
332
+ }
333
+
334
+
335
+ return $dir;
336
+ }
337
+ return false;
338
+ }
339
+
340
+ /**
341
+ * Write data to file
342
+ * An alternative function for file_put_contents which is disabled on some hosts
343
+ *
344
+ * @param string $file
345
+ * @param string $contents
346
+ * @param int | false $mode
347
+ * @return boolean
348
+ */
349
+ function wpstg_put_contents( $file, $contents, $mode = false ) {
350
+ $fp = @fopen( $file, 'wb' );
351
+
352
+ if( !$fp ) {
353
+ return false;
354
+ }
355
+
356
+ mbstring_binary_safe_encoding();
357
+
358
+ $data_length = strlen( $contents );
359
+
360
+ $bytes_written = fwrite( $fp, $contents );
361
+
362
+ reset_mbstring_encoding();
363
+
364
+ fclose( $fp );
365
+
366
+ if( $data_length !== $bytes_written ) {
367
+ return false;
368
+ }
369
+
370
+ wpstg_chmod( $file, $mode );
371
+
372
+ return true;
373
+ }
374
+
375
+ /**
376
+ * Change chmod of file or folder
377
+ * @param string $file path to file
378
+ * @param mixed $mode false or specific octal value like 0755
379
+ * @param type $recursive
380
+ * @return boolean
381
+ */
382
+ function wpstg_chmod( $file, $mode = false ) {
383
+ if( !$mode ) {
384
+ if( @is_file( $file ) ) {
385
+ if( defined( 'FS_CHMOD_FILE' ) ) {
386
+ $mode = FS_CHMOD_FILE;
387
+ } else {
388
+ $mode = ( int ) 0644;
389
+ }
390
+ } elseif( @is_dir( $file ) ) {
391
+ if( defined( 'FS_CHMOD_FILE' ) ) {
392
+ $mode = FS_CHMOD_DIR;
393
+ } else {
394
+ $mode = ( int ) 0755;
395
+ }
396
+ } else {
397
+ return false;
398
+ }
399
+ }
400
+
401
+ if( !@is_dir( $file ) ) {
402
+ return @chmod( $file, $mode );
403
+ }
404
+
405
+ return true;
406
+ }
407
+
408
+ /**
409
+ * Create file if it does not exist
410
+ *
411
+ * @param string $path
412
+ * @param (int|false) $chmod The permissions as octal number (or false to skip chmod)
413
+ * @param (string|int) $chown A user name or number (or false to skip chown).
414
+ * @return boolean true on success, false on failure.
415
+ */
416
+ function wpstg_mkdir( $path, $chmod = false, $chown = false ) {
417
+ // Safe mode fails with a trailing slash under certain PHP versions.
418
+ $path = untrailingslashit( $path );
419
+ if( empty( $path ) ) {
420
+ return false;
421
+ }
422
+
423
+ if( !$chmod ) {
424
+ $chmod = FS_CHMOD_DIR;
425
+ }
426
+
427
+ if( !@mkdir( $path ) ) {
428
+ return false;
429
+ }
430
+ wpstg_chmod( $path, $chmod );
431
+
432
+ if( $chown ) {
433
+ wpstg_chown( $path, $chown );
434
+ }
435
+
436
+ return true;
437
+ }
438
+
439
+ /**
440
+ * Changes the owner of a file or directory.
441
+ *
442
+ *
443
+ * @param string $file Path to the file or directory.
444
+ * @param string|int $owner A user name or number.
445
+ * @param bool $recursive Optional. If set to true, changes file owner recursively.
446
+ * Default false.
447
+ * @return bool True on success, false on failure.
448
+ */
449
+ function wpstg_chown( $file, $owner ) {
450
+ if( !@file_exists( $file ) ) {
451
+ return false;
452
+ }
453
+
454
+ if( !@is_dir( $file ) ) {
455
+ return @chown( $file, $owner );
456
+ }
457
+ return true;
458
+ }
apps/Core/WPStaging.php CHANGED
@@ -29,7 +29,7 @@ final class WPStaging {
29
  /**
30
  * Plugin version
31
  */
32
- const VERSION = "2.6.2";
33
 
34
  /**
35
  * Plugin name
@@ -44,7 +44,7 @@ final class WPStaging {
44
  /**
45
  * Compatible WP Version
46
  */
47
- const WP_COMPATIBLE = "5.2.2";
48
 
49
  /**
50
  * Slug: Either wp-staging or wp-staging-pro
29
  /**
30
  * Plugin version
31
  */
32
+ const VERSION = "2.6.3";
33
 
34
  /**
35
  * Plugin name
44
  /**
45
  * Compatible WP Version
46
  */
47
+ const WP_COMPATIBLE = "5.2.4";
48
 
49
  /**
50
  * Slug: Either wp-staging or wp-staging-pro
readme.txt CHANGED
@@ -9,68 +9,70 @@ 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: 5.2
12
- Stable tag: 2.6.2
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.
16
 
17
  == Description ==
18
 
19
- <strong>This cloning and staging plugin is well tested and work in progress. <br>
20
- If you find an issue, please open a [support ticket](https://wp-staging.com/support/ "support ticket").
21
- </strong>
22
- <br /><br />
23
- <strong>Note: </strong> For pushing & migrating plugins and theme files to live site, check out the pro edition [https://wp-staging.com/](https://wp-staging.com/ "WP Staging Pro")
24
- <br /><br />
25
-
26
  <h3>WP Staging for WordPress Migration & Cloning </h3>
27
- This duplicator plugin allows you to create an staging or development environment in seconds* <br /> <br />
 
28
  It creates a clone of your website into a subfolder of your main WordPress installation including an entire copy of your database.
29
  <br /> <br />
30
- This sounds pretty simple and yes it is! All the hard time-consumptive database and file copying stuff including URL replacements is done in the background. The plugin even automatically does an entire search & replace of all links.
31
- <br /> <br />
32
- I created this plugin because all other solutions are way too complex, overloaded with dozens of options or having server requirements which are not available on most shared hosting solutions.
33
- <br /> <br />
34
- All these reasons prevent user from testing new plugins and updates first before installing them on their live website, so its time to release a plugin which has the potential to be merged into everyone´s wordpress workflow.
35
  <br /><br />
36
- <p><small><em>* Time of creation depends on size of your database and files.</em></small></p>
37
-
38
- WP Staging helps you to prevent your website from being broken or unavailable because of installing untested plugin updates!
39
- <p>Note: WordPress 5.0 has been shipped with a new visual editor called Gutenberg. Use WP Staging to check if Gutenberg editor is working as intended on your website and that all used plugins are compatible with that new editor.</p>
40
-
41
 
42
  [youtube https://www.youtube.com/watch?v=Ye3fC6cdB3A]
43
 
 
 
44
  = Main Features =
45
 
46
- * <strong>Easy: </strong> Staging migration applicable for everyone. No configuration needed!
47
- * <strong>Fast: </strong> Migration process takes only a few minutes, depending on the site's size and server I/O power
48
- * <strong>Safe: </strong> Only administrators have access to the staging website
49
- <br /><br />
50
- <strong>Even safer:</strong>
51
- <br>
52
- * Admin bar reflects that you are working on a staging site
53
- * Extensive logging if duplication or migration process has been failed.
54
- * New: Compatible with WordFence & All In One WP Security & Firewall
 
 
 
 
 
 
 
 
 
 
55
 
56
- Supports all main web servers including Apache, Nginx, and Microsoft IIS
57
 
58
  = Additional Features WP Staging Pro Edition =
59
 
60
- * Wordpress migration and cloning of WordPress multisites
61
- * Choose a separate database and select a custom directory for cloning
62
  * Clone your website into a subdomain
63
  * Specify certain user roles for accessing the staging site
64
- * Copy all modifications on staging site to the production website
65
 
66
  <strong>Change your workflow of updating themes and plugins data:</strong>
67
 
68
  1. Use WP Staging to clone a production website for staging, testing or backup purposes
69
  2. Create a backup of your website
70
- 3. Customize theme, configuration, and plugins or install new plugins
71
- 4. Test everything on your staging site first
72
- 5. Everything running as intended? Start the migration and copy all modifications to your production site!
73
-
74
 
75
  <h3> Why should I use a staging website? </h3>
76
 
@@ -151,6 +153,17 @@ https://wp-staging.com
151
 
152
  == Changelog ==
153
 
 
 
 
 
 
 
 
 
 
 
 
154
  = 2.6.2 =
155
  * Fix: Do not show warning "Preparing Data Step3: Failed to update rewrite_rules in wpstg0_options"
156
  * Fix: Change error "Table wpstgtmp_options does not exist" to warning
@@ -245,18 +258,16 @@ https://wp-staging.com
245
  * Tweak: clean up search & replace method
246
  * Tweak: Better FAQ
247
 
248
- = 2.4.8 =
249
- * Fix: Prevent throwing an error when table prefix of table usermeta cannot be changed
250
- * Fix: WP Staging does not run with old WordPress version 3.2
251
- * Fix: Do not show rating box on posts and pages editor
252
- * New: Support WordPress 5.0.3
253
- * New: Add FAQ to footer
254
-
255
- = 2.4.7 =
256
- * New: Support WordPress 5.0.2
257
 
258
- = 2.4.6 =
259
- * Fix: Can not login to staging site . Changed minimum user capability to 'manage_options' instead 'administrator'
260
 
 
 
261
 
262
- Complete changelog: [https://wp-staging.com/wp-staging-changelog](https://wp-staging.com/wp-staging-changelog)
 
 
 
 
 
 
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: 5.2
12
+ Stable tag: 2.6.3
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.
16
 
17
  == Description ==
18
 
 
 
 
 
 
 
 
19
  <h3>WP Staging for WordPress Migration & Cloning </h3>
20
+ This duplicator plugin allows you to create an exact copy of your entire website for staging or development purposes in seconds.
21
+ (Exact time depends on the size of your website)<br /><br />
22
  It creates a clone of your website into a subfolder of your main WordPress installation including an entire copy of your database.
23
  <br /> <br />
24
+ <strong>Note: </strong> For pushing & migrating plugins and theme files to live site, check out the pro edition [https://wp-staging.com/](https://wp-staging.com/ "WP Staging Pro")
25
+ <br /><br />
26
+ All the time-consumptive database and file copying operations are done in the background. The plugin even automatically does an entire search & replace of all serialized links and paths.
 
 
27
  <br /><br />
28
+ This plugin works even on the smallest shared hosting servers.
29
+ <br /><br />
30
+ <br /><br />
31
+ WP Staging can help you to protect your website from being broken or unavailable because of installing untested plugin updates!
 
32
 
33
  [youtube https://www.youtube.com/watch?v=Ye3fC6cdB3A]
34
 
35
+ <p>Note: WordPress 5.x has been shipped with a new visual editor called Gutenberg. Use WP Staging to check if Gutenberg editor is working as intended on your website and that all used plugins are compatible with that new editor.</p>
36
+
37
  = Main Features =
38
 
39
+ * No SASS service. All data belongs to you and stays on your server.
40
+ * Easy to use! Just install it and click on "CREATE NEW STAGING SITE".
41
+ * No server timeouts on huge websites or/and small hosting servers
42
+ * Staging website will be available from example.com/staging-site
43
+ * Very fast - Migration process takes only a few seconds or minutes, depending on the website's size and server I/O power.
44
+ * Only administrators can access the staging website. Login with the same credentials you use on your production site.
45
+ * SEO friendly: The staging website is unavailable to search engines due to a custom login prompt and no-index header.
46
+ * The admin bar on the staging website will be orange colored and shows clearly that you work on the staging site.
47
+ * All database tables will be duplicated and get a new prefix beginning with wpstg(int)_.
48
+ * As default the staging website will be copied into a subfolder.
49
+ * Extensive logging features
50
+ * Supports all main web servers including Apache, Nginx, and Microsoft IIS
51
+ * <strong>[Premium]: </strong>Choose a separate database and select a custom directory for cloning
52
+ * <strong>[Premium]: </strong>Make the staging website available from a subdomain like dev.example.com
53
+ * <strong>[Premium]: </strong>Push & migrate plugins and themes to production website.
54
+ * <strong>[Premium]: </strong>Define user roles that should receive access to the staging site only. For instance, clients or external developers.
55
+ * <strong>[Premium]: </strong>Migration and cloning of WordPress multisites
56
+
57
+ > Note: Some features are Premium. Which means you need WP Staging Pro to use those features. You can [get WP Staging Premium here](https://wp-staging.com)!
58
 
59
+ * New: Compatible with WordFence & All In One WP Security & Firewall
60
 
61
  = Additional Features WP Staging Pro Edition =
62
 
63
+ * Cloning and migration of WordPress multisites
64
+ * Define a separate database and a custom directory for cloning
65
  * Clone your website into a subdomain
66
  * Specify certain user roles for accessing the staging site
67
+ * Copy all modifications from staging site to the production website
68
 
69
  <strong>Change your workflow of updating themes and plugins data:</strong>
70
 
71
  1. Use WP Staging to clone a production website for staging, testing or backup purposes
72
  2. Create a backup of your website
73
+ 3. Customize theme, configuration, update or install new plugins
74
+ 4. Test everything on your staging site
75
+ 5. If everything works on the staging site start the migration and copy all modifications to your production site!
 
76
 
77
  <h3> Why should I use a staging website? </h3>
78
 
153
 
154
  == Changelog ==
155
 
156
+ = 2.6.3 =
157
+ * New Support for WordPress 5.2.4
158
+ * New: Allow wildcards for excluding files
159
+ * New: Add hook "wpstg_clone_action_staging" to execute code on staging site after cloning
160
+ * Tweak: Improved support for custom uploads folder if user customized UPLOADS constant or upload_path in DB
161
+ * Fix: Better compatibility with Windows IIS server
162
+ * Fix: External links are broken after cloning if ABSPATH is equal to /www/
163
+ * Fix: use an alternative method for file_put_contents as it is not supported on all systems due to file permission issues
164
+ * Fix: Redundant and duplicated update comments in wp-config.php in staging site
165
+
166
+
167
  = 2.6.2 =
168
  * Fix: Do not show warning "Preparing Data Step3: Failed to update rewrite_rules in wpstg0_options"
169
  * Fix: Change error "Table wpstgtmp_options does not exist" to warning
258
  * Tweak: clean up search & replace method
259
  * Tweak: Better FAQ
260
 
 
 
 
 
 
 
 
 
 
261
 
262
+ Complete changelog: [https://wp-staging.com/wp-staging-changelog](https://wp-staging.com/wp-staging-changelog)
 
263
 
264
+ == Upgrade Notice ==
265
+ * Install this version for supporting latest WordPress version
266
 
267
+ * New: Allow wildcards for excluding files
268
+ * New: Add hook "wpstg_clone_action_staging" to execute code on staging site after cloning
269
+ * Tweak: Improved support for custom uploads folder if user customized UPLOADS constant or upload_path in DB
270
+ * Fix: Better compatibility with Windows IIS server
271
+ * Fix: External links are broken after cloning if ABSPATH is equal to /www/
272
+ * Fix: use an alternative method for file_put_contents as it is not supported on all systems due to file permission issues
273
+ * Fix: Redundant and duplicated update comments in wp-config.php in staging site
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.6.2
11
  * Text Domain: wp-staging
12
  * Domain Path: /languages/
13
 
@@ -51,7 +51,7 @@ if( !defined( 'WPSTG_PLUGIN_URL' ) ) {
51
 
52
  // Version
53
  if( !defined( 'WPSTG_VERSION' ) ) {
54
- define( 'WPSTG_VERSION', '2.6.2' );
55
  }
56
 
57
  // Must use version of the optimizer
7
  * Author: WP-Staging
8
  * Author URI: https://wp-staging.com
9
  * Contributors: ReneHermi, ilgityildirim
10
+ * Version: 2.6.3
11
  * Text Domain: wp-staging
12
  * Domain Path: /languages/
13
 
51
 
52
  // Version
53
  if( !defined( 'WPSTG_VERSION' ) ) {
54
+ define( 'WPSTG_VERSION', '2.6.3' );
55
  }
56
 
57
  // Must use version of the optimizer