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

Version Description

  • New: Update for WP 5.2.1
  • New: Better corporate identity and more friendly UI colors for staging sites listings and button
  • New: Better warning notices before updating process is executed
  • New: Add tooltips for explaining navigation buttons
  • New: Check if UPLOAD constant is defined and use this value for uploads folder destination
  • New: Show notice if user tries to clone a staging website.
  • Fix: Staging sites listing entries appeared on the cloned website.
  • Fix: Do not search & replace through "_PHP_Incomplete_ClassName" definitions
  • Fix: Prevent wordfence firewall rule interrupting the clone deletion method
  • Fix: Excluded wp staging directory from deleting process is ignored and will be deleted either way
  • Fix: Strip whitespaces in cloning site internal names
Download this release

Release Info

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

Code changes from version 2.5.8 to 2.5.9

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
- }