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

Version Description

  • Security: Do not allow to create a new staging site into a subfolder which already exists
  • New: Option to allow adjustment of the allowed maximum size of files that are going to be copied while cloning.
  • New: Add multisite informations in system info log
  • New: Option to allow adjustment of the allowed maximum size of files that are going to be copied while cloning.
  • New: Use the new progress bar for clone updating process
  • Fix: Progress bar for step 'database' is not filling up to 100%
  • Fix: If cloning update process is interupted it may happen that staging site is not available any longer. (Updating the clone does not copy index.php to staging site again)
  • Fix: Progress bar not shown as intented for clone updating process
  • Fix: Can not open upload folder in file selection menu
  • Fix: Undefined object $this->tables
  • Fix: wp-config.php not copied when previous clone updating process has been failed
  • Fix: Parameter must be an array or an object that implements Callable
  • Fix: Skip search & replace for objects where key is null
  • Fix: Search & Replace not working if serialized object contains _PHP_IncompleteClass_Name
  • Tweaks: remove term "error" from several log entries
  • Tweak: Remove certain debugging notices from the default log window
Download this release

Release Info

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

Code changes from version 2.2.9 to 2.3.0

apps/Backend/Modules/Jobs/Cloning.php CHANGED
@@ -121,7 +121,7 @@ class Cloning extends Job
121
  /**
122
  * Create a new staging prefix which does not already exists in database
123
  */
124
- public function setStagingPrefix(){
125
 
126
  // Get & find a new prefix that does not already exist in database.
127
  // Loop through up to 1000 different possible prefixes should be enough here;)
121
  /**
122
  * Create a new staging prefix which does not already exists in database
123
  */
124
+ private function setStagingPrefix() {
125
 
126
  // Get & find a new prefix that does not already exist in database.
127
  // Loop through up to 1000 different possible prefixes should be enough here;)
apps/Backend/Modules/Jobs/Data.php CHANGED
@@ -10,6 +10,7 @@ if( !defined( "WPINC" ) ) {
10
  use WPStaging\Utils\Logger;
11
  use WPStaging\WPStaging;
12
  use WPStaging\Utils\Helper;
 
13
 
14
  /**
15
  * Class Data
@@ -33,6 +34,12 @@ class Data extends JobExecutable {
33
  */
34
  private $homeUrl;
35
 
 
 
 
 
 
 
36
  /**
37
  * Initialize
38
  */
@@ -41,6 +48,8 @@ class Data extends JobExecutable {
41
 
42
  $this->prefix = $this->options->prefix;
43
 
 
 
44
  $helper = new Helper();
45
 
46
  $this->homeUrl = $helper->get_home_url();
@@ -52,12 +61,23 @@ class Data extends JobExecutable {
52
  }
53
  }
54
 
 
 
 
 
 
 
 
 
 
 
 
55
  /**
56
  * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
57
  * @return void
58
  */
59
  protected function calculateTotalSteps() {
60
- $this->options->totalSteps = 11;
61
  }
62
 
63
  /**
@@ -186,9 +206,15 @@ class Data extends JobExecutable {
186
  protected function step1() {
187
  $this->log( "Preparing Data Step1: Updating siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_INFO );
188
 
 
189
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
190
  return true;
191
  }
 
 
 
 
 
192
 
193
  // Installed in sub-directory
194
  if( $this->isSubDir() ) {
@@ -227,9 +253,15 @@ class Data extends JobExecutable {
227
 
228
  $this->log( "Preparing Data Step2: Updating row wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}" );
229
 
 
230
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
231
  return true;
232
  }
 
 
 
 
 
233
 
234
  $result = $this->db->query(
235
  $this->db->prepare(
@@ -263,10 +295,17 @@ class Data extends JobExecutable {
263
 
264
  $this->log( "Preparing Data Step3: Updating rewrite_rules in {$this->prefix}options {$this->db->last_error}" );
265
 
 
266
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
267
  return true;
268
  }
269
 
 
 
 
 
 
 
270
  $result = $this->db->query(
271
  $this->db->prepare(
272
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'rewrite_rules'", ' '
@@ -287,12 +326,19 @@ class Data extends JobExecutable {
287
  * @return bool
288
  */
289
  protected function step4() {
290
- $this->log( "Preparing Data Step4: Updating db prefix in {$this->prefix}usermeta. Error: {$this->db->last_error}" );
291
 
 
292
  if( false === $this->isTable( $this->prefix . 'usermeta' ) ) {
293
  return true;
294
  }
295
 
 
 
 
 
 
 
296
  $update = $this->db->query(
297
  $this->db->prepare(
298
  "UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, %s, %s) WHERE meta_key LIKE %s", $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
@@ -305,37 +351,36 @@ class Data extends JobExecutable {
305
  return false;
306
  }
307
 
308
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
309
- return true;
310
- }
311
-
312
- $this->log( "Updating db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
313
-
314
- // Filter the rows below. Do not update them!
315
- $filters = array(
316
- 'wp_mail_smtp',
317
- 'wp_mail_smtp_version',
318
- 'wp_mail_smtp_debug',
319
- );
320
-
321
- $filters = apply_filters('wpstg_filter_options_replace', $filters);
322
-
323
- $where = "";
324
- foreach($filters as $filter){
325
- $where .= " AND option_name <> '" . $filter . "'";
326
- }
327
-
328
- $resultUserMeta = $this->db->query(
329
- $this->db->prepare(
330
- "UPDATE IGNORE {$this->prefix}options SET option_name= replace(option_name, %s, %s) WHERE option_name LIKE %s" . $where, $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
331
- )
332
- );
333
-
334
- if( !$resultUserMeta ) {
335
- $this->log( "Preparing Data Step4: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_ERROR );
336
- $this->returnException( "Data Crunching Step 4: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
337
- return false;
338
- }
339
 
340
  return true;
341
  }
@@ -425,10 +470,17 @@ class Data extends JobExecutable {
425
 
426
  $this->log( "Preparing Data Step7: Updating wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}" );
427
 
 
428
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
429
  return true;
430
  }
431
 
 
 
 
 
 
 
432
  $result = $this->db->query(
433
  $this->db->prepare(
434
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_rmpermalinks_executed'", ' '
@@ -453,10 +505,17 @@ class Data extends JobExecutable {
453
 
454
  $this->log( "Preparing Data Step8: Updating permalink_structure in {$this->prefix}options {$this->db->last_error}" );
455
 
 
456
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
457
  return true;
458
  }
459
 
 
 
 
 
 
 
460
  $result = $this->db->query(
461
  $this->db->prepare(
462
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'permalink_structure'", ' '
@@ -485,6 +544,12 @@ class Data extends JobExecutable {
485
  return true;
486
  }
487
 
 
 
 
 
 
 
488
  $result = $this->db->query(
489
  $this->db->prepare(
490
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'blog_public'", '0'
@@ -586,6 +651,56 @@ class Data extends JobExecutable {
586
  return true;
587
  }
588
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
589
  /**
590
  * Return URL to staging site
591
  * @return string
10
  use WPStaging\Utils\Logger;
11
  use WPStaging\WPStaging;
12
  use WPStaging\Utils\Helper;
13
+ use WPStaging\Utils\Strings;
14
 
15
  /**
16
  * Class Data
34
  */
35
  private $homeUrl;
36
 
37
+ /**
38
+ * Tables e.g wpstg3_options
39
+ * @var array
40
+ */
41
+ private $tables;
42
+
43
  /**
44
  * Initialize
45
  */
48
 
49
  $this->prefix = $this->options->prefix;
50
 
51
+ $this->getTables();
52
+
53
  $helper = new Helper();
54
 
55
  $this->homeUrl = $helper->get_home_url();
61
  }
62
  }
63
 
64
+ /**
65
+ * Get a list of tables to copy
66
+ */
67
+ private function getTables() {
68
+ $strings = new Strings();
69
+ $this->tables = array();
70
+ foreach ( $this->options->tables as $table ) {
71
+ $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->db->prefix, null, $table );
72
+ }
73
+ }
74
+
75
  /**
76
  * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
77
  * @return void
78
  */
79
  protected function calculateTotalSteps() {
80
+ $this->options->totalSteps = 12;
81
  }
82
 
83
  /**
206
  protected function step1() {
207
  $this->log( "Preparing Data Step1: Updating siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_INFO );
208
 
209
+ // Skip - Table does not exist
210
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
211
  return true;
212
  }
213
+ // Skip - Table is not selected or updated
214
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
215
+ $this->log( "Preparing Data Step1: Skipping" );
216
+ return true;
217
+ }
218
 
219
  // Installed in sub-directory
220
  if( $this->isSubDir() ) {
253
 
254
  $this->log( "Preparing Data Step2: Updating row wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}" );
255
 
256
+ // Skip - Table does not exist
257
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
258
  return true;
259
  }
260
+ // Skip - Table is not selected or updated
261
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
262
+ $this->log( "Preparing Data Step2: Skipping" );
263
+ return true;
264
+ }
265
 
266
  $result = $this->db->query(
267
  $this->db->prepare(
295
 
296
  $this->log( "Preparing Data Step3: Updating rewrite_rules in {$this->prefix}options {$this->db->last_error}" );
297
 
298
+ // Skip - Table does not exist
299
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
300
  return true;
301
  }
302
 
303
+ // Skip - Table is not selected or updated
304
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
305
+ $this->log( "Preparing Data Step3: Skipping" );
306
+ return true;
307
+ }
308
+
309
  $result = $this->db->query(
310
  $this->db->prepare(
311
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'rewrite_rules'", ' '
326
  * @return bool
327
  */
328
  protected function step4() {
329
+ $this->log( "Preparing Data Step4: Updating db prefix in {$this->prefix}usermeta. " );
330
 
331
+ // Skip - Table does not exist
332
  if( false === $this->isTable( $this->prefix . 'usermeta' ) ) {
333
  return true;
334
  }
335
 
336
+ // Skip - Table is not selected or updated
337
+ if( !in_array( $this->prefix . 'usermeta', $this->tables ) ) {
338
+ $this->log( "Preparing Data Step4: Skipping" );
339
+ return true;
340
+ }
341
+
342
  $update = $this->db->query(
343
  $this->db->prepare(
344
  "UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, %s, %s) WHERE meta_key LIKE %s", $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
351
  return false;
352
  }
353
 
354
+ // if( false === $this->isTable( $this->prefix . 'options' ) ) {
355
+ // return true;
356
+ // }
357
+ // $this->log( "Updating db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
358
+ //
359
+ // // Filter the rows below. Do not update them!
360
+ // $filters = array(
361
+ // 'wp_mail_smtp',
362
+ // 'wp_mail_smtp_version',
363
+ // 'wp_mail_smtp_debug',
364
+ // );
365
+ //
366
+ // $filters = apply_filters('wpstg_filter_options_replace', $filters);
367
+ //
368
+ // $where = "";
369
+ // foreach($filters as $filter){
370
+ // $where .= " AND option_name <> '" . $filter . "'";
371
+ // }
372
+ //
373
+ // $updateOptions = $this->db->query(
374
+ // $this->db->prepare(
375
+ // "UPDATE IGNORE {$this->prefix}options SET option_name= replace(option_name, %s, %s) WHERE option_name LIKE %s" . $where, $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
376
+ // )
377
+ // );
378
+ //
379
+ // if( !$updateOptions ) {
380
+ // $this->log( "Preparing Data Step4: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_ERROR );
381
+ // $this->returnException( "Data Crunching Step 4: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
382
+ // return false;
383
+ // }
 
384
 
385
  return true;
386
  }
470
 
471
  $this->log( "Preparing Data Step7: Updating wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}" );
472
 
473
+ // Skip - Table does not exist
474
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
475
  return true;
476
  }
477
 
478
+ // Skip - Table is not selected or updated
479
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
480
+ $this->log( "Preparing Data Step7: Skipping" );
481
+ return true;
482
+ }
483
+
484
  $result = $this->db->query(
485
  $this->db->prepare(
486
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_rmpermalinks_executed'", ' '
505
 
506
  $this->log( "Preparing Data Step8: Updating permalink_structure in {$this->prefix}options {$this->db->last_error}" );
507
 
508
+ // Skip - Table does not exist
509
  if( false === $this->isTable( $this->prefix . 'options' ) ) {
510
  return true;
511
  }
512
 
513
+ // Skip - Table is not selected or updated
514
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
515
+ $this->log( "Preparing Data Step8: Skipping" );
516
+ return true;
517
+ }
518
+
519
  $result = $this->db->query(
520
  $this->db->prepare(
521
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'permalink_structure'", ' '
544
  return true;
545
  }
546
 
547
+ // Skip - Table is not selected or updated
548
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
549
+ $this->log( "Preparing Data Step9: Skipping" );
550
+ return true;
551
+ }
552
+
553
  $result = $this->db->query(
554
  $this->db->prepare(
555
  "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'blog_public'", '0'
651
  return true;
652
  }
653
 
654
+ /**
655
+ * Update Table Prefix in wp_options
656
+ * @return bool
657
+ */
658
+ protected function step12() {
659
+ $this->log( "Preparing Data Step12: Updating db prefix in {$this->prefix}options. Error: {$this->db->last_error}" );
660
+
661
+ // Skip - Table does not exist
662
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
663
+ return true;
664
+ }
665
+
666
+ // Skip - Table is not selected or updated
667
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
668
+ $this->log( "Preparing Data Step12: Skipping" );
669
+ return true;
670
+ }
671
+
672
+
673
+ $this->log( "Updating db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
674
+
675
+ // Filter the rows below. Do not update them!
676
+ $filters = array(
677
+ 'wp_mail_smtp',
678
+ 'wp_mail_smtp_version',
679
+ 'wp_mail_smtp_debug',
680
+ );
681
+
682
+ $filters = apply_filters( 'wpstg_filter_options_replace', $filters );
683
+
684
+ $where = "";
685
+ foreach ( $filters as $filter ) {
686
+ $where .= " AND option_name <> '" . $filter . "'";
687
+ }
688
+
689
+ $updateOptions = $this->db->query(
690
+ $this->db->prepare(
691
+ "UPDATE IGNORE {$this->prefix}options SET option_name= replace(option_name, %s, %s) WHERE option_name LIKE %s" . $where, $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
692
+ )
693
+ );
694
+
695
+ if( !$updateOptions ) {
696
+ $this->log( "Preparing Data Step12: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_ERROR );
697
+ $this->returnException( "Data Crunching Step 15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
698
+ return false;
699
+ }
700
+
701
+ return true;
702
+ }
703
+
704
  /**
705
  * Return URL to staging site
706
  * @return string
apps/Backend/Modules/Jobs/Database.php CHANGED
@@ -35,9 +35,25 @@ class Database extends JobExecutable
35
  // Variables
36
  $this->total = count($this->options->tables);
37
  $this->db = WPStaging::getInstance()->get("wpdb");
 
 
38
  }
39
 
 
40
  /**
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
42
  * @return void
43
  */
@@ -262,4 +278,5 @@ class Database extends JobExecutable
262
  )
263
  );
264
  }
 
265
  }
35
  // Variables
36
  $this->total = count($this->options->tables);
37
  $this->db = WPStaging::getInstance()->get("wpdb");
38
+ $this->isFatalError();
39
+
40
  }
41
 
42
+
43
  /**
44
+ * Return fatal error and stops here if subfolder already exists
45
+ * and mainJob is not updating the clone
46
+ * @return boolean
47
+ */
48
+ private function isFatalError(){
49
+ $path = trailingslashit(get_home_path()) . $this->options->cloneDirectoryName;
50
+ if (isset($this->options->mainJob) && $this->options->mainJob !== 'updating' && is_dir($path)){
51
+ $this->returnException( " Can not continue! Change the name of the clone or delete existing folder. Then try again. Folder already exists: " . $path );
52
+ }
53
+ return false;
54
+ }
55
+
56
+ /**
57
  * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
58
  * @return void
59
  */
278
  )
279
  );
280
  }
281
+
282
  }
apps/Backend/Modules/Jobs/Delete.php CHANGED
@@ -367,71 +367,15 @@ class Delete extends Job {
367
  );
368
  }
369
 
370
- /**
371
- * Delete contents of the directory if there are no directories in it and then delete itself
372
- * @param string $path
373
- * @return mixed
374
- */
375
- // private function processDirectory($path) {
376
- // // We hit the limit, stop
377
- // if ($this->shouldStop($path)) {
378
- // $this->updateJob();
379
- // return false;
380
- // }
381
- //
382
- // $this->totalRecursion++;
383
- //
384
- // $contents = new \DirectoryIterator($path);
385
- //
386
- // foreach ($contents as $content => $value) {
387
- //
388
- // // Skip dots
389
- // if ($content->isDot())
390
- //
391
- //
392
- // // Get into the directory
393
- // if (!$content->isLink() && $content->isDir()) {
394
- // return $this->processDirectory($content->getRealPath());
395
- // }
396
- //
397
- // // Delete file
398
- // if ($content->isFile()) {
399
- // @unlink($content->getRealPath());
400
- // }
401
- // }
402
- //
403
- // // Delete directory
404
- // $this->job->lastDeletedDirectory = realpath($path . "/..");
405
- // @rmdir($path);
406
- // $this->updateJob();
407
- // $this->processDirectory($this->job->nextDirectoryToDelete);
408
- // }
409
 
410
- /**
411
- * @param string $path
412
- * @return bool
413
- */
414
- // private function shouldStop($path) {
415
- // // Just to make sure the root dir is never deleted!
416
- // if ($path === get_home_path()) {
417
- // $this->log("Fatal Error: Trying to delete root of WP installation!", Logger::TYPE_CRITICAL);
418
- // return true;
419
- // }
420
- //
421
- // // Check if threshold is reached and is valid dir
422
- // return (
423
- // $this->isOverThreshold() ||
424
- // !is_dir($path) ||
425
- // $this->isDirectoryDeletingFinished()
426
- // );
427
- // }
428
 
429
  /**
430
  *
431
  * @return boolean
432
  */
433
- public function isFatalError() {
434
- if (rtrim($this->clone->path, "/") == rtrim(get_home_path(), "/")) {
 
435
  return true;
436
  }
437
  return false;
@@ -465,18 +409,6 @@ class Delete extends Job {
465
  wp_die(json_encode($response));
466
  }
467
 
468
- /**
469
- * Get json response
470
- * return json
471
- */
472
- // private function returnException($message = ''){
473
- // wp_die( json_encode(array(
474
- // 'job' => 'delete',
475
- // 'status' => false,
476
- // 'message' => $message,
477
- // 'error' => true
478
- // )));
479
- // }
480
  /**
481
  * Get json response
482
  * return json
367
  );
368
  }
369
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
371
 
372
  /**
373
  *
374
  * @return boolean
375
  */
376
+ public function isFatalError(){
377
+ $homePath = rtrim(get_home_path(), "/");
378
+ if (rtrim($this->clone->path,"/") == $homePath){
379
  return true;
380
  }
381
  return false;
409
  wp_die(json_encode($response));
410
  }
411
 
 
 
 
 
 
 
 
 
 
 
 
 
412
  /**
413
  * Get json response
414
  * return json
apps/Backend/Modules/Jobs/Files.php CHANGED
@@ -166,19 +166,24 @@ class Files extends JobExecutable {
166
 
167
  // File is excluded
168
  if ($this->isFileExcluded($file)) {
169
- $this->log("Skipping file by rule: {$file}", Logger::TYPE_INFO);
170
  return false;
171
  }
172
 
173
  // File is over maximum allowed file size (8MB)
174
- if ($fileSize >= 8000000) {
175
  $this->log("Skipping big file: {$file}", Logger::TYPE_INFO);
176
  return false;
177
  }
178
 
179
  // Invalid file, skipping it as if succeeded
180
- if (!is_file($file) || !is_readable($file)) {
181
- $this->log("Can't read file or file doesn't exist {$file}");
 
 
 
 
 
182
  return true;
183
  }
184
 
@@ -225,58 +230,8 @@ class Files extends JobExecutable {
225
  return $destinationPath;
226
  }
227
 
228
- /**
229
- * Copy File using PHP
230
- * @param string $file
231
- * @param string $destination
232
- * @return bool
233
- */
234
- // private function copy($file, $destination) {
235
- // // Get file size
236
- // $fileSize = filesize($file);
237
- //
238
- //
239
- // // File is over batch size
240
- // if ($fileSize >= $this->settings->batchSize) {
241
- // $this->log("Trying to copy big file: {$file} -> {$destination}", Logger::TYPE_INFO);
242
- // return $this->copyBig($file, $destination, $this->settings->batchSize);
243
- // }
244
- //
245
- // // Attempt to copy
246
- // if (!@copy($file, $destination)) {
247
- // $this->log("Failed to copy file to destination: {$file} -> {$destination}", Logger::TYPE_ERROR);
248
- // return false;
249
- // }
250
- //
251
- // return true;
252
- // }
253
 
254
- /**
255
- * Copy bigger files than $this->settings->batchSize
256
- * @param string $file
257
- * @param string $destination
258
- * @return bool
259
- *
260
- * @deprecated since version 2.0.0 (Supported only in php 5.5.11 and later)
261
- */
262
- // private function copyBig($file, $destination)
263
- // {
264
- // $bytes = 0;
265
- // $fileInput = new \SplFileObject($file, "rb");
266
- // $fileOutput = new \SplFileObject($destination, 'w');
267
- //
268
- // $this->log("Copying big file; {$file} -> {$destination}");
269
- //
270
- // while (!$fileInput->eof())
271
- // {
272
- // $bytes += $fileOutput->fwrite($fileInput->fread($this->settings->batchSize));
273
- // }
274
- //
275
- // $fileInput = null;
276
- // $fileOutput= null;
277
- //
278
- // return ($bytes > 0);
279
- // }
280
 
281
  /**
282
  * Copy bigger files than $this->settings->batchSize
@@ -326,6 +281,14 @@ class Files extends JobExecutable {
326
  break;
327
  }
328
  }
 
 
 
 
 
 
 
 
329
  return $excluded;
330
  }
331
 
@@ -354,7 +317,7 @@ class Files extends JobExecutable {
354
  * @param string $directory
355
  * @return boolean
356
  */
357
- protected function isExtraDirectory($directory) {
358
  foreach ($this->options->extraDirectories as $extraDirectory) {
359
  if (strpos($directory, $extraDirectory) === 0) {
360
  return true;
166
 
167
  // File is excluded
168
  if ($this->isFileExcluded($file)) {
169
+ $this->debugLog("Skipping file by rule: {$file}", Logger::TYPE_INFO);
170
  return false;
171
  }
172
 
173
  // File is over maximum allowed file size (8MB)
174
+ if ($fileSize >= $this->settings->maxFileSize * 1000000) {
175
  $this->log("Skipping big file: {$file}", Logger::TYPE_INFO);
176
  return false;
177
  }
178
 
179
  // Invalid file, skipping it as if succeeded
180
+ if (!is_file($file)) {
181
+ $this->debugLog("Not a file {$file}");
182
+ return true;
183
+ }
184
+ // Invalid file, skipping it as if succeeded
185
+ if (!is_readable($file)) {
186
+ $this->log("Can't read file {$file}");
187
  return true;
188
  }
189
 
230
  return $destinationPath;
231
  }
232
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
 
234
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
 
236
  /**
237
  * Copy bigger files than $this->settings->batchSize
281
  break;
282
  }
283
  }
284
+
285
+ // Do not copy wp-config.php if the clone gets updated. This is for security purposes,
286
+ // because if the updating process fails, the staging site would not be accessable any longer
287
+ if (isset($this->options->mainJob ) && $this->options->mainJob == "updating" && stripos(strrev($file), strrev("wp-config.php")) === 0){
288
+ $excluded = true;
289
+ }
290
+
291
+
292
  return $excluded;
293
  }
294
 
317
  * @param string $directory
318
  * @return boolean
319
  */
320
+ private function isExtraDirectory($directory) {
321
  foreach ($this->options->extraDirectories as $extraDirectory) {
322
  if (strpos($directory, $extraDirectory) === 0) {
323
  return true;
apps/Backend/Modules/Jobs/Job.php CHANGED
@@ -1,357 +1,337 @@
1
  <?php
 
2
  namespace WPStaging\Backend\Modules\Jobs;
3
 
4
  // No Direct Access
5
- if (!defined("WPINC"))
6
- {
7
- die;
8
  }
9
 
10
  use WPStaging\Backend\Modules\Jobs\Interfaces\JobInterface;
11
  use WPStaging\Utils\Logger;
12
  use WPStaging\WPStaging;
13
  use WPStaging\Utils\Cache;
 
14
 
15
  /**
16
  * Class Job
17
  * @package WPStaging\Backend\Modules\Jobs
18
  */
19
- abstract class Job implements JobInterface
20
- {
21
-
22
- const EXECUTION_TIME_RATIO = 0.8;
23
-
24
- const MAX_MEMORY_RATIO = 0.8;
25
-
26
- /**
27
- * @var Cache
28
- */
29
- protected $cache;
30
-
31
- /**
32
- * @var Logger
33
- */
34
- protected $logger;
35
-
36
- /**
37
- * @var bool
38
- */
39
- protected $hasLoggedFileNameSet = false;
40
-
41
- /**
42
- * @var object
43
- */
44
- protected $options;
45
-
46
- /**
47
- * @var object
48
- */
49
- protected $settings;
50
-
51
- /**
52
- * System total maximum memory consumption
53
- * @var int
54
- */
55
- protected $maxMemoryLimit;
56
-
57
- /**
58
- * Script maximum memory consumption
59
- * @var int
60
- */
61
- protected $memoryLimit;
62
-
63
- /**
64
- * @var int
65
- */
66
- protected $maxExecutionTime;
67
-
68
-
69
- /**
70
- * @var int
71
- */
72
- protected $executionLimit;
73
-
74
- /**
75
- * @var int
76
- */
77
- protected $totalRecursion;
78
-
79
- /**
80
- * @var int
81
- */
82
- protected $maxRecursionLimit;
83
-
84
- /**
85
- * @var int
86
- */
87
- protected $start;
88
-
89
- /**
90
- * Job constructor.
91
- */
92
- public function __construct()
93
- {
94
- // Get max limits
95
- $this->start = $this->time();
96
- $this->maxMemoryLimit = $this->getMemoryInBytes(@ini_get("memory_limit"));
97
-
98
-
99
- //$this->maxExecutionTime = (int) ini_get("max_execution_time");
100
- $this->maxExecutionTime = (int) 30;
101
-
 
 
 
 
102
  // if ($this->maxExecutionTime < 1 || $this->maxExecutionTime > 30)
103
  // {
104
  // $this->maxExecutionTime = 30;
105
  // }
106
-
107
- // Services
108
- $this->cache = new Cache(-1, \WPStaging\WPStaging::getContentDir());
109
- $this->logger = WPStaging::getInstance()->get("logger");
110
-
111
- // Settings and Options
112
- $this->options = $this->cache->get("clone_options");
113
- //$this->settings = json_decode(json_encode(get_option("wpstg_settings", array())));
114
- $this->settings = (object) get_option("wpstg_settings", array());
115
-
116
- if (!$this->options)
117
- {
118
- $this->options = new \stdClass();
119
- }
120
-
121
- if (isset($this->options->existingClones) && is_object($this->options->existingClones))
122
- {
123
- $this->options->existingClones = json_decode(json_encode($this->options->existingClones), true);
124
- }
125
-
126
- // check default options
127
- if ( !isset($this->settings) ||
128
- !isset($this->settings->queryLimit) ||
129
- !isset($this->settings->querySRLimit) ||
130
- !isset($this->settings->batchSize) ||
131
- !isset($this->settings->cpuLoad) ||
132
- !isset($this->settings->fileLimit)
133
- )
134
-
135
- {
136
- $this->settings = new \stdClass();
137
- $this->setDefaultSettings();
138
- }
139
-
140
- // Set limits accordingly to CPU LIMITS
141
- $this->setLimits();
142
-
143
- $this->maxRecursionLimit = (int) ini_get("xdebug.max_nesting_level");
144
-
145
- /*
146
- * This is needed to make sure that maxRecursionLimit = -1
147
- * if xdebug is not used in production env.
148
- * For using xdebug, maxRecursionLimit must be larger
149
- * otherwise xdebug is throwing an error 500 while debugging
150
- */
151
- if ($this->maxRecursionLimit < 1)
152
- {
153
- $this->maxRecursionLimit = -1;
154
- }
155
- else
156
- {
157
- $this->maxRecursionLimit = $this->maxRecursionLimit - 50; // just to make sure
158
- }
159
-
160
- if (method_exists($this, "initialize"))
161
- {
162
- $this->initialize();
163
- }
164
- }
165
-
166
- /**
167
- * Job destructor
168
- */
169
- public function __destruct()
170
- {
171
- // Commit logs
172
- $this->logger->commit();
173
- }
174
-
175
- /**
176
- * Set default settings
177
- */
178
- protected function setDefaultSettings(){
179
- $this->settings->queryLimit = "5000";
180
- $this->settings->querySRLimit = "5000";
181
- $this->settings->fileLimit = "1";
182
- $this->settings->batchSize = "2";
183
- $this->settings->cpuLoad = 'medium';
184
- update_option('wpstg_settings', $this->settings);
185
- }
186
-
187
- /**
188
- * Set limits accordingly to
189
- */
190
- protected function setLimits()
191
- {
192
-
193
- if (!isset($this->settings->cpuLoad))
194
- {
195
- $this->settings->cpuLoad = "medium";
196
- }
197
-
198
- $memoryLimit= self::MAX_MEMORY_RATIO;
199
- $timeLimit = self::EXECUTION_TIME_RATIO;
200
-
201
- switch($this->settings->cpuLoad)
202
- {
203
- case "medium":
204
- //$memoryLimit= $memoryLimit / 2; // 0.4
205
- $timeLimit = $timeLimit / 2;
206
- break;
207
- case "low":
208
- //$memoryLimit= $memoryLimit / 4; // 0.2
209
- $timeLimit = $timeLimit / 4;
210
- break;
211
-
212
- case "fast": // 0.8
213
- default:
214
- break;
215
- }
216
-
217
- $this->memoryLimit = $this->maxMemoryLimit * $memoryLimit;
218
- $this->executionLimit = $this->maxExecutionTime * $timeLimit;
219
- }
220
-
221
- /**
222
- * Save options
223
- * @param null|array|object $options
224
- * @return bool
225
- */
226
- protected function saveOptions($options = null)
227
- {
228
- // Get default options
229
- if (null === $options)
230
- {
231
- $options = $this->options;
232
- }
233
-
234
- // Ensure that it is an object
235
- $options = json_decode(json_encode($options));
236
- return $this->cache->save("clone_options", $options);
237
- }
238
-
239
- /**
240
- * @return object
241
- */
242
- public function getOptions()
243
- {
244
- return $this->options;
245
- }
246
-
247
- /**
248
- * @param string $memory
249
- * @return int
250
- */
251
- protected function getMemoryInBytes($memory)
252
- {
253
- // Handle unlimited ones
254
- if (1 > (int) $memory)
255
- {
256
- //return (int) $memory;
257
- // 128 MB default value
258
- return (int) 134217728;
259
- }
260
-
261
- $bytes = (int) $memory; // grab only the number
262
- $size = trim(str_replace($bytes, null, strtolower($memory))); // strip away number and lower-case it
263
-
264
- // Actual calculation
265
- switch($size)
266
- {
267
- case 'k':
268
- $bytes *= 1024;
269
- break;
270
- case 'm':
271
- $bytes *= (1024 * 1024);
272
- break;
273
- case 'g':
274
- $bytes *= (1024 * 1024 * 1024);
275
- break;
276
- }
277
-
278
- return $bytes;
279
- }
280
-
281
- /**
282
- * Format bytes into ini_set favorable form
283
- * @param int $bytes
284
- * @return string
285
- */
286
- protected function formatBytes($bytes)
287
- {
288
- if ((int) $bytes < 1)
289
- {
290
- return '';
291
- }
292
-
293
- $units = array('B', 'K', 'M', 'G'); // G since PHP 5.1.x so we are good!
294
-
295
- $bytes = (int) $bytes;
296
- $base = log($bytes) / log(1000);
297
- $pow = pow(1000, $base - floor($base));
298
-
299
- return round($pow, 0) . $units[(int) floor($base)];
300
- }
301
-
302
- /**
303
- * Get current time in seconds
304
- * @return float
305
- */
306
- protected function time()
307
- {
308
- $time = microtime();
309
- $time = explode(' ', $time);
310
- $time = $time[1] + $time[0];
311
- return $time;
312
- }
313
-
314
- /**
315
- * @return bool
316
- */
317
- protected function isOverThreshold()
318
- {
319
- // Check if the memory is over threshold
320
- $usedMemory = (int) @memory_get_usage(true);
321
-
322
- $this->debugLog('Used Memory: ' . $this->formatBytes( $usedMemory ) . ' Max Memory Limit: ' . $this->formatBytes( $this->maxMemoryLimit ) . ' Max Script Memory Limit: ' . $this->formatBytes( $this->memoryLimit), Logger::TYPE_DEBUG );
323
-
324
- if ($usedMemory >= $this->memoryLimit)
325
- {
326
- $this->log('Used Memory: ' . $this->formatBytes($usedMemory) . ' Memory Limit: ' . $this->formatBytes($this->maxMemoryLimit) . ' Max Script memory limit: ' . $this->formatBytes( $this->memoryLimit ), Logger::TYPE_ERROR );
327
- //$this->resetMemory();
328
- return true;
329
- }
330
-
331
- if ($this->isRecursionLimit())
332
- {
333
- //$this->log('RESET RECURSION');
334
- return true;
335
- }
336
-
337
- // Check if execution time is over threshold
338
- ///$time = round($this->start + $this->time(), 4);
339
- $time = round($this->time() - $this->start, 4);
340
-
341
- if ($time >= $this->executionLimit)
342
- {
343
- $this->debugLog('RESET TIME: current time: ' . $time . ', Start Time: ' . $this->start . ', exec time limit: ' . $this->executionLimit);
344
- return true;
345
- }
346
-
347
- return false;
348
- }
349
-
350
- /**
351
- * Attempt to reset memory
352
- * @return bool
353
- * memory
354
- */
355
  // protected function resetMemory()
356
  // {
357
  // $newMemoryLimit = $this->maxMemoryLimit * 2;
@@ -379,13 +359,13 @@ abstract class Job implements JobInterface
379
  // return true;
380
  // }
381
 
382
- /**
383
- * Attempt to reset time
384
- * @return bool
385
- *
386
- * @deprecated since version 2.0.0
387
 
388
- */
389
  // protected function resetTime()
390
  // {
391
  // // Attempt to reset timeout
@@ -400,12 +380,12 @@ abstract class Job implements JobInterface
400
  // return true;
401
  // }
402
 
403
- /**
404
- * Reset time limit and memory
405
- * @return bool
406
- *
407
- * @deprecated since version 2.0.0
408
- */
409
  // protected function reset()
410
  // {
411
  // // Attempt to reset time
@@ -423,68 +403,64 @@ abstract class Job implements JobInterface
423
  // return true;
424
  // }
425
 
426
- /**
427
- * Checks if calls are over recursion limit
428
- * @return bool
429
- */
430
- protected function isRecursionLimit()
431
- {
432
- return ($this->maxRecursionLimit > 0 && $this->totalRecursion >= $this->maxRecursionLimit);
433
- }
434
-
435
- /**
436
- * @param string $msg
437
- * @param string $type
438
- */
439
- protected function log($msg, $type = Logger::TYPE_INFO)
440
- {
441
-
442
- if (!isset($this->options->clone)){
443
- $this->options->clone = date(DATE_ATOM, mktime(0, 0, 0, 7, 1, 2000));
444
- }
445
-
446
- if (false === $this->hasLoggedFileNameSet && 0 < strlen($this->options->clone))
447
- {
448
- $this->logger->setFileName($this->options->clone);
449
- $this->hasLoggedFileNameSet = true;
450
- }
451
-
452
- $this->logger->add($msg, $type);
453
- }
454
- /**
455
- * @param string $msg
456
- * @param string $type
457
- */
458
- protected function debugLog($msg, $type = Logger::TYPE_INFO)
459
- {
460
-
461
- if (!isset($this->options->clone)){
462
- $this->options->clone = date(DATE_ATOM, mktime(0, 0, 0, 7, 1, 2000));
463
- }
464
-
465
- if (false === $this->hasLoggedFileNameSet && 0 < strlen($this->options->clone))
466
- {
467
- $this->logger->setFileName($this->options->clone);
468
- $this->hasLoggedFileNameSet = true;
469
- }
470
-
471
-
472
- if (isset($this->settings->debugMode)){
473
- $this->logger->add($msg, $type);
474
- }
475
-
476
- }
477
-
478
- /**
479
- * Throw a errror message via json and stop further execution
480
- * @param string $message
481
- */
482
- protected function returnException($message = ''){
483
- wp_die( json_encode(array(
484
- 'job' => isset($this->options->currentJob) ? $this->options->currentJob : '',
485
- 'status' => false,
486
- 'message' => $message,
487
- 'error' => true
488
- )));
489
- }
490
- }
1
  <?php
2
+
3
  namespace WPStaging\Backend\Modules\Jobs;
4
 
5
  // No Direct Access
6
+ if( !defined( "WPINC" ) ) {
7
+ die;
 
8
  }
9
 
10
  use WPStaging\Backend\Modules\Jobs\Interfaces\JobInterface;
11
  use WPStaging\Utils\Logger;
12
  use WPStaging\WPStaging;
13
  use WPStaging\Utils\Cache;
14
+ use WPStaging\Utils\Multisite;
15
 
16
  /**
17
  * Class Job
18
  * @package WPStaging\Backend\Modules\Jobs
19
  */
20
+ abstract class Job implements JobInterface {
21
+
22
+ const EXECUTION_TIME_RATIO = 0.8;
23
+ const MAX_MEMORY_RATIO = 0.8;
24
+
25
+ /**
26
+ * @var Cache
27
+ */
28
+ protected $cache;
29
+
30
+ /**
31
+ * @var Logger
32
+ */
33
+ protected $logger;
34
+
35
+ /**
36
+ * @var bool
37
+ */
38
+ protected $hasLoggedFileNameSet = false;
39
+
40
+ /**
41
+ * @var object
42
+ */
43
+ protected $options;
44
+
45
+ /**
46
+ * @var object
47
+ */
48
+ protected $settings;
49
+
50
+ /**
51
+ * System total maximum memory consumption
52
+ * @var int
53
+ */
54
+ protected $maxMemoryLimit;
55
+
56
+ /**
57
+ * Script maximum memory consumption
58
+ * @var int
59
+ */
60
+ protected $memoryLimit;
61
+
62
+ /**
63
+ * @var int
64
+ */
65
+ protected $maxExecutionTime;
66
+
67
+ /**
68
+ * @var int
69
+ */
70
+ protected $executionLimit;
71
+
72
+ /**
73
+ * @var int
74
+ */
75
+ protected $totalRecursion;
76
+
77
+ /**
78
+ * @var int
79
+ */
80
+ protected $maxRecursionLimit;
81
+
82
+ /**
83
+ * Multisite Home Url
84
+ * @var string
85
+ */
86
+ protected $multisiteHomeUrl;
87
+
88
+ /**
89
+ * @var int
90
+ */
91
+ protected $start;
92
+
93
+ /**
94
+ * Job constructor.
95
+ */
96
+ public function __construct() {
97
+ // Get max limits
98
+ $this->start = $this->time();
99
+ $this->maxMemoryLimit = $this->getMemoryInBytes( @ini_get( "memory_limit" ) );
100
+
101
+ $multisite = new Multisite;
102
+ $this->multisiteHomeUrl = $multisite->getHomeURL();
103
+
104
+ //$this->maxExecutionTime = (int) ini_get("max_execution_time");
105
+ $this->maxExecutionTime = ( int ) 30;
106
+
107
  // if ($this->maxExecutionTime < 1 || $this->maxExecutionTime > 30)
108
  // {
109
  // $this->maxExecutionTime = 30;
110
  // }
111
+ // Services
112
+ $this->cache = new Cache( -1, \WPStaging\WPStaging::getContentDir() );
113
+ $this->logger = WPStaging::getInstance()->get( "logger" );
114
+
115
+ // Settings and Options
116
+ $this->options = $this->cache->get( "clone_options" );
117
+
118
+ $this->settings = ( object ) get_option( "wpstg_settings", array() );
119
+
120
+ if( !$this->options ) {
121
+ $this->options = new \stdClass();
122
+ }
123
+
124
+ if( isset( $this->options->existingClones ) && is_object( $this->options->existingClones ) ) {
125
+ $this->options->existingClones = json_decode( json_encode( $this->options->existingClones ), true );
126
+ }
127
+
128
+ // check default options
129
+ if( !isset( $this->settings ) ||
130
+ !isset( $this->settings->queryLimit ) ||
131
+ !isset( $this->settings->querySRLimit ) ||
132
+ !isset( $this->settings->batchSize ) ||
133
+ !isset( $this->settings->cpuLoad ) ||
134
+ !isset( $this->settings->maxFileSize ) ||
135
+ !isset( $this->settings->fileLimit )
136
+ ) {
137
+ $this->settings = new \stdClass();
138
+ $this->setDefaultSettings();
139
+ }
140
+
141
+ // Set limits accordingly to CPU LIMITS
142
+ $this->setLimits();
143
+
144
+ $this->maxRecursionLimit = ( int ) ini_get( "xdebug.max_nesting_level" );
145
+
146
+ /*
147
+ * This is needed to make sure that maxRecursionLimit = -1
148
+ * if xdebug is not used in production env.
149
+ * For using xdebug, maxRecursionLimit must be larger
150
+ * otherwise xdebug is throwing an error 500 while debugging
151
+ */
152
+ if( $this->maxRecursionLimit < 1 ) {
153
+ $this->maxRecursionLimit = -1;
154
+ } else {
155
+ $this->maxRecursionLimit = $this->maxRecursionLimit - 50; // just to make sure
156
+ }
157
+
158
+ if( method_exists( $this, "initialize" ) ) {
159
+ $this->initialize();
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Job destructor
165
+ */
166
+ public function __destruct() {
167
+ // Commit logs
168
+ $this->logger->commit();
169
+ }
170
+
171
+ /**
172
+ * Set default settings
173
+ */
174
+ protected function setDefaultSettings() {
175
+ $this->settings->queryLimit = "20000";
176
+ $this->settings->querySRLimit = "5000";
177
+ $this->settings->fileLimit = "1";
178
+ $this->settings->batchSize = "2";
179
+ $this->settings->cpuLoad = 'medium';
180
+ $this->settings->maxFileSize = 8;
181
+ update_option( 'wpstg_settings', $this->settings );
182
+ }
183
+
184
+ /**
185
+ * Set limits accordingly to
186
+ */
187
+ protected function setLimits() {
188
+
189
+ if( !isset( $this->settings->cpuLoad ) ) {
190
+ $this->settings->cpuLoad = "medium";
191
+ }
192
+
193
+ $memoryLimit = self::MAX_MEMORY_RATIO;
194
+ $timeLimit = self::EXECUTION_TIME_RATIO;
195
+
196
+ switch ( $this->settings->cpuLoad ) {
197
+ case "medium":
198
+ //$memoryLimit= $memoryLimit / 2; // 0.4
199
+ $timeLimit = $timeLimit / 2;
200
+ break;
201
+ case "low":
202
+ //$memoryLimit= $memoryLimit / 4; // 0.2
203
+ $timeLimit = $timeLimit / 4;
204
+ break;
205
+
206
+ case "fast": // 0.8
207
+ default:
208
+ break;
209
+ }
210
+
211
+ $this->memoryLimit = $this->maxMemoryLimit * $memoryLimit;
212
+ $this->executionLimit = $this->maxExecutionTime * $timeLimit;
213
+ }
214
+
215
+ /**
216
+ * Save options
217
+ * @param null|array|object $options
218
+ * @return bool
219
+ */
220
+ protected function saveOptions( $options = null ) {
221
+ // Get default options
222
+ if( null === $options ) {
223
+ $options = $this->options;
224
+ }
225
+
226
+ // Ensure that it is an object
227
+ $options = json_decode( json_encode( $options ) );
228
+ return $this->cache->save( "clone_options", $options );
229
+ }
230
+
231
+ /**
232
+ * @return object
233
+ */
234
+ public function getOptions() {
235
+ return $this->options;
236
+ }
237
+
238
+ /**
239
+ * @param string $memory
240
+ * @return int
241
+ */
242
+ protected function getMemoryInBytes( $memory ) {
243
+ // Handle unlimited ones
244
+ if( 1 > ( int ) $memory ) {
245
+ //return (int) $memory;
246
+ // 128 MB default value
247
+ return ( int ) 134217728;
248
+ }
249
+
250
+ $bytes = ( int ) $memory; // grab only the number
251
+ $size = trim( str_replace( $bytes, null, strtolower( $memory ) ) ); // strip away number and lower-case it
252
+ // Actual calculation
253
+ switch ( $size ) {
254
+ case 'k':
255
+ $bytes *= 1024;
256
+ break;
257
+ case 'm':
258
+ $bytes *= (1024 * 1024);
259
+ break;
260
+ case 'g':
261
+ $bytes *= (1024 * 1024 * 1024);
262
+ break;
263
+ }
264
+
265
+ return $bytes;
266
+ }
267
+
268
+ /**
269
+ * Format bytes into ini_set favorable form
270
+ * @param int $bytes
271
+ * @return string
272
+ */
273
+ protected function formatBytes( $bytes ) {
274
+ if( ( int ) $bytes < 1 ) {
275
+ return '';
276
+ }
277
+
278
+ $units = array('B', 'K', 'M', 'G'); // G since PHP 5.1.x so we are good!
279
+
280
+ $bytes = ( int ) $bytes;
281
+ $base = log( $bytes ) / log( 1000 );
282
+ $pow = pow( 1000, $base - floor( $base ) );
283
+
284
+ return round( $pow, 0 ) . $units[( int ) floor( $base )];
285
+ }
286
+
287
+ /**
288
+ * Get current time in seconds
289
+ * @return float
290
+ */
291
+ protected function time() {
292
+ $time = microtime();
293
+ $time = explode( ' ', $time );
294
+ $time = $time[1] + $time[0];
295
+ return $time;
296
+ }
297
+
298
+ /**
299
+ * @return bool
300
+ */
301
+ protected function isOverThreshold() {
302
+ // Check if the memory is over threshold
303
+ $usedMemory = ( int ) @memory_get_usage( true );
304
+
305
+ $this->debugLog( 'Used Memory: ' . $this->formatBytes( $usedMemory ) . ' Max Memory Limit: ' . $this->formatBytes( $this->maxMemoryLimit ) . ' Max Script Memory Limit: ' . $this->formatBytes( $this->memoryLimit ), Logger::TYPE_DEBUG );
306
+
307
+ if( $usedMemory >= $this->memoryLimit ) {
308
+ $this->log( 'Used Memory: ' . $this->formatBytes( $usedMemory ) . ' Memory Limit: ' . $this->formatBytes( $this->maxMemoryLimit ) . ' Max Script memory limit: ' . $this->formatBytes( $this->memoryLimit ), Logger::TYPE_ERROR );
309
+ //$this->resetMemory();
310
+ return true;
311
+ }
312
+
313
+ if( $this->isRecursionLimit() ) {
314
+ //$this->log('RESET RECURSION');
315
+ return true;
316
+ }
317
+
318
+ // Check if execution time is over threshold
319
+ ///$time = round($this->start + $this->time(), 4);
320
+ $time = round( $this->time() - $this->start, 4 );
321
+
322
+ if( $time >= $this->executionLimit ) {
323
+ $this->debugLog( 'RESET TIME: current time: ' . $time . ', Start Time: ' . $this->start . ', exec time limit: ' . $this->executionLimit );
324
+ return true;
325
+ }
326
+
327
+ return false;
328
+ }
329
+
330
+ /**
331
+ * Attempt to reset memory
332
+ * @return bool
333
+ * memory
334
+ */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
  // protected function resetMemory()
336
  // {
337
  // $newMemoryLimit = $this->maxMemoryLimit * 2;
359
  // return true;
360
  // }
361
 
362
+ /**
363
+ * Attempt to reset time
364
+ * @return bool
365
+ *
366
+ * @deprecated since version 2.0.0
367
 
368
+ */
369
  // protected function resetTime()
370
  // {
371
  // // Attempt to reset timeout
380
  // return true;
381
  // }
382
 
383
+ /**
384
+ * Reset time limit and memory
385
+ * @return bool
386
+ *
387
+ * @deprecated since version 2.0.0
388
+ */
389
  // protected function reset()
390
  // {
391
  // // Attempt to reset time
403
  // return true;
404
  // }
405
 
406
+ /**
407
+ * Checks if calls are over recursion limit
408
+ * @return bool
409
+ */
410
+ protected function isRecursionLimit() {
411
+ return ($this->maxRecursionLimit > 0 && $this->totalRecursion >= $this->maxRecursionLimit);
412
+ }
413
+
414
+ /**
415
+ * @param string $msg
416
+ * @param string $type
417
+ */
418
+ protected function log( $msg, $type = Logger::TYPE_INFO ) {
419
+
420
+ if( !isset( $this->options->clone ) ) {
421
+ $this->options->clone = date( DATE_ATOM, mktime( 0, 0, 0, 7, 1, 2000 ) );
422
+ }
423
+
424
+ if( false === $this->hasLoggedFileNameSet && 0 < strlen( $this->options->clone ) ) {
425
+ $this->logger->setFileName( $this->options->clone );
426
+ $this->hasLoggedFileNameSet = true;
427
+ }
428
+
429
+ $this->logger->add( $msg, $type );
430
+ }
431
+
432
+ /**
433
+ * @param string $msg
434
+ * @param string $type
435
+ */
436
+ protected function debugLog( $msg, $type = Logger::TYPE_INFO ) {
437
+
438
+ if( !isset( $this->options->clone ) ) {
439
+ $this->options->clone = date( DATE_ATOM, mktime( 0, 0, 0, 7, 1, 2000 ) );
440
+ }
441
+
442
+ if( false === $this->hasLoggedFileNameSet && 0 < strlen( $this->options->clone ) ) {
443
+ $this->logger->setFileName( $this->options->clone );
444
+ $this->hasLoggedFileNameSet = true;
445
+ }
446
+
447
+
448
+ if( isset( $this->settings->debugMode ) ) {
449
+ $this->logger->add( $msg, $type );
450
+ }
451
+ }
452
+
453
+ /**
454
+ * Throw a errror message via json and stop further execution
455
+ * @param string $message
456
+ */
457
+ protected function returnException( $message = '' ) {
458
+ wp_die( json_encode( array(
459
+ 'job' => isset( $this->options->currentJob ) ? $this->options->currentJob : '',
460
+ 'status' => false,
461
+ 'message' => $message,
462
+ 'error' => true
463
+ ) ) );
464
+ }
465
+
466
+ }
 
 
 
 
apps/Backend/Modules/Jobs/Multisite/Data.php ADDED
@@ -0,0 +1,924 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
+
5
+ // No Direct Access
6
+ if( !defined( "WPINC" ) ) {
7
+ die;
8
+ }
9
+
10
+ use WPStaging\Utils\Logger;
11
+ use WPStaging\WPStaging;
12
+ use WPStaging\Utils\Helper;
13
+ use WPStaging\Utils\Multisite;
14
+ use WPStaging\Utils\Strings;
15
+ use WPStaging\Backend\Modules\Jobs\JobExecutable;
16
+
17
+ /**
18
+ * Class Data
19
+ * @package WPStaging\Backend\Modules\Jobs
20
+ */
21
+ class Data extends JobExecutable {
22
+
23
+ /**
24
+ * @var \wpdb
25
+ */
26
+ private $db;
27
+
28
+ /**
29
+ * @var string
30
+ */
31
+ private $prefix;
32
+
33
+ /**
34
+ * Tables e.g wpstg3_options
35
+ * @var array
36
+ */
37
+ private $tables;
38
+
39
+ /**
40
+ * Initialize
41
+ */
42
+ public function initialize() {
43
+ $this->db = WPStaging::getInstance()->get( "wpdb" );
44
+
45
+ $this->prefix = $this->options->prefix;
46
+
47
+ $this->getTables();
48
+
49
+ // Fix current step
50
+ if( 0 == $this->options->currentStep ) {
51
+ $this->options->currentStep = 1;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Get a list of tables to copy
57
+ */
58
+ private function getTables(){
59
+ $strings = new Strings();
60
+ $this->tables = array();
61
+ foreach($this->options->tables as $table){
62
+ $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->db->prefix, null, $table );
63
+ }
64
+ // Add extra global tables from main multisite (wpstgx_users and wpstgx_usermeta)
65
+ $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->db->prefix, null, 'users' );
66
+ $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->db->prefix, null, 'usermeta' );
67
+
68
+ }
69
+ /**
70
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
71
+ * @return void
72
+ */
73
+ protected function calculateTotalSteps() {
74
+ $this->options->totalSteps = 15;
75
+ }
76
+
77
+ /**
78
+ * Start Module
79
+ * @return object
80
+ */
81
+ public function start() {
82
+ // Execute steps
83
+ $this->run();
84
+
85
+ // Save option, progress
86
+ $this->saveOptions();
87
+
88
+ return ( object ) $this->response;
89
+ }
90
+
91
+ /**
92
+ * Execute the Current Step
93
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
94
+ * @return bool
95
+ */
96
+ protected function execute() {
97
+ // Fatal error. Let this happen never and break here immediately
98
+ if( $this->isRoot() ) {
99
+ return false;
100
+ }
101
+
102
+ // Over limits threshold
103
+ if( $this->isOverThreshold() ) {
104
+ // Prepare response and save current progress
105
+ $this->prepareResponse( false, false );
106
+ $this->saveOptions();
107
+ return false;
108
+ }
109
+
110
+ // No more steps, finished
111
+ if( $this->isFinished() ) {
112
+ $this->prepareResponse( true, false );
113
+ return false;
114
+ }
115
+
116
+ // Execute step
117
+ $stepMethodName = "step" . $this->options->currentStep;
118
+ if( !$this->{$stepMethodName}() ) {
119
+ $this->prepareResponse( false, false );
120
+ return false;
121
+ }
122
+
123
+ // Prepare Response
124
+ $this->prepareResponse();
125
+
126
+ // Not finished
127
+ return true;
128
+ }
129
+
130
+ /**
131
+ * Checks Whether There is Any Job to Execute or Not
132
+ * @return bool
133
+ */
134
+ protected function isFinished() {
135
+ return (
136
+ $this->options->currentStep > $this->options->totalSteps ||
137
+ !method_exists( $this, "step" . $this->options->currentStep )
138
+ );
139
+ }
140
+
141
+ /**
142
+ * Check if current operation is done on the root folder or on the live DB
143
+ * @return boolean
144
+ */
145
+ protected function isRoot() {
146
+
147
+ // Prefix is the same as the one of live site
148
+ $wpdb = WPStaging::getInstance()->get( "wpdb" );
149
+ if( $wpdb->prefix === $this->prefix ) {
150
+ return true;
151
+ }
152
+
153
+ // CloneName is empty
154
+ $name = ( array ) $this->options->cloneDirectoryName;
155
+ if( empty( $name ) ) {
156
+ return true;
157
+ }
158
+
159
+ // Live Path === Staging path
160
+ if( $this->multisiteHomeUrl . $this->options->cloneDirectoryName === $this->multisiteHomeUrl ) {
161
+ return true;
162
+ }
163
+
164
+ return false;
165
+ }
166
+
167
+ /**
168
+ * Check if table exists
169
+ * @param string $table
170
+ * @return boolean
171
+ */
172
+ protected function isTable( $table ) {
173
+ if( $this->db->get_var( "SHOW TABLES LIKE '{$table}'" ) != $table ) {
174
+ $this->log( "Table {$table} does not exists", Logger::TYPE_ERROR );
175
+ return false;
176
+ }
177
+ return true;
178
+ }
179
+
180
+ /**
181
+ * Get the install sub directory if WP is installed in sub directory
182
+ * @return string
183
+ */
184
+ protected function getSubDir() {
185
+ return '/';
186
+
187
+ // $home = get_option( 'home' );
188
+ // $siteurl = get_option( 'siteurl' );
189
+ //
190
+ // if( empty( $home ) || empty( $siteurl ) ) {
191
+ // return '/';
192
+ // }
193
+ //
194
+ // $dir = str_replace( $home, '', $siteurl );
195
+ // return '/' . str_replace( '/', '', $dir ) . '/';
196
+ }
197
+
198
+ /**
199
+ * Get path to wp-config.php if it's located in parent folder and not root level
200
+ * @return mixed string | boolean
201
+ */
202
+ // protected function getPathWpConfig() {
203
+ // $dir = trailingslashit( dirname( ABSPATH ) );
204
+ //
205
+ // if( is_file( $dir . 'wp-config.php' ) ) {
206
+ // return $dir . 'wp-config.php';
207
+ // }
208
+ // return false;
209
+ // }
210
+
211
+ /**
212
+ * Copy wp-config.php if it is located outside of root one level up
213
+ * @todo Needs some more testing before it will be released
214
+ * @return boolean
215
+ */
216
+ // protected function step0(){
217
+ // $this->log( "Preparing Data Step0: Check if wp-config.php is located in root path", Logger::TYPE_INFO );
218
+ //
219
+ // $dir = trailingslashit( dirname( ABSPATH ) );
220
+ //
221
+ // $source = $dir . 'wp-config.php';
222
+ //
223
+ // $destination = trailingslashit(ABSPATH) . $this->clone->cloneDirectoryName . DIRECTORY_SEPARATOR . 'wp-config.php';
224
+ //
225
+ //
226
+ // // Do not do anything
227
+ // if( (!is_file( $source ) && !is_link($source)) || is_file($destination) ) {
228
+ // $this->log( "Preparing Data Step0: Skip it", Logger::TYPE_INFO );
229
+ // return true;
230
+ // }
231
+ //
232
+ // // Copy target of a symbolic link
233
+ // if (is_link($source)){
234
+ // $this->log( "Preparing Data Step0: Symbolic link found...", Logger::TYPE_INFO );
235
+ // if (!@copy(readlink($source), $destination)) {
236
+ // $this->log("Preparing Data Step0: Failed to copy wp-config.php {$source} -> {$destination}", Logger::TYPE_ERROR);
237
+ // return true;
238
+ // }
239
+ // }
240
+ //
241
+ // // regular copy of wp-config.php
242
+ // if (!@copy($source, $destination)) {
243
+ // $this->log("Preparing Data Step0: Failed to copy wp-config.php {$source} -> {$destination}", Logger::TYPE_ERROR);
244
+ // return true;
245
+ // }
246
+ // $this->log( "Preparing Data Step0: Successfull", Logger::TYPE_INFO );
247
+ // return true;
248
+ //
249
+ // }
250
+
251
+ /**
252
+ * Replace "siteurl" and "home"
253
+ * @return bool
254
+ */
255
+ protected function step1() {
256
+ $this->log( "Preparing Data Step1: Updating siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_INFO );
257
+
258
+ // Skip - Table does not exist
259
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
260
+ return true;
261
+ }
262
+ // Skip - Table is not selected or updated
263
+ if (!in_array($this->prefix . 'options', $this->tables)){
264
+ $this->log("Preparing Data Step1: Skipping");
265
+ return true;
266
+ }
267
+
268
+ // Installed in sub-directory
269
+ if( $this->isSubDir() ) {
270
+ $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . rtrim( $this->multisiteHomeUrl, "/" ) . $this->getSubDir() . $this->options->cloneDirectoryName );
271
+ // Replace URLs
272
+ $result = $this->db->query(
273
+ $this->db->prepare(
274
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", rtrim( $this->multisiteHomeUrl, "/" ) . $this->getSubDir() . $this->options->cloneDirectoryName
275
+ )
276
+ );
277
+ } else {
278
+ $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . rtrim( $this->multisiteHomeUrl, "/" ) . '/' . $this->options->cloneDirectoryName );
279
+ // Replace URLs
280
+ $result = $this->db->query(
281
+ $this->db->prepare(
282
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", $this->multisiteHomeUrl . '/' . $this->options->cloneDirectoryName
283
+ )
284
+ );
285
+ }
286
+
287
+
288
+ // All good
289
+ if( $result ) {
290
+ return true;
291
+ }
292
+
293
+ $this->log( "Preparing Data Step1: Failed to update siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
294
+ return false;
295
+ }
296
+
297
+ /**
298
+ * Update "wpstg_is_staging_site"
299
+ * @return bool
300
+ */
301
+ protected function step2() {
302
+
303
+ $this->log( "Preparing Data Step2: Updating row wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}" );
304
+
305
+ // Skip - Table does not exist
306
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
307
+ return true;
308
+ }
309
+ // Skip - Table is not selected or updated
310
+ if (!in_array($this->prefix . 'options', $this->tables)){
311
+ $this->log("Preparing Data Step2: Skipping");
312
+ return true;
313
+ }
314
+
315
+ $result = $this->db->query(
316
+ $this->db->prepare(
317
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_is_staging_site'", "true"
318
+ )
319
+ );
320
+
321
+ // No errors but no option name such as wpstg_is_staging_site
322
+ if( '' === $this->db->last_error && 0 == $result ) {
323
+ $result = $this->db->query(
324
+ $this->db->prepare(
325
+ "INSERT INTO {$this->prefix}options (option_name,option_value) VALUES ('wpstg_is_staging_site',%s)", "true"
326
+ )
327
+ );
328
+ }
329
+
330
+ // All good
331
+ if( $result ) {
332
+ return true;
333
+ }
334
+
335
+ $this->log( "Preparing Data Step2: Failed to update wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
336
+ return false;
337
+ }
338
+
339
+ /**
340
+ * Update rewrite_rules
341
+ * @return bool
342
+ */
343
+ protected function step3() {
344
+
345
+ $this->log( "Preparing Data Step3: Updating rewrite_rules in {$this->prefix}options {$this->db->last_error}" );
346
+
347
+ // Skip - Table does not exist
348
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
349
+ return true;
350
+ }
351
+
352
+ // Skip - Table is not selected or updated
353
+ if (!in_array($this->prefix . 'options', $this->tables)){
354
+ $this->log( "Preparing Data Step3: Skipping" );
355
+ return true;
356
+ }
357
+
358
+ $result = $this->db->query(
359
+ $this->db->prepare(
360
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'rewrite_rules'", ' '
361
+ )
362
+ );
363
+
364
+ // All good
365
+ if( $result ) {
366
+ return true;
367
+ }
368
+
369
+ $this->log( "Preparing Data Step3: Failed to update rewrite_rules in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
370
+ return true;
371
+ }
372
+
373
+ /**
374
+ * Update Table Prefix in wp_usermeta and wp_options
375
+ * @return bool
376
+ */
377
+ protected function step4() {
378
+ $this->log( "Preparing Data Step4: Updating db prefix in {$this->prefix}usermeta. {$this->db->last_error}" );
379
+
380
+ // Skip - Table does not exist
381
+ if( false === $this->isTable( $this->prefix . 'usermeta' ) ) {
382
+ return true;
383
+ }
384
+
385
+ // Skip - Table is not selected or updated
386
+ if (!in_array($this->prefix . 'usermeta', $this->tables)){
387
+ $this->log("Preparing Data Step4: Skipping");
388
+ return true;
389
+ }
390
+
391
+ $update = $this->db->query(
392
+ $this->db->prepare(
393
+ "UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, %s, %s) WHERE meta_key LIKE %s", $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
394
+ )
395
+ );
396
+
397
+ if( !$update ) {
398
+ $this->log( "Preparing Data Step4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}", Logger::TYPE_ERROR );
399
+ $this->returnException( "Data Crunching Step 4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}" );
400
+ return false;
401
+ }
402
+
403
+ // if( false === $this->isTable( $this->prefix . 'options' ) ) {
404
+ // return true;
405
+ // }
406
+
407
+ // $this->log( "Updating db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
408
+ //
409
+ // // Filter the rows below. Do not update them!
410
+ // $filters = array(
411
+ // 'wp_mail_smtp',
412
+ // 'wp_mail_smtp_version',
413
+ // 'wp_mail_smtp_debug',
414
+ // );
415
+ //
416
+ // $filters = apply_filters('wpstg_filter_options_replace', $filters);
417
+ //
418
+ // $where = "";
419
+ // foreach($filters as $filter){
420
+ // $where .= " AND option_name <> '" . $filter . "'";
421
+ // }
422
+ //
423
+ // $updateOptions = $this->db->query(
424
+ // $this->db->prepare(
425
+ // "UPDATE IGNORE {$this->prefix}options SET option_name= replace(option_name, %s, %s) WHERE option_name LIKE %s" . $where, $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
426
+ // )
427
+ // );
428
+ //
429
+ // if( !$updateOptions ) {
430
+ // $this->log( "Preparing Data Step4: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_ERROR );
431
+ // $this->returnException( "Data Crunching Step 4: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
432
+ // return false;
433
+ // }
434
+
435
+ return true;
436
+ }
437
+
438
+ /**
439
+ * Update $table_prefix in wp-config.php
440
+ * @return bool
441
+ */
442
+ protected function step5() {
443
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
444
+
445
+ $this->log( "Preparing Data Step5: Updating table_prefix in {$path} to " . $this->prefix );
446
+ if( false === ($content = file_get_contents( $path )) ) {
447
+ $this->log( "Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR );
448
+ return false;
449
+ }
450
+
451
+ // Replace table prefix
452
+ $content = str_replace( '$table_prefix', '$table_prefix = \'' . $this->prefix . '\';//', $content );
453
+
454
+ // Replace URLs
455
+ $content = str_replace( $this->multisiteHomeUrl, $this->multisiteHomeUrl . '/' . $this->options->cloneDirectoryName, $content );
456
+
457
+ if( false === @file_put_contents( $path, $content ) ) {
458
+ $this->log( "Preparing Data Step5: Failed to update $table_prefix in {$path} to " . $this->prefix . ". Can't save contents", Logger::TYPE_ERROR );
459
+ return false;
460
+ }
461
+
462
+ return true;
463
+ }
464
+
465
+ /**
466
+ * Reset index.php to original file
467
+ * This is needed if live site is located in subfolder
468
+ * Check first if main wordpress is used in subfolder and index.php in parent directory
469
+ * @see: https://codex.wordpress.org/Giving_WordPress_Its_Own_Directory
470
+ * @return bool
471
+ */
472
+ protected function step6() {
473
+
474
+ if( !$this->isSubDir() ) {
475
+ $this->debugLog( "Preparing Data Step6: WP installation is not in a subdirectory! All good, skipping this step" );
476
+ return true;
477
+ }
478
+
479
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/index.php";
480
+
481
+ if( false === ($content = file_get_contents( $path )) ) {
482
+ $this->log( "Preparing Data Step6: Failed to reset {$path} for sub directory; can't read contents", Logger::TYPE_ERROR );
483
+ return false;
484
+ }
485
+
486
+
487
+ if( !preg_match( "/(require(.*)wp-blog-header.php' \);)/", $content, $matches ) ) {
488
+ $this->log(
489
+ "Preparing Data Step6: Failed to reset index.php for sub directory; wp-blog-header.php is missing", Logger::TYPE_ERROR
490
+ );
491
+ return false;
492
+ }
493
+ $this->log( "Preparing Data: WP installation is in a subdirectory. Progressing..." );
494
+
495
+ $pattern = "/require(.*) dirname(.*) __FILE__ (.*) \. '(.*)wp-blog-header.php'(.*);/";
496
+
497
+ $replace = "require( dirname( __FILE__ ) . '/wp-blog-header.php' ); // " . $matches[0];
498
+ $replace.= " // Changed by WP-Staging";
499
+
500
+
501
+
502
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
503
+ $this->log( "Preparing Data: Failed to reset index.php for sub directory; replacement failed", Logger::TYPE_ERROR );
504
+ return false;
505
+ }
506
+
507
+ if( false === @file_put_contents( $path, $content ) ) {
508
+ $this->log( "Preparing Data: Failed to reset index.php for sub directory; can't save contents", Logger::TYPE_ERROR );
509
+ return false;
510
+ }
511
+ $this->Log( "Preparing Data: Finished Step 6 successfully" );
512
+ return true;
513
+ }
514
+
515
+ /**
516
+ * Update wpstg_rmpermalinks_executed
517
+ * @return bool
518
+ */
519
+ protected function step7() {
520
+
521
+ $this->log( "Preparing Data Step7: Updating wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}" );
522
+
523
+ // Skip - Table does not exist
524
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
525
+ return true;
526
+ }
527
+
528
+ // Skip - Table is not selected or updated
529
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
530
+ $this->log("Preparing Data Step7: Skipping");
531
+ return true;
532
+ }
533
+
534
+ $result = $this->db->query(
535
+ $this->db->prepare(
536
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_rmpermalinks_executed'", ' '
537
+ )
538
+ );
539
+
540
+ // All good
541
+ if( $result ) {
542
+ $this->Log( "Preparing Data Step7: Finished Step 7 successfully" );
543
+ return true;
544
+ }
545
+
546
+ $this->log( "Failed to update wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_WARNING );
547
+ return true;
548
+ }
549
+
550
+ /**
551
+ * Update permalink_structure
552
+ * @return bool
553
+ */
554
+ protected function step8() {
555
+
556
+ $this->log( "Preparing Data Step8: Updating permalink_structure in {$this->prefix}options {$this->db->last_error}" );
557
+
558
+ // Skip - Table does not exist
559
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
560
+ return true;
561
+ }
562
+
563
+ // Skip - Table is not selected or updated
564
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
565
+ $this->log( "Preparing Data Step8: Skipping" );
566
+ return true;
567
+ }
568
+
569
+ $result = $this->db->query(
570
+ $this->db->prepare(
571
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'permalink_structure'", ' '
572
+ )
573
+ );
574
+
575
+ // All good
576
+ if( $result ) {
577
+ $this->Log( "Preparing Data Step8: Finished Step 8 successfully" );
578
+ return true;
579
+ }
580
+
581
+ $this->log( "Failed to update permalink_structure in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
582
+ return true;
583
+ }
584
+
585
+ /**
586
+ * Update blog_public option to not allow staging site to be indexed by search engines
587
+ * @return bool
588
+ */
589
+ protected function step9() {
590
+
591
+ $this->log( "Preparing Data Step9: Set staging site to noindex" );
592
+
593
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
594
+ return true;
595
+ }
596
+
597
+ // Skip - Table is not selected or updated
598
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
599
+ $this->log( "Preparing Data Step9: Skipping" );
600
+ return true;
601
+ }
602
+
603
+ $result = $this->db->query(
604
+ $this->db->prepare(
605
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'blog_public'", '0'
606
+ )
607
+ );
608
+
609
+ // All good
610
+ if( $result ) {
611
+ $this->Log( "Preparing Data Step9: Finished Step 9 successfully" );
612
+ return true;
613
+ }
614
+
615
+ $this->log( "Can not update staging site to noindex. Possible already done!", Logger::TYPE_WARNING );
616
+ return true;
617
+ }
618
+
619
+ /**
620
+ * Update WP_HOME in wp-config.php
621
+ * @return bool
622
+ */
623
+ protected function step10() {
624
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
625
+
626
+ $this->log( "Preparing Data Step10: Updating WP_HOME in wp-config.php to " . $this->getStagingSiteUrl() );
627
+
628
+ if( false === ($content = file_get_contents( $path )) ) {
629
+ $this->log( "Preparing Data Step10: Failed to update WP_HOME in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
630
+ return false;
631
+ }
632
+
633
+
634
+ // Get WP_HOME from wp-config.php
635
+ preg_match( "/define\s*\(\s*'WP_HOME'\s*,\s*(.*)\s*\);/", $content, $matches );
636
+
637
+ if( !empty( $matches[1] ) ) {
638
+ $matches[1];
639
+
640
+ $pattern = "/define\s*\(\s*'WP_HOME'\s*,\s*(.*)\s*\);/";
641
+
642
+ $replace = "define('WP_HOME','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
643
+ $replace.= " // Changed by WP-Staging";
644
+
645
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
646
+ $this->log( "Preparing Data: Failed to reset index.php for sub directory; replacement failed", Logger::TYPE_ERROR );
647
+ return false;
648
+ }
649
+ } else {
650
+ $this->log( "Preparing Data Step10: WP_HOME not defined in wp-config.php. Skipping this step." );
651
+ }
652
+
653
+ if( false === @file_put_contents( $path, $content ) ) {
654
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR );
655
+ return false;
656
+ }
657
+ $this->Log( "Preparing Data: Finished Step 11 successfully" );
658
+ return true;
659
+ }
660
+
661
+ /**
662
+ * Update WP_SITEURL in wp-config.php
663
+ * @return bool
664
+ */
665
+ protected function step11() {
666
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
667
+
668
+ $this->log( "Preparing Data Step11: Updating WP_SITEURL in wp-config.php to " . $this->getStagingSiteUrl() );
669
+
670
+ if( false === ($content = file_get_contents( $path )) ) {
671
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
672
+ return false;
673
+ }
674
+
675
+
676
+ // Get WP_SITEURL from wp-config.php
677
+ preg_match( "/define\s*\(\s*'WP_SITEURL'\s*,\s*(.*)\s*\);/", $content, $matches );
678
+
679
+ if( !empty( $matches[1] ) ) {
680
+ $matches[1];
681
+
682
+ $pattern = "/define\s*\(\s*'WP_SITEURL'\s*,\s*(.*)\s*\);/";
683
+
684
+ $replace = "define('WP_SITEURL','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
685
+ $replace.= " // Changed by WP-Staging";
686
+
687
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
688
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL", Logger::TYPE_ERROR );
689
+ return false;
690
+ }
691
+ } else {
692
+ $this->log( "Preparing Data Step11: WP_SITEURL not defined in wp-config.php. Skipping this step." );
693
+ }
694
+
695
+
696
+ if( false === @file_put_contents( $path, $content ) ) {
697
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR );
698
+ return false;
699
+ }
700
+ $this->Log( "Preparing Data: Finished Step 11 successfully" );
701
+ return true;
702
+ }
703
+
704
+ /**
705
+ * Update WP_ALLOW_MULTISITE constant in wp-config.php
706
+ * @return bool
707
+ */
708
+ protected function step12() {
709
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
710
+
711
+ $this->log( "Preparing Data Step12: Updating WP_ALLOW_MULTISITE in wp-config.php to false" );
712
+
713
+ if( false === ($content = file_get_contents( $path )) ) {
714
+ $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
715
+ return false;
716
+ }
717
+
718
+
719
+ // Get WP_SITEURL from wp-config.php
720
+ preg_match( "/define\s*\(\s*'WP_ALLOW_MULTISITE'\s*,\s*(.*)\s*\);/", $content, $matches );
721
+
722
+ if( !empty( $matches[1] ) ) {
723
+ $matches[1];
724
+
725
+ $pattern = "/define\s*\(\s*'WP_ALLOW_MULTISITE'\s*,\s*(.*)\s*\);/";
726
+
727
+ $replace = "define('WP_ALLOW_MULTISITE',false); // " . $matches[1];
728
+ $replace.= " // Changed by WP-Staging";
729
+
730
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
731
+ $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE", Logger::TYPE_ERROR );
732
+ return false;
733
+ }
734
+ } else {
735
+ $this->log( "Preparing Data Step12: WP_ALLOW_MULTISITE not defined in wp-config.php. Skipping this step." );
736
+ }
737
+
738
+
739
+ if( false === @file_put_contents( $path, $content ) ) {
740
+ $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE. Can't save contents", Logger::TYPE_ERROR );
741
+ return false;
742
+ }
743
+ $this->Log( "Preparing Data: Finished Step 12 successfully" );
744
+ return true;
745
+ }
746
+
747
+ /**
748
+ * Update MULTISITE constant in wp-config.php
749
+ * @return bool
750
+ */
751
+ protected function step13() {
752
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
753
+
754
+ $this->log( "Preparing Data Step13: Updating MULTISITE in wp-config.php to false" );
755
+
756
+ if( false === ($content = file_get_contents( $path )) ) {
757
+ $this->log( "Preparing Data Step13: Failed to update MULTISITE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
758
+ return false;
759
+ }
760
+
761
+
762
+ // Get WP_SITEURL from wp-config.php
763
+ preg_match( "/define\s*\(\s*'MULTISITE'\s*,\s*(.*)\s*\);/", $content, $matches );
764
+
765
+ if( !empty( $matches[1] ) ) {
766
+ $matches[1];
767
+
768
+ $pattern = "/define\s*\(\s*'MULTISITE'\s*,\s*(.*)\s*\);/";
769
+
770
+ $replace = "define('MULTISITE',false); // " . $matches[1];
771
+ $replace.= " // Changed by WP-Staging";
772
+
773
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
774
+ $this->log( "Preparing Data Step13: Failed to update MULTISITE", Logger::TYPE_ERROR );
775
+ return false;
776
+ }
777
+ } else {
778
+ $this->log( "Preparing Data Step13: MULTISITE not defined in wp-config.php. Skipping this step." );
779
+ }
780
+
781
+
782
+ if( false === @file_put_contents( $path, $content ) ) {
783
+ $this->log( "Preparing Data Step13: Failed to update MULTISITE. Can't save contents", Logger::TYPE_ERROR );
784
+ return false;
785
+ }
786
+ $this->Log( "Preparing Data: Finished Step 13 successfully" );
787
+ return true;
788
+ }
789
+
790
+ /**
791
+ * Get active_sitewide_plugins from wp_sitemeta and active_plugins from subsite
792
+ * Merge both arrays and copy them to the staging site into active_plugins
793
+ */
794
+ protected function step14() {
795
+
796
+
797
+ $this->log( "Data Crunching Step 14: Updating active_plugins" );
798
+
799
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
800
+ $this->log( 'Data Crunching Step 14: Fatal Error ' . $this->prefix . 'options does not exist' );
801
+ $this->returnException( 'Data Crunching Step 8: Fatal Error ' . $this->prefix . 'options does not exist' );
802
+ return false;
803
+ }
804
+
805
+ // Skip - Table is not selected or updated
806
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
807
+ $this->log("Preparing Data Step14: Skipping");
808
+ return true;
809
+ }
810
+
811
+ // Get active_plugins value from sub site options table
812
+ $active_plugins = $this->db->get_var( "SELECT option_value FROM {$this->db->prefix}options WHERE option_name = 'active_plugins' " );
813
+
814
+ if( !$active_plugins ) {
815
+ $this->log( "Data Crunching Step 14: Option active_plugins are empty " );
816
+ $active_plugins = array();
817
+ }
818
+ // Get active_sitewide_plugins value from main multisite wp_sitemeta table
819
+ $active_sitewide_plugins = $this->db->get_var( "SELECT meta_value FROM {$this->db->base_prefix}sitemeta WHERE meta_key = 'active_sitewide_plugins' " );
820
+
821
+ if( !$active_sitewide_plugins ) {
822
+ $this->log( "Data Crunching Step 14: Options {$this->db->base_prefix}active_sitewide_plugins is empty " );
823
+ $active_sitewide_plugins = array();
824
+ }
825
+
826
+ $active_sitewide_plugins = unserialize( $active_sitewide_plugins );
827
+ $active_plugins = unserialize( $active_plugins );
828
+
829
+ $all_plugins = array_merge( $active_plugins, array_keys( $active_sitewide_plugins ) );
830
+
831
+ sort( $all_plugins );
832
+
833
+
834
+ // Update active_plugins
835
+ $update = $this->db->query(
836
+ "UPDATE {$this->prefix}options SET option_value = '" . serialize( $all_plugins ) . "' WHERE option_name = 'active_plugins'"
837
+ );
838
+
839
+ if( false === $update ) {
840
+ $this->log( "Data Crunching Step 14: Can not update option active_plugins in {$this->prefix}options", Logger::TYPE_WARNING );
841
+ //$this->returnException( "Data Crunching Step 8: Can not update row wpstg_version in {$this->tmpPrefix}options - db error: " . $this->db->last_error );
842
+ return false;
843
+ }
844
+
845
+ $this->log( "Data Crunching Step 14: Successfull!" );
846
+ return true;
847
+ }
848
+
849
+ /**
850
+ * Update Table Prefix in wp_options
851
+ * @return bool
852
+ */
853
+ protected function step15() {
854
+ $this->log( "Preparing Data Step15: Updating db prefix in {$this->prefix}options. Error: {$this->db->last_error}" );
855
+
856
+ // Skip - Table does not exist
857
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
858
+ return true;
859
+ }
860
+
861
+ // Skip - Table is not selected or updated
862
+ if (!in_array($this->prefix . 'options', $this->tables)){
863
+ $this->log("Preparing Data Step4: Skipping");
864
+ return true;
865
+ }
866
+
867
+
868
+ $this->log( "Updating db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
869
+
870
+ // Filter the rows below. Do not update them!
871
+ $filters = array(
872
+ 'wp_mail_smtp',
873
+ 'wp_mail_smtp_version',
874
+ 'wp_mail_smtp_debug',
875
+ );
876
+
877
+ $filters = apply_filters('wpstg_filter_options_replace', $filters);
878
+
879
+ $where = "";
880
+ foreach($filters as $filter){
881
+ $where .= " AND option_name <> '" . $filter . "'";
882
+ }
883
+
884
+ $updateOptions = $this->db->query(
885
+ $this->db->prepare(
886
+ "UPDATE IGNORE {$this->prefix}options SET option_name= replace(option_name, %s, %s) WHERE option_name LIKE %s" . $where, $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
887
+ )
888
+ );
889
+
890
+ if( !$updateOptions ) {
891
+ $this->log( "Preparing Data Step15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_ERROR );
892
+ $this->returnException( "Data Crunching Step 15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
893
+ return false;
894
+ }
895
+
896
+ return true;
897
+ }
898
+
899
+
900
+
901
+ /**
902
+ * Return URL to staging site
903
+ * @return string
904
+ */
905
+ protected function getStagingSiteUrl() {
906
+ if( $this->isSubDir() ) {
907
+ return rtrim( $this->multisiteHomeUrl, "/" ) . $this->getSubDir() . $this->options->cloneDirectoryName;
908
+ }
909
+
910
+ return rtrim( $this->multisiteHomeUrl, "/" ) . '/' . $this->options->cloneDirectoryName;
911
+ }
912
+
913
+ /**
914
+ * Check if WP is installed in subdir
915
+ * @return boolean
916
+ */
917
+ protected function isSubDir() {
918
+ if( get_option( 'siteurl' ) !== get_option( 'home' ) ) {
919
+ return true;
920
+ }
921
+ return false;
922
+ }
923
+
924
+ }
apps/Backend/Modules/Jobs/Multisite/Database.php ADDED
@@ -0,0 +1,315 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
+
5
+ // No Direct Access
6
+ if( !defined( "WPINC" ) ) {
7
+ die;
8
+ }
9
+
10
+ use WPStaging\WPStaging;
11
+ use WPStaging\Utils\Strings;
12
+ use WPStaging\Backend\Modules\Jobs\JobExecutable;
13
+
14
+ /**
15
+ * Class Database
16
+ * @package WPStaging\Backend\Modules\Jobs
17
+ */
18
+ class Database extends JobExecutable {
19
+
20
+ /**
21
+ * @var int
22
+ */
23
+ private $total = 0;
24
+
25
+ /**
26
+ * @var \WPDB
27
+ */
28
+ private $db;
29
+
30
+ /**
31
+ * Initialize
32
+ */
33
+ public function initialize() {
34
+ // Add 2 to total table count because we need to copy two more tables from the main multisite installation wp_users and wp_usermeta
35
+ $this->total = count( $this->options->tables ) + 2;
36
+ $this->db = WPStaging::getInstance()->get( "wpdb" );
37
+ $this->isFatalError();
38
+
39
+ }
40
+
41
+
42
+ /**
43
+ * Return fatal error and stops here if subfolder already exists
44
+ * and mainJob is not updating the clone
45
+ * @return boolean
46
+ */
47
+ private function isFatalError(){
48
+ $path = trailingslashit(get_home_path()) . $this->options->cloneDirectoryName;
49
+ if (isset($this->options->mainJob) && $this->options->mainJob !== 'updating' && is_dir($path)){
50
+ $this->returnException( " Can not continue! Change the name of the clone or delete existing folder. Then try again. Folder already exists: " . $path );
51
+ }
52
+ return false;
53
+ }
54
+
55
+ /**
56
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
57
+ * @return void
58
+ */
59
+ protected function calculateTotalSteps() {
60
+ $this->options->totalSteps = $this->total === 0 ? 1 : $this->total;
61
+ }
62
+
63
+ /**
64
+ * Execute the Current Step
65
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
66
+ * @return bool
67
+ */
68
+ protected function execute() {
69
+
70
+
71
+ // Over limits threshold
72
+ if( $this->isOverThreshold() ) {
73
+ // Prepare response and save current progress
74
+ $this->prepareResponse( false, false );
75
+ $this->saveOptions();
76
+ return false;
77
+ }
78
+
79
+ // No more steps, finished
80
+ //if( $this->options->currentStep > $this->total || !isset( $this->options->tables[$this->options->currentStep] ) ) {
81
+ if( $this->options->currentStep > $this->total ) {
82
+ $this->prepareResponse( true, false );
83
+ return false;
84
+ }
85
+
86
+ // Copy table
87
+ //if (!$this->copyTable($this->options->tables[$this->options->currentStep]->name))
88
+ if( isset($this->options->tables[$this->options->currentStep]) && !$this->copyTable( $this->options->tables[$this->options->currentStep] ) ) {
89
+ // Prepare Response
90
+ $this->prepareResponse( false, false );
91
+
92
+ // Not finished
93
+ return true;
94
+ }
95
+
96
+ $this->copyWpUsers();
97
+
98
+ $this->copyWpUsermeta();
99
+
100
+ // Prepare Response
101
+ $this->prepareResponse();
102
+
103
+ // Not finished
104
+ return true;
105
+ }
106
+
107
+ /**
108
+ * Get new prefix for the staging site
109
+ * @return string
110
+ */
111
+ private function getStagingPrefix() {
112
+ $stagingPrefix = $this->options->prefix;
113
+ // Make sure prefix of staging site is NEVER identical to prefix of live site!
114
+ if( $stagingPrefix == $this->db->prefix ) {
115
+ wp_die( 'Fatal error 7: The new database table prefix ' . $stagingPrefix . ' would be identical to the table prefix of the live site. Please open a support ticket at support@wp-staging.com' );
116
+ }
117
+ return $stagingPrefix;
118
+ }
119
+
120
+ /**
121
+ * No worries, SQL queries don't eat from PHP execution time!
122
+ * @param string $tableName
123
+ * @return bool
124
+ */
125
+ private function copyTable( $tableName ) {
126
+
127
+ $strings = new Strings();
128
+ $tableName = is_object( $tableName ) ? $tableName->name : $tableName;
129
+ $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->prefix, null, $tableName );
130
+
131
+ // Drop table if necessary
132
+ $this->dropTable( $newTableName );
133
+
134
+ // Save current job
135
+ $this->setJob( $newTableName );
136
+
137
+ // Beginning of the job
138
+ if( !$this->startJob( $newTableName, $tableName ) ) {
139
+ return true;
140
+ }
141
+
142
+ // Copy data
143
+ $this->copyData( $newTableName, $tableName );
144
+
145
+ // Finish the step
146
+ return $this->finishStep();
147
+ }
148
+
149
+ /**
150
+ * Copy multisite global user table wp_users to wpstgX_users
151
+ * @return bool
152
+ */
153
+ private function copyWpUsers() {
154
+ $strings = new Strings();
155
+ $tableName = $this->db->base_prefix . 'users';
156
+ $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->base_prefix, null, $tableName );
157
+
158
+ // Drop table if necessary
159
+ $this->dropTable( $newTableName );
160
+
161
+ // Save current job
162
+ $this->setJob( $newTableName );
163
+
164
+ // Beginning of the job
165
+ if( !$this->startJob( $newTableName, $tableName ) ) {
166
+ return true;
167
+ }
168
+
169
+ // Copy data
170
+ $this->copyData( $newTableName, $tableName );
171
+
172
+ // Finish the step
173
+ return $this->finishStep();
174
+ }
175
+
176
+ /**
177
+ * Copy multisite global user table wp_usermeta to wpstgX_users
178
+ * @return bool
179
+ */
180
+ private function copyWpUsermeta() {
181
+ $strings = new Strings();
182
+ $tableName = $this->db->base_prefix . 'usermeta';
183
+ $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->base_prefix, null, $tableName );
184
+
185
+ // Drop table if necessary
186
+ $this->dropTable( $newTableName );
187
+
188
+ // Save current job
189
+ $this->setJob( $newTableName );
190
+
191
+ // Beginning of the job
192
+ if( !$this->startJob( $newTableName, $tableName ) ) {
193
+ return true;
194
+ }
195
+ // Copy data
196
+ $this->copyData( $newTableName, $tableName );
197
+
198
+ // Finish the step
199
+ return $this->finishStep();
200
+ }
201
+
202
+ /**
203
+ * Copy data from old table to new table
204
+ * @param string $new
205
+ * @param string $old
206
+ */
207
+ private function copyData( $new, $old ) {
208
+ $rows = $this->options->job->start + $this->settings->queryLimit;
209
+ $this->log(
210
+ "DB Copy: {$old} as {$new} from {$this->options->job->start} to {$rows} records"
211
+ );
212
+
213
+ $limitation = '';
214
+
215
+ if( 0 < ( int ) $this->settings->queryLimit ) {
216
+ $limitation = " LIMIT {$this->settings->queryLimit} OFFSET {$this->options->job->start}";
217
+ }
218
+
219
+ $this->db->query(
220
+ "INSERT INTO {$new} SELECT * FROM {$old} {$limitation}"
221
+ );
222
+
223
+ // Set new offset
224
+ $this->options->job->start += $this->settings->queryLimit;
225
+ }
226
+
227
+ /**
228
+ * Set the job
229
+ * @param string $table
230
+ */
231
+ private function setJob( $table ) {
232
+ if( isset( $this->options->job->current ) ) {
233
+ return;
234
+ }
235
+
236
+ $this->options->job->current = $table;
237
+ $this->options->job->start = 0;
238
+ }
239
+
240
+ /**
241
+ * Start Job
242
+ * @param string $new
243
+ * @param string $old
244
+ * @return bool
245
+ */
246
+ private function startJob( $new, $old ) {
247
+ if( 0 != $this->options->job->start ) {
248
+ return true;
249
+ }
250
+
251
+ $this->log( "DB Copy: Creating table {$new}" );
252
+
253
+ $this->db->query( "CREATE TABLE {$new} LIKE {$old}" );
254
+
255
+ $this->options->job->total = ( int ) $this->db->get_var( "SELECT COUNT(1) FROM {$old}" );
256
+
257
+ if( 0 == $this->options->job->total ) {
258
+ $this->finishStep();
259
+ return false;
260
+ }
261
+
262
+ return true;
263
+ }
264
+
265
+ /**
266
+ * Finish the step
267
+ */
268
+ private function finishStep() {
269
+ // This job is not finished yet
270
+ if( $this->options->job->total > $this->options->job->start ) {
271
+ return false;
272
+ }
273
+
274
+ // Add it to cloned tables listing
275
+ $this->options->clonedTables[] = isset($this->options->tables[$this->options->currentStep]) ? $this->options->tables[$this->options->currentStep] : false;
276
+
277
+ // Reset job
278
+ $this->options->job = new \stdClass();
279
+
280
+ return true;
281
+ }
282
+
283
+ /**
284
+ * Drop table if necessary
285
+ * @param string $new
286
+ */
287
+ private function dropTable( $new ) {
288
+ $old = $this->db->get_var( $this->db->prepare( "SHOW TABLES LIKE %s", $new ) );
289
+
290
+ if( !$this->shouldDropTable( $new, $old ) ) {
291
+ return;
292
+ }
293
+
294
+ $this->log( "DB Copy: {$new} already exists, dropping it first" );
295
+ $this->db->query( "DROP TABLE {$new}" );
296
+ }
297
+
298
+ /**
299
+ * Check if table needs to be dropped
300
+ * @param string $new
301
+ * @param string $old
302
+ * @return bool
303
+ */
304
+ private function shouldDropTable( $new, $old ) {
305
+ return (
306
+ $old === $new &&
307
+ (
308
+ !isset( $this->options->job->current ) ||
309
+ !isset( $this->options->job->start ) ||
310
+ 0 == $this->options->job->start
311
+ )
312
+ );
313
+ }
314
+
315
+ }
apps/Backend/Modules/Jobs/Multisite/Directories.php ADDED
@@ -0,0 +1,602 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
+
5
+ // No Direct Access
6
+ if( !defined( "WPINC" ) ) {
7
+ die;
8
+ }
9
+
10
+ use WPStaging\WPStaging;
11
+ use WPStaging\Utils\Logger;
12
+ use WPStaging\Utils\Strings;
13
+ use WPStaging\Iterators\RecursiveDirectoryIterator;
14
+ use WPStaging\Iterators\RecursiveFilterNewLine;
15
+ use WPStaging\Iterators\RecursiveFilterExclude;
16
+ use WPStaging\Backend\Modules\Jobs\JobExecutable;
17
+
18
+ /**
19
+ * Class Files
20
+ * @package WPStaging\Backend\Modules\Directories
21
+ */
22
+ class Directories extends JobExecutable {
23
+
24
+ /**
25
+ * @var array
26
+ */
27
+ private $files = array();
28
+
29
+ /**
30
+ * Total steps to do
31
+ * @var int
32
+ */
33
+ private $total = 5;
34
+
35
+ /**
36
+ * path to the cache file
37
+ * @var string
38
+ */
39
+ private $filename;
40
+
41
+ /**
42
+ * Initialize
43
+ */
44
+ public function initialize() {
45
+ $this->filename = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
46
+ }
47
+
48
+ /**
49
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
50
+ * @return void
51
+ */
52
+ protected function calculateTotalSteps() {
53
+
54
+ $this->options->totalSteps = $this->total + count( $this->options->extraDirectories );
55
+ }
56
+
57
+ /**
58
+ * Start Module
59
+ * @return object
60
+ */
61
+ public function start() {
62
+
63
+ // Execute steps
64
+ $this->run();
65
+
66
+ // Save option, progress
67
+ $this->saveProgress();
68
+
69
+ return ( object ) $this->response;
70
+ }
71
+
72
+ /**
73
+ * Step 0
74
+ * Get WP Root files
75
+ * Does not collect any sub folders
76
+ */
77
+ private function getWpRootFiles() {
78
+
79
+ // open file handle
80
+ $files = $this->open( $this->filename, 'a' );
81
+
82
+
83
+ try {
84
+
85
+ // Iterate over wp root directory
86
+ $iterator = new \DirectoryIterator( ABSPATH );
87
+
88
+ $this->log( "Scanning / for files" );
89
+
90
+ // Write path line
91
+ foreach ( $iterator as $item ) {
92
+ if( !$item->isDot() && $item->isFile() ) {
93
+ if( $this->write( $files, $iterator->getFilename() . PHP_EOL ) ) {
94
+ $this->options->totalFiles++;
95
+
96
+ // Add current file size
97
+ $this->options->totalFileSize += $iterator->getSize();
98
+ }
99
+ }
100
+ }
101
+ } catch ( \Exception $e ) {
102
+ $this->returnException( 'Error: ' . $e->getMessage() );
103
+ //throw new \Exception('Out of disk space.');
104
+ } catch ( \Exception $e ) {
105
+ // Skip bad file permissions
106
+ }
107
+
108
+ $this->close( $files );
109
+ return true;
110
+ }
111
+
112
+ /**
113
+ * Step 2
114
+ * Get WP Content Files without multisite folder wp-content/uploads/sites
115
+ */
116
+ private function getWpContentFiles() {
117
+
118
+ // Skip it
119
+ if( $this->isDirectoryExcluded( WP_CONTENT_DIR ) ) {
120
+ return true;
121
+ }
122
+
123
+
124
+ // open file handle
125
+ $files = $this->open( $this->filename, 'a' );
126
+
127
+ /**
128
+ * Excluded folders relative to the folder to iterate
129
+ */
130
+ $excludePaths = array(
131
+ 'cache',
132
+ 'plugins' . DIRECTORY_SEPARATOR . 'wps-hide-login',
133
+ 'uploads' . DIRECTORY_SEPARATOR . 'sites'
134
+ );
135
+
136
+ /**
137
+ * Get user excluded folders
138
+ */
139
+ $directory = array();
140
+ foreach ($this->options->excludedDirectories as $dir){
141
+ if( strpos($dir, WP_CONTENT_DIR) !== false){
142
+ $directory[] = ltrim(str_replace(WP_CONTENT_DIR, '', $dir), '/');
143
+ }
144
+ }
145
+
146
+ $excludePaths = array_merge( $excludePaths, $directory );
147
+
148
+ // $excludeFolders = array(
149
+ // 'cache',
150
+ // 'node_modules',
151
+ // 'nbproject',
152
+ // 'wps-hide-login'
153
+ // );
154
+
155
+ try {
156
+
157
+ // Iterate over content directory
158
+ $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( WP_CONTENT_DIR );
159
+
160
+ // Exclude new line file names
161
+ $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
162
+
163
+ // Exclude sites, uploads, plugins or themes
164
+ $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, $excludePaths);
165
+ // Recursively iterate over content directory
166
+ $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
167
+
168
+ $this->log( "Scanning /wp-content for its sub-directories and files" );
169
+
170
+ // Write path line
171
+ foreach ( $iterator as $item ) {
172
+ if( $item->isFile() ) {
173
+ if( $this->write( $files, 'wp-content' . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
174
+ $this->options->totalFiles++;
175
+
176
+ // Add current file size
177
+ $this->options->totalFileSize += $iterator->getSize();
178
+ }
179
+ }
180
+ }
181
+ } catch ( \Exception $e ) {
182
+ //$this->returnException('Out of disk space.');
183
+ throw new \Exception( 'Error: ' . $e->getMessage() );
184
+ } catch ( \Exception $e ) {
185
+ // Skip bad file permissions
186
+ }
187
+
188
+ // close the file handler
189
+ $this->close( $files );
190
+ return true;
191
+ }
192
+
193
+ /**
194
+ * Step 2
195
+ * @return boolean
196
+ * @throws \Exception
197
+ */
198
+ private function getWpIncludesFiles() {
199
+
200
+ // Skip it
201
+ if( $this->isDirectoryExcluded( ABSPATH . 'wp-includes' . DIRECTORY_SEPARATOR ) ) {
202
+ return true;
203
+ }
204
+
205
+ // open file handle and attach data to end of file
206
+ $files = $this->open( $this->filename, 'a' );
207
+
208
+ try {
209
+
210
+ // Iterate over wp-admin directory
211
+ $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( ABSPATH . 'wp-includes' . DIRECTORY_SEPARATOR );
212
+
213
+ // Exclude new line file names
214
+ $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
215
+
216
+ // Exclude uploads, plugins or themes
217
+ // Recursively iterate over wp-includes directory
218
+ $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
219
+
220
+ $this->log( "Scanning /wp-includes for its sub-directories and files" );
221
+
222
+ // Write files
223
+ foreach ( $iterator as $item ) {
224
+ if( $item->isFile() ) {
225
+ if( $this->write( $files, 'wp-includes' . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
226
+ $this->options->totalFiles++;
227
+
228
+ // Add current file size
229
+ $this->options->totalFileSize += $iterator->getSize();
230
+ }
231
+ }
232
+ }
233
+ } catch ( \Exception $e ) {
234
+ //$this->returnException('Out of disk space.');
235
+ throw new \Exception( 'Error: ' . $e->getMessage() );
236
+ } catch ( \Exception $e ) {
237
+ // Skip bad file permissions
238
+ }
239
+
240
+ // close the file handler
241
+ $this->close( $files );
242
+ return true;
243
+ }
244
+
245
+ /**
246
+ * Step 3
247
+ * @return boolean
248
+ * @throws \Exception
249
+ */
250
+ private function getWpAdminFiles() {
251
+
252
+ // Skip it
253
+ if( $this->isDirectoryExcluded( ABSPATH . 'wp-admin' . DIRECTORY_SEPARATOR ) ) {
254
+ return true;
255
+ }
256
+
257
+ // open file handle and attach data to end of file
258
+ $files = $this->open( $this->filename, 'a' );
259
+
260
+ try {
261
+
262
+ // Iterate over wp-admin directory
263
+ $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( ABSPATH . 'wp-admin' . DIRECTORY_SEPARATOR );
264
+
265
+ // Exclude new line file names
266
+ $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
267
+
268
+ // Exclude uploads, plugins or themes
269
+ // Recursively iterate over content directory
270
+ $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
271
+
272
+ $this->log( "Scanning /wp-admin for its sub-directories and files" );
273
+
274
+ // Write path line
275
+ foreach ( $iterator as $item ) {
276
+ if( $item->isFile() ) {
277
+ if( $this->write( $files, 'wp-admin' . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
278
+ $this->options->totalFiles++;
279
+ // Add current file size
280
+ $this->options->totalFileSize += $iterator->getSize();
281
+ }
282
+ }
283
+ }
284
+ } catch ( \Exception $e ) {
285
+ $this->returnException( 'Error: ' . $e->getMessage() );
286
+ //throw new \Exception('Error: ' . $e->getMessage());
287
+ } catch ( \Exception $e ) {
288
+ // Skip bad file permissions
289
+ }
290
+
291
+ // close the file handler
292
+ $this->close( $files );
293
+ return true;
294
+ }
295
+
296
+ /**
297
+ * Step 4
298
+ * Get WP Content Uploads Files multisite folder wp-content/uploads/sites
299
+ */
300
+ private function getWpContentUploadsSites() {
301
+
302
+ if(is_main_site()){
303
+ return true;
304
+ }
305
+
306
+ $blogId = get_current_blog_id();
307
+
308
+ $path = WP_CONTENT_DIR . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . $blogId . DIRECTORY_SEPARATOR;
309
+
310
+ // Skip it
311
+ if( $this->isDirectoryExcluded( $path ) ) {
312
+ return true;
313
+ }
314
+
315
+
316
+ // open file handle
317
+ $files = $this->open( $this->filename, 'a' );
318
+
319
+ /**
320
+ * Excluded folders relative to the folder to iterate
321
+ */
322
+ $excludePaths = array(
323
+ 'cache',
324
+ 'plugins' . DIRECTORY_SEPARATOR . 'wps-hide-login',
325
+ 'uploads' . DIRECTORY_SEPARATOR . 'sites'
326
+ );
327
+
328
+ /**
329
+ * Get user excluded folders
330
+ */
331
+ $directory = array();
332
+ foreach ($this->options->excludedDirectories as $dir){
333
+ if( strpos($dir, $path) !== false){
334
+ $directory[] = ltrim(str_replace($path, '', $dir), '/');
335
+ }
336
+ }
337
+
338
+ $excludePaths = array_merge( $excludePaths, $directory );
339
+
340
+ try {
341
+
342
+ // Iterate over content directory
343
+ $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( $path );
344
+
345
+ // Exclude new line file names
346
+ $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
347
+
348
+ // Exclude sites, uploads, plugins or themes
349
+ $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, $excludePaths);
350
+ // Recursively iterate over content directory
351
+ $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
352
+ $this->log( "Scanning /wp-content/uploads/sites/{$blogId} for its sub-directories and files" );
353
+
354
+ // Write path line
355
+ foreach ( $iterator as $item ) {
356
+ if( $item->isFile() ) {
357
+ $test = $iterator->getSubPathName();
358
+ if( $this->write( $files, 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . $blogId . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
359
+ $this->options->totalFiles++;
360
+
361
+ // Add current file size
362
+ $this->options->totalFileSize += $iterator->getSize();
363
+ }
364
+ }
365
+ }
366
+ } catch ( \Exception $e ) {
367
+ //$this->returnException('Out of disk space.');
368
+ throw new \Exception( 'Error: ' . $e->getMessage() );
369
+ } catch ( \Exception $e ) {
370
+ // Skip bad file permissions
371
+ }
372
+
373
+ // close the file handler
374
+ $this->close( $files );
375
+ return true;
376
+ }
377
+
378
+ /**
379
+ * Step 5 - x
380
+ * Get extra folders of the wp root level
381
+ * Does not collect wp-includes, wp-admin and wp-content folder
382
+ */
383
+ private function getExtraFiles( $folder ) {
384
+
385
+
386
+ // open file handle and attach data to end of file
387
+ $files = $this->open( $this->filename, 'a' );
388
+
389
+ try {
390
+
391
+ // Iterate over wp-admin directory
392
+ $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( $folder );
393
+
394
+ // Exclude new line file names
395
+ $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
396
+
397
+ // Exclude wp core folders
398
+ // $exclude = array('wp-includes',
399
+ // 'wp-admin',
400
+ // 'wp-content');
401
+ //
402
+ // $excludeMore = array();
403
+ // foreach ($this->options->excludedDirectories as $key => $value){
404
+ // $excludeMore[] = $this->getLastElemAfterString('/', $value);
405
+ // }
406
+ //$exclude = array_merge($exclude, $excludeMore);
407
+
408
+ $exclude = array();
409
+
410
+ $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, $exclude );
411
+ // Recursively iterate over content directory
412
+ $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
413
+
414
+ $strings = new Strings();
415
+ $this->log( "Scanning {$strings->getLastElemAfterString( '/', $folder )} for its sub-directories and files" );
416
+
417
+ // Write path line
418
+ foreach ( $iterator as $item ) {
419
+ if( $item->isFile() ) {
420
+ if( $this->write( $files, $strings->getLastElemAfterString( '/', $folder ) . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
421
+ $this->options->totalFiles++;
422
+ // Add current file size
423
+ $this->options->totalFileSize += $iterator->getSize();
424
+ }
425
+ }
426
+ }
427
+ } catch ( \Exception $e ) {
428
+ $this->returnException( 'Error: ' . $e->getMessage() );
429
+ } catch ( \Exception $e ) {
430
+ // Skip bad file permissions
431
+ }
432
+
433
+ // close the file handler
434
+ $this->close( $files );
435
+ return true;
436
+
437
+ }
438
+
439
+ /**
440
+ * Closes a file handle
441
+ *
442
+ * @param resource $handle File handle to close
443
+ * @return boolean
444
+ */
445
+ public function close( $handle ) {
446
+ return @fclose( $handle );
447
+ }
448
+
449
+ /**
450
+ * Opens a file in specified mode
451
+ *
452
+ * @param string $file Path to the file to open
453
+ * @param string $mode Mode in which to open the file
454
+ * @return resource
455
+ * @throws Exception
456
+ */
457
+ public function open( $file, $mode ) {
458
+
459
+ $file_handle = @fopen( $file, $mode );
460
+ if( false === $file_handle ) {
461
+ $this->returnException( sprintf( __( 'Unable to open %s with mode %s', 'wpstg' ), $file, $mode ) );
462
+ //throw new Exception(sprintf(__('Unable to open %s with mode %s', 'wpstg'), $file, $mode));
463
+ }
464
+
465
+ return $file_handle;
466
+ }
467
+
468
+ /**
469
+ * Write contents to a file
470
+ *
471
+ * @param resource $handle File handle to write to
472
+ * @param string $content Contents to write to the file
473
+ * @return integer
474
+ * @throws Exception
475
+ * @throws Exception
476
+ */
477
+ public function write( $handle, $content ) {
478
+ $write_result = @fwrite( $handle, $content );
479
+ if( false === $write_result ) {
480
+ if( ( $meta = \stream_get_meta_data( $handle ) ) ) {
481
+ //$this->returnException(sprintf(__('Unable to write to: %s', 'wpstg'), $meta['uri']));
482
+ throw new \Exception( sprintf( __( 'Unable to write to: %s', 'wpstg' ), $meta['uri'] ) );
483
+ }
484
+ } elseif( strlen( $content ) !== $write_result ) {
485
+ //$this->returnException(__('Out of disk space.', 'wpstg'));
486
+ throw new \Exception( __( 'Out of disk space.', 'wpstg' ) );
487
+ }
488
+
489
+ return $write_result;
490
+ }
491
+
492
+ /**
493
+ * Execute the Current Step
494
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
495
+ * @return bool
496
+ */
497
+ protected function execute() {
498
+
499
+ // No job left to execute
500
+ if( $this->isFinished() ) {
501
+ $this->prepareResponse( true, false );
502
+ return false;
503
+ }
504
+
505
+
506
+ if( $this->options->currentStep == 0 ) {
507
+ $this->getWpRootFiles();
508
+ $this->prepareResponse( false, true );
509
+ return false;
510
+ }
511
+
512
+ if( $this->options->currentStep == 1 ) {
513
+ $this->getWpContentFiles();
514
+ $this->prepareResponse( false, true );
515
+ return false;
516
+ }
517
+
518
+ if( $this->options->currentStep == 2 ) {
519
+ $this->getWpIncludesFiles();
520
+ $this->prepareResponse( false, true );
521
+ return false;
522
+ }
523
+
524
+ if( $this->options->currentStep == 3 ) {
525
+ $this->getWpAdminFiles();
526
+ $this->prepareResponse( false, true );
527
+ return false;
528
+ }
529
+
530
+ if( $this->options->currentStep == 4 ) {
531
+ $this->getWpContentUploadsSites();
532
+ $this->prepareResponse( false, true );
533
+ return false;
534
+ }
535
+
536
+ if( isset( $this->options->extraDirectories[$this->options->currentStep - $this->total] ) ) {
537
+ $this->getExtraFiles( $this->options->extraDirectories[$this->options->currentStep - $this->total] );
538
+ $this->prepareResponse( false, true );
539
+ return false;
540
+ }
541
+
542
+
543
+ // Prepare response
544
+ $this->prepareResponse( false, true );
545
+ // Not finished
546
+ return true;
547
+ }
548
+
549
+ /**
550
+ * Checks Whether There is Any Job to Execute or Not
551
+ * @return bool
552
+ */
553
+ protected function isFinished() {
554
+ if( $this->options->currentStep >= $this->options->totalSteps ) {
555
+ return true;
556
+ }
557
+
558
+ // return (
559
+ // //$this->options->currentStep > $this->total ||
560
+ // $this->options->currentStep >= $this->options->totalSteps
561
+ // );
562
+ }
563
+
564
+ /**
565
+ * Save files
566
+ * @return bool
567
+ */
568
+ protected function saveProgress() {
569
+ return $this->saveOptions();
570
+ }
571
+
572
+ /**
573
+ * Get files
574
+ * @return void
575
+ */
576
+ protected function getFiles() {
577
+ $fileName = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
578
+
579
+ if( false === ($this->files = @file_get_contents( $fileName )) ) {
580
+ $this->files = array();
581
+ return;
582
+ }
583
+
584
+ $this->files = explode( PHP_EOL, $this->files );
585
+ }
586
+
587
+ /**
588
+ * Check if directory is excluded
589
+ * @param string $directory
590
+ * @return bool
591
+ */
592
+ protected function isDirectoryExcluded( $directory ) {
593
+ foreach ( $this->options->excludedDirectories as $excludedDirectory ) {
594
+ if( strpos( $directory, $excludedDirectory ) === 0 ) {
595
+ return true;
596
+ }
597
+ }
598
+
599
+ return false;
600
+ }
601
+
602
+ }
apps/Backend/Modules/Jobs/Multisite/Files.php ADDED
@@ -0,0 +1,336 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
+ use WPStaging\Backend\Modules\Jobs\JobExecutable;
5
+
6
+ // No Direct Access
7
+ use WPStaging\Utils\Logger;
8
+
9
+ if (!defined("WPINC")) {
10
+ die;
11
+ }
12
+
13
+ /**
14
+ * Class Files
15
+ * @package WPStaging\Backend\Modules\Jobs
16
+ */
17
+ class Files extends JobExecutable {
18
+
19
+ /**
20
+ * @var \SplFileObject
21
+ */
22
+ private $file;
23
+
24
+ /**
25
+ * @var int
26
+ */
27
+ private $maxFilesPerRun;
28
+
29
+ /**
30
+ * @var string
31
+ */
32
+ private $destination;
33
+
34
+ /**
35
+ * Initialization
36
+ */
37
+ public function initialize() {
38
+
39
+ $this->destination = ABSPATH . $this->options->cloneDirectoryName . DIRECTORY_SEPARATOR;
40
+
41
+ $filePath = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
42
+
43
+ if (is_file($filePath)) {
44
+ $this->file = new \SplFileObject($filePath, 'r');
45
+ }
46
+
47
+ // Informational logs
48
+ if (0 == $this->options->currentStep) {
49
+ $this->log("Copying files...");
50
+ }
51
+
52
+ $this->settings->batchSize = $this->settings->batchSize * 1000000;
53
+ $this->maxFilesPerRun = $this->settings->fileLimit;
54
+ }
55
+
56
+ /**
57
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
58
+ * @return void
59
+ */
60
+ protected function calculateTotalSteps() {
61
+ $this->options->totalSteps = ceil($this->options->totalFiles / $this->maxFilesPerRun);
62
+ }
63
+
64
+ /**
65
+ * Execute the Current Step
66
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
67
+ * @return bool
68
+ */
69
+ protected function execute() {
70
+ // Finished
71
+ if ($this->isFinished()) {
72
+ $this->log("Copying files finished");
73
+ $this->prepareResponse(true, false);
74
+ return false;
75
+ }
76
+
77
+ // Get files and copy'em
78
+ if (!$this->getFilesAndCopy()) {
79
+ $this->prepareResponse(false, false);
80
+ return false;
81
+ }
82
+
83
+ // Prepare and return response
84
+ $this->prepareResponse();
85
+
86
+ // Not finished
87
+ return true;
88
+ }
89
+
90
+ /**
91
+ * Get files and copy
92
+ * @return bool
93
+ */
94
+ private function getFilesAndCopy() {
95
+ // Over limits threshold
96
+ if ($this->isOverThreshold()) {
97
+ // Prepare response and save current progress
98
+ $this->prepareResponse(false, false);
99
+ $this->saveOptions();
100
+ return false;
101
+ }
102
+
103
+ // Go to last copied line and than to next one
104
+ //if ($this->options->copiedFiles != 0) {
105
+ if (isset($this->options->copiedFiles) && $this->options->copiedFiles != 0) {
106
+ $this->file->seek($this->options->copiedFiles - 1);
107
+ }
108
+
109
+ $this->file->setFlags(\SplFileObject::SKIP_EMPTY | \SplFileObject::READ_AHEAD);
110
+
111
+
112
+ // Loop x files at a time
113
+ for ($i = 0; $i < $this->maxFilesPerRun; $i++) {
114
+
115
+ // Increment copied files
116
+ // Do this anytime to make sure to not stuck in the same step / files
117
+ $this->options->copiedFiles++;
118
+
119
+ // End of file
120
+ if ($this->file->eof()) {
121
+ break;
122
+ }
123
+
124
+
125
+ $file = $this->file->fgets();
126
+
127
+ $this->copyFile($file);
128
+ }
129
+
130
+ $totalFiles = $this->options->copiedFiles;
131
+ // Log this only every 50 entries to keep the log small and to not block the rendering browser
132
+ if ($this->options->copiedFiles %50 == 0){
133
+ $this->log("Total {$totalFiles} files processed");
134
+ }
135
+
136
+ return true;
137
+ }
138
+
139
+ /**
140
+ * Checks Whether There is Any Job to Execute or Not
141
+ * @return bool
142
+ */
143
+ private function isFinished() {
144
+ return (
145
+ $this->options->currentStep > $this->options->totalSteps ||
146
+ $this->options->copiedFiles >= $this->options->totalFiles
147
+ );
148
+ }
149
+
150
+ /**
151
+ * @param string $file
152
+ * @return bool
153
+ */
154
+ private function copyFile($file) {
155
+ $file = trim(ABSPATH . $file);
156
+
157
+ $directory = dirname($file);
158
+
159
+ // Get file size
160
+ $fileSize = filesize($file);
161
+
162
+ // Directory is excluded
163
+ if ($this->isDirectoryExcluded($directory)) {
164
+ $this->debugLog("Skipping directory by rule: {$file}", Logger::TYPE_INFO);
165
+ return false;
166
+ }
167
+
168
+ // File is excluded
169
+ if ($this->isFileExcluded($file)) {
170
+ $this->log("Skipping file by rule: {$file}", Logger::TYPE_INFO);
171
+ return false;
172
+ }
173
+
174
+ // File is over maximum allowed file size (8MB)
175
+ if ($fileSize >= $this->settings->maxFileSize * 1000000) {
176
+ $this->log("Skipping big file: {$file}", Logger::TYPE_INFO);
177
+ return false;
178
+ }
179
+
180
+ // Invalid file, skipping it as if succeeded
181
+ if (!is_file($file) || !is_readable($file)) {
182
+ $this->log("Can't read file or file doesn't exist {$file}");
183
+ return true;
184
+ }
185
+
186
+ // Failed to get destination
187
+ if (false === ($destination = $this->getDestination($file))) {
188
+ $this->log("Can't get the destination of {$file}");
189
+ return false;
190
+ }
191
+
192
+ // File is over batch size
193
+ if ($fileSize >= $this->settings->batchSize) {
194
+ $this->log("Trying to copy big file: {$file} -> {$destination}", Logger::TYPE_INFO);
195
+ return $this->copyBig($file, $destination, $this->settings->batchSize);
196
+ }
197
+
198
+ // Attempt to copy
199
+ if (!@copy($file, $destination)) {
200
+ $this->log("Failed to copy file to destination: {$file} -> {$destination}", Logger::TYPE_ERROR);
201
+ return false;
202
+ }
203
+
204
+ return true;
205
+
206
+ // Good old PHP
207
+ //return $this->copy($file, $destination);
208
+ }
209
+
210
+ /**
211
+ * Gets destination file and checks if the directory exists, if it does not attempts to create it.
212
+ * If creating destination directory fails, it returns false, gives destination full path otherwise
213
+ * @param string $file
214
+ * @return bool|string
215
+ */
216
+ private function getDestination($file) {
217
+ $file = $this->replaceMultisiteUploadFolder($file);
218
+ $relativePath = str_replace(ABSPATH, null, $file);
219
+ $destinationPath = $this->destination . $relativePath;
220
+ $destinationDirectory = dirname($destinationPath);
221
+
222
+ if (!is_dir($destinationDirectory) && !@mkdir($destinationDirectory, 0775, true)) {
223
+ $this->log("Files: Can not create directory {$destinationDirectory}", Logger::TYPE_ERROR);
224
+ return false;
225
+ }
226
+
227
+ return $destinationPath;
228
+ }
229
+
230
+ /**
231
+ * Copy bigger files than $this->settings->batchSize
232
+ * @param string $src
233
+ * @param string $dst
234
+ * @param int $buffersize
235
+ * @return boolean
236
+ */
237
+ private function copyBig($src, $dst, $buffersize) {
238
+ $src = fopen($src, 'r');
239
+ $dest = fopen($dst, 'w');
240
+
241
+ // Try first method:
242
+ while (!feof($src)) {
243
+ if (false === fwrite($dest, fread($src, $buffersize))) {
244
+ $error = true;
245
+ }
246
+ }
247
+ // Try second method if first one failed
248
+ if (isset($error) && ($error === true)) {
249
+ while (!feof($src)) {
250
+ if (false === stream_copy_to_stream($src, $dest, 1024)) {
251
+ $this->log("Can not copy big file; {$src} -> {$dest}");
252
+ fclose($src);
253
+ fclose($dest);
254
+ return false;
255
+ }
256
+ }
257
+ }
258
+ // Close any open handler
259
+ fclose($src);
260
+ fclose($dest);
261
+ return true;
262
+ }
263
+
264
+ /**
265
+ * Check if file is excluded from copying process
266
+ *
267
+ * @param string $file filename including ending
268
+ * @return boolean
269
+ */
270
+ private function isFileExcluded($file) {
271
+ $excluded = false;
272
+ foreach ($this->options->excludedFiles as $excludedFile) {
273
+ if (stripos(strrev($file), strrev($excludedFile)) === 0) {
274
+ $excluded = true;
275
+ break;
276
+ }
277
+ }
278
+
279
+ // Do not copy wp-config.php if the clone gets updated. This is for security purposes,
280
+ // because if the updating process fails, the staging site is not accessable any longer
281
+ if (isset($this->options->mainJob ) && $this->options->mainJob == "updating" && stripos(strrev($file), strrev("wp-config.php")) === 0){
282
+ $excluded = true;
283
+ }
284
+
285
+
286
+ return $excluded;
287
+ }
288
+
289
+ /**
290
+ * Check if directory is excluded from copying
291
+ * @param string $directory
292
+ * @return bool
293
+ */
294
+ private function isDirectoryExcluded($directory) {
295
+ // Make sure that wp-staging-pro directory / plugin is never excluded
296
+ if (false !== strpos( $directory, 'wp-staging' ) || false !== strpos( $directory, 'wp-staging-pro' ) ){
297
+ return false;
298
+ }
299
+
300
+ foreach ($this->options->excludedDirectories as $excludedDirectory) {
301
+ if (strpos($directory, $excludedDirectory) === 0 && !$this->isExtraDirectory($directory)) {
302
+ return true;
303
+ }
304
+ }
305
+
306
+ return false;
307
+ }
308
+
309
+ /**
310
+ * Check if directory is an extra directory and should be copied
311
+ * @param string $directory
312
+ * @return boolean
313
+ */
314
+ private function isExtraDirectory($directory) {
315
+ foreach ($this->options->extraDirectories as $extraDirectory) {
316
+ if (strpos($directory, $extraDirectory) === 0) {
317
+ return true;
318
+ }
319
+ }
320
+
321
+ return false;
322
+ }
323
+
324
+ /**
325
+ * Replace relative path of file if its located in multisite upload folder wp-content/uploads/sites/x/
326
+ * @return boolean
327
+ */
328
+ private function replaceMultisiteUploadFolder( $file ) {
329
+ $search = 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . get_current_blog_id();
330
+ $replace = 'wp-content' . DIRECTORY_SEPARATOR . 'uploads';
331
+
332
+ return str_replace($search, $replace, $file);
333
+
334
+ }
335
+
336
+ }
apps/Backend/Modules/Jobs/Multisite/Finish.php ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace WPStaging\Backend\Modules\Jobs\Multisite;
3
+
4
+ use WPStaging\WPStaging;
5
+ use WPStaging\Backend\Modules\Jobs\Job;
6
+ use WPStaging\Utils\Multisite;
7
+
8
+ //error_reporting( E_ALL );
9
+
10
+ /**
11
+ * Class Finish
12
+ * @package WPStaging\Backend\Modules\Jobs
13
+ */
14
+ class Finish extends Job
15
+ {
16
+ /**
17
+ * Clone Key
18
+ * @var string
19
+ */
20
+ private $clone = '';
21
+
22
+
23
+
24
+ /**
25
+ * Start Module
26
+ * @return object
27
+ */
28
+ public function start()
29
+ {
30
+ // sanitize the clone name before saving
31
+ $this->clone = preg_replace("#\W+#", '-', strtolower($this->options->clone));
32
+
33
+ // Delete Cache Files
34
+ $this->deleteCacheFiles();
35
+
36
+ // Prepare clone records & save scanned directories for delete job later
37
+ $this->prepareCloneDataRecords();
38
+
39
+ $multisite = new Multisite;
40
+
41
+ $return = array(
42
+ "directoryName" => $this->options->cloneDirectoryName,
43
+ "path" => ABSPATH . $this->options->cloneDirectoryName,
44
+ //"url" => get_site_url() . '/' . $this->options->cloneDirectoryName,
45
+ "url" => $this->multisiteHomeUrl . '/' . $this->options->cloneDirectoryName,
46
+ "number" => $this->options->cloneNumber,
47
+ "version" => \WPStaging\WPStaging::VERSION,
48
+ "status" => 'finished',
49
+ "prefix" => $this->options->prefix,
50
+ "last_msg" => $this->logger->getLastLogMsg(),
51
+ "job" => $this->options->currentJob,
52
+ "percentage" => 100
53
+
54
+ );
55
+
56
+ //$this->flush();
57
+
58
+ return (object) $return;
59
+ }
60
+
61
+ /**
62
+ * Delete Cache Files
63
+ */
64
+ protected function deleteCacheFiles()
65
+ {
66
+ $this->log("Finish: Deleting clone job's cache files...");
67
+
68
+ // Clean cache files
69
+ $this->cache->delete("clone_options");
70
+ $this->cache->delete("files_to_copy");
71
+
72
+ $this->log("Finish: Clone job's cache files have been deleted!");
73
+ }
74
+
75
+ /**
76
+ * Prepare clone records
77
+ * @return bool
78
+ */
79
+ protected function prepareCloneDataRecords()
80
+ {
81
+ // Check if clones still exist
82
+ $this->log("Finish: Verifying existing clones...");
83
+
84
+ // Clone data already exists
85
+ if (isset($this->options->existingClones[$this->options->clone]))
86
+ {
87
+ $this->log("Finish: Clone data already exists, no need to update, the job finished");
88
+ return true;
89
+ }
90
+
91
+ // Save new clone data
92
+ $this->log("Finish: {$this->options->clone}'s clone job's data is not in database, generating data");
93
+
94
+ // sanitize the clone name before saving
95
+ //$clone = preg_replace("#\W+#", '-', strtolower($this->options->clone));
96
+
97
+ $this->options->existingClones[$this->clone] = array(
98
+ "directoryName" => $this->options->cloneDirectoryName,
99
+ "path" => ABSPATH . $this->options->cloneDirectoryName,
100
+ //"url" => get_site_url() . '/' . $this->options->cloneDirectoryName,
101
+ "url" => $this->multisiteHomeUrl . '/' . $this->options->cloneDirectoryName,
102
+ "number" => $this->options->cloneNumber,
103
+ "version" => \WPStaging\WPStaging::VERSION,
104
+ "status" => false,
105
+ "prefix" => $this->options->prefix,
106
+ );
107
+
108
+ if (false === update_option("wpstg_existing_clones_beta", $this->options->existingClones))
109
+ {
110
+ $this->log("Finish: Failed to save {$this->options->clone}'s clone job data to database'");
111
+ return false;
112
+ }
113
+
114
+ return true;
115
+ }
116
+ }
apps/Backend/Modules/Jobs/Multisite/SearchReplace.php ADDED
@@ -0,0 +1,653 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
+
5
+ // No Direct Access
6
+ if( !defined( "WPINC" ) ) {
7
+ die;
8
+ }
9
+
10
+ use WPStaging\WPStaging;
11
+ use WPStaging\Utils\Strings;
12
+ use WPStaging\Utils\Helper;
13
+ use WPStaging\Utils\Multisite;
14
+ use WPStaging\Backend\Modules\Jobs\JobExecutable;
15
+
16
+ /**
17
+ * Class Database
18
+ * @package WPStaging\Backend\Modules\Jobs
19
+ */
20
+ class SearchReplace extends JobExecutable {
21
+
22
+ /**
23
+ * @var int
24
+ */
25
+ private $total = 0;
26
+
27
+ /**
28
+ * @var \WPDB
29
+ */
30
+ public $db;
31
+
32
+ /**
33
+ * The prefix of the new database tables which are used for the live site after updating tables
34
+ * @var string
35
+ */
36
+ public $tmpPrefix;
37
+
38
+ /**
39
+ * Initialize
40
+ */
41
+ public function initialize() {
42
+ $this->total = count( $this->options->tables );
43
+ $this->db = WPStaging::getInstance()->get( "wpdb" );
44
+ $this->tmpPrefix = $this->options->prefix;
45
+ }
46
+
47
+ public function start() {
48
+ // Skip job. Nothing to do
49
+ if( $this->options->totalSteps === 0 ) {
50
+ $this->prepareResponse( true, false );
51
+ }
52
+
53
+ $this->run();
54
+
55
+ // Save option, progress
56
+ $this->saveOptions();
57
+
58
+ return ( object ) $this->response;
59
+ }
60
+
61
+ /**
62
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
63
+ * @return void
64
+ */
65
+ protected function calculateTotalSteps() {
66
+ $this->options->totalSteps = $this->total;
67
+ }
68
+
69
+ /**
70
+ * Execute the Current Step
71
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
72
+ * @return bool
73
+ */
74
+ protected function execute() {
75
+ // Over limits threshold
76
+ if( $this->isOverThreshold() ) {
77
+ // Prepare response and save current progress
78
+ $this->prepareResponse( false, false );
79
+ $this->saveOptions();
80
+ return false;
81
+ }
82
+
83
+ // No more steps, finished
84
+ if( $this->options->currentStep > $this->total || !isset( $this->options->tables[$this->options->currentStep] ) ) {
85
+ $this->prepareResponse( true, false );
86
+ return false;
87
+ }
88
+
89
+ // Table is excluded
90
+ if( in_array( $this->options->tables[$this->options->currentStep], $this->options->excludedTables ) ) {
91
+ $this->prepareResponse();
92
+ return true;
93
+ }
94
+
95
+ // Search & Replace
96
+ if( !$this->stopExecution() && !$this->updateTable( $this->options->tables[$this->options->currentStep] ) ) {
97
+ // Prepare Response
98
+ $this->prepareResponse( false, false );
99
+
100
+ // Not finished
101
+ return true;
102
+ }
103
+
104
+
105
+ // Prepare Response
106
+ $this->prepareResponse();
107
+
108
+ // Not finished
109
+ return true;
110
+ }
111
+
112
+ // private function convertExcludedTables() {
113
+ // $tmp = array();
114
+ // foreach ( $this->options->excludedTables as $table ) {
115
+ // $tmp[] = str_replace( $this->options->prefix, $this->tmpPrefix, $table );
116
+ // }
117
+ // $this->options->excludedTables = $tmp;
118
+ // }
119
+
120
+ /**
121
+ * Stop Execution immediately
122
+ * return mixed bool | json
123
+ */
124
+ private function stopExecution() {
125
+ if( $this->db->prefix == $this->tmpPrefix ) {
126
+ $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.' );
127
+ }
128
+ return false;
129
+ }
130
+
131
+ /**
132
+ * Copy Tables
133
+ * @param string $tableName
134
+ * @return bool
135
+ */
136
+ private function updateTable( $tableName ) {
137
+ $strings = new Strings();
138
+ $table = $strings->str_replace_first( $this->db->prefix, '', $tableName );
139
+ $newTableName = $this->tmpPrefix . $table;
140
+
141
+ // Save current job
142
+ $this->setJob( $newTableName );
143
+
144
+ // Beginning of the job
145
+ if( !$this->startJob( $newTableName, $tableName ) ) {
146
+ return true;
147
+ }
148
+ // Copy data
149
+ $this->startReplace( $newTableName );
150
+
151
+ // Finis the step
152
+ return $this->finishStep();
153
+ }
154
+
155
+ /**
156
+ * Start search replace job
157
+ * @param string $new
158
+ * @param string $old
159
+ */
160
+ private function startReplace( $new ) {
161
+ $rows = $this->options->job->start + $this->settings->querySRLimit;
162
+ $this->log(
163
+ "DB Processing: Table {$new} {$this->options->job->start} to {$rows} records"
164
+ );
165
+
166
+ // Search & Replace
167
+ $this->searchReplace( $new, $rows, array() );
168
+
169
+ // Set new offset
170
+ $this->options->job->start += $this->settings->querySRLimit;
171
+ }
172
+
173
+ /**
174
+ * Returns the number of pages in a table.
175
+ * @access public
176
+ * @return int
177
+ */
178
+ private function get_pages_in_table( $table ) {
179
+ $table = esc_sql( $table );
180
+ $rows = $this->db->get_var( "SELECT COUNT(*) FROM $table" );
181
+ $pages = ceil( $rows / $this->settings->querySRLimit );
182
+ return absint( $pages );
183
+ }
184
+
185
+ /**
186
+ * Gets the columns in a table.
187
+ * @access public
188
+ * @param string $table The table to check.
189
+ * @return array
190
+ */
191
+ private function get_columns( $table ) {
192
+ $primary_key = null;
193
+ $columns = array();
194
+ $fields = $this->db->get_results( 'DESCRIBE ' . $table );
195
+ if( is_array( $fields ) ) {
196
+ foreach ( $fields as $column ) {
197
+ $columns[] = $column->Field;
198
+ if( $column->Key == 'PRI' ) {
199
+ $primary_key = $column->Field;
200
+ }
201
+ }
202
+ }
203
+ return array($primary_key, $columns);
204
+ }
205
+
206
+ /**
207
+ * Adapated from interconnect/it's search/replace script, adapted from Better Search Replace
208
+ *
209
+ * Modified to use WordPress wpdb functions instead of PHP's native mysql/pdo functions,
210
+ * and to be compatible with batch processing.
211
+ *
212
+ * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
213
+ *
214
+ * @access public
215
+ * @param string $table The table to run the replacement on.
216
+ * @param int $page The page/block to begin the query on.
217
+ * @param array $args An associative array containing arguements for this run.
218
+ * @return array
219
+ */
220
+ private function searchReplace( $table, $page, $args ) {
221
+
222
+
223
+ // Load up the default settings for this chunk.
224
+ $table = esc_sql( $table );
225
+ $current_page = $this->options->job->start + $this->settings->querySRLimit;
226
+ $pages = $this->get_pages_in_table( $table );
227
+ //$done = false;
228
+
229
+
230
+ if( $this->isSubDir() ) {
231
+ // Search URL example.com/staging and root path to staging site /var/www/htdocs/staging
232
+ $args['search_for'] = array(
233
+ rtrim( $this->multisiteHomeUrl, "/" ) . $this->getSubDir(),
234
+ ABSPATH
235
+ );
236
+
237
+
238
+ $args['replace_with'] = array(
239
+ rtrim( $this->multisiteHomeUrl, "/" ) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName,
240
+ rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName
241
+ );
242
+ } else {
243
+ $args['search_for'] = array(
244
+ rtrim( $this->multisiteHomeUrl, '/' ),
245
+ ABSPATH
246
+ );
247
+ $args['replace_with'] = array(
248
+ rtrim( $this->multisiteHomeUrl, '/' ) . '/' . $this->options->cloneDirectoryName,
249
+ rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName
250
+ );
251
+ }
252
+
253
+ // // Search URL example.com/staging and root path to staging site /var/www/htdocs/staging
254
+ // $args['search_for'] = array(
255
+ // $this->multisiteHomeUrl,
256
+ // ABSPATH
257
+ // );
258
+ //
259
+ //
260
+ // $args['replace_with'] = array(
261
+ // rtrim( $this->multisiteHomeUrl, '/' ) . '/' . $this->options->cloneDirectoryName,
262
+ // rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName
263
+ // );
264
+ $args['replace_guids'] = 'off';
265
+ $args['dry_run'] = 'off';
266
+ $args['case_insensitive'] = false;
267
+ $args['replace_guids'] = 'off';
268
+
269
+ // Get a list of columns in this table.
270
+ list( $primary_key, $columns ) = $this->get_columns( $table );
271
+
272
+ // Bail out early if there isn't a primary key.
273
+ // We commented this to search & replace through tables which have no primary keys like wp_revslider_slides
274
+ // @todo test this carefully. If it causes (performance) issues we need to activate it again!
275
+ // @since 2.4.4
276
+
277
+ // if( null === $primary_key ) {
278
+ // return false;
279
+ // }
280
+
281
+ $current_row = 0;
282
+ $start = $this->options->job->start;
283
+ $end = $this->settings->querySRLimit;
284
+
285
+ // Grab the content of the table.
286
+ $data = $this->db->get_results( "SELECT * FROM $table LIMIT $start, $end", ARRAY_A );
287
+
288
+ // Filter certain rows (of other plugins)
289
+ $filter = array(
290
+ 'Admin_custome_login_Slidshow',
291
+ 'Admin_custome_login_Social',
292
+ 'Admin_custome_login_logo',
293
+ 'Admin_custome_login_text',
294
+ 'Admin_custome_login_login',
295
+ 'Admin_custome_login_top',
296
+ 'Admin_custome_login_dashboard',
297
+ 'Admin_custome_login_Version',
298
+ );
299
+
300
+ apply_filters('wpstg_fiter_search_replace_rows', $filter);
301
+
302
+ // Loop through the data.
303
+ foreach ( $data as $row ) {
304
+ //$current_row++;
305
+ $update_sql = array();
306
+ $where_sql = array();
307
+ $upd = false;
308
+
309
+ // Skip rows below
310
+ if (isset($row['option_name']) && in_array($row['option_name'], $filter)){
311
+ continue;
312
+ }
313
+
314
+ // Skip rows with transients (They can store huge data and we need to save memory)
315
+ if( isset( $row['option_name'] ) && strpos( $row['option_name'], '_transient' ) === 0 ) {
316
+ continue;
317
+ }
318
+
319
+ foreach ( $columns as $column ) {
320
+
321
+ $dataRow = $row[$column];
322
+
323
+ if( $column == $primary_key ) {
324
+ $where_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
325
+ continue;
326
+ }
327
+
328
+ // Skip GUIDs by default.
329
+ if( 'on' !== $args['replace_guids'] && 'guid' == $column ) {
330
+ continue;
331
+ }
332
+
333
+
334
+ // Check options table
335
+ if( $this->options->prefix . 'options' === $table ) {
336
+
337
+ // Skip certain options
338
+ if( isset( $should_skip ) && true === $should_skip ) {
339
+ $should_skip = false;
340
+ continue;
341
+ }
342
+
343
+ // Skip this row
344
+ if( 'wpstg_existing_clones_beta' === $dataRow ||
345
+ 'wpstg_existing_clones' === $dataRow ||
346
+ 'wpstg_settings' === $dataRow ||
347
+ 'wpstg_license_status' === $dataRow ||
348
+ 'siteurl' === $dataRow ||
349
+ 'home' === $dataRow
350
+ ) {
351
+ $should_skip = true;
352
+ }
353
+ }
354
+
355
+ // Run a search replace on the data that'll respect the serialisation.
356
+ $i = 0;
357
+ foreach ( $args['search_for'] as $replace ) {
358
+ $dataRow = $this->recursive_unserialize_replace( $args['search_for'][$i], $args['replace_with'][$i], $dataRow, false, $args['case_insensitive'] );
359
+ $i++;
360
+ }
361
+ unset( $replace );
362
+ unset( $i );
363
+
364
+ // Something was changed
365
+ if( $row[$column] != $dataRow ) {
366
+ $update_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
367
+ $upd = true;
368
+ }
369
+ }
370
+
371
+ // Determine what to do with updates.
372
+ if( $args['dry_run'] === 'on' ) {
373
+ // Don't do anything if a dry run
374
+ } elseif( $upd && !empty( $where_sql ) ) {
375
+ // If there are changes to make, run the query.
376
+ $sql = 'UPDATE ' . $table . ' SET ' . implode( ', ', $update_sql ) . ' WHERE ' . implode( ' AND ', array_filter( $where_sql ) );
377
+ $result = $this->db->query( $sql );
378
+
379
+ if( !$result ) {
380
+ $this->log( "Error updating row {$current_row}", \WPStaging\Utils\Logger::TYPE_ERROR );
381
+ }
382
+ }
383
+ } // end row loop
384
+ unset( $row );
385
+ unset( $update_sql );
386
+ unset( $where_sql );
387
+ unset( $sql );
388
+
389
+
390
+ // DB Flush
391
+ $this->db->flush();
392
+ return true;
393
+ }
394
+
395
+ /**
396
+ * Adapted from interconnect/it's search/replace script.
397
+ *
398
+ * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
399
+ *
400
+ * Take a serialised array and unserialise it replacing elements as needed and
401
+ * unserialising any subordinate arrays and performing the replace on those too.
402
+ *
403
+ * @access private
404
+ * @param string $from String we're looking to replace.
405
+ * @param string $to What we want it to be replaced with
406
+ * @param array $data Used to pass any subordinate arrays back to in.
407
+ * @param boolean $serialised Does the array passed via $data need serialising.
408
+ * @param sting|boolean $case_insensitive Set to 'on' if we should ignore case, false otherwise.
409
+ *
410
+ * @return string|array The original array with all elements replaced as needed.
411
+ */
412
+ private function recursive_unserialize_replace( $from = '', $to = '', $data = '', $serialised = false, $case_insensitive = false ) {
413
+ try {
414
+
415
+ if( is_string( $data ) && !is_serialized_string( $data ) && ( $unserialized = $this->unserialize( $data ) ) !== false ) {
416
+ $data = $this->recursive_unserialize_replace( $from, $to, $unserialized, true, $case_insensitive );
417
+ } elseif( is_array( $data ) ) {
418
+ $_tmp = array();
419
+ foreach ( $data as $key => $value ) {
420
+ $_tmp[$key] = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
421
+ }
422
+
423
+ $data = $_tmp;
424
+ unset( $_tmp );
425
+ }
426
+
427
+ // Submitted by Tina Matter
428
+ elseif( $this->isValidObject($data) ) {
429
+ $_tmp = $data; // new $data_class( );
430
+ $props = get_object_vars( $data );
431
+ foreach ( $props as $key => $value ) {
432
+ $_tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
433
+ }
434
+
435
+ $data = $_tmp;
436
+ unset( $_tmp );
437
+ } elseif( is_serialized_string( $data ) ) {
438
+ if (false !== ($data = $this->unserialize($data)) ) {
439
+ $data = $this->str_replace( $from, $to, $data, $case_insensitive );
440
+ $data = serialize( $data );
441
+ }
442
+ } else {
443
+ if( is_string( $data ) ) {
444
+ $data = $this->str_replace( $from, $to, $data, $case_insensitive );
445
+ }
446
+ }
447
+
448
+ if( $serialised ) {
449
+ return serialize( $data );
450
+ }
451
+ } catch ( Exception $error ) {
452
+
453
+ }
454
+
455
+ return $data;
456
+ }
457
+
458
+ /**
459
+ * Check if the object is a valid one and not __PHP_Incomplete_Class_Name
460
+ * Can not use is_object alone because in php 7.2 it's returning true even though object is __PHP_Incomplete_Class_Name
461
+ * @return boolean
462
+ */
463
+ private function isValidObject($data){
464
+ if( !is_object( $data ) || gettype( $data ) != 'object' ) {
465
+ return false;
466
+ }
467
+
468
+ $invalid_class_props = get_object_vars( $data );
469
+
470
+ if (!isset($invalid_class_props['__PHP_Incomplete_Class_Name'])){
471
+ // Assume it must be an valid object
472
+ return true;
473
+ }
474
+
475
+ $invalid_object_class = $invalid_class_props['__PHP_Incomplete_Class_Name'];
476
+
477
+ if( !empty( $invalid_object_class ) ) {
478
+ return false;
479
+ }
480
+
481
+ // Assume it must be an valid object
482
+ return true;
483
+ }
484
+
485
+ /**
486
+ * Mimics the mysql_real_escape_string function. Adapted from a post by 'feedr' on php.net.
487
+ * @link http://php.net/manual/en/function.mysql-real-escape-string.php#101248
488
+ * @access public
489
+ * @param string $input The string to escape.
490
+ * @return string
491
+ */
492
+ private function mysql_escape_mimic( $input ) {
493
+ if( is_array( $input ) ) {
494
+ return array_map( __METHOD__, $input );
495
+ }
496
+ if( !empty( $input ) && is_string( $input ) ) {
497
+ return str_replace( array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $input );
498
+ }
499
+
500
+ return $input;
501
+ }
502
+
503
+ /**
504
+ * Return unserialized object or array
505
+ *
506
+ * @param string $serialized_string Serialized string.
507
+ * @param string $method The name of the caller method.
508
+ *
509
+ * @return mixed, false on failure
510
+ */
511
+ private static function unserialize( $serialized_string ) {
512
+ if( !is_serialized( $serialized_string ) ) {
513
+ return false;
514
+ }
515
+
516
+ $serialized_string = trim( $serialized_string );
517
+ $unserialized_string = @unserialize( $serialized_string );
518
+
519
+ return $unserialized_string;
520
+ }
521
+
522
+ /**
523
+ * Wrapper for str_replace
524
+ *
525
+ * @param string $from
526
+ * @param string $to
527
+ * @param string $data
528
+ * @param string|bool $case_insensitive
529
+ *
530
+ * @return string
531
+ */
532
+ private function str_replace( $from, $to, $data, $case_insensitive = false ) {
533
+ if( 'on' === $case_insensitive ) {
534
+ $data = str_ireplace( $from, $to, $data );
535
+ } else {
536
+ $data = str_replace( $from, $to, $data );
537
+ }
538
+
539
+ return $data;
540
+ }
541
+
542
+ /**
543
+ * Set the job
544
+ * @param string $table
545
+ */
546
+ private function setJob( $table ) {
547
+ if( !empty( $this->options->job->current ) ) {
548
+ return;
549
+ }
550
+
551
+ $this->options->job->current = $table;
552
+ $this->options->job->start = 0;
553
+ }
554
+
555
+ /**
556
+ * Start Job
557
+ * @param string $new
558
+ * @param string $old
559
+ * @return bool
560
+ */
561
+ private function startJob( $new, $old ) {
562
+ if( 0 != $this->options->job->start ) {
563
+ return true;
564
+ }
565
+
566
+ $this->options->job->total = ( int ) $this->db->get_var( "SELECT COUNT(1) FROM {$old}" );
567
+
568
+ if( 0 == $this->options->job->total ) {
569
+ $this->finishStep();
570
+ return false;
571
+ }
572
+
573
+ return true;
574
+ }
575
+
576
+ /**
577
+ * Finish the step
578
+ */
579
+ private function finishStep() {
580
+ // This job is not finished yet
581
+ if( $this->options->job->total > $this->options->job->start ) {
582
+ return false;
583
+ }
584
+
585
+ // Add it to cloned tables listing
586
+ $this->options->clonedTables[] = $this->options->tables[$this->options->currentStep];
587
+
588
+ // Reset job
589
+ $this->options->job = new \stdClass();
590
+
591
+ return true;
592
+ }
593
+
594
+ /**
595
+ * Drop table if necessary
596
+ * @param string $new
597
+ */
598
+ private function dropTable( $new ) {
599
+ $old = $this->db->get_var( $this->db->prepare( "SHOW TABLES LIKE %s", $new ) );
600
+
601
+ if( !$this->shouldDropTable( $new, $old ) ) {
602
+ return;
603
+ }
604
+
605
+ $this->log( "DB Processing: {$new} already exists, dropping it first" );
606
+ $this->db->query( "DROP TABLE {$new}" );
607
+ }
608
+
609
+ /**
610
+ * Check if table needs to be dropped
611
+ * @param string $new
612
+ * @param string $old
613
+ * @return bool
614
+ */
615
+ private function shouldDropTable( $new, $old ) {
616
+ return (
617
+ $old == $new &&
618
+ (
619
+ !isset( $this->options->job->current ) ||
620
+ !isset( $this->options->job->start ) ||
621
+ 0 == $this->options->job->start
622
+ )
623
+ );
624
+ }
625
+
626
+ /**
627
+ * Check if WP is installed in subdir
628
+ * @return boolean
629
+ */
630
+ private function isSubDir() {
631
+ if( get_option( 'siteurl' ) !== get_option( 'home' ) ) {
632
+ return true;
633
+ }
634
+ return false;
635
+ }
636
+
637
+ /**
638
+ * Get the install sub directory if WP is installed in sub directory
639
+ * @return string
640
+ */
641
+ private function getSubDir() {
642
+ $home = get_option( 'home' );
643
+ $siteurl = get_option( 'siteurl' );
644
+
645
+ if( empty( $home ) || empty( $siteurl ) ) {
646
+ return '/';
647
+ }
648
+
649
+ $dir = str_replace( $home, '', $siteurl );
650
+ return '/' . str_replace( '/', '', $dir );
651
+ }
652
+
653
+ }
apps/Backend/Modules/Jobs/Scan.php CHANGED
@@ -1,10 +1,10 @@
1
  <?php
 
2
  namespace WPStaging\Backend\Modules\Jobs;
3
 
4
  // No Direct Access
5
- if (!defined("WPINC"))
6
- {
7
- die;
8
  }
9
 
10
  use WPStaging\Utils\Directories;
@@ -14,212 +14,182 @@ use WPStaging\WPStaging;
14
  * Class Scan
15
  * @package WPStaging\Backend\Modules\Jobs
16
  */
17
- class Scan extends Job
18
- {
19
-
20
- /**
21
- * @var array
22
- */
23
- private $directories = array();
24
-
25
- /**
26
- * @var Directories
27
- */
28
- private $objDirectories;
29
-
30
-
31
- /**
32
- * Upon class initialization
33
- */
34
- protected function initialize()
35
- {
36
- $this->objDirectories = new Directories();
37
-
38
- // Database Tables
39
- $this->getTables();
40
-
41
- // Get directories
42
- $this->directories();
43
-
44
- $this->db = WPStaging::getInstance()->get('wpdb');
45
- $this->prefix = $this->db->prefix;
46
-
47
-
48
-
49
- }
50
-
51
- /**
52
- * Start Module
53
- * @return $this
54
- */
55
- public function start()
56
- {
57
- // Basic Options
58
- $this->options->root = str_replace(array("\\", '/'), DIRECTORY_SEPARATOR, ABSPATH);
59
- $this->options->existingClones = get_option("wpstg_existing_clones_beta", array());
60
- $this->options->current = null;
61
-
62
- if (isset($_POST["clone"]) && array_key_exists($_POST["clone"], $this->options->existingClones))
63
- {
64
- $this->options->current = $_POST["clone"];
65
- }
66
-
67
- // Tables
68
- //$this->options->excludedTables = array();
69
- $this->options->clonedTables = array();
70
-
71
- // Files
72
- $this->options->totalFiles = 0;
73
- $this->options->totalFileSize = 0;
74
- $this->options->copiedFiles = 0;
75
-
76
-
77
- // Directories
78
- $this->options->includedDirectories = array();
79
- $this->options->includedExtraDirectories= array();
80
- $this->options->excludedDirectories = array();
81
- $this->options->extraDirectories = array();
82
- $this->options->directoriesToCopy = array();
83
- $this->options->scannedDirectories = array();
84
-
85
- // Job
86
- $this->options->currentJob = "database";
87
- $this->options->currentStep = 0;
88
- $this->options->totalSteps = 0;
89
-
90
- // Delete previous cached files
91
- $this->cache->delete("files_to_copy");
92
- $this->cache->delete("clone_options");
93
-
94
- // Save options
95
- $this->saveOptions();
96
-
97
- return $this;
98
- }
99
-
100
- /**
101
- * Get relative WP uploads path
102
- * @return string
103
- */
104
- protected function getUploadDir(){
105
- $uploads = wp_upload_dir();
106
- return $uploads['basedir'];
107
- }
108
-
109
- /**
110
- * Get WP media folder
111
- *
112
- * @return string
113
- */
114
- // protected function getUploadFolder(){
115
- // $uploads = wp_upload_dir();
116
- // return wp_basedir($uploads['baseurl']);
117
- // }
118
-
119
- /**
120
- * Format bytes into human readable form
121
- * @param int $bytes
122
- * @param int $precision
123
- * @return string
124
- */
125
- public function formatSize($bytes, $precision = 2)
126
- {
127
- if ((double) $bytes < 1)
128
- {
129
- return '';
130
- }
131
-
132
- $units = array('B', "KB", "MB", "GB", "TB");
133
-
134
- $bytes = (double) $bytes;
135
- $base = log($bytes) / log(1000); // 1024 would be for MiB KiB etc
136
- $pow = pow(1000, $base - floor($base)); // Same rule for 1000
137
-
138
- return round($pow, $precision) . ' ' . $units[(int) floor($base)];
139
- }
140
-
141
- /**
142
- * @param null|string $directories
143
- * @param bool $forceDisabled
144
- * @return string
145
- */
146
- public function directoryListing($directories = null, $forceDisabled = false)
147
- {
148
- if (null == $directories)
149
- {
150
- $directories = $this->directories;
151
- }
152
-
153
- // Sort results
154
- uksort($directories, 'strcasecmp');
155
-
156
- $output = '';
157
- foreach ($directories as $name => $directory)
158
- {
159
- // Not a directory, possibly a symlink, therefore we will skip it
160
- if (!is_array($directory)) {
161
- continue;
162
- }
163
-
164
- // Need to preserve keys so no array_shift()
165
- $data = reset($directory);
166
- unset($directory[key($directory)]);
167
-
168
- $isChecked = (
169
- empty($this->options->includedDirectories) ||
170
- in_array($data["path"], $this->options->includedDirectories)
171
- );
172
-
173
- //$isDisabled = ($this->options->existingClones && isset($this->options->existingClones[$name]));
174
-
175
- // Include wp core folders and their sub dirs.
176
- // Exclude all other folders (default setting)
177
- $isDisabled = ($name !== 'wp-admin' &&
178
- $name !== 'wp-includes' &&
179
- $name !== 'wp-content') &&
180
- false === strpos( strrev($data["path"]), strrev("wp-admin") ) &&
181
- false === strpos( strrev($data["path"]), strrev("wp-includes") ) &&
182
- false === strpos( strrev($data["path"]), strrev("wp-content") )
183
- ? true : false;
184
-
185
- // Extra class to differentiate between wp core and non core folders
186
- $class = !$isDisabled ? 'wpstg-root' : 'wpstg-extra';
187
-
188
-
189
- $output .= "<div class='wpstg-dir'>";
190
- $output .= "<input type='checkbox' class='wpstg-check-dir " . $class . "'";
191
-
192
- if ($isChecked && !$isDisabled && !$forceDisabled) $output .= " checked";
193
- //if ($forceDisabled || $isDisabled) $output .= " disabled";
194
-
195
- $output .= " name='selectedDirectories[]' value='{$data["path"]}'>";
196
-
197
- $output .= "<a href='#' class='wpstg-expand-dirs ";
198
- if (!$isChecked || $isDisabled) $output .= " disabled";
199
- $output .= "'>{$name}";
200
- $output .= "</a>";
201
-
202
- $output .= "<span class='wpstg-size-info'>{$this->formatSize($data["size"])}</span>";
203
-
204
- if (!empty($directory))
205
- {
206
- $output .= "<div class='wpstg-dir wpstg-subdir'>";
207
- $output .= $this->directoryListing($directory, $isDisabled);
208
- $output .= "</div>";
209
- }
210
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
  $output .= "</div>";
212
- }
 
 
 
213
 
214
- return $output;
215
- }
216
 
217
- /**
218
- * Checks if there is enough free disk space to create staging site
219
- * Returns null when can't run disk_free_space function one way or another
220
- * @return bool|null
221
- */
222
- public function hasFreeDiskSpace() {
223
  if( !function_exists( "disk_free_space" ) ) {
224
  return null;
225
  }
@@ -229,16 +199,16 @@ class Scan extends Job
229
  if( false === $freeSpace ) {
230
  $data = array(
231
  'freespace' => false,
232
- 'usedspace' => $this->formatSize($this->getDirectorySizeInclSubdirs(ABSPATH))
233
  );
234
- echo json_encode($data);
235
  die();
236
  }
237
 
238
 
239
  $data = array(
240
- 'freespace' => $this->formatSize($freeSpace),
241
- 'usedspace' => $this->formatSize($this->getDirectorySizeInclSubdirs(ABSPATH))
242
  );
243
 
244
  echo json_encode( $data );
@@ -246,195 +216,174 @@ class Scan extends Job
246
  }
247
 
248
  /**
249
- * Get Database Tables
250
- */
251
- protected function getTables()
252
- {
253
- $wpDB = WPStaging::getInstance()->get("wpdb");
254
-
255
- if (strlen($wpDB->prefix) > 0)
256
- {
257
- $prefix = str_replace('_', '', $wpDB->prefix);
258
- $sql = "SHOW TABLE STATUS LIKE '{$wpDB->prefix}%'";
259
- }
260
- else
261
- {
262
- $sql = "SHOW TABLE STATUS";
263
- }
264
-
265
- $tables = $wpDB->get_results($sql);
266
-
267
- $currentTables = array();
268
-
269
- // Reset excluded Tables than loop through all tables
270
- $this->options->excludedTables = array();
271
- foreach ($tables as $table)
272
- {
273
-
274
- // Exclude WP Staging Tables
275
  // if (0 === strpos($table->Name, "wpstg"))
276
  // {
277
  // continue;
278
  // }
279
- // Create array of unchecked tables
280
- if (!empty($wpDB->prefix) && 0 !== strpos($table->Name, $wpDB->prefix))
281
- {
282
- $this->options->excludedTables[] = $table->Name;
283
- }
284
-
285
-
286
- $currentTables[] = array(
287
- "name" => $table->Name,
288
- "size" => ($table->Data_length + $table->Index_length)
289
- );
290
- }
291
-
292
- $this->options->tables = json_decode(json_encode($currentTables));
293
- }
294
-
295
- /**
296
- * Get directories and main meta data about'em recursively
297
- */
298
- protected function directories()
299
- {
300
- $directories = new \DirectoryIterator(ABSPATH);
301
-
302
- foreach($directories as $directory)
303
- {
304
- // Not a valid directory
305
- if (false === ($path = $this->getPath($directory)))
306
- {
307
- continue;
308
- }
309
-
310
- $this->handleDirectory($path);
311
-
312
- // Get Sub-directories
313
- $this->getSubDirectories($directory->getRealPath());
314
- }
315
-
316
- // Gather Plugins
317
- $this->getSubDirectories(WP_PLUGIN_DIR);
318
-
319
- // Gather Themes
320
- $this->getSubDirectories(WP_CONTENT_DIR . DIRECTORY_SEPARATOR . "themes");
321
-
322
- // Gather Default Uploads Folder
323
- //$this->getSubDirectories(WP_CONTENT_DIR . DIRECTORY_SEPARATOR . "uploads");
324
-
325
- // Gather Custom Uploads Folder if there is one
326
- //$this->getSubDirectories( $this->getUploadDir() );
327
- }
328
-
329
- /**
330
- * @param string $path
331
- */
332
- protected function getSubDirectories($path)
333
- {
334
- $directories = new \DirectoryIterator($path);
335
-
336
- foreach($directories as $directory)
337
- {
338
- // Not a valid directory
339
- if (false === ($path = $this->getPath($directory)))
340
- {
341
- continue;
342
- }
343
-
344
- $this->handleDirectory($path);
345
- }
346
- }
347
-
348
- /**
349
- * Get Path from $directory
350
- * @param \SplFileInfo $directory
351
- * @return string|false
352
- */
353
- protected function getPath($directory)
354
- {
355
-
356
- /*
357
  * Do not follow root path like src/web/..
358
  * This must be done before \SplFileInfo->isDir() is used!
359
  * Prevents open base dir restriction fatal errors
360
  */
361
- if (strpos( $directory->getRealPath(), ABSPATH ) !== 0 ) {
362
  return false;
363
  }
364
- $path = str_replace(ABSPATH, null, $directory->getRealPath());
365
-
366
- // Using strpos() for symbolic links as they could create nasty stuff in nix stuff for directory structures
367
- if (!$directory->isDir() || strlen($path) < 1)
368
- {
369
- return false;
370
- }
371
-
372
- return $path;
373
- }
374
-
375
- /**
376
- * Organizes $this->directories
377
- * @param string $path
378
- */
379
- protected function handleDirectory($path)
380
- {
381
- $directoryArray = explode(DIRECTORY_SEPARATOR, $path);
382
- $total = is_array($directoryArray) || $directoryArray instanceof Countable ? count($directoryArray) : 0;
383
-
384
- if ($total < 1)
385
- {
386
- return;
387
- }
388
-
389
- $total = $total - 1;
390
- $currentArray = &$this->directories;
391
-
392
- for ($i = 0; $i <= $total; $i++)
393
- {
394
- if (!isset($currentArray[$directoryArray[$i]]))
395
- {
396
- $currentArray[$directoryArray[$i]] = array();
397
- }
398
-
399
- $currentArray = &$currentArray[$directoryArray[$i]];
400
-
401
- // Attach meta data to the end
402
- if ($i < $total)
403
- {
404
- continue;
405
- }
406
-
407
- $fullPath = ABSPATH . $path;
408
- $size = $this->getDirectorySize($fullPath);
409
-
410
- $currentArray["metaData"] = array(
411
- "size" => $size,
412
- "path" => ABSPATH . $path,
413
- );
414
- }
415
- }
416
-
417
- /**
418
- * Gets size of given directory
419
- * @param string $path
420
- * @return int|null
421
- */
422
- protected function getDirectorySize($path)
423
- {
424
- if (!isset($this->settings->checkDirectorySize) || '1' !== $this->settings->checkDirectorySize)
425
- {
426
- return null;
427
- }
428
-
429
- return $this->objDirectories->size($path);
430
- }
431
-
432
- /**
433
- * Get total size of a directory including all its subdirectories
434
- * @param string $dir
435
- * @return int
436
- */
437
- function getDirectorySizeInclSubdirs( $dir ) {
438
  $size = 0;
439
  foreach ( glob( rtrim( $dir, '/' ) . '/*', GLOB_NOSORT ) as $each ) {
440
  $size += is_file( $each ) ? filesize( $each ) : $this->getDirectorySizeInclSubdirs( $each );
@@ -442,4 +391,13 @@ class Scan extends Job
442
  return $size;
443
  }
444
 
445
- }
 
 
 
 
 
 
 
 
 
1
  <?php
2
+
3
  namespace WPStaging\Backend\Modules\Jobs;
4
 
5
  // No Direct Access
6
+ if( !defined( "WPINC" ) ) {
7
+ die;
 
8
  }
9
 
10
  use WPStaging\Utils\Directories;
14
  * Class Scan
15
  * @package WPStaging\Backend\Modules\Jobs
16
  */
17
+ class Scan extends Job {
18
+
19
+ /**
20
+ * @var array
21
+ */
22
+ private $directories = array();
23
+
24
+ /**
25
+ * @var Directories
26
+ */
27
+ private $objDirectories;
28
+
29
+ /**
30
+ * Upon class initialization
31
+ */
32
+ protected function initialize() {
33
+ $this->objDirectories = new Directories();
34
+
35
+ // Database Tables
36
+ $this->getTables();
37
+
38
+ // Get directories
39
+ $this->directories();
40
+
41
+ $this->db = WPStaging::getInstance()->get( 'wpdb' );
42
+ $this->prefix = $this->db->prefix;
43
+ }
44
+
45
+ /**
46
+ * Start Module
47
+ * @return $this
48
+ */
49
+ public function start() {
50
+ // Basic Options
51
+ $this->options->root = str_replace( array("\\", '/'), DIRECTORY_SEPARATOR, ABSPATH );
52
+ $this->options->existingClones = get_option( "wpstg_existing_clones_beta", array() );
53
+ $this->options->current = null;
54
+
55
+ if( isset( $_POST["clone"] ) && array_key_exists( $_POST["clone"], $this->options->existingClones ) ) {
56
+ $this->options->current = $_POST["clone"];
57
+ }
58
+
59
+ // Tables
60
+ //$this->options->excludedTables = array();
61
+ $this->options->clonedTables = array();
62
+
63
+ // Files
64
+ $this->options->totalFiles = 0;
65
+ $this->options->totalFileSize = 0;
66
+ $this->options->copiedFiles = 0;
67
+
68
+
69
+ // Directories
70
+ $this->options->includedDirectories = array();
71
+ $this->options->includedExtraDirectories = array();
72
+ $this->options->excludedDirectories = array();
73
+ $this->options->extraDirectories = array();
74
+ $this->options->directoriesToCopy = array();
75
+ $this->options->scannedDirectories = array();
76
+
77
+ // Job
78
+ $this->options->currentJob = "database";
79
+ $this->options->currentStep = 0;
80
+ $this->options->totalSteps = 0;
81
+
82
+ // Define mainJob to differentiate between cloning, updating and pushing
83
+ $this->options->mainJob = 'cloning';
84
+
85
+ // Delete previous cached files
86
+ $this->cache->delete( "files_to_copy" );
87
+ $this->cache->delete( "clone_options" );
88
+
89
+ // Save options
90
+ $this->saveOptions();
91
+
92
+ return $this;
93
+ }
94
+
95
+ /**
96
+ * Format bytes into human readable form
97
+ * @param int $bytes
98
+ * @param int $precision
99
+ * @return string
100
+ */
101
+ public function formatSize( $bytes, $precision = 2 ) {
102
+ if( ( double ) $bytes < 1 ) {
103
+ return '';
104
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
 
106
+ $units = array('B', "KB", "MB", "GB", "TB");
107
+
108
+ $bytes = ( double ) $bytes;
109
+ $base = log( $bytes ) / log( 1000 ); // 1024 would be for MiB KiB etc
110
+ $pow = pow( 1000, $base - floor( $base ) ); // Same rule for 1000
111
+
112
+ return round( $pow, $precision ) . ' ' . $units[( int ) floor( $base )];
113
+ }
114
+
115
+ /**
116
+ * @param null|string $directories
117
+ * @param bool $forceDisabled
118
+ * @return string
119
+ */
120
+ public function directoryListing( $directories = null, $forceDisabled = false ) {
121
+ if( null == $directories ) {
122
+ $directories = $this->directories;
123
+ }
124
+
125
+ // Sort results
126
+ uksort( $directories, 'strcasecmp' );
127
+
128
+ $output = '';
129
+ foreach ( $directories as $name => $directory ) {
130
+ // Not a directory, possibly a symlink, therefore we will skip it
131
+ if( !is_array( $directory ) ) {
132
+ continue;
133
+ }
134
+
135
+ // Need to preserve keys so no array_shift()
136
+ $data = reset( $directory );
137
+ unset( $directory[key( $directory )] );
138
+
139
+ $isChecked = (
140
+ empty( $this->options->includedDirectories ) ||
141
+ in_array( $data["path"], $this->options->includedDirectories )
142
+ );
143
+
144
+ //$isDisabled = ($this->options->existingClones && isset($this->options->existingClones[$name]));
145
+ // Include wp core folders and their sub dirs.
146
+ // Exclude all other folders (default setting)
147
+ $isDisabled = ($name !== 'wp-admin' &&
148
+ $name !== 'wp-includes' &&
149
+ $name !== 'wp-content') &&
150
+ false === strpos( strrev( $data["path"] ), strrev( "wp-admin" ) ) &&
151
+ false === strpos( strrev( $data["path"] ), strrev( "wp-includes" ) ) &&
152
+ false === strpos( strrev( $data["path"] ), strrev( "wp-content" ) ) ? true : false;
153
+
154
+ // Extra class to differentiate between wp core and non core folders
155
+ $class = !$isDisabled ? 'wpstg-root' : 'wpstg-extra';
156
+
157
+
158
+ $output .= "<div class='wpstg-dir'>";
159
+ $output .= "<input type='checkbox' class='wpstg-check-dir " . $class . "'";
160
+
161
+ if( $isChecked && !$isDisabled && !$forceDisabled )
162
+ $output .= " checked";
163
+ //if ($forceDisabled || $isDisabled) $output .= " disabled";
164
+
165
+ $output .= " name='selectedDirectories[]' value='{$data["path"]}'>";
166
+
167
+ $output .= "<a href='#' class='wpstg-expand-dirs ";
168
+ if( !$isChecked || $isDisabled )
169
+ $output .= " disabled";
170
+ $output .= "'>{$name}";
171
+ $output .= "</a>";
172
+
173
+ $output .= "<span class='wpstg-size-info'>{$this->formatSize( $data["size"] )}</span>";
174
+
175
+ if( !empty( $directory ) ) {
176
+ $output .= "<div class='wpstg-dir wpstg-subdir'>";
177
+ $output .= $this->directoryListing( $directory, $isDisabled );
178
  $output .= "</div>";
179
+ }
180
+
181
+ $output .= "</div>";
182
+ }
183
 
184
+ return $output;
185
+ }
186
 
187
+ /**
188
+ * Checks if there is enough free disk space to create staging site
189
+ * Returns null when can't run disk_free_space function one way or another
190
+ * @return bool|null
191
+ */
192
+ public function hasFreeDiskSpace() {
193
  if( !function_exists( "disk_free_space" ) ) {
194
  return null;
195
  }
199
  if( false === $freeSpace ) {
200
  $data = array(
201
  'freespace' => false,
202
+ 'usedspace' => $this->formatSize( $this->getDirectorySizeInclSubdirs( ABSPATH ) )
203
  );
204
+ echo json_encode( $data );
205
  die();
206
  }
207
 
208
 
209
  $data = array(
210
+ 'freespace' => $this->formatSize( $freeSpace ),
211
+ 'usedspace' => $this->formatSize( $this->getDirectorySizeInclSubdirs( ABSPATH ) )
212
  );
213
 
214
  echo json_encode( $data );
216
  }
217
 
218
  /**
219
+ * Get Database Tables
220
+ */
221
+ protected function getTables() {
222
+ $wpDB = WPStaging::getInstance()->get( "wpdb" );
223
+
224
+ if( strlen( $wpDB->prefix ) > 0 ) {
225
+ //$prefix = str_replace('_', '', $wpDB->prefix);
226
+ $sql = "SHOW TABLE STATUS LIKE '{$wpDB->prefix}%'";
227
+ } else {
228
+ $sql = "SHOW TABLE STATUS";
229
+ }
230
+
231
+ $tables = $wpDB->get_results( $sql );
232
+
233
+ $currentTables = array();
234
+
235
+ // Reset excluded Tables than loop through all tables
236
+ $this->options->excludedTables = array();
237
+ foreach ( $tables as $table ) {
238
+
239
+ // Exclude WP Staging Tables
 
 
 
 
 
240
  // if (0 === strpos($table->Name, "wpstg"))
241
  // {
242
  // continue;
243
  // }
244
+ // Create array of unchecked tables
245
+ if( !empty( $wpDB->prefix ) && 0 !== strpos( $table->Name, $wpDB->prefix ) ) {
246
+ $this->options->excludedTables[] = $table->Name;
247
+ }
248
+
249
+
250
+ $currentTables[] = array(
251
+ "name" => $table->Name,
252
+ "size" => ($table->Data_length + $table->Index_length)
253
+ );
254
+ }
255
+
256
+ $this->options->tables = json_decode( json_encode( $currentTables ) );
257
+ }
258
+
259
+ /**
260
+ * Get directories and main meta data about'em recursively
261
+ */
262
+ protected function directories() {
263
+ $directories = new \DirectoryIterator( ABSPATH );
264
+
265
+ foreach ( $directories as $directory ) {
266
+ // Not a valid directory
267
+ if( false === ($path = $this->getPath( $directory )) ) {
268
+ continue;
269
+ }
270
+
271
+ $this->handleDirectory( $path );
272
+
273
+ // Get Sub-directories
274
+ $this->getSubDirectories( $directory->getRealPath() );
275
+ }
276
+
277
+ // Gather Plugins
278
+ $this->getSubDirectories( WP_PLUGIN_DIR );
279
+
280
+ // Gather Themes
281
+ $this->getSubDirectories( WP_CONTENT_DIR . DIRECTORY_SEPARATOR . "themes" );
282
+
283
+ // Gather Default Uploads Folder
284
+ //$this->getSubDirectories(WP_CONTENT_DIR . DIRECTORY_SEPARATOR . "uploads");
285
+ // Gather Custom Uploads Folder if there is one
286
+ $this->getSubDirectories( $this->getUploadDir() );
287
+ }
288
+
289
+ /**
290
+ * @param string $path
291
+ */
292
+ protected function getSubDirectories( $path ) {
293
+ $directories = new \DirectoryIterator( $path );
294
+
295
+ foreach ( $directories as $directory ) {
296
+ // Not a valid directory
297
+ if( false === ($path = $this->getPath( $directory )) ) {
298
+ continue;
299
+ }
300
+
301
+ $this->handleDirectory( $path );
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Get Path from $directory
307
+ * @param \SplFileInfo $directory
308
+ * @return string|false
309
+ */
310
+ protected function getPath( $directory ) {
311
+
312
+ /*
 
 
 
 
 
 
 
 
 
313
  * Do not follow root path like src/web/..
314
  * This must be done before \SplFileInfo->isDir() is used!
315
  * Prevents open base dir restriction fatal errors
316
  */
317
+ if( strpos( $directory->getRealPath(), ABSPATH ) !== 0 ) {
318
  return false;
319
  }
320
+ $path = str_replace( ABSPATH, null, $directory->getRealPath() );
321
+
322
+ // Using strpos() for symbolic links as they could create nasty stuff in nix stuff for directory structures
323
+ if( !$directory->isDir() || strlen( $path ) < 1 ) {
324
+ return false;
325
+ }
326
+
327
+ return $path;
328
+ }
329
+
330
+ /**
331
+ * Organizes $this->directories
332
+ * @param string $path
333
+ */
334
+ protected function handleDirectory( $path ) {
335
+
336
+ $directoryArray = explode( DIRECTORY_SEPARATOR, $path );
337
+ $total = (is_array($directoryArray) || $directoryArray instanceof Countable ) ? count( $directoryArray ) : 0;
338
+
339
+ if( $total < 1 ) {
340
+ return;
341
+ }
342
+
343
+ $total = $total - 1;
344
+ $currentArray = &$this->directories;
345
+
346
+ for ( $i = 0; $i <= $total; $i++ ) {
347
+ if( !isset( $currentArray[$directoryArray[$i]] ) ) {
348
+ $currentArray[$directoryArray[$i]] = array();
349
+ }
350
+
351
+ $currentArray = &$currentArray[$directoryArray[$i]];
352
+
353
+ // Attach meta data to the end
354
+ if( $i < $total ) {
355
+ continue;
356
+ }
357
+
358
+ $fullPath = ABSPATH . $path;
359
+ $size = $this->getDirectorySize( $fullPath );
360
+
361
+ $currentArray["metaData"] = array(
362
+ "size" => $size,
363
+ "path" => ABSPATH . $path,
364
+ );
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Gets size of given directory
370
+ * @param string $path
371
+ * @return int|null
372
+ */
373
+ protected function getDirectorySize( $path ) {
374
+ if( !isset( $this->settings->checkDirectorySize ) || '1' !== $this->settings->checkDirectorySize ) {
375
+ return null;
376
+ }
377
+
378
+ return $this->objDirectories->size( $path );
379
+ }
380
+
381
+ /**
382
+ * Get total size of a directory including all its subdirectories
383
+ * @param string $dir
384
+ * @return int
385
+ */
386
+ function getDirectorySizeInclSubdirs( $dir ) {
 
 
 
 
 
 
 
387
  $size = 0;
388
  foreach ( glob( rtrim( $dir, '/' ) . '/*', GLOB_NOSORT ) as $each ) {
389
  $size += is_file( $each ) ? filesize( $each ) : $this->getDirectorySizeInclSubdirs( $each );
391
  return $size;
392
  }
393
 
394
+ /**
395
+ * Get relative WP uploads path
396
+ * @return string
397
+ */
398
+ protected function getUploadDir() {
399
+ $uploads = wp_upload_dir();
400
+ return $uploads['basedir'];
401
+ }
402
+
403
+ }
apps/Backend/Modules/Jobs/SearchReplace.php CHANGED
@@ -1,664 +1,699 @@
1
- <?php
2
-
3
- namespace WPStaging\Backend\Modules\Jobs;
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
-
14
- /**
15
- * Class Database
16
- * @package WPStaging\Backend\Modules\Jobs
17
- */
18
- class SearchReplace extends JobExecutable {
19
-
20
- /**
21
- * @var int
22
- */
23
- private $total = 0;
24
-
25
- /**
26
- * @var \WPDB
27
- */
28
- public $db;
29
-
30
- /**
31
- *
32
- * @var string
33
- */
34
- private $homeUrl;
35
-
36
- /**
37
- * The prefix of the new database tables which are used for the live site after updating tables
38
- * @var string
39
- */
40
- public $tmpPrefix;
41
-
42
- /**
43
- * Initialize
44
- */
45
- public function initialize() {
46
- $this->total = count( $this->options->tables );
47
- $this->db = WPStaging::getInstance()->get( "wpdb" );
48
- //$this->tmpPrefix = 'wpstgtmp_';
49
- $this->tmpPrefix = $this->options->prefix;
50
- $helper = new Helper();
51
- $this->homeUrl = $helper->get_home_url();
52
- }
53
-
54
- public function start() {
55
- // Skip job. Nothing to do
56
- if( $this->options->totalSteps === 0 ) {
57
- $this->prepareResponse( true, false );
58
- }
59
-
60
- $this->run();
61
-
62
- // Save option, progress
63
- $this->saveOptions();
64
-
65
- return ( object ) $this->response;
66
- }
67
-
68
- /**
69
- * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
70
- * @return void
71
- */
72
- protected function calculateTotalSteps() {
73
- $this->options->totalSteps = $this->total;
74
- }
75
-
76
- /**
77
- * Execute the Current Step
78
- * Returns false when over threshold limits are hit or when the job is done, true otherwise
79
- * @return bool
80
- */
81
- protected function execute() {
82
- // Over limits threshold
83
- if( $this->isOverThreshold() ) {
84
- // Prepare response and save current progress
85
- $this->prepareResponse( false, false );
86
- $this->saveOptions();
87
- return false;
88
- }
89
-
90
- // No more steps, finished
91
- if( $this->options->currentStep > $this->total || !isset( $this->options->tables[$this->options->currentStep] ) ) {
92
- $this->prepareResponse( true, false );
93
- return false;
94
- }
95
-
96
- // Table is excluded
97
- if( in_array( $this->options->tables[$this->options->currentStep], $this->options->excludedTables ) ) {
98
- $this->prepareResponse();
99
- return true;
100
- }
101
-
102
- // Search & Replace
103
- if( !$this->stopExecution() && !$this->updateTable( $this->options->tables[$this->options->currentStep] ) ) {
104
- // Prepare Response
105
- $this->prepareResponse( false, false );
106
-
107
- // Not finished
108
- return true;
109
- }
110
-
111
-
112
- // Prepare Response
113
- $this->prepareResponse();
114
-
115
- // Not finished
116
- return true;
117
- }
118
-
119
- // private function convertExcludedTables() {
120
- // $tmp = array();
121
- // foreach ( $this->options->excludedTables as $table ) {
122
- // $tmp[] = str_replace( $this->options->prefix, $this->tmpPrefix, $table );
123
- // }
124
- // $this->options->excludedTables = $tmp;
125
- // }
126
-
127
- /**
128
- * Stop Execution immediately
129
- * return mixed bool | json
130
- */
131
- private function stopExecution() {
132
- if( $this->db->prefix == $this->tmpPrefix ) {
133
- $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.' );
134
- }
135
- return false;
136
- }
137
-
138
- /**
139
- * Copy Tables
140
- * @param string $tableName
141
- * @return bool
142
- */
143
- private function updateTable( $tableName ) {
144
- $strings = new Strings();
145
- $table = $strings->str_replace_first( $this->db->prefix, '', $tableName );
146
- $newTableName = $this->tmpPrefix . $table;
147
-
148
- // Save current job
149
- $this->setJob( $newTableName );
150
-
151
- // Beginning of the job
152
- if( !$this->startJob( $newTableName, $tableName ) ) {
153
- return true;
154
- }
155
- // Copy data
156
- $this->startReplace( $newTableName );
157
-
158
- // Finis the step
159
- return $this->finishStep();
160
- }
161
-
162
- /**
163
- * Start search replace job
164
- * @param string $new
165
- * @param string $old
166
- */
167
- private function startReplace( $new ) {
168
- $rows = $this->options->job->start + $this->settings->querySRLimit;
169
- $this->log(
170
- "DB Processing: Table {$new} {$this->options->job->start} to {$rows} records"
171
- );
172
-
173
- // Search & Replace
174
- $this->searchReplace( $new, $rows, array() );
175
-
176
- // Set new offset
177
- $this->options->job->start += $this->settings->querySRLimit;
178
- }
179
-
180
- /**
181
- * Returns the number of pages in a table.
182
- * @access public
183
- * @return int
184
- */
185
- private function get_pages_in_table( $table ) {
186
- $table = esc_sql( $table );
187
- $rows = $this->db->get_var( "SELECT COUNT(*) FROM $table" );
188
- $pages = ceil( $rows / $this->settings->querySRLimit );
189
- return absint( $pages );
190
- }
191
-
192
- /**
193
- * Gets the columns in a table.
194
- * @access public
195
- * @param string $table The table to check.
196
- * @return array
197
- */
198
- private function get_columns( $table ) {
199
- $primary_key = null;
200
- $columns = array();
201
- $fields = $this->db->get_results( 'DESCRIBE ' . $table );
202
- if( is_array( $fields ) ) {
203
- foreach ( $fields as $column ) {
204
- $columns[] = $column->Field;
205
- if( $column->Key == 'PRI' ) {
206
- $primary_key = $column->Field;
207
- }
208
- }
209
- }
210
- return array($primary_key, $columns);
211
- }
212
-
213
- /**
214
- * Adapated from interconnect/it's search/replace script, adapted from Better Search Replace
215
- *
216
- * Modified to use WordPress wpdb functions instead of PHP's native mysql/pdo functions,
217
- * and to be compatible with batch processing.
218
- *
219
- * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
220
- *
221
- * @access public
222
- * @param string $table The table to run the replacement on.
223
- * @param int $page The page/block to begin the query on.
224
- * @param array $args An associative array containing arguements for this run.
225
- * @return array
226
- */
227
- private function searchReplace( $table, $page, $args ) {
228
-
229
-
230
- // Load up the default settings for this chunk.
231
- $table = esc_sql( $table );
232
- $current_page = $this->options->job->start + $this->settings->querySRLimit;
233
- $pages = $this->get_pages_in_table( $table );
234
- //$done = false;
235
-
236
-
237
- if( $this->isSubDir() ) {
238
- // Search URL example.com/staging and root path to staging site /var/www/htdocs/staging
239
- $args['search_for'] = array(
240
- rtrim( $this->homeUrl, "/" ) . $this->getSubDir(),
241
- rtrim( ABSPATH, '/' ),
242
- str_replace('/', '\/', rtrim( $this->homeUrl, '/' )) . str_replace('/', '\/', $this->getSubDir()) // // Used by revslider and several visual editors
243
-
244
- );
245
-
246
- $args['replace_with'] = array(
247
- rtrim( $this->homeUrl, "/" ) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName,
248
- rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName,
249
- str_replace('/', '\/', rtrim( $this->homeUrl, "/" )) . str_replace('/', '\/', $this->getSubDir()) . '\/' . $this->options->cloneDirectoryName, // Used by revslider and several visual editors
250
- );
251
- } else {
252
- $args['search_for'] = array(
253
- rtrim( $this->homeUrl, '/' ),
254
- rtrim( ABSPATH, '/' ),
255
- str_replace('/', '\/' , rtrim( $this->homeUrl, '/' ))
256
- );
257
- $args['replace_with'] = array(
258
- rtrim( $this->homeUrl, '/' ) . '/' . $this->options->cloneDirectoryName,
259
- rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName,
260
- str_replace('/', '\/', rtrim( $this->homeUrl, '/' )) . '\/' . $this->options->cloneDirectoryName,
261
- );
262
- }
263
-
264
- // // Search URL example.com/staging and root path to staging site /var/www/htdocs/staging
265
- // $args['search_for'] = array(
266
- // $this->homeUrl,
267
- // ABSPATH
268
- // );
269
- //
270
- //
271
- // $args['replace_with'] = array(
272
- // rtrim( $this->homeUrl, '/' ) . '/' . $this->options->cloneDirectoryName,
273
- // rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName
274
- // );
275
- $args['replace_guids'] = 'off';
276
- $args['dry_run'] = 'off';
277
- $args['case_insensitive'] = false;
278
- $args['replace_guids'] = 'off';
279
-
280
- // Get a list of columns in this table.
281
- list( $primary_key, $columns ) = $this->get_columns( $table );
282
-
283
- // Bail out early if there isn't a primary key.
284
- // We commented this to search & replace through tables which have no primary keys like wp_revslider_slides
285
- // @todo test this carefully. If it causes (performance) issues we need to activate it again!
286
- // @since 2.4.4
287
-
288
- // if( null === $primary_key ) {
289
- // return false;
290
- // }
291
-
292
- $current_row = 0;
293
- $start = $this->options->job->start;
294
- $end = $this->settings->querySRLimit;
295
-
296
- // Grab the content of the table.
297
- $data = $this->db->get_results( "SELECT * FROM $table LIMIT $start, $end", ARRAY_A );
298
-
299
- // Filter certain rows (of other plugins)
300
- $filter = array(
301
- 'Admin_custome_login_Slidshow',
302
- 'Admin_custome_login_Social',
303
- 'Admin_custome_login_logo',
304
- 'Admin_custome_login_text',
305
- 'Admin_custome_login_login',
306
- 'Admin_custome_login_top',
307
- 'Admin_custome_login_dashboard',
308
- 'Admin_custome_login_Version',
309
- );
310
-
311
- apply_filters('wpstg_fiter_search_replace_rows', $filter);
312
-
313
- // Loop through the data.
314
- foreach ( $data as $row ) {
315
- $current_row++;
316
- $update_sql = array();
317
- $where_sql = array();
318
- $upd = false;
319
-
320
- // Skip rows below
321
- if (isset($row['option_name']) && in_array($row['option_name'], $filter)){
322
- continue;
323
- }
324
-
325
- // Skip rows with transients (They can store huge data and we need to save memory)
326
- if( isset( $row['option_name'] ) && strpos( $row['option_name'], '_transient' ) === 0 ) {
327
- continue;
328
- }
329
-
330
- foreach ( $columns as $column ) {
331
-
332
- $dataRow = $row[$column];
333
-
334
- if( $column == $primary_key ) {
335
- $where_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
336
- continue;
337
- }
338
-
339
- // Skip GUIDs by default.
340
- if( 'on' !== $args['replace_guids'] && 'guid' == $column ) {
341
- continue;
342
- }
343
-
344
-
345
- // Check options table
346
- if( $this->options->prefix . 'options' === $table ) {
347
-
348
- // Skip certain options
349
- if( isset( $should_skip ) && true === $should_skip ) {
350
- $should_skip = false;
351
- continue;
352
- }
353
-
354
- // Skip this row
355
- if( 'wpstg_existing_clones_beta' === $dataRow ||
356
- 'wpstg_existing_clones' === $dataRow ||
357
- 'wpstg_settings' === $dataRow ||
358
- 'wpstg_license_status' === $dataRow ||
359
- 'siteurl' === $dataRow ||
360
- 'home' === $dataRow
361
- ) {
362
- $should_skip = true;
363
- }
364
- }
365
-
366
- // Run a search replace on the data that'll respect the serialisation.
367
- $i = 0;
368
- foreach ( $args['search_for'] as $replace ) {
369
- $dataRow = $this->recursive_unserialize_replace( $args['search_for'][$i], $args['replace_with'][$i], $dataRow, false, $args['case_insensitive'] );
370
- $i++;
371
- }
372
- unset( $replace );
373
- unset( $i );
374
-
375
- // Something was changed
376
- if( $row[$column] != $dataRow ) {
377
- $update_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
378
- $upd = true;
379
- }
380
- }
381
-
382
- // Determine what to do with updates.
383
- if( $args['dry_run'] === 'on' ) {
384
- // Don't do anything if a dry run
385
- } elseif( $upd && !empty( $where_sql ) ) {
386
- // If there are changes to make, run the query.
387
- $sql = 'UPDATE ' . $table . ' SET ' . implode( ', ', $update_sql ) . ' WHERE ' . implode( ' AND ', array_filter( $where_sql ) );
388
- $result = $this->db->query( $sql );
389
-
390
- if( !$result ) {
391
- $this->log( "Error updating row {$current_row}", \WPStaging\Utils\Logger::TYPE_ERROR );
392
- }
393
- }
394
- } // end row loop
395
- unset( $row );
396
- unset( $update_sql );
397
- unset( $where_sql );
398
- unset( $sql );
399
-
400
-
401
- // DB Flush
402
- $this->db->flush();
403
- return true;
404
- }
405
-
406
- /**
407
- * Adapted from interconnect/it's search/replace script.
408
- *
409
- * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
410
- *
411
- * Take a serialised array and unserialise it replacing elements as needed and
412
- * unserialising any subordinate arrays and performing the replace on those too.
413
- *
414
- * @access private
415
- * @param string $from String we're looking to replace.
416
- * @param string $to What we want it to be replaced with
417
- * @param array $data Used to pass any subordinate arrays back to in.
418
- * @param boolean $serialised Does the array passed via $data need serialising.
419
- * @param sting|boolean $case_insensitive Set to 'on' if we should ignore case, false otherwise.
420
- *
421
- * @return string|array The original array with all elements replaced as needed.
422
- */
423
- private function recursive_unserialize_replace( $from = '', $to = '', $data = '', $serialised = false, $case_insensitive = false ) {
424
- try {
425
-
426
- if( is_string( $data ) && !is_serialized_string( $data ) && ( $unserialized = $this->unserialize( $data ) ) !== false ) {
427
- $data = $this->recursive_unserialize_replace( $from, $to, $unserialized, true, $case_insensitive );
428
- } elseif( is_array( $data ) ) {
429
- $_tmp = array();
430
- foreach ( $data as $key => $value ) {
431
- $_tmp[$key] = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
432
- }
433
-
434
- $data = $_tmp;
435
- unset( $_tmp );
436
- }
437
-
438
- // Submitted by Tina Matter
439
- elseif( $this->isValidObject($data) ) {
440
- $_tmp = $data; // new $data_class( );
441
- $props = get_object_vars( $data );
442
- foreach ( $props as $key => $value ) {
443
- $_tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
444
- }
445
-
446
- $data = $_tmp;
447
- unset($_tmp);
448
- } elseif (is_serialized_string($data)) {
449
- if (false !== ($data = $this->unserialize($data)) ) {
450
- $data = $this->str_replace($from, $to, $data, $case_insensitive);
451
- $data = serialize($data);
452
- }
453
- } else {
454
- if( is_string( $data ) ) {
455
- $data = $this->str_replace( $from, $to, $data, $case_insensitive );
456
- }
457
- }
458
-
459
- if( $serialised ) {
460
- return serialize( $data );
461
- }
462
- } catch ( Exception $error ) {
463
-
464
- }
465
-
466
- return $data;
467
- }
468
-
469
- /**
470
- * Check if the object is a valid one and not __PHP_Incomplete_Class_Name
471
- * Can not use is_object alone because in php 7.2 it's returning true even though object is __PHP_Incomplete_Class_Name
472
- * @return boolean
473
- */
474
- private function isValidObject($data){
475
- if( !is_object( $data ) || gettype( $data ) != 'object' ) {
476
- return false;
477
- }
478
-
479
- $invalid_class_props = get_object_vars( $data );
480
-
481
- if (!isset($invalid_class_props['__PHP_Incomplete_Class_Name'])){
482
- // Assume it must be an valid object
483
- return true;
484
- }
485
-
486
- $invalid_object_class = $invalid_class_props['__PHP_Incomplete_Class_Name'];
487
-
488
- if( !empty( $invalid_object_class ) ) {
489
- return false;
490
- }
491
-
492
- // Assume it must be an valid object
493
- return true;
494
- }
495
-
496
- /**
497
- * Mimics the mysql_real_escape_string function. Adapted from a post by 'feedr' on php.net.
498
- * @link http://php.net/manual/en/function.mysql-real-escape-string.php#101248
499
- * @access public
500
- * @param string $input The string to escape.
501
- * @return string
502
- */
503
- private function mysql_escape_mimic( $input ) {
504
- if( is_array( $input ) ) {
505
- return array_map( __METHOD__, $input );
506
- }
507
- if( !empty( $input ) && is_string( $input ) ) {
508
- return str_replace( array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $input );
509
- }
510
-
511
- return $input;
512
- }
513
-
514
- /**
515
- * Return unserialized object or array
516
- *
517
- * @param string $serialized_string Serialized string.
518
- * @param string $method The name of the caller method.
519
- *
520
- * @return mixed, false on failure
521
- */
522
- private static function unserialize( $serialized_string ) {
523
- if( !is_serialized( $serialized_string ) ) {
524
- return false;
525
- }
526
-
527
- $serialized_string = trim( $serialized_string );
528
- $unserialized_string = @unserialize( $serialized_string );
529
-
530
- return $unserialized_string;
531
- }
532
-
533
- /**
534
- * Wrapper for str_replace
535
- *
536
- * @param string $from
537
- * @param string $to
538
- * @param string $data
539
- * @param string|bool $case_insensitive
540
- *
541
- * @return string
542
- */
543
- private function str_replace( $from, $to, $data, $case_insensitive = false ) {
544
- if( 'on' === $case_insensitive ) {
545
- $data = str_ireplace( $from, $to, $data );
546
- } else {
547
- $data = str_replace( $from, $to, $data );
548
- }
549
-
550
- return $data;
551
- }
552
-
553
- /**
554
- * Set the job
555
- * @param string $table
556
- */
557
- private function setJob( $table ) {
558
- if( !empty( $this->options->job->current ) ) {
559
- return;
560
- }
561
-
562
- $this->options->job->current = $table;
563
- $this->options->job->start = 0;
564
- }
565
-
566
- /**
567
- * Start Job
568
- * @param string $new
569
- * @param string $old
570
- * @return bool
571
- */
572
- private function startJob( $new, $old ) {
573
- if( 0 != $this->options->job->start ) {
574
- return true;
575
- }
576
-
577
- $this->options->job->total = ( int ) $this->db->get_var( "SELECT COUNT(1) FROM {$old}" );
578
-
579
- if( 0 == $this->options->job->total ) {
580
- $this->finishStep();
581
- return false;
582
- }
583
-
584
- return true;
585
- }
586
-
587
- /**
588
- * Finish the step
589
- */
590
- private function finishStep() {
591
- // This job is not finished yet
592
- if( $this->options->job->total > $this->options->job->start ) {
593
- return false;
594
- }
595
-
596
- // Add it to cloned tables listing
597
- $this->options->clonedTables[] = $this->options->tables[$this->options->currentStep];
598
-
599
- // Reset job
600
- $this->options->job = new \stdClass();
601
-
602
- return true;
603
- }
604
-
605
- /**
606
- * Drop table if necessary
607
- * @param string $new
608
- */
609
- private function dropTable( $new ) {
610
- $old = $this->db->get_var( $this->db->prepare( "SHOW TABLES LIKE %s", $new ) );
611
-
612
- if( !$this->shouldDropTable( $new, $old ) ) {
613
- return;
614
- }
615
-
616
- $this->log( "DB Processing: {$new} already exists, dropping it first" );
617
- $this->db->query( "DROP TABLE {$new}" );
618
- }
619
-
620
- /**
621
- * Check if table needs to be dropped
622
- * @param string $new
623
- * @param string $old
624
- * @return bool
625
- */
626
- private function shouldDropTable( $new, $old ) {
627
- return (
628
- $old == $new &&
629
- (
630
- !isset( $this->options->job->current ) ||
631
- !isset( $this->options->job->start ) ||
632
- 0 == $this->options->job->start
633
- )
634
- );
635
- }
636
-
637
- /**
638
- * Check if WP is installed in subdir
639
- * @return boolean
640
- */
641
- private function isSubDir(){
642
- if ( get_option( 'siteurl' ) !== get_option( 'home' ) ) {
643
- return true;
644
- }
645
- return false;
646
- }
647
-
648
- /**
649
- * Get the install sub directory if WP is installed in sub directory
650
- * @return string
651
- */
652
- private function getSubDir() {
653
- $home = get_option( 'home' );
654
- $siteurl = get_option( 'siteurl' );
655
-
656
- if( empty( $home ) || empty( $siteurl ) ) {
657
- return '/';
658
- }
659
-
660
- $dir = str_replace( $home, '', $siteurl );
661
- return '/' . str_replace( '/', '', $dir );
662
- }
663
-
664
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs;
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
+
14
+ /**
15
+ * Class Database
16
+ * @package WPStaging\Backend\Modules\Jobs
17
+ */
18
+ class SearchReplace extends JobExecutable {
19
+
20
+ /**
21
+ * @var int
22
+ */
23
+ private $total = 0;
24
+
25
+ /**
26
+ * @var \WPDB
27
+ */
28
+ public $db;
29
+
30
+ /**
31
+ *
32
+ * @var string
33
+ */
34
+ private $homeUrl;
35
+
36
+ /**
37
+ * The prefix of the new database tables which are used for the live site after updating tables
38
+ * @var string
39
+ */
40
+ public $tmpPrefix;
41
+
42
+ /**
43
+ * Initialize
44
+ */
45
+ public function initialize() {
46
+ $this->total = count( $this->options->tables );
47
+ $this->db = WPStaging::getInstance()->get( "wpdb" );
48
+ //$this->tmpPrefix = 'wpstgtmp_';
49
+ $this->tmpPrefix = $this->options->prefix;
50
+ $helper = new Helper();
51
+ $this->homeUrl = $helper->get_home_url();
52
+ }
53
+
54
+ public function start() {
55
+ // Skip job. Nothing to do
56
+ if( $this->options->totalSteps === 0 ) {
57
+ $this->prepareResponse( true, false );
58
+ }
59
+
60
+ $this->run();
61
+
62
+ // Save option, progress
63
+ $this->saveOptions();
64
+
65
+ return ( object ) $this->response;
66
+ }
67
+
68
+ /**
69
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
70
+ * @return void
71
+ */
72
+ protected function calculateTotalSteps() {
73
+ $this->options->totalSteps = $this->total;
74
+ }
75
+
76
+ /**
77
+ * Execute the Current Step
78
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
79
+ * @return bool
80
+ */
81
+ protected function execute() {
82
+ // Over limits threshold
83
+ if( $this->isOverThreshold() ) {
84
+ // Prepare response and save current progress
85
+ $this->prepareResponse( false, false );
86
+ $this->saveOptions();
87
+ return false;
88
+ }
89
+
90
+ // No more steps, finished
91
+ if( $this->options->currentStep > $this->total || !isset( $this->options->tables[$this->options->currentStep] ) ) {
92
+ $this->prepareResponse( true, false );
93
+ return false;
94
+ }
95
+
96
+ // Table is excluded
97
+ if( in_array( $this->options->tables[$this->options->currentStep], $this->options->excludedTables ) ) {
98
+ $this->prepareResponse();
99
+ return true;
100
+ }
101
+
102
+ // Search & Replace
103
+ if( !$this->stopExecution() && !$this->updateTable( $this->options->tables[$this->options->currentStep] ) ) {
104
+ // Prepare Response
105
+ $this->prepareResponse( false, false );
106
+
107
+ // Not finished
108
+ return true;
109
+ }
110
+
111
+
112
+ // Prepare Response
113
+ $this->prepareResponse();
114
+
115
+ // Not finished
116
+ return true;
117
+ }
118
+
119
+ // private function convertExcludedTables() {
120
+ // $tmp = array();
121
+ // foreach ( $this->options->excludedTables as $table ) {
122
+ // $tmp[] = str_replace( $this->options->prefix, $this->tmpPrefix, $table );
123
+ // }
124
+ // $this->options->excludedTables = $tmp;
125
+ // }
126
+
127
+ /**
128
+ * Stop Execution immediately
129
+ * return mixed bool | json
130
+ */
131
+ private function stopExecution() {
132
+ if( $this->db->prefix == $this->tmpPrefix ) {
133
+ $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.' );
134
+ }
135
+ return false;
136
+ }
137
+
138
+ /**
139
+ * Copy Tables
140
+ * @param string $tableName
141
+ * @return bool
142
+ */
143
+ private function updateTable( $tableName ) {
144
+ $strings = new Strings();
145
+ $table = $strings->str_replace_first( $this->db->prefix, '', $tableName );
146
+ $newTableName = $this->tmpPrefix . $table;
147
+
148
+ // Save current job
149
+ $this->setJob( $newTableName );
150
+
151
+ // Beginning of the job
152
+ if( !$this->startJob( $newTableName, $tableName ) ) {
153
+ return true;
154
+ }
155
+ // Copy data
156
+ $this->startReplace( $newTableName );
157
+
158
+ // Finis the step
159
+ return $this->finishStep();
160
+ }
161
+
162
+ /**
163
+ * Start search replace job
164
+ * @param string $new
165
+ * @param string $old
166
+ */
167
+ private function startReplace( $new ) {
168
+ $rows = $this->options->job->start + $this->settings->querySRLimit;
169
+ $this->log(
170
+ "DB Processing: Table {$new} {$this->options->job->start} to {$rows} records"
171
+ );
172
+
173
+ // Search & Replace
174
+ $this->searchReplace( $new, $rows, array() );
175
+
176
+ // Set new offset
177
+ $this->options->job->start += $this->settings->querySRLimit;
178
+ }
179
+
180
+ /**
181
+ * Returns the number of pages in a table.
182
+ * @access public
183
+ * @return int
184
+ */
185
+ private function get_pages_in_table( $table ) {
186
+ $table = esc_sql( $table );
187
+ $rows = $this->db->get_var( "SELECT COUNT(*) FROM $table" );
188
+ $pages = ceil( $rows / $this->settings->querySRLimit );
189
+ return absint( $pages );
190
+ }
191
+
192
+ /**
193
+ * Gets the columns in a table.
194
+ * @access public
195
+ * @param string $table The table to check.
196
+ * @return array
197
+ */
198
+ private function get_columns( $table ) {
199
+ $primary_key = null;
200
+ $columns = array();
201
+ $fields = $this->db->get_results( 'DESCRIBE ' . $table );
202
+ if( is_array( $fields ) ) {
203
+ foreach ( $fields as $column ) {
204
+ $columns[] = $column->Field;
205
+ if( $column->Key == 'PRI' ) {
206
+ $primary_key = $column->Field;
207
+ }
208
+ }
209
+ }
210
+ return array($primary_key, $columns);
211
+ }
212
+
213
+ /**
214
+ * Adapated from interconnect/it's search/replace script, adapted from Better Search Replace
215
+ *
216
+ * Modified to use WordPress wpdb functions instead of PHP's native mysql/pdo functions,
217
+ * and to be compatible with batch processing.
218
+ *
219
+ * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
220
+ *
221
+ * @access public
222
+ * @param string $table The table to run the replacement on.
223
+ * @param int $page The page/block to begin the query on.
224
+ * @param array $args An associative array containing arguements for this run.
225
+ * @return array
226
+ */
227
+ private function searchReplace( $table, $page, $args ) {
228
+
229
+
230
+ // Load up the default settings for this chunk.
231
+ $table = esc_sql( $table );
232
+ $current_page = $this->options->job->start + $this->settings->querySRLimit;
233
+ $pages = $this->get_pages_in_table( $table );
234
+ //$done = false;
235
+
236
+
237
+ if( $this->isSubDir() ) {
238
+ // Search URL example.com/staging and root path to staging site /var/www/htdocs/staging
239
+ $args['search_for'] = array(
240
+ rtrim( $this->homeUrl, "/" ) . $this->getSubDir(),
241
+ rtrim( ABSPATH, '/' ),
242
+ str_replace('/', '\/', rtrim( $this->homeUrl, '/' )) . str_replace('/', '\/', $this->getSubDir()) // // Used by revslider and several visual editors
243
+
244
+ );
245
+
246
+ $args['replace_with'] = array(
247
+ rtrim( $this->homeUrl, "/" ) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName,
248
+ rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName,
249
+ str_replace('/', '\/', rtrim( $this->homeUrl, "/" )) . str_replace('/', '\/', $this->getSubDir()) . '\/' . $this->options->cloneDirectoryName, // Used by revslider and several visual editors
250
+ );
251
+ } else {
252
+ $args['search_for'] = array(
253
+ rtrim( $this->homeUrl, '/' ),
254
+ rtrim( ABSPATH, '/' ),
255
+ str_replace('/', '\/' , rtrim( $this->homeUrl, '/' ))
256
+ );
257
+ $args['replace_with'] = array(
258
+ rtrim( $this->homeUrl, '/' ) . '/' . $this->options->cloneDirectoryName,
259
+ rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName,
260
+ str_replace('/', '\/', rtrim( $this->homeUrl, '/' )) . '\/' . $this->options->cloneDirectoryName,
261
+ );
262
+ }
263
+
264
+
265
+ $args['replace_guids'] = 'off';
266
+ $args['dry_run'] = 'off';
267
+ $args['case_insensitive'] = false;
268
+ $args['replace_guids'] = 'off';
269
+
270
+ // Get a list of columns in this table.
271
+ list( $primary_key, $columns ) = $this->get_columns( $table );
272
+
273
+ // Bail out early if there isn't a primary key.
274
+ // We commented this to search & replace through tables which have no primary keys like wp_revslider_slides
275
+ // @todo test this carefully. If it causes (performance) issues we need to activate it again!
276
+ // @since 2.4.4
277
+
278
+ // if( null === $primary_key ) {
279
+ // return false;
280
+ // }
281
+
282
+ $current_row = 0;
283
+ $start = $this->options->job->start;
284
+ $end = $this->settings->querySRLimit;
285
+
286
+ // Grab the content of the table.
287
+ $data = $this->db->get_results( "SELECT * FROM $table LIMIT $start, $end", ARRAY_A );
288
+
289
+ // Filter certain rows (of other plugins)
290
+ $filter = array(
291
+ 'Admin_custome_login_Slidshow',
292
+ 'Admin_custome_login_Social',
293
+ 'Admin_custome_login_logo',
294
+ 'Admin_custome_login_text',
295
+ 'Admin_custome_login_login',
296
+ 'Admin_custome_login_top',
297
+ 'Admin_custome_login_dashboard',
298
+ 'Admin_custome_login_Version',
299
+ );
300
+
301
+ apply_filters('wpstg_fiter_search_replace_rows', $filter);
302
+
303
+ // Loop through the data.
304
+ foreach ( $data as $row ) {
305
+ $current_row++;
306
+ $update_sql = array();
307
+ $where_sql = array();
308
+ $upd = false;
309
+
310
+ // Skip rows below
311
+ if (isset($row['option_name']) && in_array($row['option_name'], $filter)){
312
+ continue;
313
+ }
314
+
315
+ // Skip rows with transients (They can store huge data and we need to save memory)
316
+ if( isset( $row['option_name'] ) && strpos( $row['option_name'], '_transient' ) === 0 ) {
317
+ continue;
318
+ }
319
+
320
+ foreach ( $columns as $column ) {
321
+
322
+ $dataRow = $row[$column];
323
+
324
+ if( $column == $primary_key ) {
325
+ $where_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
326
+ continue;
327
+ }
328
+
329
+ // Skip GUIDs by default.
330
+ if( 'on' !== $args['replace_guids'] && 'guid' == $column ) {
331
+ continue;
332
+ }
333
+
334
+
335
+ // Check options table
336
+ if( $this->options->prefix . 'options' === $table ) {
337
+
338
+ // Skip certain options
339
+ if( isset( $should_skip ) && true === $should_skip ) {
340
+ $should_skip = false;
341
+ continue;
342
+ }
343
+
344
+ // Skip this row
345
+ if( 'wpstg_existing_clones_beta' === $dataRow ||
346
+ 'wpstg_existing_clones' === $dataRow ||
347
+ 'wpstg_settings' === $dataRow ||
348
+ 'wpstg_license_status' === $dataRow ||
349
+ 'siteurl' === $dataRow ||
350
+ 'home' === $dataRow
351
+ ) {
352
+ $should_skip = true;
353
+ }
354
+ }
355
+
356
+ // Run a search replace on the data that'll respect the serialisation.
357
+ $i = 0;
358
+ foreach ( $args['search_for'] as $replace ) {
359
+ $dataRow = $this->recursive_unserialize_replace( $args['search_for'][$i], $args['replace_with'][$i], $dataRow, false, $args['case_insensitive'] );
360
+ $i++;
361
+ }
362
+ unset( $replace );
363
+ unset( $i );
364
+
365
+ // Something was changed
366
+ if( $row[$column] != $dataRow ) {
367
+ $update_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
368
+ $upd = true;
369
+ }
370
+ }
371
+
372
+ // Determine what to do with updates.
373
+ if( $args['dry_run'] === 'on' ) {
374
+ // Don't do anything if a dry run
375
+ } elseif( $upd && !empty( $where_sql ) ) {
376
+ // If there are changes to make, run the query.
377
+ $sql = 'UPDATE ' . $table . ' SET ' . implode( ', ', $update_sql ) . ' WHERE ' . implode( ' AND ', array_filter( $where_sql ) );
378
+ $result = $this->db->query( $sql );
379
+
380
+ if( !$result ) {
381
+ $this->log( "Error updating row {$current_row}", \WPStaging\Utils\Logger::TYPE_ERROR );
382
+ }
383
+ }
384
+ } // end row loop
385
+ unset( $row );
386
+ unset( $update_sql );
387
+ unset( $where_sql );
388
+ unset( $sql );
389
+
390
+
391
+ // DB Flush
392
+ $this->db->flush();
393
+ return true;
394
+ }
395
+
396
+ /**
397
+ * Adapted from interconnect/it's search/replace script.
398
+ *
399
+ * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
400
+ *
401
+ * Take a serialised array and unserialise it replacing elements as needed and
402
+ * unserialising any subordinate arrays and performing the replace on those too.
403
+ *
404
+ * @access private
405
+ * @param string $from String we're looking to replace.
406
+ * @param string $to What we want it to be replaced with
407
+ * @param array $data Used to pass any subordinate arrays back to in.
408
+ * @param boolean $serialised Does the array passed via $data need serialising.
409
+ * @param sting|boolean $case_insensitive Set to 'on' if we should ignore case, false otherwise.
410
+ *
411
+ * @return string|array The original array with all elements replaced as needed.
412
+ */
413
+ // private function recursive_unserialize_replace( $from = '', $to = '', $data = '', $serialised = false, $case_insensitive = false ) {
414
+ // try {
415
+ //
416
+ // if( is_string( $data ) && !is_serialized_string( $data ) && ( $unserialized = $this->unserialize( $data ) ) !== false ) {
417
+ // $data = $this->recursive_unserialize_replace( $from, $to, $unserialized, true, $case_insensitive );
418
+ // } elseif( is_array( $data ) ) {
419
+ // $_tmp = array();
420
+ // foreach ( $data as $key => $value ) {
421
+ // $_tmp[$key] = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
422
+ // }
423
+ //
424
+ // $data = $_tmp;
425
+ // unset( $_tmp );
426
+ // }
427
+ //
428
+ // // Submitted by Tina Matter
429
+ // elseif( $this->isValidObject($data) ) {
430
+ // $_tmp = $data; // new $data_class( );
431
+ // $props = get_object_vars( $data );
432
+ // foreach ( $props as $key => $value ) {
433
+ // if( $key === '' || ord( $key[0] ) === 0 ) {
434
+ // continue;
435
+ // }
436
+ // $_tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
437
+ // }
438
+ //
439
+ // $data = $_tmp;
440
+ // unset($_tmp);
441
+ // } elseif (is_serialized_string($data)) {
442
+ // if (false !== ($data = $this->unserialize($data)) ) {
443
+ // $data = $this->str_replace($from, $to, $data, $case_insensitive);
444
+ // $data = serialize($data);
445
+ // }
446
+ // } else {
447
+ // if( is_string( $data ) ) {
448
+ // $data = $this->str_replace( $from, $to, $data, $case_insensitive );
449
+ // }
450
+ // }
451
+ //
452
+ // if( $serialised ) {
453
+ // return serialize( $data );
454
+ // }
455
+ // } catch ( Exception $error ) {
456
+ //
457
+ // }
458
+ //
459
+ // return $data;
460
+ // }
461
+ private function recursive_unserialize_replace( $from = '', $to = '', $data = '', $serialized = false, $case_insensitive = false ) {
462
+ try {
463
+ // Some unserialized data cannot be re-serialized eg. SimpleXMLElements
464
+ if( is_serialized( $data ) && ( $unserialized = @unserialize( $data ) ) !== false ) {
465
+ $data = $this->recursive_unserialize_replace( $from, $to, $unserialized, true, $case_insensitive );
466
+ } elseif( is_array( $data ) ) {
467
+ $tmp = array();
468
+ foreach ( $data as $key => $value ) {
469
+ $tmp[$key] = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
470
+ }
471
+
472
+ $data = $tmp;
473
+ unset( $tmp );
474
+ } elseif( is_object( $data ) ) {
475
+ $tmp = $data;
476
+ $props = get_object_vars( $data );
477
+ foreach ( $props as $key => $value ) {
478
+ if( $key === '' || ord( $key[0] ) === 0 ) {
479
+ continue;
480
+ }
481
+ $tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
482
+ }
483
+
484
+ $data = $tmp;
485
+ unset( $tmp );
486
+ } else {
487
+ if( is_string( $data ) ) {
488
+ if( !empty( $from ) && !empty( $to ) ) {
489
+ $data = $this->str_replace( $from, $to, $data, $case_insensitive );
490
+ }
491
+ }
492
+ }
493
+
494
+ if( $serialized ) {
495
+ return serialize( $data );
496
+ }
497
+ } catch ( Exception $error ) {
498
+
499
+ }
500
+
501
+ return $data;
502
+ }
503
+
504
+ /**
505
+ * Check if the object is a valid one and not __PHP_Incomplete_Class_Name
506
+ * Can not use is_object alone because in php 7.2 it's returning true even though object is __PHP_Incomplete_Class_Name
507
+ * @return boolean
508
+ */
509
+ // private function isValidObject($data){
510
+ // if( !is_object( $data ) || gettype( $data ) != 'object' ) {
511
+ // return false;
512
+ // }
513
+ //
514
+ // $invalid_class_props = get_object_vars( $data );
515
+ //
516
+ // if (!isset($invalid_class_props['__PHP_Incomplete_Class_Name'])){
517
+ // // Assume it must be an valid object
518
+ // return true;
519
+ // }
520
+ //
521
+ // $invalid_object_class = $invalid_class_props['__PHP_Incomplete_Class_Name'];
522
+ //
523
+ // if( !empty( $invalid_object_class ) ) {
524
+ // return false;
525
+ // }
526
+ //
527
+ // // Assume it must be an valid object
528
+ // return true;
529
+ // }
530
+
531
+ /**
532
+ * Mimics the mysql_real_escape_string function. Adapted from a post by 'feedr' on php.net.
533
+ * @link http://php.net/manual/en/function.mysql-real-escape-string.php#101248
534
+ * @access public
535
+ * @param string $input The string to escape.
536
+ * @return string
537
+ */
538
+ private function mysql_escape_mimic( $input ) {
539
+ if( is_array( $input ) ) {
540
+ return array_map( __METHOD__, $input );
541
+ }
542
+ if( !empty( $input ) && is_string( $input ) ) {
543
+ return str_replace( array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $input );
544
+ }
545
+
546
+ return $input;
547
+ }
548
+
549
+ /**
550
+ * Return unserialized object or array
551
+ *
552
+ * @param string $serialized_string Serialized string.
553
+ * @param string $method The name of the caller method.
554
+ *
555
+ * @return mixed, false on failure
556
+ */
557
+ private static function unserialize( $serialized_string ) {
558
+ if( !is_serialized( $serialized_string ) ) {
559
+ return false;
560
+ }
561
+
562
+ $serialized_string = trim( $serialized_string );
563
+ $unserialized_string = @unserialize( $serialized_string );
564
+
565
+ return $unserialized_string;
566
+ }
567
+
568
+ /**
569
+ * Wrapper for str_replace
570
+ *
571
+ * @param string $from
572
+ * @param string $to
573
+ * @param string $data
574
+ * @param string|bool $case_insensitive
575
+ *
576
+ * @return string
577
+ */
578
+ private function str_replace( $from, $to, $data, $case_insensitive = false ) {
579
+ if( 'on' === $case_insensitive ) {
580
+ $data = str_ireplace( $from, $to, $data );
581
+ } else {
582
+ $data = str_replace( $from, $to, $data );
583
+ }
584
+
585
+ return $data;
586
+ }
587
+
588
+ /**
589
+ * Set the job
590
+ * @param string $table
591
+ */
592
+ private function setJob( $table ) {
593
+ if( !empty( $this->options->job->current ) ) {
594
+ return;
595
+ }
596
+
597
+ $this->options->job->current = $table;
598
+ $this->options->job->start = 0;
599
+ }
600
+
601
+ /**
602
+ * Start Job
603
+ * @param string $new
604
+ * @param string $old
605
+ * @return bool
606
+ */
607
+ private function startJob( $new, $old ) {
608
+ if( 0 != $this->options->job->start ) {
609
+ return true;
610
+ }
611
+
612
+ $this->options->job->total = ( int ) $this->db->get_var( "SELECT COUNT(1) FROM {$old}" );
613
+
614
+ if( 0 == $this->options->job->total ) {
615
+ $this->finishStep();
616
+ return false;
617
+ }
618
+
619
+ return true;
620
+ }
621
+
622
+ /**
623
+ * Finish the step
624
+ */
625
+ private function finishStep() {
626
+ // This job is not finished yet
627
+ if( $this->options->job->total > $this->options->job->start ) {
628
+ return false;
629
+ }
630
+
631
+ // Add it to cloned tables listing
632
+ $this->options->clonedTables[] = $this->options->tables[$this->options->currentStep];
633
+
634
+ // Reset job
635
+ $this->options->job = new \stdClass();
636
+
637
+ return true;
638
+ }
639
+
640
+ /**
641
+ * Drop table if necessary
642
+ * @param string $new
643
+ */
644
+ private function dropTable( $new ) {
645
+ $old = $this->db->get_var( $this->db->prepare( "SHOW TABLES LIKE %s", $new ) );
646
+
647
+ if( !$this->shouldDropTable( $new, $old ) ) {
648
+ return;
649
+ }
650
+
651
+ $this->log( "DB Processing: {$new} already exists, dropping it first" );
652
+ $this->db->query( "DROP TABLE {$new}" );
653
+ }
654
+
655
+ /**
656
+ * Check if table needs to be dropped
657
+ * @param string $new
658
+ * @param string $old
659
+ * @return bool
660
+ */
661
+ private function shouldDropTable( $new, $old ) {
662
+ return (
663
+ $old == $new &&
664
+ (
665
+ !isset( $this->options->job->current ) ||
666
+ !isset( $this->options->job->start ) ||
667
+ 0 == $this->options->job->start
668
+ )
669
+ );
670
+ }
671
+
672
+ /**
673
+ * Check if WP is installed in subdir
674
+ * @return boolean
675
+ */
676
+ private function isSubDir(){
677
+ if ( get_option( 'siteurl' ) !== get_option( 'home' ) ) {
678
+ return true;
679
+ }
680
+ return false;
681
+ }
682
+
683
+ /**
684
+ * Get the install sub directory if WP is installed in sub directory
685
+ * @return string
686
+ */
687
+ private function getSubDir() {
688
+ $home = get_option( 'home' );
689
+ $siteurl = get_option( 'siteurl' );
690
+
691
+ if( empty( $home ) || empty( $siteurl ) ) {
692
+ return '/';
693
+ }
694
+
695
+ $dir = str_replace( $home, '', $siteurl );
696
+ return '/' . str_replace( '/', '', $dir );
697
+ }
698
+
699
+ }
apps/Backend/Modules/Jobs/Updating.php CHANGED
@@ -38,6 +38,9 @@ class Updating extends Job
38
  $this->options->extraDirectories = array();
39
  $this->options->excludedFiles = array('.htaccess', '.DS_Store', '.git', '.svn', '.tmp', 'desktop.ini', '.gitignore', '.log');
40
 
 
 
 
41
  // Job
42
  $this->options->job = new \stdClass();
43
 
@@ -113,11 +116,9 @@ class Updating extends Job
113
  // prefix not defined! Happens if staging site has ben generated with older version of wpstg
114
  // Try to get staging prefix from wp-config.php of staging site
115
  $this->options->prefix = $this->options->existingClones[$this->options->clone]['prefix'];
116
- //wp_die($this->options->prefix);
117
  if (empty($this->options->prefix)) {
118
  // Throw error if wp-config.php is not readable
119
  $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
120
- //wp_die($path);
121
  if (false === ($content = @file_get_contents($path))) {
122
  $this->log("Can not open {$path}. Can't read contents", Logger::TYPE_ERROR);
123
  $this->returnException("Fatal Error: Can not read {$path} to get correct table prefix. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com");
@@ -125,7 +126,6 @@ class Updating extends Job
125
  } else {
126
  // Get prefix from wp-config.php
127
  preg_match("/table_prefix\s*=\s*'(\w*)';/", $content, $matches);
128
- //wp_die(var_dump($matches));
129
 
130
  if (!empty($matches[1])) {
131
  $this->options->prefix = $matches[1];
@@ -143,7 +143,6 @@ class Updating extends Job
143
  }
144
 
145
  // Else
146
- //wp_die($this->options->prefix);
147
  return $this->options->prefix;
148
  }
149
 
38
  $this->options->extraDirectories = array();
39
  $this->options->excludedFiles = array('.htaccess', '.DS_Store', '.git', '.svn', '.tmp', 'desktop.ini', '.gitignore', '.log');
40
 
41
+ // Define mainJob to differentiate between cloning, updating and pushing
42
+ $this->options->mainJob = 'updating';
43
+
44
  // Job
45
  $this->options->job = new \stdClass();
46
 
116
  // prefix not defined! Happens if staging site has ben generated with older version of wpstg
117
  // Try to get staging prefix from wp-config.php of staging site
118
  $this->options->prefix = $this->options->existingClones[$this->options->clone]['prefix'];
 
119
  if (empty($this->options->prefix)) {
120
  // Throw error if wp-config.php is not readable
121
  $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
 
122
  if (false === ($content = @file_get_contents($path))) {
123
  $this->log("Can not open {$path}. Can't read contents", Logger::TYPE_ERROR);
124
  $this->returnException("Fatal Error: Can not read {$path} to get correct table prefix. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com");
126
  } else {
127
  // Get prefix from wp-config.php
128
  preg_match("/table_prefix\s*=\s*'(\w*)';/", $content, $matches);
 
129
 
130
  if (!empty($matches[1])) {
131
  $this->options->prefix = $matches[1];
143
  }
144
 
145
  // Else
 
146
  return $this->options->prefix;
147
  }
148
 
apps/Backend/Modules/SystemInfo.php CHANGED
@@ -8,118 +8,110 @@ use WPStaging\WPStaging;
8
  use WPStaging\Utils;
9
 
10
  // No Direct Access
11
- if (!defined("WPINC"))
12
- {
13
- die;
14
  }
15
 
16
  /**
17
  * Class SystemInfo
18
  * @package WPStaging\Backend\Modules
19
  */
20
- class SystemInfo extends InjectionAware
21
- {
22
-
23
- /**
24
- * @var bool
25
- */
26
- private $isMultiSite;
27
-
28
- /**
29
  *
30
  * @var obj
31
  */
32
  private $helper;
33
 
34
  /**
35
- * Initialize class
36
- */
37
- public function initialize()
38
- {
39
- $this->isMultiSite = is_multisite();
40
  $this->helper = new Utils\Helper();
41
- }
42
-
43
- /**
44
- * Magic method
45
- * @return string
46
- */
47
- public function __toString()
48
- {
49
- return $this->get();
50
- }
51
-
52
- /**
53
- * Get System Information as text
54
- * @return string
55
- */
56
  public function get() {
57
  $output = "### Begin System Info ###" . PHP_EOL . PHP_EOL;
58
 
59
- $output .= $this->wpstaging();
60
-
61
- $output .= $this->site();
62
-
63
- $output .= $this->browser();
64
-
65
- $output .= $this->wp();
66
-
67
- $output .= $this->plugins();
68
-
69
- $output .= $this->multiSitePlugins();
70
-
71
- $output .= $this->server();
72
-
73
- $output .= $this->php();
74
-
75
- $output .= $this->phpExtensions();
76
-
77
- $output .= PHP_EOL . "### End System Info ###";
78
-
79
- return $output;
80
- }
81
-
82
- /**
83
- * @param string $string
84
- * @return string
85
- */
86
- public function header($string)
87
- {
88
- return PHP_EOL . "-- {$string}" . PHP_EOL . PHP_EOL;
89
- }
90
-
91
- /**
92
- * Formating title and the value
93
- * @param string $title
94
- * @param string $value
95
- * @return string
96
- */
97
- public function info($title, $value)
98
- {
99
- return str_pad($title, 56, ' ', STR_PAD_RIGHT) . $value . PHP_EOL;
100
- }
101
-
102
- /**
103
- * Theme Information
104
- * @return string
105
- */
106
- public function theme()
107
- {
108
- // Versions earlier than 3.4
109
- if (get_bloginfo("version") < "3.4" )
110
- {
111
- $themeData = get_theme_data(get_stylesheet_directory() . "/style.css");
112
- return "{$themeData["Name"]} {$themeData["Version"]}";
113
- }
114
-
115
- $themeData = wp_get_theme();
116
- return "{$themeData->Name} {$themeData->Version}";
117
- }
118
-
119
- /**
120
- * Site Information
121
- * @return string
122
- */
123
  public function site() {
124
  $output = "-- Site Info" . PHP_EOL . PHP_EOL;
125
  $output .= $this->info( "Site URL:", site_url() );
@@ -128,33 +120,34 @@ class SystemInfo extends InjectionAware
128
  $output .= $this->info( "ABSPATH:", ABSPATH );
129
  $output .= $this->info( "Installed in subdir:", ( $this->isSubDir() ? 'Yes' : 'No' ) );
130
  $output .= $this->info( "Multisite:", ($this->isMultiSite ? "Yes" : "No" ) );
 
 
 
 
131
 
132
- return apply_filters("wpstg_sysinfo_after_site_info", $output);
133
- }
134
-
135
- /**
136
- * Wp Staging plugin Information
137
- * @return string
138
- */
139
- public function wpstaging() {
140
  // Get wpstg settings
141
  $settings = ( object ) get_option( 'wpstg_settings', array() );
142
 
143
- // Clones data < 1.6.x
144
  $clones = ( object ) get_option( 'wpstg_existing_clones', array() );
145
  // Clones data version > 2.x
146
  $clonesBeta = get_option( 'wpstg_existing_clones_beta' );
147
 
148
 
149
  $output = "-- WP Staging Settings" . PHP_EOL . PHP_EOL;
150
- $output .= $this->info( "Query Limit:", isset( $settings->queryLimit ) ? $settings->queryLimit : 'undefined' );
151
- $output .= $this->info( "File Copy Limit:", isset( $settings->fileLimit ) ? $settings->fileLimit : 'undefined' );
152
- $output .= $this->info( "Batch Size:", isset( $settings->batchSize ) ? $settings->batchSize : 'undefined' );
153
- $output .= $this->info( "CPU Load:", isset( $settings->cpuLoad ) ? $settings->cpuLoad : 'undefined' );
154
- $output .= $this->info( "WP in Subdir:", isset( $settings->wpSubDirectory ) ? $settings->wpSubDirectory : 'false' );
155
- $output .= $this->info( "Login Custom Link:", isset( $settings->loginSlug ) ? $settings->loginSlug : 'false' );
156
 
157
- $output .= PHP_EOL . PHP_EOL . "-- Available Sites Version < 1.6.x" . PHP_EOL . PHP_EOL;
158
 
159
  $i = 1;
160
  foreach ( $clones as $key => $value ) {
@@ -163,13 +156,13 @@ class SystemInfo extends InjectionAware
163
  $output .= PHP_EOL . PHP_EOL . "-- Available Sites Version > 2.0.x" . PHP_EOL . PHP_EOL;
164
 
165
  foreach ( $clonesBeta as $key => $clone ) {
166
- $output .= $this->info( "Number:", isset( $clone['number'] ) ? $clone['number'] : 'undefined' );
167
- $output .= $this->info( "directoryName:", isset( $clone['directoryName'] ) ? $clone['directoryName'] : 'undefined' );
168
- $output .= $this->info( "Path:", isset( $clone['path'] ) ? $clone['path'] : 'undefined' );
169
- $output .= $this->info( "URL:", isset( $clone['url'] ) ? $clone['url'] : 'undefined' );
170
- $output .= $this->info( "DB Prefix:", isset( $clone['prefix'] ) ? $clone['prefix'] : 'undefined' );
171
- $output .= $this->info( "DB Prefix wp-config.php:", $this->getStagingPrefix($clone));
172
- $output .= $this->info( "Version:", isset( $clone['version'] ) ? $clone['version'] : 'undefined' ) . PHP_EOL . PHP_EOL;
173
  }
174
 
175
 
@@ -179,357 +172,331 @@ class SystemInfo extends InjectionAware
179
 
180
 
181
  //$output .= PHP_EOL . PHP_EOL;
182
-
183
- $output .= $this->info( "Plugin Version:", get_option('wpstg_version', 'undefined') );
184
- $output .= $this->info( "Install Date:", get_option('wpstg_installDate', 'undefined') );
185
- $output .= $this->info( "Upgraded from:", get_option('wpstg_version_upgraded_from', 'undefined') );
186
- $output .= $this->info( "Is Staging Site:", get_option('wpstg_is_staging_site', 'undefined') ) . PHP_EOL . PHP_EOL;
187
 
188
 
189
  return apply_filters( "wpstg_sysinfo_after_wpstaging_info", $output );
190
  }
191
 
192
  /**
193
- * Browser Information
194
- * @return string
195
- */
196
- public function browser()
197
- {
198
- $output = $this->header("User Browser");
199
- $output .= (new Browser);
200
-
201
- return apply_filters("wpstg_sysinfo_after_user_browser", $output);
202
- }
203
-
204
- /**
205
- * Frontpage Information when frontpage is set to "page"
206
- * @return string
207
- */
208
- public function frontPage()
209
- {
210
- if (get_option("show_on_front") !== "page")
211
- {
212
- return '';
213
- }
214
-
215
- $frontPageID = get_option("page_on_front");
216
- $blogPageID = get_option("page_for_posts");
217
-
218
- // Front Page
219
- $pageFront = ($frontPageID != 0) ? get_the_title($frontPageID) . " (#{$frontPageID})" : "Unset";
220
- // Blog Page ID
221
- $pageBlog = ($blogPageID != 0) ? get_the_title($blogPageID) . " (#{$blogPageID})" : "Unset";
222
-
223
- $output = $this->info("Page On Front:", $pageFront);
224
- $output .= $this->info("Page For Posts:", $pageBlog);
225
-
226
- return $output;
227
- }
228
-
229
- /**
230
- * Check wp_remote_post() functionality
231
- * @return string
232
- */
233
- public function wpRemotePost()
234
- {
235
- // Make sure wp_remote_post() is working
236
- $wpRemotePost = "wp_remote_post() does not work";
237
-
238
- // Send request
239
- $response = wp_remote_post(
240
- "https://www.paypal.com/cgi-bin/webscr",
241
- array(
242
- "sslverify" => false,
243
- "timeout" => 60,
244
- "user-agent" => "WPSTG/" . WPStaging::VERSION,
245
- "body" => array("cmd" => "_notify-validate")
246
- )
247
- );
248
-
249
- // Validate it worked
250
- if (!is_wp_error($response) && 200 <= $response["response"]["code"] && 300> $response["response"]["code"])
251
- {
252
- $wpRemotePost = "wp_remote_post() works";
253
- }
254
-
255
- return $this->info("Remote Post:", $wpRemotePost);
256
- }
257
-
258
- /**
259
- * WordPress Configuration
260
- * @return string
261
- */
262
- public function wp()
263
- {
264
- $output = $this->header("WordPress Configuration");
265
- $output .= $this->info("Version:", get_bloginfo("version"));
266
- $output .= $this->info("Language:", (defined("WPLANG") && WPLANG) ? WPLANG : "en_US");
267
-
268
- $permalinkStructure = get_option("permalink_structure");;
269
- $output .= $this->info("Permalink Structure:", ($permalinkStructure) ? $permalinkStructure : "Default");
270
-
271
- $output .= $this->info("Active Theme:", $this->theme());
272
- $output .= $this->info("Show On Front:", get_option("show_on_front"));
273
-
274
- // Frontpage information
275
- $output .= $this->frontPage();
276
-
277
- // WP Remote Post
278
- $output .= $this->wpRemotePost();
279
-
280
- // Table Prefix
281
- $wpDB = $this->di->get("wpdb");
282
- $tablePrefix = "DB Prefix: " . $wpDB->prefix . ' ';
283
- $tablePrefix .= "Length: " . strlen($wpDB->prefix) . " Status: ";
284
- $tablePrefix .= (strlen($wpDB->prefix) > 16) ? " ERROR: Too long" : " Acceptable";
285
-
286
- $output .= $this->info("Table Prefix:", $tablePrefix);
287
 
288
  // Constants
289
  $output .= $this->info( "WP Content Path:", WP_CONTENT_DIR );
290
  $output .= $this->info( "WP Plugin Dir:", WP_PLUGIN_DIR );
291
  if (defined('UPLOADS'))
292
- $output .= $this->info( "WP UPLOADS CONST:", UPLOADS );
293
  $uploads = wp_upload_dir();
294
- $output .= $this->info( "WP Uploads Dir:", wp_basename( $uploads['baseurl'] ) );
295
  if (defined('WP_TEMP_DIR'))
296
- $output .= $this->info( "WP Temp Dir:", WP_TEMP_DIR );
297
-
298
- // WP Debug
299
- $output .= $this->info("WP_DEBUG:", (defined("WP_DEBUG")) ? WP_DEBUG ? "Enabled" : "Disabled" : "Not set");
300
- $output .= $this->info("Memory Limit:", WP_MEMORY_LIMIT);
301
- $output .= $this->info("Registered Post Stati:", implode(", ", \get_post_stati()));
302
-
303
- return apply_filters("wpstg_sysinfo_after_wpstg_config", $output);
304
- }
305
-
306
- /**
307
- * List of Active Plugins
308
- * @param array $plugins
309
- * @param array $activePlugins
310
- * @return string
311
- */
312
- public function activePlugins($plugins, $activePlugins)
313
- {
314
- $output = $this->header("WordPress Active Plugins");
315
-
316
- foreach ($plugins as $path => $plugin)
317
- {
318
- if (!in_array($path, $activePlugins))
319
- {
320
- continue;
321
- }
322
-
323
- $output .= "{$plugin["Name"]}: {$plugin["Version"]}" . PHP_EOL;
324
- }
325
-
326
- return apply_filters("wpstg_sysinfo_after_wordpress_plugins", $output);
327
- }
328
-
329
- /**
330
- * List of Inactive Plugins
331
- * @param array $plugins
332
- * @param array $activePlugins
333
- * @return string
334
- */
335
- public function inactivePlugins($plugins, $activePlugins)
336
- {
337
- $output = $this->header("WordPress Inactive Plugins");
338
-
339
- foreach ($plugins as $path => $plugin)
340
- {
341
- if (in_array($path, $activePlugins))
342
- {
343
- continue;
344
- }
345
-
346
- $output .= "{$plugin["Name"]}: {$plugin["Version"]}" . PHP_EOL;
347
- }
348
-
349
- return apply_filters("wpstg_sysinfo_after_wordpress_plugins_inactive", $output);
350
- }
351
-
352
- /**
353
- * Get list of active and inactive plugins
354
- * @return string
355
- */
356
- public function plugins()
357
- {
358
- // Get plugins and active plugins
359
- $plugins = get_plugins();
360
- $activePlugins = get_option("active_plugins", array());
361
-
362
- // Active plugins
363
- $output = $this->activePlugins($plugins, $activePlugins);
364
- $output .= $this->inactivePlugins($plugins, $activePlugins);
365
-
366
- return $output;
367
- }
368
-
369
- /**
370
- * Multisite Plugins
371
- * @return string
372
- */
373
- public function multiSitePlugins()
374
- {
375
- if (!$this->isMultiSite)
376
- {
377
- return '';
378
- }
379
-
380
- $output = $this->header("Network Active Plugins");
381
-
382
- $plugins = wp_get_active_network_plugins();
383
- $activePlugins = get_site_option("active_sitewide_plugins", array());
384
-
385
- foreach ($plugins as $pluginPath)
386
- {
387
- $pluginBase = plugin_basename($pluginPath);
388
-
389
- if (!array_key_exists($pluginBase, $activePlugins))
390
- {
391
- continue;
392
- }
393
-
394
- $plugin = get_plugin_data($pluginPath);
395
-
396
- $output .= "{$plugin["Name"]}: {$plugin["Version"]}" . PHP_EOL;
397
- }
398
- unset($plugins, $activePlugins);
399
-
400
- return $output;
401
- }
402
-
403
- /**
404
- * Server Information
405
- * @return string
406
- */
407
- public function server()
408
- {
409
- // Server Configuration
410
- $output = $this->header("Webserver Configuration");
411
-
412
- $output .= $this->info("PHP Version:", PHP_VERSION);
413
- $output .= $this->info("MySQL Version:", $this->di->get("wpdb")->db_version());
414
- $output .= $this->info("Webserver Info:", $_SERVER["SERVER_SOFTWARE"]);
415
-
416
- return apply_filters("wpstg_sysinfo_after_webserver_config", $output);
417
- }
418
-
419
- /**
420
- * PHP Configuration
421
- * @return string
422
- */
423
- public function php()
424
- {
425
- $output = $this->header("PHP Configuration");
426
- $output .= $this->info("Safe Mode:", ($this->isSafeModeEnabled() ? "Enabled" : "Disabled"));
427
- $output .= $this->info("Memory Limit:", ini_get("memory_limit"));
428
- $output .= $this->info("Upload Max Size:", ini_get("upload_max_filesize"));
429
- $output .= $this->info("Post Max Size:", ini_get("post_max_size"));
430
- $output .= $this->info("Upload Max Filesize:", ini_get("upload_max_filesize"));
431
- $output .= $this->info("Time Limit:", ini_get("max_execution_time"));
432
- $output .= $this->info("Max Input Vars:", ini_get("max_input_vars"));
433
-
434
- $displayErrors = ini_get("display_errors");
435
- $output .= $this->info("Display Errors:", ($displayErrors) ? "On ({$displayErrors})" : "N/A");
436
-
437
- return apply_filters("wpstg_sysinfo_after_php_config", $output);
438
- }
439
-
440
- /**
441
- * Check if PHP is on Safe Mode
442
- * @return bool
443
- */
444
- public function isSafeModeEnabled()
445
- {
446
- return (
447
- version_compare(PHP_VERSION, "5.4.0", '<') &&
448
- @ini_get("safe_mode")
449
- );
450
- }
451
-
452
- /**
453
- * Checks if function exists or not
454
- * @param string $functionName
455
- * @return string
456
- */
457
- public function isSupported($functionName)
458
- {
459
- return (function_exists($functionName)) ? "Supported" : "Not Supported";
460
- }
461
-
462
- /**
463
- * Checks if class or extension is loaded / exists to determine if it is installed or not
464
- * @param string $name
465
- * @param bool $isClass
466
- * @return string
467
- */
468
- public function isInstalled($name, $isClass = true)
469
- {
470
- if (true === $isClass)
471
- {
472
- return (class_exists($name)) ? "Installed" : "Not Installed";
473
- }
474
- else
475
- {
476
- return (extension_loaded($name)) ? "Installed" : "Not Installed";
477
- }
478
- }
479
-
480
- /**
481
- * Gets Installed Important PHP Extensions
482
- * @return string
483
- */
484
- public function phpExtensions()
485
- {
486
- // Important PHP Extensions
487
- $output = $this->header("PHP Extensions");
488
- $output .= $this->info("cURL:", $this->isSupported("curl_init"));
489
- $output .= $this->info("fsockopen:", $this->isSupported("fsockopen"));
490
- $output .= $this->info("SOAP Client:", $this->isInstalled("SoapClient"));
491
- $output .= $this->info("Suhosin:", $this->isInstalled("suhosin", false));
492
-
493
- return apply_filters("wpstg_sysinfo_after_php_ext", $output);
494
- }
495
-
496
- /**
497
- * Check if WP is installed in subdir
498
- * @return boolean
499
- */
500
- private function isSubDir(){
501
- if ( get_option( 'siteurl' ) !== get_option( 'home' ) ) {
502
- return true;
503
- }
504
- return false;
505
- }
506
-
507
- /**
508
- * Check and return prefix of the staging site
509
- */
510
- /**
511
- * Try to get the staging prefix from wp-config.php of staging site
512
- * @param array $clone
513
- * @return sting
514
- */
515
- private function getStagingPrefix($clone=array()) {
516
- // Throw error
517
- $path = ABSPATH . $clone['directoryName'] . "/wp-config.php";
518
- if (false === ($content = @file_get_contents($path))) {
519
- return 'Can\'t find staging wp-config.php';
520
- } else {
521
-
522
- // Get prefix from wp-config.php
523
- //preg_match_all("/table_prefix\s*=\s*'(\w*)';/", $content, $matches);
524
- preg_match("/table_prefix\s*=\s*'(\w*)';/", $content, $matches);
525
- //wp_die(var_dump($matches));
526
-
527
- if (!empty($matches[1])) {
528
- return $matches[1];
529
- } else {
530
- return 'No table_prefix in wp-config.php';
531
- }
532
- }
533
-
534
- }
535
- }
8
  use WPStaging\Utils;
9
 
10
  // No Direct Access
11
+ if( !defined( "WPINC" ) ) {
12
+ die;
 
13
  }
14
 
15
  /**
16
  * Class SystemInfo
17
  * @package WPStaging\Backend\Modules
18
  */
19
+ class SystemInfo extends InjectionAware {
20
+
21
+ /**
22
+ * @var bool
23
+ */
24
+ private $isMultiSite;
25
+
26
+ /**
 
27
  *
28
  * @var obj
29
  */
30
  private $helper;
31
 
32
  /**
33
+ * Initialize class
34
+ */
35
+ public function initialize() {
36
+ $this->isMultiSite = is_multisite();
 
37
  $this->helper = new Utils\Helper();
38
+ }
39
+
40
+ /**
41
+ * Magic method
42
+ * @return string
43
+ */
44
+ public function __toString() {
45
+ return $this->get();
46
+ }
47
+
48
+ /**
49
+ * Get System Information as text
50
+ * @return string
51
+ */
 
52
  public function get() {
53
  $output = "### Begin System Info ###" . PHP_EOL . PHP_EOL;
54
 
55
+ $output .= $this->wpstaging();
56
+
57
+ $output .= $this->site();
58
+
59
+ $output .= $this->browser();
60
+
61
+ $output .= $this->wp();
62
+
63
+ $output .= $this->plugins();
64
+
65
+ $output .= $this->multiSitePlugins();
66
+
67
+ $output .= $this->server();
68
+
69
+ $output .= $this->php();
70
+
71
+ $output .= $this->phpExtensions();
72
+
73
+ $output .= PHP_EOL . "### End System Info ###";
74
+
75
+ return $output;
76
+ }
77
+
78
+ /**
79
+ * @param string $string
80
+ * @return string
81
+ */
82
+ public function header( $string ) {
83
+ return PHP_EOL . "-- {$string}" . PHP_EOL . PHP_EOL;
84
+ }
85
+
86
+ /**
87
+ * Formating title and the value
88
+ * @param string $title
89
+ * @param string $value
90
+ * @return string
91
+ */
92
+ public function info( $title, $value ) {
93
+ return str_pad( $title, 56, ' ', STR_PAD_RIGHT ) . $value . PHP_EOL;
94
+ }
95
+
96
+ /**
97
+ * Theme Information
98
+ * @return string
99
+ */
100
+ public function theme() {
101
+ // Versions earlier than 3.4
102
+ if( get_bloginfo( "version" ) < "3.4" ) {
103
+ $themeData = get_theme_data( get_stylesheet_directory() . "/style.css" );
104
+ return "{$themeData["Name"]} {$themeData["Version"]}";
105
+ }
106
+
107
+ $themeData = wp_get_theme();
108
+ return "{$themeData->Name} {$themeData->Version}";
109
+ }
110
+
111
+ /**
112
+ * Site Information
113
+ * @return string
114
+ */
 
 
 
 
115
  public function site() {
116
  $output = "-- Site Info" . PHP_EOL . PHP_EOL;
117
  $output .= $this->info( "Site URL:", site_url() );
120
  $output .= $this->info( "ABSPATH:", ABSPATH );
121
  $output .= $this->info( "Installed in subdir:", ( $this->isSubDir() ? 'Yes' : 'No' ) );
122
  $output .= $this->info( "Multisite:", ($this->isMultiSite ? "Yes" : "No" ) );
123
+ $output .= $this->info( "Multisite Blog ID:", get_current_blog_id() );
124
+
125
+ return apply_filters( "wpstg_sysinfo_after_site_info", $output );
126
+ }
127
 
128
+ /**
129
+ * Wp Staging plugin Information
130
+ * @return string
131
+ */
132
+ public function wpstaging() {
 
 
 
133
  // Get wpstg settings
134
  $settings = ( object ) get_option( 'wpstg_settings', array() );
135
 
136
+ // Clones data < 1.1.6.x
137
  $clones = ( object ) get_option( 'wpstg_existing_clones', array() );
138
  // Clones data version > 2.x
139
  $clonesBeta = get_option( 'wpstg_existing_clones_beta' );
140
 
141
 
142
  $output = "-- WP Staging Settings" . PHP_EOL . PHP_EOL;
143
+ $output .= $this->info( "Query Limit:", isset( $settings->queryLimit ) ? $settings->queryLimit : 'undefined' );
144
+ $output .= $this->info( "File Copy Limit:", isset( $settings->fileLimit ) ? $settings->fileLimit : 'undefined' );
145
+ $output .= $this->info( "Batch Size:", isset( $settings->batchSize ) ? $settings->batchSize : 'undefined' );
146
+ $output .= $this->info( "CPU Load:", isset( $settings->cpuLoad ) ? $settings->cpuLoad : 'undefined' );
147
+ $output .= $this->info( "WP in Subdir:", isset( $settings->wpSubDirectory ) ? $settings->wpSubDirectory : 'false' );
148
+ $output .= $this->info( "Login Custom Link:", isset( $settings->loginSlug ) ? $settings->loginSlug : 'false' );
149
 
150
+ $output .= PHP_EOL . PHP_EOL . "-- Available Sites Version < 1.1.6.x" . PHP_EOL . PHP_EOL;
151
 
152
  $i = 1;
153
  foreach ( $clones as $key => $value ) {
156
  $output .= PHP_EOL . PHP_EOL . "-- Available Sites Version > 2.0.x" . PHP_EOL . PHP_EOL;
157
 
158
  foreach ( $clonesBeta as $key => $clone ) {
159
+ $output .= $this->info( "Number:", isset( $clone['number'] ) ? $clone['number'] : 'undefined' );
160
+ $output .= $this->info( "directoryName:", isset( $clone['directoryName'] ) ? $clone['directoryName'] : 'undefined' );
161
+ $output .= $this->info( "Path:", isset( $clone['path'] ) ? $clone['path'] : 'undefined' );
162
+ $output .= $this->info( "URL:", isset( $clone['url'] ) ? $clone['url'] : 'undefined' );
163
+ $output .= $this->info( "DB Prefix:", isset( $clone['prefix'] ) ? $clone['prefix'] : 'undefined' );
164
+ $output .= $this->info( "DB Prefix wp-config.php:", $this->getStagingPrefix( $clone ) );
165
+ $output .= $this->info( "Version:", isset( $clone['version'] ) ? $clone['version'] : 'undefined' ) . PHP_EOL . PHP_EOL;
166
  }
167
 
168
 
172
 
173
 
174
  //$output .= PHP_EOL . PHP_EOL;
175
+
176
+ $output .= $this->info( "Plugin Version:", get_option( 'wpstg_version', 'undefined' ) );
177
+ $output .= $this->info( "Install Date:", get_option( 'wpstg_installDate', 'undefined' ) );
178
+ $output .= $this->info( "Upgraded from:", get_option( 'wpstg_version_upgraded_from', 'undefined' ) );
179
+ $output .= $this->info( "Is Staging Site:", get_option( 'wpstg_is_staging_site', 'undefined' ) ) . PHP_EOL . PHP_EOL;
180
 
181
 
182
  return apply_filters( "wpstg_sysinfo_after_wpstaging_info", $output );
183
  }
184
 
185
  /**
186
+ * Browser Information
187
+ * @return string
188
+ */
189
+ public function browser() {
190
+ $output = $this->header( "User Browser" );
191
+ $output .= (new Browser);
192
+
193
+ return apply_filters( "wpstg_sysinfo_after_user_browser", $output );
194
+ }
195
+
196
+ /**
197
+ * Frontpage Information when frontpage is set to "page"
198
+ * @return string
199
+ */
200
+ public function frontPage() {
201
+ if( get_option( "show_on_front" ) !== "page" ) {
202
+ return '';
203
+ }
204
+
205
+ $frontPageID = get_option( "page_on_front" );
206
+ $blogPageID = get_option( "page_for_posts" );
207
+
208
+ // Front Page
209
+ $pageFront = ($frontPageID != 0) ? get_the_title( $frontPageID ) . " (#{$frontPageID})" : "Unset";
210
+ // Blog Page ID
211
+ $pageBlog = ($blogPageID != 0) ? get_the_title( $blogPageID ) . " (#{$blogPageID})" : "Unset";
212
+
213
+ $output = $this->info( "Page On Front:", $pageFront );
214
+ $output .= $this->info( "Page For Posts:", $pageBlog );
215
+
216
+ return $output;
217
+ }
218
+
219
+ /**
220
+ * Check wp_remote_post() functionality
221
+ * @return string
222
+ */
223
+ public function wpRemotePost() {
224
+ // Make sure wp_remote_post() is working
225
+ $wpRemotePost = "wp_remote_post() does not work";
226
+
227
+ // Send request
228
+ $response = wp_remote_post(
229
+ "https://www.paypal.com/cgi-bin/webscr", array(
230
+ "sslverify" => false,
231
+ "timeout" => 60,
232
+ "user-agent" => "WPSTG/" . WPStaging::VERSION,
233
+ "body" => array("cmd" => "_notify-validate")
234
+ )
235
+ );
236
+
237
+ // Validate it worked
238
+ if( !is_wp_error( $response ) && 200 <= $response["response"]["code"] && 300 > $response["response"]["code"] ) {
239
+ $wpRemotePost = "wp_remote_post() works";
240
+ }
241
+
242
+ return $this->info( "Remote Post:", $wpRemotePost );
243
+ }
244
+
245
+ /**
246
+ * WordPress Configuration
247
+ * @return string
248
+ */
249
+ public function wp() {
250
+ $output = $this->header( "WordPress Configuration" );
251
+ $output .= $this->info( "Version:", get_bloginfo( "version" ) );
252
+ $output .= $this->info( "Language:", (defined( "WPLANG" ) && WPLANG) ? WPLANG : "en_US" );
253
+
254
+ $permalinkStructure = get_option( "permalink_structure" );
255
+ ;
256
+ $output .= $this->info( "Permalink Structure:", ($permalinkStructure) ? $permalinkStructure : "Default" );
257
+
258
+ $output .= $this->info( "Active Theme:", $this->theme() );
259
+ $output .= $this->info( "Show On Front:", get_option( "show_on_front" ) );
260
+
261
+ // Frontpage information
262
+ $output .= $this->frontPage();
263
+
264
+ // WP Remote Post
265
+ $output .= $this->wpRemotePost();
266
+
267
+ // Table Prefix
268
+ $wpDB = $this->di->get( "wpdb" );
269
+ $tablePrefix = "DB Prefix: " . $wpDB->prefix . ' ';
270
+ $tablePrefix .= "Length: " . strlen( $wpDB->prefix ) . " Status: ";
271
+ $tablePrefix .= (strlen( $wpDB->prefix ) > 16) ? " ERROR: Too long" : " Acceptable";
272
+
273
+ $output .= $this->info( "Table Prefix:", $tablePrefix );
 
 
 
 
 
 
274
 
275
  // Constants
276
  $output .= $this->info( "WP Content Path:", WP_CONTENT_DIR );
277
  $output .= $this->info( "WP Plugin Dir:", WP_PLUGIN_DIR );
278
  if (defined('UPLOADS'))
279
+ $output .= $this->info( "WP UPLOADS CONST:", UPLOADS );
280
  $uploads = wp_upload_dir();
281
+ $output .= $this->info( "WP Uploads Dir:", $uploads['basedir'] );
282
  if (defined('WP_TEMP_DIR'))
283
+ $output .= $this->info( "WP Temp Dir:", WP_TEMP_DIR );
284
+
285
+ // WP Debug
286
+ $output .= $this->info( "WP_DEBUG:", (defined( "WP_DEBUG" )) ? WP_DEBUG ? "Enabled" : "Disabled" : "Not set" );
287
+ $output .= $this->info( "Memory Limit:", WP_MEMORY_LIMIT );
288
+ $output .= $this->info( "Registered Post Stati:", implode( ", ", \get_post_stati() ) );
289
+
290
+ return apply_filters( "wpstg_sysinfo_after_wpstg_config", $output );
291
+ }
292
+
293
+ /**
294
+ * List of Active Plugins
295
+ * @param array $plugins
296
+ * @param array $activePlugins
297
+ * @return string
298
+ */
299
+ public function activePlugins( $plugins, $activePlugins ) {
300
+ $output = $this->header( "WordPress Active Plugins" );
301
+
302
+ foreach ( $plugins as $path => $plugin ) {
303
+ if( !in_array( $path, $activePlugins ) ) {
304
+ continue;
305
+ }
306
+
307
+ $output .= "{$plugin["Name"]}: {$plugin["Version"]}" . PHP_EOL;
308
+ }
309
+
310
+ return apply_filters( "wpstg_sysinfo_after_wordpress_plugins", $output );
311
+ }
312
+
313
+ /**
314
+ * List of Inactive Plugins
315
+ * @param array $plugins
316
+ * @param array $activePlugins
317
+ * @return string
318
+ */
319
+ public function inactivePlugins( $plugins, $activePlugins ) {
320
+ $output = $this->header( "WordPress Inactive Plugins" );
321
+
322
+ foreach ( $plugins as $path => $plugin ) {
323
+ if( in_array( $path, $activePlugins ) ) {
324
+ continue;
325
+ }
326
+
327
+ $output .= "{$plugin["Name"]}: {$plugin["Version"]}" . PHP_EOL;
328
+ }
329
+
330
+ return apply_filters( "wpstg_sysinfo_after_wordpress_plugins_inactive", $output );
331
+ }
332
+
333
+ /**
334
+ * Get list of active and inactive plugins
335
+ * @return string
336
+ */
337
+ public function plugins() {
338
+ // Get plugins and active plugins
339
+ $plugins = get_plugins();
340
+ $activePlugins = get_option( "active_plugins", array() );
341
+
342
+ // Active plugins
343
+ $output = $this->activePlugins( $plugins, $activePlugins );
344
+ $output .= $this->inactivePlugins( $plugins, $activePlugins );
345
+
346
+ return $output;
347
+ }
348
+
349
+ /**
350
+ * Multisite Plugins
351
+ * @return string
352
+ */
353
+ public function multiSitePlugins() {
354
+ if( !$this->isMultiSite ) {
355
+ return '';
356
+ }
357
+
358
+ $output = $this->header( "Network Active Plugins" );
359
+
360
+ $plugins = wp_get_active_network_plugins();
361
+ $activePlugins = get_site_option( "active_sitewide_plugins", array() );
362
+
363
+ foreach ( $plugins as $pluginPath ) {
364
+ $pluginBase = plugin_basename( $pluginPath );
365
+
366
+ if( !array_key_exists( $pluginBase, $activePlugins ) ) {
367
+ continue;
368
+ }
369
+
370
+ $plugin = get_plugin_data( $pluginPath );
371
+
372
+ $output .= "{$plugin["Name"]}: {$plugin["Version"]}" . PHP_EOL;
373
+ }
374
+ unset( $plugins, $activePlugins );
375
+
376
+ return $output;
377
+ }
378
+
379
+ /**
380
+ * Server Information
381
+ * @return string
382
+ */
383
+ public function server() {
384
+ // Server Configuration
385
+ $output = $this->header( "Webserver Configuration" );
386
+
387
+ $output .= $this->info( "PHP Version:", PHP_VERSION );
388
+ $output .= $this->info( "MySQL Version:", $this->di->get( "wpdb" )->db_version() );
389
+ $output .= $this->info( "Webserver Info:", $_SERVER["SERVER_SOFTWARE"] );
390
+
391
+ return apply_filters( "wpstg_sysinfo_after_webserver_config", $output );
392
+ }
393
+
394
+ /**
395
+ * PHP Configuration
396
+ * @return string
397
+ */
398
+ public function php() {
399
+ $output = $this->header( "PHP Configuration" );
400
+ $output .= $this->info( "Safe Mode:", ($this->isSafeModeEnabled() ? "Enabled" : "Disabled" ) );
401
+ $output .= $this->info( "Memory Limit:", ini_get( "memory_limit" ) );
402
+ $output .= $this->info( "Upload Max Size:", ini_get( "upload_max_filesize" ) );
403
+ $output .= $this->info( "Post Max Size:", ini_get( "post_max_size" ) );
404
+ $output .= $this->info( "Upload Max Filesize:", ini_get( "upload_max_filesize" ) );
405
+ $output .= $this->info( "Time Limit:", ini_get( "max_execution_time" ) );
406
+ $output .= $this->info( "Max Input Vars:", ini_get( "max_input_vars" ) );
407
+
408
+ $displayErrors = ini_get( "display_errors" );
409
+ $output .= $this->info( "Display Errors:", ($displayErrors) ? "On ({$displayErrors})" : "N/A" );
410
+
411
+ return apply_filters( "wpstg_sysinfo_after_php_config", $output );
412
+ }
413
+
414
+ /**
415
+ * Check if PHP is on Safe Mode
416
+ * @return bool
417
+ */
418
+ public function isSafeModeEnabled() {
419
+ return (
420
+ version_compare( PHP_VERSION, "5.4.0", '<' ) &&
421
+ @ini_get( "safe_mode" )
422
+ );
423
+ }
424
+
425
+ /**
426
+ * Checks if function exists or not
427
+ * @param string $functionName
428
+ * @return string
429
+ */
430
+ public function isSupported( $functionName ) {
431
+ return (function_exists( $functionName )) ? "Supported" : "Not Supported";
432
+ }
433
+
434
+ /**
435
+ * Checks if class or extension is loaded / exists to determine if it is installed or not
436
+ * @param string $name
437
+ * @param bool $isClass
438
+ * @return string
439
+ */
440
+ public function isInstalled( $name, $isClass = true ) {
441
+ if( true === $isClass ) {
442
+ return (class_exists( $name )) ? "Installed" : "Not Installed";
443
+ } else {
444
+ return (extension_loaded( $name )) ? "Installed" : "Not Installed";
445
+ }
446
+ }
447
+
448
+ /**
449
+ * Gets Installed Important PHP Extensions
450
+ * @return string
451
+ */
452
+ public function phpExtensions() {
453
+ // Important PHP Extensions
454
+ $output = $this->header( "PHP Extensions" );
455
+ $output .= $this->info( "cURL:", $this->isSupported( "curl_init" ) );
456
+ $output .= $this->info( "fsockopen:", $this->isSupported( "fsockopen" ) );
457
+ $output .= $this->info( "SOAP Client:", $this->isInstalled( "SoapClient" ) );
458
+ $output .= $this->info( "Suhosin:", $this->isInstalled( "suhosin", false ) );
459
+
460
+ return apply_filters( "wpstg_sysinfo_after_php_ext", $output );
461
+ }
462
+
463
+ /**
464
+ * Check if WP is installed in subdir
465
+ * @return boolean
466
+ */
467
+ private function isSubDir() {
468
+ if( get_option( 'siteurl' ) !== get_option( 'home' ) ) {
469
+ return true;
470
+ }
471
+ return false;
472
+ }
473
+
474
+ /**
475
+ * Check and return prefix of the staging site
476
+ */
477
+ /**
478
+ * Try to get the staging prefix from wp-config.php of staging site
479
+ * @param array $clone
480
+ * @return sting
481
+ */
482
+ private function getStagingPrefix( $clone = array() ) {
483
+ // Throw error
484
+ $path = ABSPATH . $clone['directoryName'] . "/wp-config.php";
485
+ if( false === ($content = @file_get_contents( $path )) ) {
486
+ return 'Can\'t find staging wp-config.php';
487
+ } else {
488
+
489
+ // Get prefix from wp-config.php
490
+ //preg_match_all("/table_prefix\s*=\s*'(\w*)';/", $content, $matches);
491
+ preg_match( "/table_prefix\s*=\s*'(\w*)';/", $content, $matches );
492
+ //wp_die(var_dump($matches));
493
+
494
+ if( !empty( $matches[1] ) ) {
495
+ return $matches[1];
496
+ } else {
497
+ return 'No table_prefix in wp-config.php';
498
+ }
499
+ }
500
+ }
501
+
502
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
apps/Backend/Modules/Views/Forms/Settings.php CHANGED
@@ -92,6 +92,21 @@ class Settings {
92
  );
93
 
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  // File Copy Batch Size
96
  $element = new Numerical(
97
  "wpstg_settings[batchSize]", array(
92
  );
93
 
94
 
95
+ // File Copy Batch Size
96
+ $element = new Numerical(
97
+ "wpstg_settings[maxFileSize]", array(
98
+ "class" => "medium-text",
99
+ "step" => 1,
100
+ "max" => 999999,
101
+ "min" => 0
102
+ )
103
+ );
104
+
105
+ $this->form["general"]->add(
106
+ $element->setLabel( "Maximum File Size (MB)" )
107
+ ->setDefault( isset( $settings->maxFileSize ) ? $settings->maxFileSize : 8 )
108
+ );
109
+
110
  // File Copy Batch Size
111
  $element = new Numerical(
112
  "wpstg_settings[batchSize]", array(
apps/Backend/views/clone/ajax/update.php CHANGED
@@ -1,29 +1,16 @@
1
  <div class=successfullying-section">
2
- <?php echo __("Copy Database Tables", "wpstg")?>
3
  <div class="wpstg-progress-bar">
4
- <div class="wpstg-progress" id="wpstg-db-progress" style="width:0"></div>
 
 
 
5
  </div>
6
- </div>
7
-
8
- <div class="wpstg-cloning-section">
9
- <?php echo __("Prepare Directories", "wpstg")?>
10
- <div class="wpstg-progress-bar">
11
- <div class="wpstg-progress" id="wpstg-directories-progress" style="width:0"></div>
12
- </div>
13
- </div>
14
-
15
- <div class="wpstg-cloning-section">
16
- <?php echo __("Copy Files", "wpstg")?>
17
- <div class="wpstg-progress-bar">
18
- <div class="wpstg-progress" id="wpstg-files-progress" style="width:0"></div>
19
- </div>
20
- </div>
21
-
22
- <div class="wpstg-cloning-section">
23
- <?php echo __("Replace Data", "wpstg")?>
24
- <div class="wpstg-progress-bar">
25
- <div class="wpstg-progress" id="wpstg-links-progress" style="width:0"></div>
26
  </div>
 
27
  </div>
28
 
29
  <button type="button" id="wpstg-cancel-cloning-update" class="wpstg-link-btn button-primary">
1
  <div class=successfullying-section">
2
+ <h2 id="wpstg-processing-header"><?php echo __("Processing, please wait...", "wpstg")?></h2>
3
  <div class="wpstg-progress-bar">
4
+ <div class="wpstg-progress" id="wpstg-progress-db" style="width:0;overflow: hidden;"></div>
5
+ <div class="wpstg-progress" id="wpstg-progress-sr" style="width:0;background-color:#3c9ee4;overflow: hidden;"></div>
6
+ <div class="wpstg-progress" id="wpstg-progress-dirs" style="width:0;background-color:#3a96d7;overflow: hidden;"></div>
7
+ <div class="wpstg-progress" id="wpstg-progress-files" style="width:0;background-color:#378cc9;overflow: hidden;"></div>
8
  </div>
9
+ <div style="clear:both;">
10
+ <div id="wpstg-processing-status"></div>
11
+ <div id="wpstg-processing-timer"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  </div>
13
+ <div style="clear: both;"></div>
14
  </div>
15
 
16
  <button type="button" id="wpstg-cancel-cloning-update" class="wpstg-link-btn button-primary">
apps/Backend/views/clone/includes/footer.php CHANGED
@@ -1,4 +1,4 @@
1
- <div style="clear:both;">Something not working? Open a <a href="https://wp-staging.com/support" target="_blank" rel="external nofollow"> support ticket</a> and we help you fixing it quickly.</div>
2
  <div id="wpstg-error-wrapper">
3
  <div id="wpstg-error-details"></div>
4
  </div>
1
+ <div style="clear:both;">Something not working? Open a <a href="https://wp-staging.com/support" target="_blank" rel="external nofollow"> support ticket</a> and we help you to fix it quickly.</div>
2
  <div id="wpstg-error-wrapper">
3
  <div id="wpstg-error-details"></div>
4
  </div>
apps/Core/Utils/Helper.php CHANGED
@@ -10,6 +10,7 @@ if (!defined("WPINC"))
10
 
11
  class Helper {
12
 
 
13
  /**
14
  * Retrieves the URL for a given site where the front end is accessible.
15
  * This is from WordPress source 4.9.5/src/wp-includes/link-template.php
@@ -68,4 +69,6 @@ class Helper {
68
  return $url;
69
  }
70
 
 
 
71
  }
10
 
11
  class Helper {
12
 
13
+
14
  /**
15
  * Retrieves the URL for a given site where the front end is accessible.
16
  * This is from WordPress source 4.9.5/src/wp-includes/link-template.php
69
  return $url;
70
  }
71
 
72
+
73
+
74
  }
apps/Core/Utils/Multisite.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Utils;
4
+
5
+ use WPStaging\Utils\Helper;
6
+
7
+ // No Direct Access
8
+ if( !defined( "WPINC" ) ) {
9
+ die;
10
+ }
11
+
12
+ class Multisite {
13
+
14
+ /**
15
+ * Get multisite main site homeurl
16
+ * @return string
17
+ */
18
+ public function getHomeURL() {
19
+ $helper = new Helper();
20
+
21
+ $url = $helper->get_home_url();
22
+ $result = parse_url( $url );
23
+ return $result['scheme'] . "://" . $result['host'];
24
+ }
25
+
26
+ }
apps/Core/WPStaging.php CHANGED
@@ -29,7 +29,7 @@ final class WPStaging {
29
  /**
30
  * Plugin version
31
  */
32
- const VERSION = "2.2.9";
33
 
34
  /**
35
  * Plugin name
29
  /**
30
  * Plugin version
31
  */
32
+ const VERSION = "2.3.0";
33
 
34
  /**
35
  * Plugin name
readme.txt CHANGED
@@ -9,7 +9,7 @@ License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
  Tags: staging, duplication, cloning, clone, migration, sandbox, test site, testing, backup, post, admin, administration, duplicate posts
10
  Requires at least: 3.6+
11
  Tested up to: 4.9
12
- Stable tag: 2.2.9
13
  Requires PHP: 5.3
14
 
15
  A duplicator plugin! Clone, duplicate and migrate live sites to independent staging and development sites that are available only to administrators.
@@ -146,10 +146,27 @@ https://wp-staging.com
146
 
147
  == Changelog ==
148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  = 2.2.9 =
150
  * Fix: Missing trailingslash results to wrong absolute paths in database after Search & Replace operation
151
 
152
-
153
  = 2.2.8 =
154
  * New: Add filter 'wpstg_filter_options_replace' to exclude certain tables from updating while cloning
155
  * New: Exclude tables for plugin wp_mail_smtp
9
  Tags: staging, duplication, cloning, clone, migration, sandbox, test site, testing, backup, post, admin, administration, duplicate posts
10
  Requires at least: 3.6+
11
  Tested up to: 4.9
12
+ Stable tag: 2.3.0
13
  Requires PHP: 5.3
14
 
15
  A duplicator plugin! Clone, duplicate and migrate live sites to independent staging and development sites that are available only to administrators.
146
 
147
  == Changelog ==
148
 
149
+ = 2.3.0 =
150
+ * Security: Do not allow to create a new staging site into a subfolder which already exists
151
+ * New: Option to allow adjustment of the allowed maximum size of files that are going to be copied while cloning.
152
+ * New: Add multisite informations in system info log
153
+ * New: Option to allow adjustment of the allowed maximum size of files that are going to be copied while cloning.
154
+ * New: Use the new progress bar for clone updating process
155
+ * Fix: Progress bar for step 'database' is not filling up to 100%
156
+ * Fix: If cloning update process is interupted it may happen that staging site is not available any longer. (Updating the clone does not copy index.php to staging site again)
157
+ * Fix: Progress bar not shown as intented for clone updating process
158
+ * Fix: Can not open upload folder in file selection menu
159
+ * Fix: Undefined object $this->tables
160
+ * Fix: wp-config.php not copied when previous clone updating process has been failed
161
+ * Fix: Parameter must be an array or an object that implements Callable
162
+ * Fix: Skip search & replace for objects where key is null
163
+ * Fix: Search & Replace not working if serialized object contains __PHP_Incomplete_Class_Name
164
+ * Tweaks: remove term "error" from several log entries
165
+ * Tweak: Remove certain debugging notices from the default log window
166
+
167
  = 2.2.9 =
168
  * Fix: Missing trailingslash results to wrong absolute paths in database after Search & Replace operation
169
 
 
170
  = 2.2.8 =
171
  * New: Add filter 'wpstg_filter_options_replace' to exclude certain tables from updating while cloning
172
  * New: Exclude tables for plugin wp_mail_smtp
wp-staging.php CHANGED
@@ -7,7 +7,7 @@
7
  * Author: WP-Staging
8
  * Author URI: https://wp-staging.com
9
  * Contributors: ReneHermi, ilgityildirim
10
- * Version: 2.2.9
11
  * Text Domain: wpstg
12
  * Domain Path: /languages/
13
 
7
  * Author: WP-Staging
8
  * Author URI: https://wp-staging.com
9
  * Contributors: ReneHermi, ilgityildirim
10
+ * Version: 2.3.0
11
  * Text Domain: wpstg
12
  * Domain Path: /languages/
13