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

Version Description

  • New: Compatible up to WordPress 5.2.2
  • New: Performance improvement for directory iterator using less server ressources
  • New: Add filter wpstg_folder_permission to set a custom folder permission like 0755, allows to overwrite FS_CHMOD_DIR if it has been defined.
  • Fix: Error conditions in class Data does not compare type strict (=
Download this release

Release Info

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

Code changes from version 2.5.8 to 2.6.0

apps/Backend/Modules/Jobs/Cloning.php CHANGED
@@ -33,7 +33,8 @@ class Cloning extends Job {
33
 
34
  // Generate Options
35
  // Clone
36
- $this->options->clone = $_POST["cloneID"];
 
37
  $this->options->cloneDirectoryName = preg_replace( "#\W+#", '-', strtolower( $this->options->clone ) );
38
  $this->options->cloneNumber = 1;
39
  $this->options->prefix = $this->setStagingPrefix();
33
 
34
  // Generate Options
35
  // Clone
36
+ //$this->options->clone = $_POST["cloneID"];
37
+ $this->options->clone = preg_replace( "#\W+#", '-', strtolower( $_POST["cloneID"] ) );
38
  $this->options->cloneDirectoryName = preg_replace( "#\W+#", '-', strtolower( $this->options->clone ) );
39
  $this->options->cloneNumber = 1;
40
  $this->options->prefix = $this->setStagingPrefix();
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 = 17;
79
  }
80
 
81
  /**
@@ -176,7 +176,7 @@ class Data extends JobExecutable {
176
  */
177
  protected function isTable( $table ) {
178
  if( $this->db->get_var( "SHOW TABLES LIKE '{$table}'" ) != $table ) {
179
- $this->log( "Table {$table} does not exist", Logger::TYPE_ERROR );
180
  return false;
181
  }
182
  return true;
@@ -196,30 +196,141 @@ class Data extends JobExecutable {
196
 
197
  $destination = $this->options->destinationDir . 'wp-config.php';
198
 
199
-
200
- // Do not do anything
201
- if( (!is_file( $source ) && !is_link( $source )) || is_file( $destination ) ) {
202
- $this->log( "Preparing Data Step0: Skip it", Logger::TYPE_INFO );
203
  return true;
204
  }
205
 
206
- // Copy target of a symbolic link
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  if( is_link( $source ) ) {
208
- $this->log( "Preparing Data Step0: Symbolic link found...", Logger::TYPE_INFO );
209
  if( !@copy( readlink( $source ), $destination ) ) {
210
  $errors = error_get_last();
211
- $this->log( "Preparing Data Step0: Failed to copy wp-config.php! Error: {$errors['message']} {$source} -> {$destination}", Logger::TYPE_ERROR );
212
- return true;
213
  }
214
  }
215
 
216
- // Copy file wp-config.php
217
  if( !@copy( $source, $destination ) ) {
218
  $errors = error_get_last();
219
- $this->log( "Preparing Data Step0: Failed to copy wp-config.php! Error: {$errors['message']} {$source} -> {$destination}", Logger::TYPE_ERROR );
220
- return true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  }
222
- $this->log( "Preparing Data Step0: Successfull", Logger::TYPE_INFO );
223
  return true;
224
  }
225
 
@@ -251,11 +362,11 @@ class Data extends JobExecutable {
251
 
252
 
253
  // All good
254
- if( false === $result ) {
255
- $this->log( "Preparing Data Step1: Failed to update siteurl and homeurl in {$this->prefix}options. Probably already did! {$this->db->last_error}", Logger::TYPE_WARNING );
256
  return true;
257
  }
258
 
 
259
  return true;
260
  }
261
 
@@ -299,12 +410,13 @@ class Data extends JobExecutable {
299
  );
300
  //}
301
  // All good
302
- if( false === $insert ) {
303
- $this->log( "Preparing Data Step2: Failed to update wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
304
  return true;
305
  }
306
- $this->log( "Preparing Data Step2: Successfull", Logger::TYPE_INFO );
307
- return true;
 
308
  }
309
 
310
  /**
@@ -317,6 +429,7 @@ class Data extends JobExecutable {
317
 
318
  // Skip - Table does not exist
319
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
 
320
  return true;
321
  }
322
 
@@ -333,11 +446,11 @@ class Data extends JobExecutable {
333
  );
334
 
335
  // All good
336
- if( false === $result ) {
337
- $this->log( "Preparing Data Step3: Failed to update rewrite_rules in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
338
  return true;
339
  }
340
 
 
341
  return true;
342
  }
343
 
@@ -373,6 +486,8 @@ class Data extends JobExecutable {
373
  return false;
374
  }
375
 
 
 
376
  return true;
377
  }
378
 
@@ -449,7 +564,7 @@ class Data extends JobExecutable {
449
  $this->log( "Preparing Data: Failed to reset index.php for sub directory; can't save contents", Logger::TYPE_ERROR );
450
  return false;
451
  }
452
- $this->Log( "Preparing Data Step 6: Finished successfully" );
453
  return true;
454
  }
455
 
@@ -463,6 +578,7 @@ class Data extends JobExecutable {
463
 
464
  // Skip - Table does not exist
465
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
 
466
  return true;
467
  }
468
 
@@ -595,7 +711,7 @@ class Data extends JobExecutable {
595
  $this->log( "Preparing Data Step10: Failed to update WP_HOME. Can't save contents", Logger::TYPE_ERROR );
596
  return false;
597
  }
598
- $this->Log( "Preparing Data Step 10: Finished successfully" );
599
  return true;
600
  }
601
 
@@ -626,7 +742,7 @@ class Data extends JobExecutable {
626
  $replace.= " // Changed by WP-Staging";
627
 
628
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
629
- $this->log( "Preparing Data Step11: Failed to update WP_SITEURL", Logger::TYPE_ERROR );
630
  return false;
631
  }
632
  } else {
@@ -635,10 +751,10 @@ class Data extends JobExecutable {
635
 
636
 
637
  if( false === @file_put_contents( $path, $content ) ) {
638
- $this->log( "Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR );
639
  return false;
640
  }
641
- $this->Log( "Preparing Data Step 11: Finished successfully" );
642
  return true;
643
  }
644
 
@@ -755,7 +871,7 @@ class Data extends JobExecutable {
755
  $this->returnException( " Preparing Data Step12: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
756
  return false;
757
  }
758
- $this->Log( "Preparing Data Step 12: Finished successfully" );
759
  return true;
760
  }
761
 
@@ -839,7 +955,7 @@ class Data extends JobExecutable {
839
  $this->log( "Preparing Data Step14: Failed to update WP_CACHE. Can't save contents", Logger::TYPE_ERROR );
840
  return false;
841
  }
842
- $this->Log( "Preparing Data Step 14: Finished successfully" );
843
  return true;
844
  }
845
 
@@ -879,7 +995,7 @@ class Data extends JobExecutable {
879
  $this->log( "Preparing Data Step15: Failed to update WP_CONTENT_DIR. Can't save contents", Logger::TYPE_ERROR );
880
  return false;
881
  }
882
- $this->Log( "Preparing Data Step 15: Finished successfully" );
883
  return true;
884
  }
885
 
@@ -919,12 +1035,97 @@ class Data extends JobExecutable {
919
  $this->log( "Preparing Data Step16: Failed to update WP_CONTENT_URL. Can't save contents", Logger::TYPE_ERROR );
920
  return false;
921
  }
922
- $this->Log( "Preparing Data Step 16: Finished successfully" );
923
  return true;
924
  }
925
 
926
  /**
927
- * Get upload path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
928
  * @return boolean|string
929
  */
930
  protected function getNewUploadPath() {
@@ -934,6 +1135,20 @@ class Data extends JobExecutable {
934
  return false;
935
  }
936
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
937
  $customSlug = str_replace( \WPStaging\WPStaging::getWPpath(), '', $uploadPath );
938
 
939
  $newUploadPath = $this->options->destinationDir . $customSlug;
75
  * @return void
76
  */
77
  protected function calculateTotalSteps() {
78
+ $this->options->totalSteps = 18;
79
  }
80
 
81
  /**
176
  */
177
  protected function isTable( $table ) {
178
  if( $this->db->get_var( "SHOW TABLES LIKE '{$table}'" ) != $table ) {
179
+ $this->log( "Preparing Data: Table {$table} does not exist.", Logger::TYPE_ERROR );
180
  return false;
181
  }
182
  return true;
196
 
197
  $destination = $this->options->destinationDir . 'wp-config.php';
198
 
199
+ // Check if there is already a valid wp-config.php in root of staging site
200
+ if( $this->isValidWpConfig( $destination ) ) {
 
 
201
  return true;
202
  }
203
 
204
+ // Check if there is a valid wp-config.php outside root of wp production site
205
+ if( $this->isValidWpConfig( $source ) ) {
206
+ // Copy it to staging site
207
+ if( $this->copy( $source, $destination ) ) {
208
+ return true;
209
+ }
210
+ }
211
+
212
+ // No valid wp-config.php found so let's copy wp stagings default wp-config.php to staging site
213
+ $source = WPSTG_PLUGIN_DIR . "apps/Backend/helpers/wp-config.php";
214
+ if( $this->copy( $source, $destination ) ) {
215
+ // add missing db credentials to wp-config.php
216
+ if( !$this->alterWpConfig( $destination ) ) {
217
+ $this->log( "Preparing Data Step0: Can not alter db credentials in wp-config.php", Logger::TYPE_INFO );
218
+ return false;
219
+ }
220
+ }
221
+
222
+ $this->log( "Preparing Data Step0: Successfull", Logger::TYPE_INFO );
223
+ return true;
224
+ }
225
+
226
+ /**
227
+ * Copy files with symlink support
228
+ * @param type $source
229
+ * @param type $destination
230
+ * @return boolean
231
+ */
232
+ protected function copy( $source, $destination ) {
233
+ // Copy symbolic link
234
  if( is_link( $source ) ) {
235
+ $this->log( "Preparing Data: Symbolic link found...", Logger::TYPE_INFO );
236
  if( !@copy( readlink( $source ), $destination ) ) {
237
  $errors = error_get_last();
238
+ $this->log( "Preparing Data: Failed to copy {$source} Error: {$errors['message']} {$source} -> {$destination}", Logger::TYPE_ERROR );
239
+ return false;
240
  }
241
  }
242
 
243
+ // Copy file
244
  if( !@copy( $source, $destination ) ) {
245
  $errors = error_get_last();
246
+ $this->log( "Preparing Data Step0: Failed to copy {$source}! Error: {$errors['message']} {$source} -> {$destination}", Logger::TYPE_ERROR );
247
+ return false;
248
+ }
249
+
250
+ return true;
251
+ }
252
+
253
+ /**
254
+ * Make sure wp-config.php contains correct db credentials
255
+ * @param type $source
256
+ * @return boolean
257
+ */
258
+ protected function alterWpConfig( $source ) {
259
+ $content = file_get_contents( $source );
260
+
261
+ if( false === ($content = file_get_contents( $source )) ) {
262
+ return false;
263
+ }
264
+
265
+ $search = "// ** MySQL settings ** //";
266
+
267
+ $replace = "// ** MySQL settings ** //\r\n
268
+ define( 'DB_NAME', '" . DB_NAME . "' );\r\n
269
+ /** MySQL database username */\r\n
270
+ define( 'DB_USER', '" . DB_USER . "' );\r\n
271
+ /** MySQL database password */\r\n
272
+ define( 'DB_PASSWORD', '" . DB_PASSWORD . "' );\r\n
273
+ /** MySQL hostname */\r\n
274
+ define( 'DB_HOST', '" . DB_HOST . "' );\r\n
275
+ /** Database Charset to use in creating database tables. */\r\n
276
+ define( 'DB_CHARSET', '" . DB_CHARSET . "' );\r\n
277
+ /** The Database Collate type. Don't change this if in doubt. */\r\n
278
+ 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
+ }
286
+
287
+ return true;
288
+ }
289
+
290
+ /**
291
+ * Check if wp-config.php contains important constants
292
+ * @param type $source
293
+ * @return boolean
294
+ */
295
+ protected function isValidWpConfig( $source ) {
296
+
297
+ if( !is_file( $source ) && !is_link( $source ) ) {
298
+ return false;
299
+ }
300
+
301
+ $content = file_get_contents( $source );
302
+
303
+ if( false === ($content = file_get_contents( $source )) ) {
304
+ return false;
305
+ }
306
+
307
+ // Get DB_NAME from wp-config.php
308
+ preg_match( "/define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
309
+
310
+ if( empty( $matches[1] ) ) {
311
+ return false;
312
+ }
313
+
314
+ // Get DB_USER from wp-config.php
315
+ preg_match( "/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
316
+
317
+ if( empty( $matches[1] ) ) {
318
+ return false;
319
+ }
320
+
321
+ // Get DB_PASSWORD from wp-config.php
322
+ preg_match( "/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
323
+
324
+ if( empty( $matches[1] ) ) {
325
+ return false;
326
+ }
327
+
328
+ // Get DB_HOST from wp-config.php
329
+ preg_match( "/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
330
+
331
+ if( empty( $matches[1] ) ) {
332
+ return false;
333
  }
 
334
  return true;
335
  }
336
 
362
 
363
 
364
  // All good
365
+ if( $result ) {
 
366
  return true;
367
  }
368
 
369
+ $this->log( "Preparing Data Step1: Skip updating siteurl and homeurl in {$this->prefix}options. Probably already did! {$this->db->last_error}", Logger::TYPE_WARNING );
370
  return true;
371
  }
372
 
410
  );
411
  //}
412
  // All good
413
+ if( $insert ) {
414
+ $this->log( "Preparing Data Step2: Successfull", Logger::TYPE_INFO );
415
  return true;
416
  }
417
+
418
+ $this->log( "Preparing Data Step2: Failed to update wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
419
+ return false;
420
  }
421
 
422
  /**
429
 
430
  // Skip - Table does not exist
431
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
432
+ $this->log( "Preparing Data Step3: Skipping" );
433
  return true;
434
  }
435
 
446
  );
447
 
448
  // All good
449
+ if( $result ) {
 
450
  return true;
451
  }
452
 
453
+ $this->log( "Preparing Data Step3: Failed to update rewrite_rules in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
454
  return true;
455
  }
456
 
486
  return false;
487
  }
488
 
489
+
490
+
491
  return true;
492
  }
493
 
564
  $this->log( "Preparing Data: Failed to reset index.php for sub directory; can't save contents", Logger::TYPE_ERROR );
565
  return false;
566
  }
567
+ $this->Log( "Preparing Data Step6: Finished successfully" );
568
  return true;
569
  }
570
 
578
 
579
  // Skip - Table does not exist
580
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
581
+ $this->log( "Preparing Data Step7: Skipping Table {$this->prefix}'options' does not exist" );
582
  return true;
583
  }
584
 
711
  $this->log( "Preparing Data Step10: Failed to update WP_HOME. Can't save contents", Logger::TYPE_ERROR );
712
  return false;
713
  }
714
+ $this->Log( "Preparing Data Step10: Finished successfully" );
715
  return true;
716
  }
717
 
742
  $replace.= " // Changed by WP-Staging";
743
 
744
  if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
745
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL to " . $this->getStagingSiteUrl(), Logger::TYPE_ERROR );
746
  return false;
747
  }
748
  } else {
751
 
752
 
753
  if( false === @file_put_contents( $path, $content ) ) {
754
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL to " . $this->getStagingSiteUrl() . " Can't save contents", Logger::TYPE_ERROR );
755
  return false;
756
  }
757
+ $this->Log( "Preparing Data Step11: Finished successfully" );
758
  return true;
759
  }
760
 
871
  $this->returnException( " Preparing Data Step12: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
872
  return false;
873
  }
874
+ $this->Log( "Preparing Data Step12: Finished successfully" );
875
  return true;
876
  }
877
 
955
  $this->log( "Preparing Data Step14: Failed to update WP_CACHE. Can't save contents", Logger::TYPE_ERROR );
956
  return false;
957
  }
958
+ $this->Log( "Preparing Data Step14: Finished successfully" );
959
  return true;
960
  }
961
 
995
  $this->log( "Preparing Data Step15: Failed to update WP_CONTENT_DIR. Can't save contents", Logger::TYPE_ERROR );
996
  return false;
997
  }
998
+ $this->Log( "Preparing Data Step15: Finished successfully" );
999
  return true;
1000
  }
1001
 
1035
  $this->log( "Preparing Data Step16: Failed to update WP_CONTENT_URL. Can't save contents", Logger::TYPE_ERROR );
1036
  return false;
1037
  }
1038
+ $this->Log( "Preparing Data Step16: Finished successfully" );
1039
  return true;
1040
  }
1041
 
1042
  /**
1043
+ * Remove UPLOADS constant in wp-config.php to reset default image folder
1044
+ * @return bool
1045
+ */
1046
+ protected function step17() {
1047
+ $path = $this->options->destinationDir . "wp-config.php";
1048
+
1049
+ $this->log( "Preparing Data Step17: Remove UPLOADS in wp-config.php" );
1050
+
1051
+ if( false === ($content = file_get_contents( $path )) ) {
1052
+ $this->log( "Preparing Data Step17: Failed to get UPLOADS in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
1053
+ return false;
1054
+ }
1055
+
1056
+
1057
+ // Get UPLOADS from wp-config.php
1058
+ preg_match( "/define\s*\(\s*['\"]UPLOADS['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
1059
+
1060
+ if( !empty( $matches[0] ) ) {
1061
+
1062
+ $pattern = "/define\s*\(\s*'UPLOADS'\s*,\s*(.*)\s*\);/";
1063
+
1064
+ $replace = "";
1065
+
1066
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1067
+ $this->log( "Preparing Data: Failed to change UPLOADS", Logger::TYPE_ERROR );
1068
+ return false;
1069
+ }
1070
+ } else {
1071
+ $this->log( "Preparing Data Step17: UPLOADS not defined in wp-config.php. Skipping this step." );
1072
+ }
1073
+
1074
+ if( false === @file_put_contents( $path, $content ) ) {
1075
+ $this->log( "Preparing Data Step17: Failed to update UPLOADS. Can't save contents", Logger::TYPE_ERROR );
1076
+ return false;
1077
+ }
1078
+ $this->Log( "Preparing Data Step17: Finished successfully" );
1079
+ return true;
1080
+ }
1081
+
1082
+ /**
1083
+ * Empty wpstg_existing_clones_beta on the staging site
1084
+ * @return bool
1085
+ */
1086
+ // protected function step18() {
1087
+ //
1088
+ // $this->log( "Preparing Data Step18: Clean up list of staging sites" );
1089
+ //
1090
+ // if( false === $this->isTable( $this->prefix . 'options' ) ) {
1091
+ // return true;
1092
+ // }
1093
+ //
1094
+ // // Skip - Table is not selected or updated
1095
+ // if( !in_array( $this->prefix . 'options', $this->tables ) ) {
1096
+ // $this->log( "Preparing Data Step18: Skipping" );
1097
+ // return true;
1098
+ // }
1099
+ //
1100
+ // $result = $this->db->query(
1101
+ // $this->db->prepare(
1102
+ // "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_existing_clones_beta'", 'a:0:{}'
1103
+ // )
1104
+ // );
1105
+ //
1106
+ // // All good
1107
+ // if( false === $result ) {
1108
+ // $this->log( "Preparing Data Step18: Can not update listing of staging sites.", Logger::TYPE_WARNING );
1109
+ // return true;
1110
+ // }
1111
+ //
1112
+ // $this->Log( "Preparing Data Step18: Finished successfully" );
1113
+ // return true;
1114
+ // }
1115
+
1116
+ /**
1117
+ * Create a robots.txt in path of the staging site
1118
+ */
1119
+ // protected function step18() {
1120
+ // $file = new RobotsTxt();
1121
+ // if( false === $file->create( $this->options->destinationDir . "robots.txt" ) ) {
1122
+ // $this->log( "Preparing Data Step18: Failed to create robots.txt", Logger::TYPE_ERROR );
1123
+ // }
1124
+ // return true;
1125
+ // }
1126
+
1127
+ /**
1128
+ * Change upload path of staging site if it has been customized on production site
1129
  * @return boolean|string
1130
  */
1131
  protected function getNewUploadPath() {
1135
  return false;
1136
  }
1137
 
1138
+ // Get absolute custom upload dir
1139
+ $uploads = wp_upload_dir();
1140
+
1141
+ // Get absolute upload dir from ABSPATH
1142
+ $uploadsAbsPath = trailingslashit( $uploads['basedir'] );
1143
+
1144
+ // Get absolute custom wp-content dir
1145
+ $wpContentDir = trailingslashit( WP_CONTENT_DIR );
1146
+
1147
+ // Do nothing if uploads path is identical to wp-content dir
1148
+ if( $uploadsAbsPath == $wpContentDir ) {
1149
+ return false;
1150
+ }
1151
+
1152
  $customSlug = str_replace( \WPStaging\WPStaging::getWPpath(), '', $uploadPath );
1153
 
1154
  $newUploadPath = $this->options->destinationDir . $customSlug;
apps/Backend/Modules/Jobs/Database.php CHANGED
@@ -120,7 +120,9 @@ class Database extends JobExecutable {
120
  $stagingPrefix = $this->options->prefix;
121
  // Make sure prefix of staging site is NEVER identical to prefix of live site!
122
  if( $stagingPrefix == $this->db->prefix ) {
123
- wp_die( 'Fatal error 7: The new database table prefix ' . $stagingPrefix . ' would be identical to the table prefix of the live site. Please open a support ticket at support@wp-staging.com' );
 
 
124
  }
125
  return $stagingPrefix;
126
  }
120
  $stagingPrefix = $this->options->prefix;
121
  // Make sure prefix of staging site is NEVER identical to prefix of live site!
122
  if( $stagingPrefix == $this->db->prefix ) {
123
+ $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';
124
+ $this->returnException($error);
125
+ wp_die( $error );
126
  }
127
  return $stagingPrefix;
128
  }
apps/Backend/Modules/Jobs/Delete.php CHANGED
@@ -18,6 +18,12 @@ class Delete extends Job {
18
  */
19
  private $clone = false;
20
 
 
 
 
 
 
 
21
  /**
22
  * @var null|object
23
  */
@@ -48,6 +54,8 @@ class Delete extends Job {
48
  public function __construct( $isExternal = false ) {
49
  parent::__construct();
50
  $this->isExternal = $isExternal;
 
 
51
  }
52
 
53
  /**
@@ -338,24 +346,24 @@ class Delete extends Job {
338
  */
339
  public function deleteDirectory() {
340
  if( $this->isFatalError() ) {
341
- $this->returnException( 'Can not delete directory: ' . $this->clone->path . '. This seems to be the root directory. Please contact support@wp-staging.com' );
342
- throw new \Exception( 'Can not delete directory: ' . $this->clone->path . ' This seems to be the root directory. Please contact support@wp-staging.com' );
343
  }
344
  // Finished or path does not exist
345
  if(
346
- empty( $this->clone->path ) ||
347
- $this->clone->path == get_home_path() ||
348
- !is_dir( $this->clone->path ) ) {
349
 
350
  $this->job->current = "finish";
351
  $this->updateJob();
352
- return $this->returnFinish();
353
  }
354
 
355
  $this->log( "Delete staging site: " . $this->clone->path, Logger::TYPE_INFO );
356
 
357
  // Just to make sure the root dir is never deleted!
358
- if( $this->clone->path == get_home_path() ) {
359
  $this->log( "Fatal Error 8: Trying to delete root of WP installation!", Logger::TYPE_CRITICAL );
360
  $this->returnException( 'Fatal Error 8: Trying to delete root of WP installation!' );
361
  }
@@ -366,10 +374,9 @@ class Delete extends Job {
366
  return;
367
  }
368
 
369
- $di = new \RecursiveDirectoryIterator( $this->clone->path, \FilesystemIterator::SKIP_DOTS );
370
  $ri = new \RecursiveIteratorIterator( $di, \RecursiveIteratorIterator::CHILD_FIRST );
371
  foreach ( $ri as $file ) {
372
- //$file->isDir() ? @rmdir($file) : unlink($file);
373
  $this->deleteFile( $file );
374
  if( $this->isOverThreshold() ) {
375
  //$this->returnException('Maximum PHP execution time exceeded. Run again and repeat the deletion process until it is sucessfully finished.');
@@ -377,10 +384,24 @@ class Delete extends Job {
377
  }
378
  }
379
 
380
- if( @rmdir( $this->clone->path ) ) {
381
- return $this->returnFinish();
 
 
 
 
 
 
 
 
 
 
 
 
382
  }
383
- return;
 
 
384
  }
385
 
386
  /**
@@ -407,12 +428,12 @@ class Delete extends Job {
407
  /**
408
  * @return bool
409
  */
410
- public function isDirectoryDeletingFinished() {
411
- return (
412
- (false === $this->forceDeleteDirectories && (!isset( $_POST["deleteDir"] ) || '1' !== $_POST["deleteDir"])) ||
413
- !is_dir( $this->clone->path ) || ABSPATH === $this->job->nextDirectoryToDelete
414
- );
415
- }
416
 
417
  /**
418
  *
@@ -420,7 +441,7 @@ class Delete extends Job {
420
  */
421
  public function isFatalError() {
422
  $homePath = rtrim( get_home_path(), "/" );
423
- if( rtrim( $this->clone->path, "/" ) == $homePath ) {
424
  return true;
425
  }
426
  return false;
@@ -430,19 +451,26 @@ class Delete extends Job {
430
  * Finish / Update Existing Clones
431
  */
432
  public function deleteFinish() {
 
 
 
 
 
433
  $existingClones = get_option( "wpstg_existing_clones_beta", array() );
434
 
435
- // Check if clones still exist
436
  $this->log( "Verifying existing clones..." );
437
  foreach ( $existingClones as $name => $clone ) {
438
- if( !is_dir( $clone["path"] ) ) {
439
  unset( $existingClones[$name] );
440
  }
 
 
 
441
  }
442
- $this->log( "Existing clones verified!" );
443
 
444
  if( false === update_option( "wpstg_existing_clones_beta", $existingClones ) ) {
445
- $this->log( "Failed to save {$this->options->clone}'s clone job data to database'" );
446
  }
447
 
448
  // Delete cached file
@@ -450,26 +478,7 @@ class Delete extends Job {
450
  $this->cache->delete( "delete_directories_{$this->clone->name}" );
451
  $this->cache->delete( "clone_options" );
452
 
453
- //return true;
454
- $response = array('delete' => 'finished');
455
  wp_die( json_encode( $response ) );
456
  }
457
 
458
- /**
459
- * Get json response
460
- * return json
461
- */
462
- private function returnFinish( $message = '' ) {
463
-
464
- $this->deleteFinish();
465
-
466
- wp_die( json_encode( array(
467
- 'job' => 'delete',
468
- 'status' => true,
469
- 'message' => $message,
470
- 'error' => false,
471
- 'delete' => 'finished'
472
- ) ) );
473
  }
474
-
475
- }
18
  */
19
  private $clone = false;
20
 
21
+ /**
22
+ * The path to delete
23
+ * @var type
24
+ */
25
+ private $deleteDir = '';
26
+
27
  /**
28
  * @var null|object
29
  */
54
  public function __construct( $isExternal = false ) {
55
  parent::__construct();
56
  $this->isExternal = $isExternal;
57
+
58
+ $this->deleteDir = !empty( $_POST['deleteDir'] ) ? urldecode( $_POST['deleteDir'] ) : '';
59
  }
60
 
61
  /**
346
  */
347
  public function deleteDirectory() {
348
  if( $this->isFatalError() ) {
349
+ $this->returnException( 'Can not delete directory: ' . $this->deleteDir . '. This seems to be the root directory. Exclude this directory from deleting and try again.' );
350
+ throw new \Exception( 'Can not delete directory: ' . $this->deleteDir . ' This seems to be the root directory. Exclude this directory from deleting and try again.' );
351
  }
352
  // Finished or path does not exist
353
  if(
354
+ empty( $this->deleteDir ) ||
355
+ $this->deleteDir == get_home_path() ||
356
+ !is_dir( $this->deleteDir ) ) {
357
 
358
  $this->job->current = "finish";
359
  $this->updateJob();
360
+ return $this->deleteFinish();
361
  }
362
 
363
  $this->log( "Delete staging site: " . $this->clone->path, Logger::TYPE_INFO );
364
 
365
  // Just to make sure the root dir is never deleted!
366
+ if( $this->deleteDir == get_home_path() ) {
367
  $this->log( "Fatal Error 8: Trying to delete root of WP installation!", Logger::TYPE_CRITICAL );
368
  $this->returnException( 'Fatal Error 8: Trying to delete root of WP installation!' );
369
  }
374
  return;
375
  }
376
 
377
+ $di = new \RecursiveDirectoryIterator( $this->deleteDir, \FilesystemIterator::SKIP_DOTS );
378
  $ri = new \RecursiveIteratorIterator( $di, \RecursiveIteratorIterator::CHILD_FIRST );
379
  foreach ( $ri as $file ) {
 
380
  $this->deleteFile( $file );
381
  if( $this->isOverThreshold() ) {
382
  //$this->returnException('Maximum PHP execution time exceeded. Run again and repeat the deletion process until it is sucessfully finished.');
384
  }
385
  }
386
 
387
+ // Delete left over staging site root folder
388
+ @rmdir( $this->deleteDir );
389
+
390
+ // Throw fatal error if the folder is still not deleted
391
+ if( is_dir($this->deleteDir) ) {
392
+ $clone = ( string ) $this->clone->path;
393
+ $response = array(
394
+ 'job' => 'delete',
395
+ 'status' => true,
396
+ 'delete' => 'finished',
397
+ 'message' => "Could not the staging site entirely. The folder {$clone}is still not empty. <br/><br/> If this happens again please contact us at support@wp-staging.com",
398
+ 'error' => true,
399
+ );
400
+ wp_die( json_encode( $response ) );
401
  }
402
+
403
+ // Successfull finish deleting job
404
+ return $this->deleteFinish();
405
  }
406
 
407
  /**
428
  /**
429
  * @return bool
430
  */
431
+ // public function isDirectoryDeletingFinished() {
432
+ // return (
433
+ // (false === $this->forceDeleteDirectories && (!isset( $_POST["deleteDir"] ) || '1' !== $_POST["deleteDir"])) ||
434
+ // !is_dir( $this->clone->path ) || ABSPATH === $this->job->nextDirectoryToDelete
435
+ // );
436
+ // }
437
 
438
  /**
439
  *
441
  */
442
  public function isFatalError() {
443
  $homePath = rtrim( get_home_path(), "/" );
444
+ if( rtrim( $this->deleteDir, "/" ) == $homePath ) {
445
  return true;
446
  }
447
  return false;
451
  * Finish / Update Existing Clones
452
  */
453
  public function deleteFinish() {
454
+
455
+ $response = array(
456
+ 'delete' => 'finished',
457
+ );
458
+
459
  $existingClones = get_option( "wpstg_existing_clones_beta", array() );
460
 
461
+ // Check if clone exist and then remove it from options
462
  $this->log( "Verifying existing clones..." );
463
  foreach ( $existingClones as $name => $clone ) {
464
+ if( $clone["path"] == $this->clone->path ) {
465
  unset( $existingClones[$name] );
466
  }
467
+ // if( !is_dir( $clone["path"] ) ) {
468
+ // unset( $existingClones[$name] );
469
+ // }
470
  }
 
471
 
472
  if( false === update_option( "wpstg_existing_clones_beta", $existingClones ) ) {
473
+ $this->log( "Delete: Nothing to save.'" );
474
  }
475
 
476
  // Delete cached file
478
  $this->cache->delete( "delete_directories_{$this->clone->name}" );
479
  $this->cache->delete( "clone_options" );
480
 
 
 
481
  wp_die( json_encode( $response ) );
482
  }
483
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
484
  }
 
 
apps/Backend/Modules/Jobs/Directories.php CHANGED
@@ -120,22 +120,41 @@ class Directories extends JobExecutable {
120
  // open file handle
121
  $files = $this->open( $this->filename, 'a' );
122
 
123
- $excludeWpContent = array(
 
 
 
124
  'cache',
125
- 'wps-hide-login',
126
- 'node_modules'
127
  );
128
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  try {
130
 
131
  // Iterate over content directory
132
  $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( WP_CONTENT_DIR );
133
 
134
- // Exclude new line file names Do not use this. Leads to error 500 on some systems
135
  // $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
136
-
137
  // Exclude uploads, plugins or themes
138
- $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, apply_filters( 'wpstg_clone_excl_folders', $excludeWpContent ) );
 
139
  // Recursively iterate over content directory
140
  $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
141
 
@@ -144,7 +163,7 @@ class Directories extends JobExecutable {
144
  // Write path line
145
  foreach ( $iterator as $item ) {
146
  if( $item->isFile() ) {
147
- $wpContentDir = str_replace(ABSPATH, '', WP_CONTENT_DIR);
148
  if( $this->write( $files, $wpContentDir . '/' . $iterator->getSubPathName() . PHP_EOL ) ) {
149
  $this->options->totalFiles++;
150
 
@@ -156,6 +175,8 @@ class Directories extends JobExecutable {
156
  } catch ( \Exception $e ) {
157
  $this->returnException( 'Error: ' . $e->getMessage() );
158
  //throw new \Exception( 'Error: ' . $e->getMessage() );
 
 
159
  }
160
 
161
  // close the file handler
@@ -186,7 +207,6 @@ class Directories extends JobExecutable {
186
 
187
  // Exclude new line file names Do not use this. Leads to error 500 on some systems
188
  // $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
189
-
190
  // Recursively iterate over wp-includes directory
191
  $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
192
 
@@ -204,10 +224,8 @@ class Directories extends JobExecutable {
204
  }
205
  }
206
  } catch ( \Exception $e ) {
207
- //$this->returnException('Out of disk space.');
208
- throw new \Exception( 'Error: ' . $e->getMessage() );
209
- } catch ( \Exception $e ) {
210
- // Skip bad file permissions
211
  }
212
 
213
  // close the file handler
@@ -238,7 +256,6 @@ class Directories extends JobExecutable {
238
 
239
  // Exclude new line file names Do not use this. Leads to error 500 on some systems
240
  // $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
241
-
242
  // Recursively iterate over content directory
243
  $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
244
 
@@ -256,8 +273,6 @@ class Directories extends JobExecutable {
256
  }
257
  } catch ( \Exception $e ) {
258
  $this->returnException( 'Error: ' . $e->getMessage() );
259
- } catch ( \Exception $e ) {
260
- // Skip bad file permissions
261
  }
262
 
263
  // close the file handler
@@ -309,8 +324,6 @@ class Directories extends JobExecutable {
309
  }
310
  } catch ( \Exception $e ) {
311
  $this->returnException( 'Error: ' . $e->getMessage() );
312
- } catch ( \Exception $e ) {
313
- // Skip bad file permissions
314
  }
315
 
316
  // close the file handler
120
  // open file handle
121
  $files = $this->open( $this->filename, 'a' );
122
 
123
+ /**
124
+ * Excluded folders relative to the folder to iterate
125
+ */
126
+ $excludePaths = array(
127
  'cache',
128
+ 'plugins/wps-hide-login',
129
+ 'uploads/sites'
130
  );
131
 
132
+ /**
133
+ * Get user excluded folders
134
+ */
135
+ $directory = array();
136
+ foreach ( $this->options->excludedDirectories as $dir ) {
137
+ // Windows compatibility fix
138
+ $dir = wpstg_replace_windows_directory_separator($dir);
139
+ $wpContentDir = wpstg_replace_windows_directory_separator(WP_CONTENT_DIR);
140
+
141
+ if( strpos( $dir, $wpContentDir ) !== false ) {
142
+ $directory[] = ltrim( str_replace( $wpContentDir, '', $dir ), '/\\' );
143
+ }
144
+ }
145
+
146
+ $excludePaths = array_merge( $excludePaths, $directory );
147
+
148
  try {
149
 
150
  // Iterate over content directory
151
  $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( WP_CONTENT_DIR );
152
 
153
+ // Exclude new line file names. Do not use this. Leads to error 500 on some systems
154
  // $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
 
155
  // Exclude uploads, plugins or themes
156
+ $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, apply_filters( 'wpstg_clone_excl_folders', $excludePaths ) );
157
+
158
  // Recursively iterate over content directory
159
  $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
160
 
163
  // Write path line
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
 
175
  } catch ( \Exception $e ) {
176
  $this->returnException( 'Error: ' . $e->getMessage() );
177
  //throw new \Exception( 'Error: ' . $e->getMessage() );
178
+ } catch ( \Exception $e ) {
179
+ // Skip bad file permissions
180
  }
181
 
182
  // close the file handler
207
 
208
  // Exclude new line file names Do not use this. Leads to error 500 on some systems
209
  // $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
 
210
  // Recursively iterate over wp-includes directory
211
  $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
212
 
224
  }
225
  }
226
  } catch ( \Exception $e ) {
227
+ $this->returnException( 'Error: ' . $e->getMessage() );
228
+ //throw new \Exception( 'Error: ' . $e->getMessage() );
 
 
229
  }
230
 
231
  // close the file handler
256
 
257
  // Exclude new line file names Do not use this. Leads to error 500 on some systems
258
  // $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
 
259
  // Recursively iterate over content directory
260
  $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
261
 
273
  }
274
  } catch ( \Exception $e ) {
275
  $this->returnException( 'Error: ' . $e->getMessage() );
 
 
276
  }
277
 
278
  // close the file handler
324
  }
325
  } catch ( \Exception $e ) {
326
  $this->returnException( 'Error: ' . $e->getMessage() );
 
 
327
  }
328
 
329
  // close the file handler
apps/Backend/Modules/Jobs/Finish.php CHANGED
@@ -46,6 +46,8 @@ class Finish extends Job {
46
  );
47
 
48
  //$this->flush();
 
 
49
 
50
  return ( object ) $return;
51
  }
@@ -75,6 +77,7 @@ class Finish extends Job {
75
  if( isset( $this->options->existingClones[$this->options->clone] ) ) {
76
  $this->options->existingClones[$this->options->clone]['datetime'] = time();
77
  $this->options->existingClones[$this->options->clone]['status'] = 'finished';
 
78
  update_option( "wpstg_existing_clones_beta", $this->options->existingClones );
79
  $this->log( "Finish: The job finished!" );
80
  return true;
46
  );
47
 
48
  //$this->flush();
49
+ do_action('wpstg_cloning_complete');
50
+
51
 
52
  return ( object ) $return;
53
  }
77
  if( isset( $this->options->existingClones[$this->options->clone] ) ) {
78
  $this->options->existingClones[$this->options->clone]['datetime'] = time();
79
  $this->options->existingClones[$this->options->clone]['status'] = 'finished';
80
+ $this->options->existingClones[$this->options->clone]['prefix'] = $this->options->prefix;
81
  update_option( "wpstg_existing_clones_beta", $this->options->existingClones );
82
  $this->log( "Finish: The job finished!" );
83
  return true;
apps/Backend/Modules/Jobs/Multisite/Data.php CHANGED
@@ -799,7 +799,7 @@ class Data extends JobExecutable {
799
  return false;
800
  }
801
 
802
- $this->log( "Data Crunching Step 14: Successfull!" );
803
  return true;
804
  }
805
 
799
  return false;
800
  }
801
 
802
+ $this->log( "Data Crunching Step 14: Successful!" );
803
  return true;
804
  }
805
 
apps/Backend/Modules/Jobs/Multisite/DataExternal.php CHANGED
@@ -823,7 +823,7 @@ class DataExternal extends JobExecutable {
823
  return false;
824
  }
825
 
826
- $this->log( "Data Crunching Step 14: Successfull!" );
827
  return true;
828
  }
829
 
823
  return false;
824
  }
825
 
826
+ $this->log( "Data Crunching Step 14: Successful!" );
827
  return true;
828
  }
829
 
apps/Backend/Modules/Jobs/Multisite/Database.php CHANGED
@@ -1,362 +1,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
- 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( $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
- wp_die( 'Fatal error 7: The new database table prefix ' . $stagingPrefix . ' would be identical to the table prefix of the live site. Please open a support ticket at support@wp-staging.com' );
129
- }
130
- return $stagingPrefix;
131
- }
132
-
133
- /**
134
- * No worries, SQL queries don't eat from PHP execution time!
135
- * @param string $tableName
136
- * @return bool
137
- */
138
- private function copyTable( $tableName ) {
139
-
140
- $strings = new Strings();
141
- $tableName = is_object( $tableName ) ? $tableName->name : $tableName;
142
- $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->prefix, null, $tableName );
143
-
144
- // Get wp_users from main site
145
- if( 'users' === $strings->str_replace_first( $this->db->prefix, null, $tableName ) ) {
146
- $tableName = $this->db->base_prefix . 'users';
147
- }
148
- // Get wp_usermeta from main site
149
- if( 'usermeta' === $strings->str_replace_first( $this->db->prefix, null, $tableName ) ) {
150
- $tableName = $this->db->base_prefix . 'usermeta';
151
- }
152
-
153
- // Drop table if necessary
154
- $this->dropTable( $newTableName );
155
-
156
- // Save current job
157
- $this->setJob( $newTableName );
158
-
159
- // Beginning of the job
160
- if( !$this->startJob( $newTableName, $tableName ) ) {
161
- return true;
162
- }
163
-
164
- // Copy data
165
- $this->copyData( $newTableName, $tableName );
166
-
167
- // Finish the step
168
- return $this->finishStep();
169
- }
170
-
171
- /**
172
- * Copy multisite global user table wp_users to wpstgX_users
173
- * @return bool
174
- */
175
- // private function copyWpUsers() {
176
- //// $strings = new Strings();
177
- //// $tableName = $this->db->base_prefix . 'users';
178
- //// $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->base_prefix, null, $tableName );
179
- //
180
- // $tableName = $this->db->base_prefix . 'users';
181
- // $newTableName = $this->getStagingPrefix() . 'users';
182
- //
183
- // $this->log( "DB Copy: Try to create table {$newTableName}" );
184
- //
185
- //
186
- // // Drop table if necessary
187
- // $this->dropTable( $newTableName );
188
- //
189
- // // Save current job
190
- // $this->setJob( $newTableName );
191
- //
192
- // // Beginning of the job
193
- // if( !$this->startJob( $newTableName, $tableName ) ) {
194
- // return true;
195
- // }
196
- //
197
- // // Copy data
198
- // $this->copyData( $newTableName, $tableName );
199
- //
200
- // // Finish the step
201
- // return $this->finishStep();
202
- // }
203
-
204
- /**
205
- * Copy multisite global user table wp_usermeta to wpstgX_users
206
- * @return bool
207
- */
208
- // private function copyWpUsermeta() {
209
- //// $strings = new Strings();
210
- //// $tableName = $this->db->base_prefix . 'usermeta';
211
- //// $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->base_prefix, null, $tableName );
212
- // $tableName = $this->db->base_prefix . 'usermeta';
213
- // $newTableName = $this->getStagingPrefix() . 'usermeta';
214
- //
215
- // $this->log( "DB Copy: Try to create table {$newTableName}" );
216
- //
217
- //
218
- // // Drop table if necessary
219
- // $this->dropTable( $newTableName );
220
- //
221
- // // Save current job
222
- // $this->setJob( $newTableName );
223
- //
224
- // // Beginning of the job
225
- // if( !$this->startJob( $newTableName, $tableName ) ) {
226
- // return true;
227
- // }
228
- // // Copy data
229
- // $this->copyData( $newTableName, $tableName );
230
- //
231
- // // Finish the step
232
- // return $this->finishStep();
233
- // }
234
-
235
- /**
236
- * Copy data from old table to new table
237
- * @param string $new
238
- * @param string $old
239
- */
240
- private function copyData( $new, $old ) {
241
- $rows = $this->options->job->start + $this->settings->queryLimit;
242
- $this->log(
243
- "DB Copy: {$old} as {$new} from {$this->options->job->start} to {$rows} records"
244
- );
245
-
246
- $limitation = '';
247
-
248
- if( 0 < ( int ) $this->settings->queryLimit ) {
249
- $limitation = " LIMIT {$this->settings->queryLimit} OFFSET {$this->options->job->start}";
250
- }
251
-
252
- $this->db->query(
253
- "INSERT INTO {$new} SELECT * FROM {$old} {$limitation}"
254
- );
255
-
256
- // Set new offset
257
- $this->options->job->start += $this->settings->queryLimit;
258
- }
259
-
260
- /**
261
- * Set the job
262
- * @param string $table
263
- */
264
- private function setJob( $table ) {
265
- if( isset( $this->options->job->current ) ) {
266
- return;
267
- }
268
-
269
- $this->options->job->current = $table;
270
- $this->options->job->start = 0;
271
- }
272
-
273
- /**
274
- * Start Job
275
- * @param string $new
276
- * @param string $old
277
- * @return bool
278
- */
279
- private function startJob( $new, $old ) {
280
-
281
- $this->options->job->total = 0;
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 = ( int ) $this->db->get_var( "SELECT COUNT(1) FROM {$old}" );
298
-
299
- if( 0 == $this->options->job->total ) {
300
- $this->finishStep();
301
- return false;
302
- }
303
-
304
- return true;
305
- }
306
-
307
- /**
308
- * Finish the step
309
- */
310
- private function finishStep() {
311
- // This job is not finished yet
312
- if( $this->options->job->total > $this->options->job->start ) {
313
- return false;
314
- }
315
-
316
- // Add it to cloned tables listing
317
- $this->options->clonedTables[] = isset( $this->options->tables[$this->options->currentStep] ) ? $this->options->tables[$this->options->currentStep] : false;
318
-
319
- // Reset job
320
- $this->options->job = new \stdClass();
321
-
322
- return true;
323
- }
324
-
325
- /**
326
- * Drop table if necessary
327
- * @param string $new
328
- */
329
- private function dropTable( $new ) {
330
-
331
- $old = $this->db->get_var( $this->db->prepare( "SHOW TABLES LIKE %s", $new ) );
332
-
333
- if( !$this->shouldDropTable( $new, $old ) ) {
334
- return;
335
- }
336
-
337
- $this->log( "DB Copy: {$new} already exists, dropping it first" );
338
- $this->db->query( "DROP TABLE {$new}" );
339
- }
340
-
341
- /**
342
- * Check if table needs to be dropped
343
- * @param string $new
344
- * @param string $old
345
- * @return bool
346
- */
347
- private function shouldDropTable( $new, $old ) {
348
-
349
-
350
-
351
- if( $old === $new &&
352
- (
353
- !isset( $this->options->job->current ) ||
354
- !isset( $this->options->job->start ) ||
355
- 0 == $this->options->job->start
356
- ) ) {
357
- return true;
358
- }
359
- return false;
360
- }
361
-
362
- }
 
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
+ }
apps/Backend/Modules/Jobs/Multisite/Directories.php CHANGED
@@ -11,7 +11,6 @@ use WPStaging\WPStaging;
11
  use WPStaging\Utils\Logger;
12
  use WPStaging\Utils\Strings;
13
  use WPStaging\Iterators\RecursiveDirectoryIterator;
14
- use WPStaging\Iterators\RecursiveFilterNewLine;
15
  use WPStaging\Iterators\RecursiveFilterExclude;
16
  use WPStaging\Backend\Modules\Jobs\JobExecutable;
17
 
@@ -127,8 +126,8 @@ class Directories extends JobExecutable {
127
  */
128
  $excludePaths = array(
129
  'cache',
130
- 'plugins' . DIRECTORY_SEPARATOR . 'wps-hide-login',
131
- 'uploads' . DIRECTORY_SEPARATOR . 'sites'
132
  );
133
 
134
  /**
@@ -136,30 +135,25 @@ class Directories extends JobExecutable {
136
  */
137
  $directory = array();
138
  foreach ( $this->options->excludedDirectories as $dir ) {
139
- if( strpos( $dir, WP_CONTENT_DIR ) !== false ) {
140
- $directory[] = ltrim( str_replace( WP_CONTENT_DIR, '', $dir ), '/' );
 
 
 
 
141
  }
142
  }
143
 
144
  $excludePaths = array_merge( $excludePaths, $directory );
145
 
146
- // $excludeFolders = array(
147
- // 'cache',
148
- // 'node_modules',
149
- // 'nbproject',
150
- // 'wps-hide-login'
151
- // );
152
-
153
  try {
154
 
155
  // Iterate over content directory
156
  $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( WP_CONTENT_DIR );
157
 
158
- // Exclude new line file names
159
- $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
160
-
161
  // Exclude sites, uploads, plugins or themes
162
  $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, apply_filters( 'wpstg_clone_mu_excl_folders', $excludePaths ) );
 
163
  // Recursively iterate over content directory
164
  $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
165
 
@@ -210,9 +204,6 @@ class Directories extends JobExecutable {
210
  // Iterate over wp-admin directory
211
  $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( \WPStaging\WPStaging::getWPpath() . 'wp-includes' . DIRECTORY_SEPARATOR );
212
 
213
- // Exclude new line file names
214
- $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
215
-
216
  // Recursively iterate over wp-includes directory
217
  $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
218
 
@@ -262,9 +253,6 @@ class Directories extends JobExecutable {
262
  // Iterate over wp-admin directory
263
  $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( \WPStaging\WPStaging::getWPpath() . 'wp-admin' . DIRECTORY_SEPARATOR );
264
 
265
- // Exclude new line file names
266
- $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
267
-
268
  // Recursively iterate over content directory
269
  $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
270
 
@@ -328,8 +316,8 @@ class Directories extends JobExecutable {
328
  */
329
  $excludePaths = array(
330
  'cache',
331
- 'plugins' . DIRECTORY_SEPARATOR . 'wps-hide-login',
332
- 'uploads' . DIRECTORY_SEPARATOR . 'sites'
333
  );
334
 
335
  /**
@@ -337,6 +325,8 @@ class Directories extends JobExecutable {
337
  */
338
  $directory = array();
339
  foreach ( $this->options->excludedDirectories as $dir ) {
 
 
340
  if( strpos( $dir, $path ) !== false ) {
341
  $directory[] = ltrim( str_replace( $path, '', $dir ), '/' );
342
  }
@@ -349,9 +339,6 @@ class Directories extends JobExecutable {
349
  // Iterate over content directory
350
  $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( $path );
351
 
352
- // Exclude new line file names
353
- $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
354
-
355
  // Exclude sites, uploads, plugins or themes
356
  $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, $excludePaths );
357
 
@@ -434,20 +421,6 @@ class Directories extends JobExecutable {
434
  // Iterate over extra directory
435
  $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( $folder );
436
 
437
- // Exclude new line file names
438
- $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
439
-
440
- // Exclude wp core folders
441
- // $exclude = array('wp-includes',
442
- // 'wp-admin',
443
- // 'wp-content');
444
- //
445
- // $excludeMore = array();
446
- // foreach ($this->options->excludedDirectories as $key => $value){
447
- // $excludeMore[] = $this->getLastElemAfterString('/', $value);
448
- // }
449
- //$exclude = array_merge($exclude, $excludeMore);
450
-
451
  $exclude = array();
452
 
453
  $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, $exclude );
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
 
126
  */
127
  $excludePaths = array(
128
  'cache',
129
+ 'plugins/wps-hide-login',
130
+ 'uploads/sites'
131
  );
132
 
133
  /**
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
 
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
 
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
 
316
  */
317
  $excludePaths = array(
318
  'cache',
319
+ 'plugins/wps-hide-login',
320
+ 'uploads/sites'
321
  );
322
 
323
  /**
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
  }
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
 
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 );
apps/Backend/Modules/Jobs/Multisite/SearchReplace.php CHANGED
@@ -1,784 +1,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
- return trailingslashit( $this->strings->getUrlWithoutScheme($this->multisiteDomainWithoutScheme) ) . $this->options->cloneDirectoryName;
200
- }
201
-
202
- /**
203
- * Get the install sub directory if WP is installed in sub directory
204
- * @return string
205
- */
206
- private function getSubDir() {
207
- $home = get_option( 'home' );
208
- $siteurl = get_option( 'siteurl' );
209
-
210
- if( empty( $home ) || empty( $siteurl ) ) {
211
- return '';
212
- }
213
-
214
- $dir = str_replace( $home, '', $siteurl );
215
- return str_replace( '/', '', $dir );
216
- }
217
-
218
- /**
219
- * Start search replace job
220
- * @param string $new
221
- * @param string $old
222
- */
223
- private function startReplace( $table ) {
224
- $rows = $this->options->job->start + $this->settings->querySRLimit;
225
- $this->log(
226
- "DB Processing: Table {$table} {$this->options->job->start} to {$rows} records"
227
- );
228
-
229
- // Search & Replace
230
- $this->searchReplace( $table, $rows, array() );
231
-
232
- // Set new offset
233
- $this->options->job->start += $this->settings->querySRLimit;
234
- }
235
-
236
- /**
237
- * Returns the number of pages in a table.
238
- * @access public
239
- * @return int
240
- */
241
- private function get_pages_in_table( $table ) {
242
-
243
- // Table does not exist
244
- $result = $this->db->query( "SHOW TABLES LIKE '{$table}'" );
245
- if( !$result || 0 === $result ) {
246
- return 0;
247
- }
248
-
249
- $table = esc_sql( $table );
250
- $rows = $this->db->get_var( "SELECT COUNT(*) FROM $table" );
251
- $pages = ceil( $rows / $this->settings->querySRLimit );
252
- return absint( $pages );
253
- }
254
-
255
- /**
256
- * Gets the columns in a table.
257
- * @access public
258
- * @param string $table The table to check.
259
- * @return array
260
- */
261
- private function get_columns( $table ) {
262
- $primary_key = null;
263
- $columns = array();
264
- $fields = $this->db->get_results( 'DESCRIBE ' . $table );
265
- if( is_array( $fields ) ) {
266
- foreach ( $fields as $column ) {
267
- $columns[] = $column->Field;
268
- if( $column->Key == 'PRI' ) {
269
- $primary_key = $column->Field;
270
- }
271
- }
272
- }
273
- return array($primary_key, $columns);
274
- }
275
-
276
- /**
277
- * Return absolute destination path
278
- * @return string
279
- */
280
- private function getAbsDestination() {
281
- if( empty( $this->options->cloneDir ) ) {
282
- return \WPStaging\WPStaging::getWPpath();
283
- }
284
- return trailingslashit( $this->options->cloneDir );
285
- }
286
-
287
- /**
288
- * Adapated from interconnect/it's search/replace script, adapted from Better Search Replace
289
- *
290
- * Modified to use WordPress wpdb functions instead of PHP's native mysql/pdo functions,
291
- * and to be compatible with batch processing.
292
- *
293
- * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
294
- *
295
- * @access public
296
- * @param string $table The table to run the replacement on.
297
- * @param int $page The page/block to begin the query on.
298
- * @param array $args An associative array containing arguments for this run.
299
- * @return array
300
- */
301
- private function searchReplace( $table, $page, $args ) {
302
-
303
- if( $this->thirdParty->isSearchReplaceExcluded( $table ) ) {
304
- $this->log( "DB Processing: Skip {$table}", \WPStaging\Utils\Logger::TYPE_INFO );
305
- return true;
306
- }
307
-
308
- // Load up the default settings for this chunk.
309
- $table = esc_sql( $table );
310
- $current_page = $this->options->job->start + $this->settings->querySRLimit;
311
- $pages = $this->get_pages_in_table( $table );
312
-
313
-
314
- // Search URL example.com/staging and root path to staging site /var/www/htdocs/staging
315
- $args['search_for'] = array(
316
- '//' . $this->getSourceHostname(),
317
- ABSPATH,
318
- '\/\/' . str_replace( '/', '\/', $this->getSourceHostname() ), // // Used by revslider and several visual editors
319
- $this->getImagePathLive()
320
- );
321
-
322
-
323
- $args['replace_with'] = array(
324
- '//' . $this->getDestinationHostname(),
325
- $this->options->destinationDir,
326
- '\/\/' . str_replace( '/', '\/', $this->getDestinationHostname() ), // Used by revslider and several visual editors
327
- $this->getImagePathStaging()
328
- );
329
-
330
- $args['replace_guids'] = 'off';
331
- $args['dry_run'] = 'off';
332
- $args['case_insensitive'] = false;
333
- $args['replace_mails'] = 'off';
334
- $args['skip_transients'] = 'on';
335
-
336
-
337
- // Allow filtering of search & replace parameters
338
- $args = apply_filters( 'wpstg_clone_searchreplace_params', $args );
339
-
340
- // Get a list of columns in this table.
341
- list( $primary_key, $columns ) = $this->get_columns( $table );
342
-
343
- // Bail out early if there isn't a primary key.
344
- // We commented this to search & replace through tables which have no primary keys like wp_revslider_slides
345
- // @todo test this carefully. If it causes (performance) issues we need to activate it again!
346
- // @since 2.4.4
347
- // if( null === $primary_key ) {
348
- // return false;
349
- // }
350
-
351
- $current_row = 0;
352
- $start = $this->options->job->start;
353
- $end = $this->settings->querySRLimit;
354
-
355
- // Grab the content of the table.
356
- $data = $this->db->get_results( "SELECT * FROM $table LIMIT $start, $end", ARRAY_A );
357
-
358
- // Filter certain rows option_name in wpstg_options
359
- $filter = array(
360
- 'Admin_custome_login_Slidshow',
361
- 'Admin_custome_login_Social',
362
- 'Admin_custome_login_logo',
363
- 'Admin_custome_login_text',
364
- 'Admin_custome_login_login',
365
- 'Admin_custome_login_top',
366
- 'Admin_custome_login_dashboard',
367
- 'Admin_custome_login_Version',
368
- 'upload_path',
369
- );
370
-
371
- $filter = apply_filters( 'wpstg_clone_searchreplace_excl_rows', $filter );
372
-
373
- // Loop through the data.
374
- foreach ( $data as $row ) {
375
- $current_row++;
376
- $update_sql = array();
377
- $where_sql = array();
378
- $upd = false;
379
-
380
- // Skip rows below
381
- if( isset( $row['option_name'] ) && in_array( $row['option_name'], $filter ) ) {
382
- continue;
383
- }
384
-
385
- // Skip rows with transients (They can store huge data and we need to save memory)
386
- if( isset( $row['option_name'] ) && 'on' === $args['skip_transients'] && false !== strpos( $row['option_name'], '_transient' ) ) {
387
- continue;
388
- }
389
-
390
- foreach ( $columns as $column ) {
391
-
392
- $dataRow = $row[$column];
393
-
394
- if( $column == $primary_key ) {
395
- $where_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
396
- continue;
397
- }
398
-
399
- // Skip GUIDs by default.
400
- if( 'on' !== $args['replace_guids'] && 'guid' == $column ) {
401
- continue;
402
- }
403
-
404
- // Skip mail addresses
405
- if( 'off' === $args['replace_mails'] && false !== strpos( $dataRow, '@' . $this->multisiteDomainWithoutScheme ) ) {
406
- continue;
407
- }
408
-
409
- // Check options table
410
- if( $this->options->prefix . 'options' === $table ) {
411
-
412
- // Skip certain options
413
- // if( isset( $should_skip ) && true === $should_skip ) {
414
- // $should_skip = false;
415
- // continue;
416
- // }
417
-
418
- // Skip this row
419
- if( 'wpstg_existing_clones_beta' === $dataRow ||
420
- 'wpstg_existing_clones' === $dataRow ||
421
- 'wpstg_settings' === $dataRow ||
422
- 'wpstg_license_status' === $dataRow ||
423
- 'siteurl' === $dataRow ||
424
- 'home' === $dataRow
425
- ) {
426
- //$should_skip = true;
427
- continue;
428
- }
429
- }
430
-
431
- // Check the path delimiter for / or \/ and remove one of those which prevents from resulting in wrong syntax like domain.com/staging\/.
432
- // 1. local.wordpress.test -> local.wordpress.test/staging
433
- // 2. local.wordpress.test\/ -> local.wordpress.test\/staging\/
434
- $tmp = $args;
435
- if( false === strpos( $dataRow, $tmp['search_for'][0] ) ) {
436
- array_shift( $tmp['search_for'] ); // rtrim( $this->homeUrl, '/' ),
437
- array_shift( $tmp['replace_with'] ); // rtrim( $this->homeUrl, '/' ) . '/' . $this->options->cloneDirectoryName,
438
- } else {
439
- unset( $tmp['search_for'][1] );
440
- unset( $tmp['replace_with'][1] );
441
- // recount array
442
- $tmp['search_for'] = array_values( $tmp['search_for'] );
443
- $tmp['replace_with'] = array_values( $tmp['replace_with'] );
444
- }
445
-
446
- // Run a search replace on the data row and respect the serialisation.
447
- $i = 0;
448
- foreach ( $tmp['search_for'] as $replace ) {
449
- $dataRow = $this->recursive_unserialize_replace( $tmp['search_for'][$i], $tmp['replace_with'][$i], $dataRow, false, $args['case_insensitive'] );
450
- $i++;
451
- }
452
- unset( $replace );
453
- unset( $i );
454
- unset( $tmp );
455
-
456
- // Something was changed
457
- if( $row[$column] != $dataRow ) {
458
- $update_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
459
- $upd = true;
460
- }
461
- }
462
-
463
- // Determine what to do with updates.
464
- if( $args['dry_run'] === 'on' ) {
465
- // Don't do anything if a dry run
466
- } elseif( $upd && !empty( $where_sql ) ) {
467
- // If there are changes to make, run the query.
468
- $sql = 'UPDATE ' . $table . ' SET ' . implode( ', ', $update_sql ) . ' WHERE ' . implode( ' AND ', array_filter( $where_sql ) );
469
- $result = $this->db->query( $sql );
470
-
471
- if( !$result ) {
472
- $this->log( "Error updating row {$current_row} SQL: {$sql}", \WPStaging\Utils\Logger::TYPE_ERROR );
473
- }
474
- }
475
- } // end row loop
476
- unset( $row );
477
- unset( $update_sql );
478
- unset( $where_sql );
479
- unset( $sql );
480
-
481
-
482
- // DB Flush
483
- $this->db->flush();
484
- return true;
485
- }
486
-
487
- /**
488
- * Get path to multisite image folder e.g. wp-content/blogs.dir/ID/files or wp-content/uploads/sites/ID
489
- * @return string
490
- */
491
- private function getImagePathLive() {
492
- // Check first which structure is used
493
- $uploads = wp_upload_dir();
494
- $basedir = $uploads['basedir'];
495
- $blogId = get_current_blog_id();
496
-
497
- if( false === strpos( $basedir, 'blogs.dir' ) ) {
498
- // Since WP 3.5
499
- $path = $blogId > 1 ?
500
- 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR :
501
- 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
502
- } else {
503
- // old blog structure
504
- $path = $blogId > 1 ?
505
- 'wp-content' . DIRECTORY_SEPARATOR . 'blogs.dir' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR . 'files' . DIRECTORY_SEPARATOR :
506
- 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
507
- }
508
- return $path;
509
- }
510
-
511
- /**
512
- * Get path to staging site image path wp-content/uploads
513
- * @return string
514
- */
515
- private function getImagePathStaging() {
516
- return 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
517
- }
518
-
519
- /**
520
- * Adapted from interconnect/it's search/replace script.
521
- *
522
- * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
523
- *
524
- * Take a serialised array and unserialise it replacing elements as needed and
525
- * unserialising any subordinate arrays and performing the replace on those too.
526
- *
527
- * @access private
528
- * @param string $from String we're looking to replace.
529
- * @param string $to What we want it to be replaced with
530
- * @param array $data Used to pass any subordinate arrays back to in.
531
- * @param boolean $serialized Does the array passed via $data need serialising.
532
- * @param sting|boolean $case_insensitive Set to 'on' if we should ignore case, false otherwise.
533
- *
534
- * @return string|array The original array with all elements replaced as needed.
535
- */
536
- private function recursive_unserialize_replace( $from = '', $to = '', $data = '', $serialized = false, $case_insensitive = false ) {
537
- try {
538
- // Some unserialized data cannot be re-serialized eg. SimpleXMLElements
539
- if( is_serialized( $data ) && ( $unserialized = @unserialize( $data ) ) !== false ) {
540
- $data = $this->recursive_unserialize_replace( $from, $to, $unserialized, true, $case_insensitive );
541
- } elseif( is_array( $data ) ) {
542
- $tmp = array();
543
- foreach ( $data as $key => $value ) {
544
- $tmp[$key] = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
545
- }
546
-
547
- $data = $tmp;
548
- unset( $tmp );
549
- } elseif( is_object( $data ) ) {
550
- $tmp = $data;
551
- $props = get_object_vars( $data );
552
- foreach ( $props as $key => $value ) {
553
- if( $key === '' || ord( $key[0] ) === 0 ) {
554
- continue;
555
- }
556
- $tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
557
- }
558
-
559
- $data = $tmp;
560
- unset( $tmp );
561
- } else {
562
- if( is_string( $data ) ) {
563
- if( !empty( $from ) && !empty( $to ) ) {
564
- $data = $this->str_replace( $from, $to, $data, $case_insensitive );
565
- }
566
- }
567
- }
568
-
569
- if( $serialized ) {
570
- return serialize( $data );
571
- }
572
- } catch ( Exception $error ) {
573
-
574
- }
575
-
576
- return $data;
577
- }
578
-
579
- /**
580
- * Check if the object is a valid one and not __PHP_Incomplete_Class_Name
581
- * Can not use is_object alone because in php 7.2 it's returning true even though object is __PHP_Incomplete_Class_Name
582
- * @return boolean
583
- */
584
- // private function isValidObject( $data ) {
585
- // if( !is_object( $data ) || gettype( $data ) != 'object' ) {
586
- // return false;
587
- // }
588
- //
589
- // $invalid_class_props = get_object_vars( $data );
590
- //
591
- // if (!isset($invalid_class_props['__PHP_Incomplete_Class_Name'])){
592
- // // Assume it must be an valid object
593
- // return true;
594
- // }
595
- //
596
- // $invalid_object_class = $invalid_class_props['__PHP_Incomplete_Class_Name'];
597
- //
598
- // if( !empty( $invalid_object_class ) ) {
599
- // return false;
600
- // }
601
- //
602
- // // Assume it must be an valid object
603
- // return true;
604
- // }
605
-
606
- /**
607
- * Mimics the mysql_real_escape_string function. Adapted from a post by 'feedr' on php.net.
608
- * @link http://php.net/manual/en/function.mysql-real-escape-string.php#101248
609
- * @access public
610
- * @param string $input The string to escape.
611
- * @return string
612
- */
613
- private function mysql_escape_mimic( $input ) {
614
- if( is_array( $input ) ) {
615
- return array_map( __METHOD__, $input );
616
- }
617
- if( !empty( $input ) && is_string( $input ) ) {
618
- return str_replace( array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $input );
619
- }
620
-
621
- return $input;
622
- }
623
-
624
- /**
625
- * Return unserialized object or array
626
- *
627
- * @param string $serialized_string Serialized string.
628
- * @param string $method The name of the caller method.
629
- *
630
- * @return mixed, false on failure
631
- */
632
- private static function unserialize( $serialized_string ) {
633
- if( !is_serialized( $serialized_string ) ) {
634
- return false;
635
- }
636
-
637
- $serialized_string = trim( $serialized_string );
638
- $unserialized_string = @unserialize( $serialized_string );
639
-
640
- return $unserialized_string;
641
- }
642
-
643
- /**
644
- * Wrapper for str_replace
645
- *
646
- * @param string $from
647
- * @param string $to
648
- * @param string $data
649
- * @param string|bool $case_insensitive
650
- *
651
- * @return string
652
- */
653
- private function str_replace( $from, $to, $data, $case_insensitive = false ) {
654
-
655
- // Add filter
656
- $excludes = apply_filters( 'wpstg_clone_searchreplace_excl', array() );
657
-
658
- // Build pattern
659
- $regexExclude = '';
660
- foreach ( $excludes as $exclude ) {
661
- $regexExclude .= $exclude . '(*SKIP)(FAIL)|';
662
- }
663
-
664
- if( 'on' === $case_insensitive ) {
665
- //$data = str_ireplace( $from, $to, $data );
666
- $data = preg_replace( '#' . $regexExclude . preg_quote( $from ) . '#i', $to, $data );
667
- } else {
668
- //$data = str_replace( $from, $to, $data );
669
- $data = preg_replace( '#' . $regexExclude . preg_quote( $from ) . '#', $to, $data );
670
- }
671
-
672
- return $data;
673
- }
674
-
675
- /**
676
- * Set the job
677
- * @param string $table
678
- */
679
- private function setJob( $table ) {
680
- if( !empty( $this->options->job->current ) ) {
681
- return;
682
- }
683
-
684
- $this->options->job->current = $table;
685
- $this->options->job->start = 0;
686
- }
687
-
688
- /**
689
- * Start Job
690
- * @param string $new
691
- * @param string $old
692
- * @return bool
693
- */
694
- private function startJob( $new, $old ) {
695
-
696
- $this->options->job->total = 0;
697
-
698
- if( 0 != $this->options->job->start ) {
699
- return true;
700
- }
701
-
702
- // Table does not exist
703
- $result = $this->db->query( "SHOW TABLES LIKE '{$old}'" );
704
- if( !$result || 0 === $result ) {
705
- return false;
706
- }
707
-
708
- $this->options->job->total = ( int ) $this->db->get_var( "SELECT COUNT(1) FROM {$old}" );
709
-
710
- if( 0 == $this->options->job->total ) {
711
- $this->finishStep();
712
- return false;
713
- }
714
-
715
- return true;
716
- }
717
-
718
- /**
719
- * Finish the step
720
- */
721
- private function finishStep() {
722
- // This job is not finished yet
723
- if( $this->options->job->total > $this->options->job->start ) {
724
- return false;
725
- }
726
-
727
- // Add it to cloned tables listing
728
- $this->options->clonedTables[] = $this->options->tables[$this->options->currentStep];
729
-
730
- // Reset job
731
- $this->options->job = new \stdClass();
732
-
733
- return true;
734
- }
735
-
736
- /**
737
- * Drop table if necessary
738
- * @param string $new
739
- */
740
- private function dropTable( $new ) {
741
- $old = $this->db->get_var( $this->db->prepare( "SHOW TABLES LIKE %s", $new ) );
742
-
743
- if( !$this->shouldDropTable( $new, $old ) ) {
744
- return;
745
- }
746
-
747
- $this->log( "DB Processing: {$new} already exists, dropping it first" );
748
- $this->db->query( "DROP TABLE {$new}" );
749
- }
750
-
751
- /**
752
- * Check if table needs to be dropped
753
- * @param string $new
754
- * @param string $old
755
- * @return bool
756
- */
757
- private function shouldDropTable( $new, $old ) {
758
- return (
759
- $old == $new &&
760
- (
761
- !isset( $this->options->job->current ) ||
762
- !isset( $this->options->job->start ) ||
763
- 0 == $this->options->job->start
764
- )
765
- );
766
- }
767
-
768
- /**
769
- * Check if WP is installed in subdir
770
- * @return boolean
771
- */
772
- private function isSubDir() {
773
- // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
774
- // This is happening much more often than you would expect
775
- $siteurl = preg_replace( '#^https?://#', '', rtrim( get_option( 'siteurl' ), '/' ) );
776
- $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
777
-
778
- if( $home !== $siteurl ) {
779
- return true;
780
- }
781
- return false;
782
- }
783
-
784
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ }
apps/Backend/Modules/Jobs/Multisite/SearchReplaceExternal.php CHANGED
@@ -1,818 +1,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 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
- $this->getImagePathLive()
329
- );
330
-
331
-
332
- $args['replace_with'] = array(
333
- '//' . $this->destinationHostname,
334
- $this->options->destinationDir,
335
- '\/\/' . str_replace( '/', '\/', $this->destinationHostname ), // Used by revslider and several visual editors
336
- $this->getImagePathStaging()
337
- );
338
-
339
- $args['replace_guids'] = 'off';
340
- $args['dry_run'] = 'off';
341
- $args['case_insensitive'] = false;
342
- $args['replace_mails'] = 'off';
343
- $args['skip_transients'] = 'on';
344
-
345
-
346
- // Allow filtering of search & replace parameters
347
- $args = apply_filters( 'wpstg_clone_searchreplace_params', $args );
348
-
349
- // Get a list of columns in this table.
350
- list( $primary_key, $columns ) = $this->get_columns( $table );
351
-
352
- // Bail out early if there isn't a primary key.
353
- // We commented this to search & replace through tables which have no primary keys like wp_revslider_slides
354
- // @todo test this carefully. If it causes (performance) issues we need to activate it again!
355
- // @since 2.4.4
356
- // if( null === $primary_key ) {
357
- // return false;
358
- // }
359
-
360
- $current_row = 0;
361
- $start = $this->options->job->start;
362
- $end = $this->settings->querySRLimit;
363
-
364
- // Grab the content of the table.
365
- $data = $this->stagingDb->get_results( "SELECT * FROM $table LIMIT $start, $end", ARRAY_A );
366
-
367
- // Filter certain rows (of other plugins)
368
- $filter = array(
369
- 'Admin_custome_login_Slidshow',
370
- 'Admin_custome_login_Social',
371
- 'Admin_custome_login_logo',
372
- 'Admin_custome_login_text',
373
- 'Admin_custome_login_login',
374
- 'Admin_custome_login_top',
375
- 'Admin_custome_login_dashboard',
376
- 'Admin_custome_login_Version',
377
- 'upload_path',
378
- );
379
-
380
- $filter = apply_filters( 'wpstg_clone_searchreplace_excl_rows', $filter );
381
-
382
- // Loop through the data.
383
- foreach ( $data as $row ) {
384
- $current_row++;
385
- $update_sql = array();
386
- $where_sql = array();
387
- $upd = false;
388
-
389
- // Skip rows below
390
- if( isset( $row['option_name'] ) && in_array( $row['option_name'], $filter ) ) {
391
- continue;
392
- }
393
-
394
- // Skip rows with transients (They can store huge data and we need to save memory)
395
- if( isset( $row['option_name'] ) && 'on' === $args['skip_transients'] && false !== strpos( $row['option_name'], '_transient' ) ) {
396
- continue;
397
- }
398
-
399
- foreach ( $columns as $column ) {
400
-
401
- $dataRow = $row[$column];
402
-
403
- if( $column == $primary_key ) {
404
- $where_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
405
- continue;
406
- }
407
-
408
- // Skip GUIDs by default.
409
- if( 'on' !== $args['replace_guids'] && 'guid' == $column ) {
410
- continue;
411
- }
412
-
413
- // Skip mail addresses
414
- if( 'off' === $args['replace_mails'] && false !== strpos( $dataRow, '@' . $this->multisiteDomainWithoutScheme ) ) {
415
- continue;
416
- }
417
-
418
- // Check options table
419
- if( $this->options->prefix . 'options' === $table ) {
420
-
421
- // Skip certain options
422
- // if( isset( $should_skip ) && true === $should_skip ) {
423
- // $should_skip = false;
424
- // continue;
425
- // }
426
-
427
- // Skip this row
428
- if( 'wpstg_existing_clones_beta' === $dataRow ||
429
- 'wpstg_existing_clones' === $dataRow ||
430
- 'wpstg_settings' === $dataRow ||
431
- 'wpstg_license_status' === $dataRow ||
432
- 'siteurl' === $dataRow ||
433
- 'home' === $dataRow
434
- ) {
435
- //$should_skip = true;
436
- continue;
437
- }
438
- }
439
-
440
- // Check the path delimiter for / or \/ and remove one of those which prevents from resulting in wrong syntax like domain.com/staging\/.
441
- // 1. local.wordpress.test -> local.wordpress.test/staging
442
- // 2. local.wordpress.test\/ -> local.wordpress.test\/staging\/
443
- $tmp = $args;
444
- if( false === strpos( $dataRow, $tmp['search_for'][0] ) ) {
445
- array_shift( $tmp['search_for'] ); // rtrim( $this->homeUrl, '/' ),
446
- array_shift( $tmp['replace_with'] ); // rtrim( $this->homeUrl, '/' ) . '/' . $this->options->cloneDirectoryName,
447
- } else {
448
- unset( $tmp['search_for'][1] );
449
- unset( $tmp['replace_with'][1] );
450
- // recount array
451
- $tmp['search_for'] = array_values( $tmp['search_for'] );
452
- $tmp['replace_with'] = array_values( $tmp['replace_with'] );
453
- }
454
-
455
- // Run a search replace on the data row and respect the serialisation.
456
- $i = 0;
457
- foreach ( $tmp['search_for'] as $replace ) {
458
- $dataRow = $this->recursive_unserialize_replace( $tmp['search_for'][$i], $tmp['replace_with'][$i], $dataRow, false, $args['case_insensitive'] );
459
- $i++;
460
- }
461
- unset( $replace );
462
- unset( $i );
463
- unset( $tmp );
464
-
465
- // Something was changed
466
- if( $row[$column] != $dataRow ) {
467
- $update_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
468
- $upd = true;
469
- }
470
- }
471
-
472
- // Determine what to do with updates.
473
- if( $args['dry_run'] === 'on' ) {
474
- // Don't do anything if a dry run
475
- } elseif( $upd && !empty( $where_sql ) ) {
476
- // If there are changes to make, run the query.
477
- $sql = 'UPDATE ' . $table . ' SET ' . implode( ', ', $update_sql ) . ' WHERE ' . implode( ' AND ', array_filter( $where_sql ) );
478
- $result = $this->stagingDb->query( $sql );
479
-
480
- if( !$result ) {
481
- $this->log( "Error updating row {$current_row} SQL: {$sql}", \WPStaging\Utils\Logger::TYPE_ERROR );
482
- }
483
- }
484
- } // end row loop
485
- unset( $row );
486
- unset( $update_sql );
487
- unset( $where_sql );
488
- unset( $sql );
489
-
490
-
491
- // DB Flush
492
- $this->stagingDb->flush();
493
- return true;
494
- }
495
-
496
- /**
497
- * Get path to multisite image folder e.g. wp-content/blogs.dir/ID/files or wp-content/uploads/sites/ID
498
- * @return string
499
- */
500
- private function getImagePathLive() {
501
- // Check first which structure is used
502
- $uploads = wp_upload_dir();
503
- $basedir = $uploads['basedir'];
504
- $blogId = get_current_blog_id();
505
-
506
- if( false === strpos( $basedir, 'blogs.dir' ) ) {
507
- // Since WP 3.5
508
- $path = $blogId > 1 ?
509
- 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR :
510
- 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
511
- } else {
512
- // old blog structure
513
- $path = $blogId > 1 ?
514
- 'wp-content' . DIRECTORY_SEPARATOR . 'blogs.dir' . DIRECTORY_SEPARATOR . get_current_blog_id() . DIRECTORY_SEPARATOR . 'files' . DIRECTORY_SEPARATOR :
515
- 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
516
- }
517
- return $path;
518
- }
519
-
520
- /**
521
- * Get path to staging site image path wp-content/uploads
522
- * @return string
523
- */
524
- private function getImagePathStaging() {
525
- return 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR;
526
- }
527
-
528
- /**
529
- * Adapted from interconnect/it's search/replace script.
530
- *
531
- * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
532
- *
533
- * Take a serialised array and unserialise it replacing elements as needed and
534
- * unserialising any subordinate arrays and performing the replace on those too.
535
- *
536
- * @access private
537
- * @param string $from String we're looking to replace.
538
- * @param string $to What we want it to be replaced with
539
- * @param array $data Used to pass any subordinate arrays back to in.
540
- * @param boolean $serialized Does the array passed via $data need serialising.
541
- * @param sting|boolean $case_insensitive Set to 'on' if we should ignore case, false otherwise.
542
- *
543
- * @return string|array The original array with all elements replaced as needed.
544
- */
545
- private function recursive_unserialize_replace( $from = '', $to = '', $data = '', $serialized = false, $case_insensitive = false ) {
546
- try {
547
- // Some unserialized data cannot be re-serialized eg. SimpleXMLElements
548
- if( is_serialized( $data ) && ( $unserialized = @unserialize( $data ) ) !== false ) {
549
- $data = $this->recursive_unserialize_replace( $from, $to, $unserialized, true, $case_insensitive );
550
- } elseif( is_array( $data ) ) {
551
- $tmp = array();
552
- foreach ( $data as $key => $value ) {
553
- $tmp[$key] = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
554
- }
555
-
556
- $data = $tmp;
557
- unset( $tmp );
558
- } elseif( is_object( $data ) ) {
559
- $tmp = $data;
560
- $props = get_object_vars( $data );
561
- foreach ( $props as $key => $value ) {
562
- if( $key === '' || ord( $key[0] ) === 0 ) {
563
- continue;
564
- }
565
- $tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
566
- }
567
-
568
- $data = $tmp;
569
- unset( $tmp );
570
- } else {
571
- if( is_string( $data ) ) {
572
- if( !empty( $from ) && !empty( $to ) ) {
573
- $data = $this->str_replace( $from, $to, $data, $case_insensitive );
574
- }
575
- }
576
- }
577
-
578
- if( $serialized ) {
579
- return serialize( $data );
580
- }
581
- } catch ( Exception $error ) {
582
-
583
- }
584
-
585
- return $data;
586
- }
587
-
588
- /**
589
- * Check if the object is a valid one and not __PHP_Incomplete_Class_Name
590
- * Can not use is_object alone because in php 7.2 it's returning true even though object is __PHP_Incomplete_Class_Name
591
- * @return boolean
592
- */
593
- // private function isValidObject( $data ) {
594
- // if( !is_object( $data ) || gettype( $data ) != 'object' ) {
595
- // return false;
596
- // }
597
- //
598
- // $invalid_class_props = get_object_vars( $data );
599
- //
600
- // if (!isset($invalid_class_props['__PHP_Incomplete_Class_Name'])){
601
- // // Assume it must be an valid object
602
- // return true;
603
- // }
604
- //
605
- // $invalid_object_class = $invalid_class_props['__PHP_Incomplete_Class_Name'];
606
- //
607
- // if( !empty( $invalid_object_class ) ) {
608
- // return false;
609
- // }
610
- //
611
- // // Assume it must be an valid object
612
- // return true;
613
- // }
614
-
615
- /**
616
- * Mimics the mysql_real_escape_string function. Adapted from a post by 'feedr' on php.net.
617
- * @link http://php.net/manual/en/function.mysql-real-escape-string.php#101248
618
- * @access public
619
- * @param string $input The string to escape.
620
- * @return string
621
- */
622
- private function mysql_escape_mimic( $input ) {
623
- if( is_array( $input ) ) {
624
- return array_map( __METHOD__, $input );
625
- }
626
- if( !empty( $input ) && is_string( $input ) ) {
627
- return str_replace( array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $input );
628
- }
629
-
630
- return $input;
631
- }
632
-
633
- /**
634
- * Return unserialized object or array
635
- *
636
- * @param string $serialized_string Serialized string.
637
- * @param string $method The name of the caller method.
638
- *
639
- * @return mixed, false on failure
640
- */
641
- private static function unserialize( $serialized_string ) {
642
- if( !is_serialized( $serialized_string ) ) {
643
- return false;
644
- }
645
-
646
- $serialized_string = trim( $serialized_string );
647
- $unserialized_string = @unserialize( $serialized_string );
648
-
649
- return $unserialized_string;
650
- }
651
-
652
- /**
653
- * Wrapper for str_replace
654
- *
655
- * @param string $from
656
- * @param string $to
657
- * @param string $data
658
- * @param string|bool $case_insensitive
659
- *
660
- * @return string
661
- */
662
- private function str_replace( $from, $to, $data, $case_insensitive = false ) {
663
-
664
- // Add filter
665
- $excludes = apply_filters( 'wpstg_clone_searchreplace_excl', array() );
666
-
667
- // Build pattern
668
- $regexExclude = '';
669
- foreach ( $excludes as $exclude ) {
670
- $regexExclude .= $exclude . '(*SKIP)(FAIL)|';
671
- }
672
-
673
- if( 'on' === $case_insensitive ) {
674
- //$data = str_ireplace( $from, $to, $data );
675
- $data = preg_replace( '#' . $regexExclude . preg_quote( $from ) . '#i', $to, $data );
676
- } else {
677
- //$data = str_replace( $from, $to, $data );
678
- $data = preg_replace( '#' . $regexExclude . preg_quote( $from ) . '#', $to, $data );
679
- }
680
-
681
- return $data;
682
- }
683
-
684
- /**
685
- * Set the job
686
- * @param string $table
687
- */
688
- private function setJob( $table ) {
689
- if( !empty( $this->options->job->current ) ) {
690
- return;
691
- }
692
-
693
- $this->options->job->current = $table;
694
- $this->options->job->start = 0;
695
- }
696
-
697
- /**
698
- * Start Job
699
- * @param string $new
700
- * @param string $old
701
- * @return bool
702
- */
703
- private function startJob( $new, $old ) {
704
-
705
- if( $this->isExcludedTable( $new ) ) {
706
- return false;
707
- }
708
-
709
- // Table does not exist
710
- $result = $this->productionDb->query( "SHOW TABLES LIKE '{$old}'" );
711
- if( !$result || 0 === $result ) {
712
- return false;
713
- }
714
-
715
- if( 0 != $this->options->job->start ) {
716
- return true;
717
- }
718
-
719
- $this->options->job->total = ( int ) $this->productionDb->get_var( "SELECT COUNT(1) FROM {$old}" );
720
-
721
- if( 0 == $this->options->job->total ) {
722
- $this->finishStep();
723
- return false;
724
- }
725
-
726
- return true;
727
- }
728
-
729
- /**
730
- * Is table excluded from search replace processing?
731
- * @param string $table
732
- * @return boolean
733
- */
734
- private function isExcludedTable( $table ) {
735
-
736
- $customTables = apply_filters( 'wpstg_clone_searchreplace_tables_exclude', array() );
737
- $defaultTables = array('blogs');
738
-
739
- $tables = array_merge( $customTables, $defaultTables );
740
-
741
- $excludedTables = array();
742
- foreach ( $tables as $key => $value ) {
743
- $excludedTables[] = $this->options->prefix . $value;
744
- }
745
-
746
- if( in_array( $table, $excludedTables ) ) {
747
- return true;
748
- }
749
- return false;
750
- }
751
-
752
- /**
753
- * Finish the step
754
- */
755
- private function finishStep() {
756
- // This job is not finished yet
757
- if( $this->options->job->total > $this->options->job->start ) {
758
- return false;
759
- }
760
-
761
- // Add it to cloned tables listing
762
- $this->options->clonedTables[] = $this->options->tables[$this->options->currentStep];
763
-
764
- // Reset job
765
- $this->options->job = new \stdClass();
766
-
767
- return true;
768
- }
769
-
770
- /**
771
- * Drop table if necessary
772
- * @param string $new
773
- */
774
- private function dropTable( $new ) {
775
- $old = $this->stagingDb->get_var( $this->stagingDb->prepare( "SHOW TABLES LIKE %s", $new ) );
776
-
777
- if( !$this->shouldDropTable( $new, $old ) ) {
778
- return;
779
- }
780
-
781
- $this->log( "DB Processing: {$new} already exists, dropping it first" );
782
- $this->stagingDb->query( "DROP TABLE {$new}" );
783
- }
784
-
785
- /**
786
- * Check if table needs to be dropped
787
- * @param string $new
788
- * @param string $old
789
- * @return bool
790
- */
791
- private function shouldDropTable( $new, $old ) {
792
- return (
793
- $old == $new &&
794
- (
795
- !isset( $this->options->job->current ) ||
796
- !isset( $this->options->job->start ) ||
797
- 0 == $this->options->job->start
798
- )
799
- );
800
- }
801
-
802
- /**
803
- * Check if WP is installed in subdir
804
- * @return boolean
805
- */
806
- private function isSubDir() {
807
- // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
808
- // This is happening much more often than you would expect
809
- $siteurl = preg_replace( '#^https?://#', '', rtrim( get_option( 'siteurl' ), '/' ) );
810
- $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
811
-
812
- if( $home !== $siteurl ) {
813
- return true;
814
- }
815
- return false;
816
- }
817
-
818
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ }
apps/Backend/Modules/Jobs/Scan.php CHANGED
@@ -213,20 +213,20 @@ class Scan extends Job {
213
  return null;
214
  }
215
 
216
- $freeSpace = @disk_free_space( \WPStaging\WPStaging::getWPpath() );
217
-
218
- if( false === $freeSpace ) {
219
- $data = array(
220
- 'freespace' => false,
221
- 'usedspace' => $this->formatSize( $this->getDirectorySizeInclSubdirs( \WPStaging\WPStaging::getWPpath() ) )
222
- );
223
- echo json_encode( $data );
224
- die();
225
- }
226
 
227
 
228
  $data = array(
229
- 'freespace' => $this->formatSize( $freeSpace ),
230
  'usedspace' => $this->formatSize( $this->getDirectorySizeInclSubdirs( \WPStaging\WPStaging::getWPpath() ) )
231
  );
232
 
213
  return null;
214
  }
215
 
216
+ // $freeSpace = @disk_free_space( \WPStaging\WPStaging::getWPpath() );
217
+ //
218
+ // if( false === $freeSpace ) {
219
+ // $data = array(
220
+ // 'freespace' => false,
221
+ // 'usedspace' => $this->formatSize( $this->getDirectorySizeInclSubdirs( \WPStaging\WPStaging::getWPpath() ) )
222
+ // );
223
+ // echo json_encode( $data );
224
+ // die();
225
+ // }
226
 
227
 
228
  $data = array(
229
+ //'freespace' => $this->formatSize( $freeSpace ),
230
  'usedspace' => $this->formatSize( $this->getDirectorySizeInclSubdirs( \WPStaging\WPStaging::getWPpath() ) )
231
  );
232
 
apps/Backend/Modules/Jobs/SearchReplace.php CHANGED
@@ -235,20 +235,20 @@ class SearchReplace extends JobExecutable {
235
  * @access public
236
  * @return int
237
  */
238
- private function get_pages_in_table( $table ) {
239
-
240
- // Table does not exist
241
- $table = str_replace( $this->options->prefix . '.', null, $table );
242
- $result = $this->db->query( "SHOW TABLES LIKE '{$table}'" );
243
- if( !$result || 0 === $result ) {
244
- return 0;
245
- }
246
-
247
- $table = esc_sql( $table );
248
- $rows = $this->db->get_var( "SELECT COUNT(*) FROM $table" );
249
- $pages = ceil( $rows / $this->settings->querySRLimit );
250
- return absint( $pages );
251
- }
252
 
253
  /**
254
  * Gets the columns in a table.
@@ -293,9 +293,9 @@ class SearchReplace extends JobExecutable {
293
  }
294
 
295
  // Load up the default settings for this chunk.
296
- $table = esc_sql( $table );
297
- $current_page = $this->options->job->start + $this->settings->querySRLimit;
298
- $pages = $this->get_pages_in_table( $table );
299
 
300
 
301
  $args['search_for'] = array(
@@ -316,7 +316,7 @@ class SearchReplace extends JobExecutable {
316
  $args['replace_guids'] = 'off';
317
  $args['dry_run'] = 'off';
318
  $args['case_insensitive'] = false;
319
- $args['replace_mails'] = 'off';
320
  $args['skip_transients'] = 'on';
321
 
322
 
@@ -336,11 +336,39 @@ class SearchReplace extends JobExecutable {
336
 
337
  $current_row = 0;
338
  $start = $this->options->job->start;
 
 
 
339
  $end = $this->settings->querySRLimit;
340
 
341
- // Grab the content of the table.
342
  $data = $this->db->get_results( "SELECT * FROM $table LIMIT $start, $end", ARRAY_A );
343
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
  // Filter certain rows (of other plugins)
345
  $filter = array(
346
  'Admin_custome_login_Slidshow',
@@ -373,7 +401,7 @@ class SearchReplace extends JobExecutable {
373
  continue;
374
  }
375
  // Skip rows with more than 5MB to save memory
376
- if( isset( $row['option_value'] ) && strlen($row['option_value']) >= 5000000 ) {
377
  continue;
378
  }
379
 
@@ -382,10 +410,10 @@ class SearchReplace extends JobExecutable {
382
 
383
  $dataRow = $row[$column];
384
 
385
-
386
  // Skip rows larger than 5MB
387
- $size = strlen($dataRow);
388
- if ($size >= 5000000){
389
  continue;
390
  }
391
 
@@ -401,20 +429,17 @@ class SearchReplace extends JobExecutable {
401
  }
402
 
403
  // Skip mail addresses
404
- if( 'off' === $args['replace_mails'] && false !== strpos( $dataRow, '@' . $this->options->homeHostname ) ) {
405
- continue;
406
- }
407
-
408
-
409
  // Check options table
410
  if( $this->options->prefix . 'options' === $table ) {
411
 
412
  // Skip certain options
413
- if( isset( $should_skip ) && true === $should_skip ) {
414
- $should_skip = false;
415
- continue;
416
- }
417
-
418
  // Skip this row
419
  if( 'wpstg_existing_clones_beta' === $dataRow ||
420
  'wpstg_existing_clones' === $dataRow ||
@@ -423,7 +448,8 @@ class SearchReplace extends JobExecutable {
423
  'siteurl' === $dataRow ||
424
  'home' === $dataRow
425
  ) {
426
- $should_skip = true;
 
427
  }
428
  }
429
 
@@ -477,13 +503,15 @@ class SearchReplace extends JobExecutable {
477
  unset( $where_sql );
478
  unset( $sql );
479
  unset( $current_row );
480
-
481
 
482
  // DB Flush
483
  $this->db->flush();
484
  return true;
485
  }
486
 
 
 
487
  /**
488
  * Adapted from interconnect/it's search/replace script.
489
  *
@@ -515,24 +543,23 @@ class SearchReplace extends JobExecutable {
515
  $data = $tmp;
516
  unset( $tmp );
517
  } elseif( is_object( $data ) ) {
518
-
519
- // Is no valid or is incomplete object
520
- // Do not use it any longer because it increases the memory consumption and can lead to memory exhausted errors
521
- // if (!$this->isValidObject($data)){
522
- // return $data;
523
- // }
524
-
525
- $tmp = $data;
526
  $props = get_object_vars( $data );
527
- foreach ( $props as $key => $value ) {
528
- if( $key === '' || ord( $key[0] ) === 0 ) {
529
- continue;
 
 
 
 
 
 
530
  }
531
- $tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
 
 
 
 
532
  }
533
-
534
- $data = $tmp;
535
- unset( $tmp );
536
  } else {
537
  if( is_string( $data ) ) {
538
  if( !empty( $from ) && !empty( $to ) ) {
@@ -556,27 +583,27 @@ class SearchReplace extends JobExecutable {
556
  * Can not use is_object alone because in php 7.2 it's returning true even though object is __PHP_Incomplete_Class_Name
557
  * @return boolean
558
  */
559
- private function isValidObject($data){
560
- if( !is_object( $data ) || gettype( $data ) != 'object' ) {
561
- return false;
562
- }
563
-
564
- $invalid_class_props = get_object_vars( $data );
565
-
566
- if (!isset($invalid_class_props['__PHP_Incomplete_Class_Name'])){
567
- // Assume it must be an valid object
568
- return true;
569
- }
570
-
571
- $invalid_object_class = $invalid_class_props['__PHP_Incomplete_Class_Name'];
572
-
573
- if( !empty( $invalid_object_class ) ) {
574
- return false;
575
- }
576
-
577
- // Assume it must be an valid object
578
- return true;
579
- }
580
 
581
  /**
582
  * Mimics the mysql_real_escape_string function. Adapted from a post by 'feedr' on php.net.
235
  * @access public
236
  * @return int
237
  */
238
+ // private function get_pages_in_table( $table ) {
239
+ //
240
+ // // Table does not exist
241
+ // $table = str_replace( $this->options->prefix . '.', null, $table );
242
+ // $result = $this->db->query( "SHOW TABLES LIKE '{$table}'" );
243
+ // if( !$result || 0 === $result ) {
244
+ // return 0;
245
+ // }
246
+ //
247
+ // $table = esc_sql( $table );
248
+ // $rows = $this->db->get_var( "SELECT COUNT(*) FROM $table" );
249
+ // $pages = ceil( $rows / $this->settings->querySRLimit );
250
+ // return absint( $pages );
251
+ // }
252
 
253
  /**
254
  * Gets the columns in a table.
293
  }
294
 
295
  // Load up the default settings for this chunk.
296
+ $table = esc_sql( $table );
297
+ //$current_page = $this->options->job->start + $this->settings->querySRLimit;
298
+ //$pages = $this->get_pages_in_table( $table );
299
 
300
 
301
  $args['search_for'] = array(
316
  $args['replace_guids'] = 'off';
317
  $args['dry_run'] = 'off';
318
  $args['case_insensitive'] = false;
319
+ //$args['replace_mails'] = 'off';
320
  $args['skip_transients'] = 'on';
321
 
322
 
336
 
337
  $current_row = 0;
338
  $start = $this->options->job->start;
339
+
340
+ //Make sure value is never smaller than 1 or greater than 20000
341
+ //$end = $this->settings->querySRLimit == '0' || empty( $this->settings->querySRLimit ) ? 1 : $this->settings->querySRLimit > 20000 ? 20000 : $this->settings->querySRLimit;
342
  $end = $this->settings->querySRLimit;
343
 
344
+ // Grab the content of the current table.
345
  $data = $this->db->get_results( "SELECT * FROM $table LIMIT $start, $end", ARRAY_A );
346
 
347
+ // // Current memory usage
348
+ // $memUsage = memory_get_usage();
349
+ //
350
+ // // 80% Memory Limit
351
+ // $memLimit = wpstg_get_memory_in_bytes( ini_get( 'memory_limit' ) ) * 0.8;
352
+ // //$memLimit = 10;
353
+ //
354
+ // // Memory limit higher than 80% - reduce the numbers and try again
355
+ // if( $memUsage >= $memLimit ) {
356
+ // unset( $data );
357
+ // $end = ceil( $end - 100 );
358
+ // $this->settings->querySRLimit = $end;
359
+ // update_option( 'wpstg_settings', $this->settings );
360
+ // // Grab again the content of the current table.
361
+ // $data = $this->db->get_results( "SELECT * FROM $table LIMIT $start, $end", ARRAY_A );
362
+ // }
363
+ //
364
+ // // If memory limit lower than 50% - increase the numbers for the next batch to get more speed
365
+ // $memLimit = wpstg_get_memory_in_bytes( ini_get( 'memory_limit' ) ) * 0.5;
366
+ // if( $memUsage < $memLimit ) {
367
+ // $end = ceil( $end + 100 );
368
+ // $this->settings->querySRLimit = $end;
369
+ // update_option( 'wpstg_settings', $this->settings );
370
+ // }
371
+
372
  // Filter certain rows (of other plugins)
373
  $filter = array(
374
  'Admin_custome_login_Slidshow',
401
  continue;
402
  }
403
  // Skip rows with more than 5MB to save memory
404
+ if( isset( $row['option_value'] ) && strlen( $row['option_value'] ) >= 5000000 ) {
405
  continue;
406
  }
407
 
410
 
411
  $dataRow = $row[$column];
412
 
413
+
414
  // Skip rows larger than 5MB
415
+ $size = strlen( $dataRow );
416
+ if( $size >= 5000000 ) {
417
  continue;
418
  }
419
 
429
  }
430
 
431
  // Skip mail addresses
432
+ // if( 'off' === $args['replace_mails'] && false !== strpos( $dataRow, '@' . $this->options->homeHostname ) ) {
433
+ // continue;
434
+ // }
 
 
435
  // Check options table
436
  if( $this->options->prefix . 'options' === $table ) {
437
 
438
  // Skip certain options
439
+ // if( isset( $should_skip ) && true === $should_skip ) {
440
+ // $should_skip = false;
441
+ // continue;
442
+ // }
 
443
  // Skip this row
444
  if( 'wpstg_existing_clones_beta' === $dataRow ||
445
  'wpstg_existing_clones' === $dataRow ||
448
  'siteurl' === $dataRow ||
449
  'home' === $dataRow
450
  ) {
451
+ //$should_skip = true;
452
+ continue;
453
  }
454
  }
455
 
503
  unset( $where_sql );
504
  unset( $sql );
505
  unset( $current_row );
506
+
507
 
508
  // DB Flush
509
  $this->db->flush();
510
  return true;
511
  }
512
 
513
+
514
+
515
  /**
516
  * Adapted from interconnect/it's search/replace script.
517
  *
543
  $data = $tmp;
544
  unset( $tmp );
545
  } elseif( is_object( $data ) ) {
 
 
 
 
 
 
 
 
546
  $props = get_object_vars( $data );
547
+
548
+ // Do a search & replace
549
+ if( empty( $props['__PHP_Incomplete_Class_Name'] ) ) {
550
+ $tmp = $data;
551
+ foreach ( $props as $key => $value ) {
552
+ if( $key === '' || ord( $key[0] ) === 0 ) {
553
+ continue;
554
+ }
555
+ $tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
556
  }
557
+ $data = $tmp;
558
+ $tmp = '';
559
+ $props = '';
560
+ unset( $tmp );
561
+ unset( $props );
562
  }
 
 
 
563
  } else {
564
  if( is_string( $data ) ) {
565
  if( !empty( $from ) && !empty( $to ) ) {
583
  * Can not use is_object alone because in php 7.2 it's returning true even though object is __PHP_Incomplete_Class_Name
584
  * @return boolean
585
  */
586
+ // private function isValidObject($data){
587
+ // if( !is_object( $data ) || gettype( $data ) != 'object' ) {
588
+ // return false;
589
+ // }
590
+ //
591
+ // $invalid_class_props = get_object_vars( $data );
592
+ //
593
+ // if (!isset($invalid_class_props['__PHP_Incomplete_Class_Name'])){
594
+ // // Assume it must be an valid object
595
+ // return true;
596
+ // }
597
+ //
598
+ // $invalid_object_class = $invalid_class_props['__PHP_Incomplete_Class_Name'];
599
+ //
600
+ // if( !empty( $invalid_object_class ) ) {
601
+ // return false;
602
+ // }
603
+ //
604
+ // // Assume it must be an valid object
605
+ // return true;
606
+ // }
607
 
608
  /**
609
  * Mimics the mysql_real_escape_string function. Adapted from a post by 'feedr' on php.net.
apps/Backend/Modules/Jobs/Updating.php CHANGED
@@ -38,13 +38,14 @@ class Updating extends Job {
38
 
39
  // Generate Options
40
  // Clone
41
- $this->options->clone = $_POST["cloneID"];
42
- $this->options->cloneDirectoryName = preg_replace( "#\W+#", '-', strtolower( $this->options->clone ) );
43
- $this->options->cloneNumber = 1;
44
- $this->options->includedDirectories = array();
45
- $this->options->excludedDirectories = array();
46
- $this->options->extraDirectories = array();
47
- $this->options->excludedFiles = array(
 
48
  '.htaccess',
49
  '.DS_Store',
50
  '.git',
38
 
39
  // Generate Options
40
  // Clone
41
+ //$this->options->clone = $_POST["cloneID"];
42
+ $this->options->clone = preg_replace( "#\W+#", '-', strtolower( $_POST["cloneID"] ) );
43
+ $this->options->cloneDirectoryName = preg_replace( "#\W+#", '-', strtolower( $this->options->clone ) );
44
+ $this->options->cloneNumber = 1;
45
+ $this->options->includedDirectories = array();
46
+ $this->options->excludedDirectories = array();
47
+ $this->options->extraDirectories = array();
48
+ $this->options->excludedFiles = array(
49
  '.htaccess',
50
  '.DS_Store',
51
  '.git',
apps/Backend/Upgrade/Upgrade.php CHANGED
@@ -79,10 +79,32 @@ class Upgrade {
79
  $this->upgrade2_1_2();
80
  $this->upgrade2_2_0();
81
  $this->upgrade2_4_4();
 
82
 
83
  $this->setVersion();
84
  }
85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  private function upgrade2_4_4() {
87
  // Previous version lower than 2.4.4
88
  if( version_compare( $this->previousVersion, '2.4.4', '<' ) ) {
79
  $this->upgrade2_1_2();
80
  $this->upgrade2_2_0();
81
  $this->upgrade2_4_4();
82
+ $this->upgrade2_5_9();
83
 
84
  $this->setVersion();
85
  }
86
 
87
+ /**
88
+ * Fix array keys of staging sites
89
+ */
90
+ private function upgrade2_5_9() {
91
+ // Previous version lower than 2.5.9
92
+ if( version_compare( $this->previousVersion, '2.5.9', '<' ) ) {
93
+
94
+ // Current options
95
+ $sites = get_option( "wpstg_existing_clones_beta", array() );
96
+
97
+ $new = array();
98
+
99
+ // Fix keys. Replace white spaces with dash character
100
+ foreach ( $sites as $oldKey => $site ) {
101
+ $key = preg_replace( "#\W+#", '-', strtolower( $oldKey ) );
102
+ $new[$key] = $sites[$oldKey];
103
+ }
104
+ update_option( "wpstg_existing_clones_beta", $new );
105
+ }
106
+ }
107
+
108
  private function upgrade2_4_4() {
109
  // Previous version lower than 2.4.4
110
  if( version_compare( $this->previousVersion, '2.4.4', '<' ) ) {
apps/Backend/helpers/wp-config.php ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The base configuration for WordPress
4
+ *
5
+ * The wp-config.php creation script uses this file during the
6
+ * installation. You don't have to use the web site, you can
7
+ * copy this file to "wp-config.php" and fill in the values.
8
+ *
9
+ * This file contains the following configurations:
10
+ *
11
+ * * MySQL settings
12
+ * * Secret keys
13
+ * * Database table prefix
14
+ * * ABSPATH
15
+ *
16
+ * @link https://codex.wordpress.org/Editing_wp-config.php
17
+ *
18
+ * @package WordPress
19
+ */
20
+
21
+ // ** MySQL settings ** //
22
+
23
+ /**
24
+ * Authentication Unique Keys and Salts.
25
+ *
26
+ * Change these to different unique phrases!
27
+ * You can generate these using the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}
28
+ * You can change these at any point in time to invalidate all existing cookies. This will force all users to have to log in again.
29
+ *
30
+ * @since 2.6.0
31
+ */
32
+ define( 'AUTH_KEY', 'cwC1]Y4Qz<-jS?V W0U*CbW~$yY9U@=-t5X{][-S0GF~LqY yt[ChoYm@?`}iJzm' );
33
+ define( 'SECURE_AUTH_KEY', 'dmZ5$.d:gCbUxX)FZ+[h-t81O>Yd5W!x<:D[q;6{A?;Q0F>fvKcjaM V0V?-XD(t' );
34
+ define( 'LOGGED_IN_KEY', '{B%(DDM,))zFtDD8gLk;N^EK`iiG<V`XNZ/k~d]ne^t/MLN85o5ILrMWC!.:cq.X' );
35
+ define( 'NONCE_KEY', 'tvt!~WL>)x{ ``SK6ZO^/R1lwZPerR?&(W>]h/da(Z^M$2?)ZDVsICxQV3?/h6)U' );
36
+ define( 'AUTH_SALT', ';%e$?CJm$s-N!a;(B;NM/>_~gDuPa(VM1t:nUvQ+LZw;e]1)_`-qwCZe,-@^{Xd%' );
37
+ define( 'SECURE_AUTH_SALT', '? nh*~x!7Jm^4E4w(y(qmaPK:$l5aB6!n6L}$IN+cXZSsE?<2~FfK|qN=s:=P+(c' );
38
+ define( 'LOGGED_IN_SALT', '=Ty=Q):}E/[pW3y4IDN@Bas/&-MInTxFXziE)9H9^rnl g7TUj-7OP*UX2Oyz=Y$' );
39
+ define( 'NONCE_SALT', 'HATY?A^EQ#F;oN8!W-oe5P%)aFaeU,E;rLFmRm&u-<g6tL9k(pyh77_,Kc _q2BY' );
40
+ define( 'WP_CACHE_KEY_SALT', '5Y@>B@S8O{a w%ASH BX!;wu/RBk.HT[~R{csF.r)5f0q/YTy%$8lwV4o0eygz};' );
41
+
42
+ /**
43
+ * WordPress Database Table prefix.
44
+ *
45
+ * You can have multiple installations in one database if you give each
46
+ * a unique prefix. Only numbers, letters, and underscores please!
47
+ */
48
+ $table_prefix = 'wp_';
49
+
50
+ /* That's all, stop editing! Happy blogging. */
51
+
52
+ /** Absolute path to the WordPress directory. */
53
+ if ( ! defined( 'ABSPATH' ) )
54
+ define( 'ABSPATH', dirname( __FILE__ ) . '/' );
55
+
56
+ /** Sets up WordPress vars and included files. */
57
+ require_once ABSPATH . 'wp-settings.php';
apps/Backend/public/css/wpstg-admin.css CHANGED
@@ -246,16 +246,17 @@
246
  }
247
 
248
  .wpstg-clone {
249
- border: 3px solid #ffffff;
250
  margin-bottom: 5px;
251
  padding: 5px 10px;
252
  width: 400px;
253
  position: relative;
254
  overflow: hidden;
255
  transition: border-color .2s ease-in-out;
256
- background-color: #DDDDDD;
257
  }
258
 
 
259
  .wpstg-clone.active {
260
  border-color: #1d94cf;;
261
  }
@@ -266,9 +267,14 @@
266
  max-width: 130px;
267
  text-decoration: none;
268
  font-weight: bold;
269
- color:#0285AE;
 
 
 
270
  }
271
 
 
 
272
  .wpstg-clone-action {
273
  background: #ffffff;
274
  border-left: 1px solid #ccc;
@@ -486,6 +492,20 @@
486
  box-shadow: 0 0 2px rgba(255, 66, 53, .8);
487
  }
488
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
489
  #wpstg-clone-path {
490
  margin-left: 10px;
491
  width: 350px;
@@ -497,8 +517,9 @@
497
 
498
  #wpstg-clone-id-error {
499
  display: block;
500
- text-align: right;
501
- margin-right: 90px;
 
502
  }
503
 
504
  #wpstg-start-cloning + .wpstg-error-msg {
@@ -537,13 +558,13 @@
537
  }
538
 
539
  .wpstg-tabs-wrapper {
540
- border: 1px solid #ddd;
541
  border-right: none;
542
- border-left: none;
543
  }
544
 
545
  .wpstg-tab-section {
546
- border: 1px solid #ddd;
547
  border-right: none;
548
  border-left: none;
549
  display: none;
@@ -557,7 +578,7 @@
557
  }
558
 
559
  .wpstg-tab-header {
560
- border: 1px solid #ddd;
561
  border-right: none;
562
  border-left: none;
563
  color: #444;
@@ -819,7 +840,7 @@
819
  .wpstg-staging-info {
820
  clear:both;
821
  float: left;
822
- color:grey;
823
  font-size: 12px;
824
  }
825
 
@@ -866,6 +887,32 @@
866
  text-decoration: none;
867
  }
868
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
869
 
870
  .wpstg-report-issue-form {
871
  position: absolute;
246
  }
247
 
248
  .wpstg-clone {
249
+ border: 5px solid #ffffff;
250
  margin-bottom: 5px;
251
  padding: 5px 10px;
252
  width: 400px;
253
  position: relative;
254
  overflow: hidden;
255
  transition: border-color .2s ease-in-out;
256
+ background-color: #25a1f0;
257
  }
258
 
259
+
260
  .wpstg-clone.active {
261
  border-color: #1d94cf;;
262
  }
267
  max-width: 130px;
268
  text-decoration: none;
269
  font-weight: bold;
270
+ color:white;
271
+ }
272
+ .wpstg-clone-title:hover{
273
+ color:#f1f1f1;
274
  }
275
 
276
+
277
+
278
  .wpstg-clone-action {
279
  background: #ffffff;
280
  border-left: 1px solid #ccc;
492
  box-shadow: 0 0 2px rgba(255, 66, 53, .8);
493
  }
494
 
495
+ #wpstg-new-clone {
496
+ background: #25a1f0;
497
+ border-color: #2188c9;
498
+ /*box-shadow: inset 0 2px 0 #006799;*/
499
+ background-color: #25a1f0;
500
+ }
501
+ #wpstg-new-clone:hover {
502
+ background: #259be6;
503
+ border-color: #2188c9;
504
+ /*border-width: 2px;*/
505
+ /*box-shadow: inset 0 2px 0 #006799;*/
506
+ background-color: #259be6;
507
+ }
508
+
509
  #wpstg-clone-path {
510
  margin-left: 10px;
511
  width: 350px;
517
 
518
  #wpstg-clone-id-error {
519
  display: block;
520
+ background-color:#f0f8ff;
521
+ padding:10px;
522
+ margin: 20px;
523
  }
524
 
525
  #wpstg-start-cloning + .wpstg-error-msg {
558
  }
559
 
560
  .wpstg-tabs-wrapper {
561
+ /*border: 1px solid #ddd;
562
  border-right: none;
563
+ border-left: none;*/
564
  }
565
 
566
  .wpstg-tab-section {
567
+ border-bottom: 1px solid #ddd;
568
  border-right: none;
569
  border-left: none;
570
  display: none;
578
  }
579
 
580
  .wpstg-tab-header {
581
+ border-bottom: 1px solid #ddd;
582
  border-right: none;
583
  border-left: none;
584
  color: #444;
840
  .wpstg-staging-info {
841
  clear:both;
842
  float: left;
843
+ color:white;
844
  font-size: 12px;
845
  }
846
 
887
  text-decoration: none;
888
  }
889
 
890
+ .wpstg-blue-primary {
891
+ display: inline-block;
892
+ text-decoration: none;
893
+ font-size: 13px;
894
+ line-height: 26px;
895
+ height: 28px;
896
+ margin: 0;
897
+ padding: 0 10px 1px;
898
+ cursor: pointer;
899
+ border-width: 1px;
900
+ border-style: solid;
901
+ -webkit-appearance: none;
902
+ border-radius: 3px;
903
+ white-space: nowrap;
904
+ box-sizing: border-box;
905
+ background: #25a1f0;
906
+ border-color: #2188c9;
907
+ /*box-shadow: 0 1px 0 #006799;*/
908
+ color: #fff;
909
+ text-decoration: none;
910
+ text-shadow: 0 -1px 1px #006799, 1px 0 1px #006799, 0 1px 1px #006799, -1px 0 1px #006799;
911
+ }
912
+ .wpstg-blue-primary:hover {
913
+ background-color: #259be6;
914
+ }
915
+
916
 
917
  .wpstg-report-issue-form {
918
  position: absolute;
apps/Backend/public/js/wpstg-admin.js CHANGED
@@ -550,7 +550,7 @@ var WPStaging = (function ($)
550
 
551
  if ($this.data("action") === "wpstg_update") {
552
  // Update Clone - confirmed
553
- if (!confirm("ATTENTION! This will overwrite your staging site with all selected data from the live site! This should be used only if you want to clone again your production site. Are you sure you want to do this? \n\nMake sure to exclude all tables and folders which you do not want to overwrite, first! \n\nDo not necessarily cancel the updating process! This can break your staging site."))
554
  {
555
  return false;
556
  }
@@ -589,8 +589,8 @@ var WPStaging = (function ($)
589
  if (false === response)
590
  {
591
  showError(
592
- "Something went wrong!<br/><br/> Go to WP Staging > Settings and lower 'File Copy Limit' and 'DB Query Limit'. Also set 'CPU Load Priority to low.'" +
593
- "Then try again. If that does not help, " +
594
  "<a href='https://wp-staging.com/support/' target='_blank'>open a support ticket</a> "
595
  );
596
  }
@@ -599,7 +599,7 @@ var WPStaging = (function ($)
599
  if (response.length < 1)
600
  {
601
  showError(
602
- "Something went wrong! No response. Go to WP Staging > Settings and lower 'File Copy Limit' and 'DB Query Limit'. Also set 'CPU Load Priority to low.'" +
603
  "and try again. If that does not help, " +
604
  "<a href='https://wp-staging.com/support/' target='_blank'>open a support ticket</a> "
605
  );
@@ -706,7 +706,6 @@ var WPStaging = (function ($)
706
  {
707
  // Add directories from the root level
708
  var extraDirectories = [];
709
-
710
  $(".wpstg-dir input:checked.wpstg-extra").each(function () {
711
  var $this = $(this);
712
  extraDirectories.push(encodeURIComponent($this.val()));
@@ -843,13 +842,15 @@ var WPStaging = (function ($)
843
  var deleteClone = function (clone)
844
  {
845
 
 
 
846
  ajax(
847
  {
848
  action: "wpstg_delete_clone",
849
  clone: clone,
850
  nonce: wpstg.nonce,
851
  excludedTables: getExcludedTables(),
852
- deleteDir: $("#deleteDirectory:checked").val()
853
  },
854
  function (response)
855
  {
550
 
551
  if ($this.data("action") === "wpstg_update") {
552
  // Update Clone - confirmed
553
+ if (!confirm("STOP! This will overwrite your staging site with all selected data from the live site! This should be used only if you want to clone again your production site. Are you sure you want to do this? \n\nMake sure to exclude all tables and folders which you do not want to overwrite, first! \n\nDo not necessarily cancel the updating process! This can break your staging site. \n\n\Make sure you have a backop of your staging website before you proceed."))
554
  {
555
  return false;
556
  }
589
  if (false === response)
590
  {
591
  showError(
592
+ "Something went wrong!<br/><br/> Go to WP Staging > Settings and lower 'File Copy Limit' and 'DB Query Limit'. Also set 'CPU Load Priority to low '" +
593
+ "and try again. If that does not help, " +
594
  "<a href='https://wp-staging.com/support/' target='_blank'>open a support ticket</a> "
595
  );
596
  }
599
  if (response.length < 1)
600
  {
601
  showError(
602
+ "Something went wrong! No response. Go to WP Staging > Settings and lower 'File Copy Limit' and 'DB Query Limit'. Also set 'CPU Load Priority to low '" +
603
  "and try again. If that does not help, " +
604
  "<a href='https://wp-staging.com/support/' target='_blank'>open a support ticket</a> "
605
  );
706
  {
707
  // Add directories from the root level
708
  var extraDirectories = [];
 
709
  $(".wpstg-dir input:checked.wpstg-extra").each(function () {
710
  var $this = $(this);
711
  extraDirectories.push(encodeURIComponent($this.val()));
842
  var deleteClone = function (clone)
843
  {
844
 
845
+ var deleteDir = $("#deleteDirectory:checked").data("deletepath");
846
+
847
  ajax(
848
  {
849
  action: "wpstg_delete_clone",
850
  clone: clone,
851
  nonce: wpstg.nonce,
852
  excludedTables: getExcludedTables(),
853
+ deleteDir: deleteDir
854
  },
855
  function (response)
856
  {
apps/Backend/views/clone/ajax/delete-confirmation.php CHANGED
@@ -1,7 +1,7 @@
1
  <div class="wpstg-notice-alert wpstg-failed">
2
  <h4 style="margin:0">
3
  <?php
4
- _e("Attention: Check carefully if the database tables and files from the selection below are safe to delete and do not belong to your live site!", "wp-staging")
5
  ?>
6
  </h4>
7
 
@@ -27,7 +27,7 @@
27
  <?php
28
  _e(
29
  'Usually the preselected data can be deleted without any risk '.
30
- 'but in case something goes wrong you better check it first.',
31
  'wp-staging'
32
  );
33
  ?>
@@ -38,14 +38,19 @@
38
 
39
  <a href="#" class="wpstg-tab-header active" data-id="#wpstg-scanning-db">
40
  <span class="wpstg-tab-triangle">&#9658;</span>
41
- <?php echo __("DB tables to remove", "wp-staging")?>
42
  </a>
43
 
44
  <!-- Database -->
45
  <div class="wpstg-tab-section" id="wpstg-scanning-db">
46
  <h4 style="margin:0;">
47
- <?php _e("Unselect database tables you do not want to delete:", "wp-staging")?>
48
  </h4>
 
 
 
 
 
49
 
50
  <?php foreach ($delete->getTables() as $table):?>
51
  <div class="wpstg-db-table">
@@ -58,9 +63,9 @@
58
  </span>
59
  </div>
60
  <?php endforeach ?>
61
- <div>
62
  <a href="#" class="wpstg-button-unselect">
63
- Un-check All
64
  </a>
65
  </div>
66
  </div>
@@ -73,13 +78,13 @@
73
 
74
  <!-- Files -->
75
  <div class="wpstg-tab-section" id="wpstg-scanning-files">
76
- <h4 style="margin:0;">
77
- <?php _e("The folder below and all of its subfolders will be deleted. Unselect the checkbox for not deleting the files.", "wp-staging") ?>
78
  </h4>
79
 
80
  <div class="wpstg-dir">
81
  <label>
82
- <input id="deleteDirectory" type="checkbox" class="wpstg-check-dir" name="deleteDirectory" value="1" checked>
83
  <?php echo $clone->path;?>
84
  <span class="wpstg-size-info"><?php echo isset($clone->size) ? $clone->size : ''; ?></span>
85
  </label>
1
  <div class="wpstg-notice-alert wpstg-failed">
2
  <h4 style="margin:0">
3
  <?php
4
+ _e("Note: The selected tables below will be deleted. <br/>Verify that before you delete them.", "wp-staging")
5
  ?>
6
  </h4>
7
 
27
  <?php
28
  _e(
29
  'Usually the preselected data can be deleted without any risk '.
30
+ 'but in case something is going wrong you better verify it.',
31
  'wp-staging'
32
  );
33
  ?>
38
 
39
  <a href="#" class="wpstg-tab-header active" data-id="#wpstg-scanning-db">
40
  <span class="wpstg-tab-triangle">&#9658;</span>
41
+ <?php echo __("Database tables to remove", "wp-staging")?>
42
  </a>
43
 
44
  <!-- Database -->
45
  <div class="wpstg-tab-section" id="wpstg-scanning-db">
46
  <h4 style="margin:0;">
47
+ <?php _e("Unselect all database tables you do not want to delete:", "wp-staging")?>
48
  </h4>
49
+ <div style="margin-bottom:6px;margin-top:6px;">
50
+ <a href="#" class="wpstg-button-unselect">
51
+ Unselect All
52
+ </a>
53
+ </div>
54
 
55
  <?php foreach ($delete->getTables() as $table):?>
56
  <div class="wpstg-db-table">
63
  </span>
64
  </div>
65
  <?php endforeach ?>
66
+ <div style="margin-bottom:6px;margin-top:6px;">
67
  <a href="#" class="wpstg-button-unselect">
68
+ Unselect All
69
  </a>
70
  </div>
71
  </div>
78
 
79
  <!-- Files -->
80
  <div class="wpstg-tab-section" id="wpstg-scanning-files">
81
+ <h4 style="margin:0;margin-bottom:10px;">
82
+ <?php _e("Selected folder and all of its subfolders and files will be deleted. <br/>Unselect it if you want to keep the staging site file data.", "wp-staging") ?>
83
  </h4>
84
 
85
  <div class="wpstg-dir">
86
  <label>
87
+ <input id="deleteDirectory" type="checkbox" class="wpstg-check-dir" name="deleteDirectory" value="1" checked data-deletepath="<?php echo urlencode($clone->path);?>">
88
  <?php echo $clone->path;?>
89
  <span class="wpstg-size-info"><?php echo isset($clone->size) ? $clone->size : ''; ?></span>
90
  </label>
apps/Backend/views/clone/ajax/scan.php CHANGED
@@ -23,7 +23,7 @@
23
  <h4 style="margin:0">
24
  <?php
25
  echo __(
26
- "Select the tables you like to clone. All tables beginning with prefix '{$scan->prefix}' have been selected already.", "wp-staging"
27
  );
28
  ?>
29
  <p></p>
@@ -128,7 +128,10 @@
128
 
129
  </div>
130
 
131
- <button type="button" class="wpstg-prev-step-link wpstg-link-btn button-primary wpstg-button">
 
 
 
132
  <?php _e( "Back", "wp-staging" ) ?>
133
  </button>
134
 
@@ -137,13 +140,11 @@ if( null !== $options->current ) {
137
  $label = __( "Update Clone", "wp-staging" );
138
  $action = 'wpstg_update';
139
 
140
- echo '<button type="button" id="wpstg-start-updating" class="wpstg-next-step-link wpstg-link-btn button-primary wpstg-button" data-action="' . $action . '">' . $label . '</button>';
141
  } else {
142
  $label = __( "Start Cloning", "wp-staging" );
143
  $action = 'wpstg_cloning';
144
 
145
- echo '<button type="button" id="wpstg-start-cloning" class="wpstg-next-step-link wpstg-link-btn button-primary wpstg-button" data-action="' . $action . '">' . $label . '</button>';
146
  }
147
  ?>
148
-
149
- <a href="#" id="wpstg-check-space"><?php _e( 'Check Free Disk Space', 'wp-staging' ); ?></a>
23
  <h4 style="margin:0">
24
  <?php
25
  echo __(
26
+ "Select the tables to copy. Tables beginning with the prefix '{$scan->prefix}' have already been selected.", "wp-staging"
27
  );
28
  ?>
29
  <p></p>
128
 
129
  </div>
130
 
131
+ <strong>Important:</strong><a href="#" id="wpstg-check-space"><?php _e( 'Check required disk space', 'wp-staging' ); ?></a>
132
+ <p></p>
133
+
134
+ <button type="button" class="wpstg-prev-step-link wpstg-link-btn wpstg-blue-primary wpstg-button">
135
  <?php _e( "Back", "wp-staging" ) ?>
136
  </button>
137
 
140
  $label = __( "Update Clone", "wp-staging" );
141
  $action = 'wpstg_update';
142
 
143
+ echo '<button type="button" id="wpstg-start-updating" class="wpstg-next-step-link wpstg-link-btn wpstg-blue-primary wpstg-button" data-action="' . $action . '">' . $label . '</button>';
144
  } else {
145
  $label = __( "Start Cloning", "wp-staging" );
146
  $action = 'wpstg_cloning';
147
 
148
+ echo '<button type="button" id="wpstg-start-cloning" class="wpstg-next-step-link wpstg-link-btn wpstg-blue-primary wpstg-button" data-action="' . $action . '">' . $label . '</button>';
149
  }
150
  ?>
 
 
apps/Backend/views/clone/ajax/single-overview.php CHANGED
@@ -1,49 +1,54 @@
1
  <div id="wpstg-step-1">
2
- <button id="wpstg-new-clone" class="wpstg-next-step-link wpstg-link-btn button-primary wpstg-button" data-action="wpstg_scanning">
3
- <?php echo __("Create new staging site", "wp-staging")?>
 
 
 
 
 
 
 
4
  </button>
5
  </div>
6
 
7
- <?php if (isset($availableClones) && !empty($availableClones)):?>
8
  <!-- Existing Clones -->
9
  <div id="wpstg-existing-clones">
10
  <h3>
11
- <?php _e("Your Staging Sites:", "wp-staging")?>
12
  </h3>
13
- <?php //wp_die(var_dump($availableClones)); ?>
14
- <?php foreach ($availableClones as $name => $data):?>
15
  <div id="<?php echo $data["directoryName"]; ?>" class="wpstg-clone">
16
 
17
- <?php $urlLogin = $data["url"];?>
18
 
19
- <a href="<?php echo $urlLogin?>" class="wpstg-clone-title" target="_blank">
20
- <?php //echo $name?>
21
  <?php echo $data["directoryName"]; ?>
22
  </a>
23
 
24
- <?php echo apply_filters("wpstg_before_stage_buttons", $html = '', $name, $data)?>
25
 
26
- <a href="<?php echo $urlLogin?>" class="wpstg-open-clone wpstg-clone-action" target="_blank">
27
- <?php _e("Open", "wp-staging"); ?>
28
  </a>
29
 
30
- <a href="#" class="wpstg-execute-clone wpstg-clone-action" data-clone="<?php echo $name?>">
31
- <?php _e("Update", "wp-staging"); ?>
32
  </a>
33
 
34
- <a href="#" class="wpstg-remove-clone wpstg-clone-action" data-clone="<?php echo $name?>">
35
- <?php _e("Delete", "wp-staging"); ?>
36
  </a>
37
-
38
- <?php echo apply_filters("wpstg_after_stage_buttons", $html = '', $name, $data)?>
39
  <div class="wpstg-staging-info">
40
  <?php
41
- $dbname = !empty( $data['databaseDatabase'] ) ? __( "Database: <span class='wpstg-bold'>" . $data['databaseDatabase'], "wp-staging" ) . '</span>' : 'Database: <span class="wpstg-bold">' . DB_NAME . '</span>';
42
- $prefix = !empty( $data['prefix'] ) ? __( "Database Prefix: <span class='wpstg-bold'>" . $data['prefix'], "wp-staging" ) . '</span> ' : '';
43
- $cloneDir = !empty( $data['path'] ) ? __( "Directory: <span class='wpstg-bold'>" . $data['path'], "wp-staging" ) . '</span> ' : 'Directory: ';
44
- $url = !empty( $data['url'] ) ? __( "URL: <span class='wpstg-bold'>" . $data['url'], "wp-staging" ) . '</span> ' : 'URL: ';
45
- $datetime = !empty( $data['datetime'] ) ? __( "Updated: <span>" . date( "D, d M Y H:i:s T", $data['datetime'] ), "wp-staging" ) . '</span> ' : '&nbsp;&nbsp;&nbsp;';
46
- $status = !empty( $data['status'] ) && $data['status'] !== 'finished' ? "Status: <span class='wpstg-bold'>" . $data['status'] . "</span>" : '&nbsp;&nbsp;&nbsp;';
47
 
48
  echo $dbname;
49
  echo '</br>';
@@ -59,10 +64,10 @@
59
  ?>
60
  </div>
61
  </div>
62
- <?php endforeach?>
63
  </div>
64
  <!-- /Existing Clones -->
65
- <?php endif?>
66
 
67
  <!-- Remove Clone -->
68
  <div id="wpstg-removing-clone">
1
  <div id="wpstg-step-1">
2
+ <!--
3
+ <?php //if ( wpstg_is_stagingsite()) {?>
4
+ <h3 style="color:red;"><?php //echo __("This is your staging website!", "wp-staging") ?></h3>
5
+ <p><?php //echo __("You can still clone it. <br>For example, to push another clone to this site for testing purposes.", "wp-staging"); ?> </p>
6
+ <p><?php //echo __("Go to the Production website > WP Staging > Sites if you want to push this site to live.","wp-staging"); ?></p>
7
+ <?php //} ?>
8
+ //-->
9
+ <button id="wpstg-new-clone" class="wpstg-next-step-link wpstg-link-btn wpstg-blue-primary wpstg-button" data-action="wpstg_scanning">
10
+ <?php echo __( "Create new staging site", "wp-staging" ) ?>
11
  </button>
12
  </div>
13
 
14
+ <?php if( isset( $availableClones ) && !empty( $availableClones ) ): ?>
15
  <!-- Existing Clones -->
16
  <div id="wpstg-existing-clones">
17
  <h3>
18
+ <?php _e( "Your Staging Sites:", "wp-staging" ) ?>
19
  </h3>
20
+ <?php //wp_die(var_dump($availableClones)); ?>
21
+ <?php foreach ( $availableClones as $name => $data ): ?>
22
  <div id="<?php echo $data["directoryName"]; ?>" class="wpstg-clone">
23
 
24
+ <?php $urlLogin = $data["url"]; ?>
25
 
26
+ <a href="<?php echo $urlLogin ?>" class="wpstg-clone-title" target="_blank">
 
27
  <?php echo $data["directoryName"]; ?>
28
  </a>
29
 
30
+ <?php echo apply_filters( "wpstg_before_stage_buttons", $html = '', $name, $data ) ?>
31
 
32
+ <a href="<?php echo $urlLogin ?>" class="wpstg-open-clone wpstg-clone-action" target="_blank" title="<?php echo __( "Open the staging site in a new tab", "wp-staging" ) ?>">
33
+ <?php _e( "Open", "wp-staging" ); ?>
34
  </a>
35
 
36
+ <a href="#" class="wpstg-execute-clone wpstg-clone-action" data-clone="<?php echo $name ?>" title="<?php echo __( "Update and overwrite the staging site. You can select certain folders and database tables in the next step.", "wp-staging" ) ?>">
37
+ <?php _e( "Update", "wp-staging" ); ?>
38
  </a>
39
 
40
+ <a href="#" class="wpstg-remove-clone wpstg-clone-action" data-clone="<?php echo $name ?>" title="<?php echo __( "Delete the staging site. You can select certain folders and database tables in the next step.", "wp-staging" ) ?>">
41
+ <?php _e( "Delete", "wp-staging" ); ?>
42
  </a>
43
+ <?php echo apply_filters( "wpstg_after_stage_buttons", $html = '', $name, $data ) ?>
 
44
  <div class="wpstg-staging-info">
45
  <?php
46
+ $dbname = !empty( $data['databaseDatabase'] ) ? __( "Database: <span class='wpstg-bold'>" . $data['databaseDatabase'], "wp-staging" ) . '</span>' : 'Database: <span class="wpstg-bold">' . DB_NAME . '</span>';
47
+ $prefix = !empty( $data['prefix'] ) ? __( "Database Prefix: <span class='wpstg-bold'>" . $data['prefix'], "wp-staging" ) . '</span> ' : '';
48
+ $cloneDir = !empty( $data['path'] ) ? __( "Directory: <span class='wpstg-bold'>" . $data['path'], "wp-staging" ) . '</span> ' : 'Directory: ';
49
+ $url = !empty( $data['url'] ) ? __( "URL: <span class='wpstg-bold'>" . $data['url'], "wp-staging" ) . '</span> ' : 'URL: ';
50
+ $datetime = !empty( $data['datetime'] ) ? __( "Updated: <span>" . date( "D, d M Y H:i:s T", $data['datetime'] ), "wp-staging" ) . '</span> ' : '&nbsp;&nbsp;&nbsp;';
51
+ $status = !empty( $data['status'] ) && $data['status'] !== 'finished' ? "Status: <span class='wpstg-bold' style='color:#ffc2c2;'>" . $data['status'] . "</span>" : '&nbsp;&nbsp;&nbsp;';
52
 
53
  echo $dbname;
54
  echo '</br>';
64
  ?>
65
  </div>
66
  </div>
67
+ <?php endforeach ?>
68
  </div>
69
  <!-- /Existing Clones -->
70
+ <?php endif ?>
71
 
72
  <!-- Remove Clone -->
73
  <div id="wpstg-removing-clone">
apps/Backend/views/clone/ajax/start.php CHANGED
@@ -13,7 +13,7 @@
13
  <div style="clear: both;"></div>
14
  </div>
15
 
16
- <button type="button" id="wpstg-cancel-cloning" class="wpstg-link-btn button-primary">
17
  <?php echo __("Cancel", "wp-staging")?>
18
  </button>
19
 
13
  <div style="clear: both;"></div>
14
  </div>
15
 
16
+ <button type="button" id="wpstg-cancel-cloning" class="wpstg-button wpstg-link-btn wpstg-blue-primary">
17
  <?php echo __("Cancel", "wp-staging")?>
18
  </button>
19
 
apps/Backend/views/clone/index.php CHANGED
@@ -8,13 +8,15 @@
8
  <?php
9
  do_action( "wpstg_notifications" );
10
 
11
- // Multi site
12
- if( is_multisite() ) {
13
- require_once($this->path . "views/clone/multi-site/index.php");
 
 
14
  }
15
  // Single site
16
  else {
17
- require_once($this->path . "views/clone/single-site/index.php");
18
  }
19
 
20
  // Footer
8
  <?php
9
  do_action( "wpstg_notifications" );
10
 
11
+ if( wpstg_is_stagingsite() ) {
12
+ // Staging site
13
+ require_once($this->path . "views/clone/staging-site/index.php");
14
+ } elseif( is_multisite() ) {
15
+ require_once($this->path . "views/clone/multi-site/index.php");
16
  }
17
  // Single site
18
  else {
19
+ require_once($this->path . "views/clone/single-site/index.php");
20
  }
21
 
22
  // Footer
apps/Backend/views/clone/staging-site/index.php ADDED
@@ -0,0 +1,4 @@
 
 
 
 
1
+ <span class="wpstg-notice-alert" style="margin-top:20px;">
2
+ <?php echo sprintf(__("This is your staging site. Go to your live site to use that function.", "wp-staging"),
3
+ admin_url() . 'admin.php?page=wpstg_clone')?>
4
+ </span>
apps/Core/Iterators/RecursiveFilterExclude.php CHANGED
@@ -15,16 +15,18 @@ class RecursiveFilterExclude extends \RecursiveFilterIterator {
15
 
16
  public function accept() {
17
 
 
 
18
  // Path contains new line character on linux
19
- if(strpos( $this->getInnerIterator()->getSubPathname(), "\n" ) !== false)
20
  return false;
21
 
22
  // Path contains new line character on Windows
23
- if(strpos( $this->getInnerIterator()->getSubPathname(), "\r" ) !== false)
24
  return false;
25
-
26
  // Part of the path is excluded
27
- if (in_array( $this->getInnerIterator()->getSubPathname(), $this->exclude ))
28
  return false;
29
 
30
  return true;
15
 
16
  public function accept() {
17
 
18
+ $subPath = $this->getInnerIterator()->getSubPathname();
19
+
20
  // Path contains new line character on linux
21
+ if(strpos( $subPath, "\n" ) !== false)
22
  return false;
23
 
24
  // Path contains new line character on Windows
25
+ if(strpos( $subPath, "\r" ) !== false)
26
  return false;
27
+
28
  // Part of the path is excluded
29
+ if (in_array( wpstg_replace_windows_directory_separator($subPath), $this->exclude ))
30
  return false;
31
 
32
  return true;
apps/Core/Utils/functions.php CHANGED
@@ -6,11 +6,12 @@
6
  * @return int
7
  */
8
  function wpstg_get_permissions_for_directory() {
9
- if( defined( 'FS_CHMOD_DIR' ) ) {
10
- return FS_CHMOD_DIR;
 
11
  }
12
 
13
- return 0755;
14
  }
15
 
16
  /**
@@ -137,7 +138,7 @@ function wpstg_is_valid_date( $date, $format = 'Y-m-d' ) {
137
  /**
138
  * Convert all values of a string or an array into url decoded values
139
  * Main use for preventing Wordfence firewall rule 'local file inclusion'
140
- * @param miced string | array $data
141
  * @return mixed string | array
142
  */
143
  function wpstg_urldecode( $data ) {
@@ -169,9 +170,39 @@ function wpstg_is_stagingsite() {
169
  return true;
170
  }
171
 
172
- if( file_exists( ABSPATH . '.wp-staging' ) ) {
173
  return true;
174
  }
175
 
176
  return false;
177
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  * @return int
7
  */
8
  function wpstg_get_permissions_for_directory() {
9
+ $octal = 0755;
10
+ if (defined('FS_CHMOD_DIR')) {
11
+ $octal = FS_CHMOD_DIR;
12
  }
13
 
14
+ return apply_filters('wpstg_folder_permission', $octal);
15
  }
16
 
17
  /**
138
  /**
139
  * Convert all values of a string or an array into url decoded values
140
  * Main use for preventing Wordfence firewall rule 'local file inclusion'
141
+ * @param mixed string | array $data
142
  * @return mixed string | array
143
  */
144
  function wpstg_urldecode( $data ) {
170
  return true;
171
  }
172
 
173
+ if( file_exists( ABSPATH . '.wp-staging')){
174
  return true;
175
  }
176
 
177
  return false;
178
  }
179
+
180
+ /**
181
+ * @param string $memory
182
+ * @return int
183
+ */
184
+ function wpstg_get_memory_in_bytes( $memory ) {
185
+ // Handle unlimited ones
186
+ if( 1 > ( int ) $memory ) {
187
+ //return (int) $memory;
188
+ // 128 MB default value
189
+ return ( int ) 134217728;
190
+ }
191
+
192
+ $bytes = ( int ) $memory; // grab only the number
193
+ $size = trim( str_replace( $bytes, null, strtolower( $memory ) ) ); // strip away number and lower-case it
194
+ // Actual calculation
195
+ switch ( $size ) {
196
+ case 'k':
197
+ $bytes *= 1024;
198
+ break;
199
+ case 'm':
200
+ $bytes *= (1024 * 1024);
201
+ break;
202
+ case 'g':
203
+ $bytes *= (1024 * 1024 * 1024);
204
+ break;
205
+ }
206
+
207
+ return $bytes;
208
+ }
apps/Core/WPStaging.php CHANGED
@@ -29,7 +29,7 @@ final class WPStaging {
29
  /**
30
  * Plugin version
31
  */
32
- const VERSION = "2.5.8";
33
 
34
  /**
35
  * Plugin name
@@ -44,9 +44,9 @@ final class WPStaging {
44
  /**
45
  * Compatible WP Version
46
  */
47
- const WP_COMPATIBLE = "5.2.0";
48
 
49
- public $wpPath;
50
 
51
  /**
52
  * Slug: Either wp-staging or wp-staging-pro
29
  /**
30
  * Plugin version
31
  */
32
+ const VERSION = "2.5.9";
33
 
34
  /**
35
  * Plugin name
44
  /**
45
  * Compatible WP Version
46
  */
47
+ const WP_COMPATIBLE = "5.2.2";
48
 
49
+ //public $wpPath;
50
 
51
  /**
52
  * Slug: Either wp-staging or wp-staging-pro
readme.txt CHANGED
@@ -9,7 +9,7 @@ License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
  Tags: staging, duplication, cloning, clone, migration, sandbox, test site, testing, backup, post, admin, administration, duplicate posts
10
  Requires at least: 3.6+
11
  Tested up to: 5.2
12
- Stable tag: 2.5.8
13
  Requires PHP: 5.3
14
 
15
  A duplicator plugin! Clone, duplicate and migrate live sites to independent staging and development sites that are available only to administrators.
@@ -151,6 +151,28 @@ https://wp-staging.com
151
 
152
  == Changelog ==
153
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  = 2.5.8 =
155
  * Fix: Remove admin notice that wp staging is not compatible with latest wp version
156
 
@@ -222,13 +244,3 @@ https://wp-staging.com
222
 
223
 
224
  Complete changelog: [https://wp-staging.com/wp-staging-changelog](https://wp-staging.com/wp-staging-changelog)
225
-
226
- == Upgrade Notice ==
227
-
228
- = 2.5.6 =
229
- * New: compatible to WordPress 5.2
230
- * New: Allow adding file .wp-staging to root of website to determine if it's a staging or production website
231
- * Tweak: Move admin notices templates to views/notices
232
- * Fix: Rating notice appears again immediately after using review later button
233
- * Fix: Convert staging site table prefix to lowercase
234
- * New: Show unfinished or interrupted clones and allow deletion of them
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.0
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.
151
 
152
  == Changelog ==
153
 
154
+ = 2.6.0 =
155
+ * New: Compatible up to WordPress 5.2.2
156
+ * New: Performance improvement for directory iterator using less server ressources
157
+ * New: Add filter wpstg_folder_permission to set a custom folder permission like 0755, allows to overwrite FS_CHMOD_DIR if it has been defined.
158
+ * Fix: Error conditions in class Data does not compare type strict (== vs. ==) resulting in interruption of clone process
159
+ * Fix: Excluded folders under wp-content level are not take into account on microsoft IIS servers
160
+
161
+
162
+
163
+ = 2.5.9 =
164
+ * New: Update for WP 5.2.1
165
+ * New: Better corporate identity and more friendly UI colors for staging sites listings and button
166
+ * New: Better warning notices before updating process is executed
167
+ * New: Add tooltips for explaining navigation buttons
168
+ * New: Check if UPLOAD constant is defined and use this value for uploads folder destination
169
+ * New: Show notice if user tries to clone a staging website.
170
+ * Fix: Staging sites listing entries appeared on the cloned website.
171
+ * Fix: Do not search & replace through "__PHP_Incomplete_Class_Name" definitions
172
+ * Fix: Prevent wordfence firewall rule interrupting the clone deletion method
173
+ * Fix: Excluded wp staging directory from deleting process is ignored and will be deleted either way
174
+ * Fix: Strip whitespaces in cloning site internal names
175
+
176
  = 2.5.8 =
177
  * Fix: Remove admin notice that wp staging is not compatible with latest wp version
178
 
244
 
245
 
246
  Complete changelog: [https://wp-staging.com/wp-staging-changelog](https://wp-staging.com/wp-staging-changelog)
 
 
 
 
 
 
 
 
 
 
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.5.8
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.5.8' );
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.0
11
  * Text Domain: wp-staging
12
  * Domain Path: /languages/
13
 
51
 
52
  // Version
53
  if( !defined( 'WPSTG_VERSION' ) ) {
54
+ define( 'WPSTG_VERSION', '2.6.0' );
55
  }
56
 
57
  // Must use version of the optimizer