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

Version Description

  • New: Compatible up to WordPress 5.4.2
  • Fix: Remove beta notice
  • Fix: Error if views are cloned
  • Fix: Fatal error if WordPress is older than 4.5
  • Fix: Merge pro/free version
  • Fix: Step switching logic does not work properly
  • Fix: Fix progress bar when certains steps are skipped
  • Fix: Change german translation for REPORT ISSUE
Download this release

Release Info

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

Code changes from version 2.7.3 to 2.7.4

Files changed (49) hide show
  1. Backend/Administrator.php +302 -240
  2. Backend/Modules/Jobs/Cloning.php +49 -32
  3. Backend/Modules/Jobs/Data.php +43 -107
  4. Backend/Modules/Jobs/DataExternal.php +374 -449
  5. Backend/Modules/Jobs/Database.php +0 -8
  6. Backend/Modules/Jobs/DatabaseExternal.php +109 -147
  7. Backend/Modules/Jobs/Files.php +4 -0
  8. Backend/Modules/Jobs/Multisite/Data.php +51 -81
  9. Backend/Modules/Jobs/Multisite/DataExternal.php +126 -160
  10. Backend/Modules/Jobs/Multisite/Database.php +0 -63
  11. Backend/Modules/Jobs/Multisite/DatabaseExternal.php +133 -122
  12. Backend/Modules/Jobs/Multisite/Files.php +4 -0
  13. Backend/Modules/Jobs/PreserveDataFirstStep.php +125 -0
  14. Backend/Modules/Jobs/PreserveDataSecondStep.php +108 -0
  15. Backend/Modules/Jobs/Scan.php +37 -69
  16. Backend/Modules/Jobs/SearchReplace.php +2 -3
  17. Backend/Modules/Jobs/SearchReplaceExternal.php +1 -0
  18. Backend/Modules/SystemInfo.php +2 -1
  19. Backend/public/css/wpstg-admin.css +1 -1
  20. Backend/public/js/wpstg-admin.js +62 -10
  21. Backend/views/_main/report-issue.php +8 -5
  22. Backend/views/clone/ajax/scan.php +2 -3
  23. Backend/views/clone/ajax/single-overview.php +7 -1
  24. Backend/views/clone/single-site/index.php +4 -4
  25. Command/Database/Export/ExportCommand.php +86 -0
  26. Command/Database/Export/ExportDto.php +261 -0
  27. Command/Database/Export/ExportException.php +13 -0
  28. Command/Database/Export/ExportHandler.php +80 -0
  29. Command/Database/Snapshot/AbstractSnapshotCommand.php +73 -0
  30. Command/Database/Snapshot/CreateSnapshotCommand.php +101 -0
  31. Command/Database/Snapshot/DeleteSnapshotCommand.php +48 -0
  32. Command/Database/Snapshot/SnapshotCommandException.php +8 -0
  33. Command/Database/Snapshot/SnapshotDto.php +126 -0
  34. Command/Database/Snapshot/SnapshotHandler.php +48 -0
  35. Command/Database/Snapshot/UpdateSnapshotCommand.php +55 -0
  36. Command/Database/SnapshotFactory.php +77 -0
  37. Core/Utils/Report.php +75 -75
  38. Core/Utils/functions.php +3 -3
  39. Core/WPStaging.php +52 -51
  40. Service/Adapter/SourceDatabase.php +55 -0
  41. Service/Permalinks/PermalinksPurge.php +23 -0
  42. Service/Utils/FileSystem.php +4 -0
  43. Service/Utils/Strings.php +26 -0
  44. languages/wp-staging-de_DE.po +3 -3
  45. readme.txt +18 -2
  46. vendor/autoload.php +1 -1
  47. vendor/composer/autoload_real.php +4 -4
  48. vendor/composer/autoload_static.php +3 -3
  49. wp-staging.php +1 -1
Backend/Administrator.php CHANGED
@@ -3,7 +3,7 @@
3
  namespace WPStaging\Backend;
4
 
5
  // No Direct Access
6
- if( !defined( "WPINC" ) ) {
7
  die;
8
  }
9
 
@@ -30,7 +30,8 @@ use WPStaging\Backend\Pro\Modules\Jobs\Processing;
30
  * Class Administrator
31
  * @package WPStaging\Backend
32
  */
33
- class Administrator extends InjectionAware {
 
34
 
35
  /**
36
  * @var string
@@ -42,17 +43,27 @@ class Administrator extends InjectionAware {
42
  */
43
  private $url;
44
 
 
 
 
 
 
 
 
 
 
45
  /**
46
  * Initialize class
47
  */
48
- public function initialize() {
 
49
  $this->defineHooks();
50
 
51
  // Path to backend
52
- $this->path = plugin_dir_path( __FILE__ );
53
 
54
  // URL to public backend folder
55
- $this->url = plugin_dir_url( __FILE__ ) . "public/";
56
 
57
  // Load plugins meta data
58
  $this->loadMeta();
@@ -61,72 +72,75 @@ class Administrator extends InjectionAware {
61
  /**
62
  * Load plugn meta data
63
  */
64
- public function loadMeta() {
 
65
  $run = new \WPStaging\Backend\Pluginmeta\Pluginmeta();
66
  }
67
 
68
  /**
69
  * Define Hooks
70
  */
71
- private function defineHooks() {
 
72
  // Get loader
73
- $loader = $this->di->get( "loader" );
74
 
75
  $Activation = new \WPStaging\Backend\Activation\Activation();
76
 
77
- if(!defined('WPSTGPRO_VERSION')) {
78
  $Welcome = new Activation\Welcome();
79
  }
80
 
81
- $loader->addAction( "activated_plugin", $Activation, 'deactivate_other_instances' );
82
- $loader->addAction( "admin_menu", $this, "addMenu", 10 );
83
- $loader->addAction( "admin_init", $this, "setOptionFormElements" );
84
- $loader->addAction( "admin_init", $this, "upgrade" );
85
- $loader->addAction( "admin_post_wpstg_download_sysinfo", $this, "systemInfoDownload" );
86
- $loader->addAction( "admin_post_wpstg_export", $this, "export" );
87
- $loader->addAction( "admin_post_wpstg_import_settings", $this, "import" );
88
- $loader->addAction( "admin_notices", $this, "messages" );
89
 
90
- if(!defined('WPSTGPRO_VERSION')){
91
- add_filter( 'admin_footer', array($this, 'loadFeedbackForm') );
92
  }
93
 
94
  // Ajax Requests
95
- $loader->addAction( "wp_ajax_wpstg_overview", $this, "ajaxOverview" );
96
- $loader->addAction( "wp_ajax_wpstg_scanning", $this, "ajaxScan" );
97
- $loader->addAction( "wp_ajax_wpstg_check_clone", $this, "ajaxcheckCloneName" );
98
- $loader->addAction( "wp_ajax_wpstg_restart", $this, "ajaxRestart" );
99
- $loader->addAction( "wp_ajax_wpstg_update", $this, "ajaxUpdateProcess" );
100
- $loader->addAction( "wp_ajax_wpstg_cloning", $this, "ajaxStartClone" );
101
- $loader->addAction( "wp_ajax_wpstg_processing", $this, "ajaxCloneDatabase" );
102
- $loader->addAction( "wp_ajax_wpstg_database_connect", $this, "ajaxDatabaseConnect" );
103
- $loader->addAction( "wp_ajax_wpstg_clone_prepare_directories", $this, "ajaxPrepareDirectories" );
104
- $loader->addAction( "wp_ajax_wpstg_clone_files", $this, "ajaxCopyFiles" );
105
- $loader->addAction( "wp_ajax_wpstg_clone_replace_data", $this, "ajaxReplaceData" );
106
- $loader->addAction( "wp_ajax_wpstg_clone_finish", $this, "ajaxFinish" );
107
- $loader->addAction( "wp_ajax_wpstg_confirm_delete_clone", $this, "ajaxDeleteConfirmation" );
108
- $loader->addAction( "wp_ajax_wpstg_delete_clone", $this, "ajaxDeleteClone" );
109
- $loader->addAction( "wp_ajax_wpstg_cancel_clone", $this, "ajaxCancelClone" );
110
- $loader->addAction( "wp_ajax_wpstg_cancel_update", $this, "ajaxCancelUpdate" );
111
- $loader->addAction( "wp_ajax_wpstg_hide_poll", $this, "ajaxHidePoll" );
112
- $loader->addAction( "wp_ajax_wpstg_hide_rating", $this, "ajaxHideRating" );
113
- $loader->addAction( "wp_ajax_wpstg_hide_later", $this, "ajaxHideLaterRating" );
114
- $loader->addAction( "wp_ajax_wpstg_hide_beta", $this, "ajaxHideBeta" );
115
- $loader->addAction( "wp_ajax_wpstg_logs", $this, "ajaxLogs" );
116
- $loader->addAction( "wp_ajax_wpstg_check_disk_space", $this, "ajaxCheckFreeSpace" );
117
- $loader->addAction( "wp_ajax_wpstg_send_report", $this, "ajaxSendReport" );
118
- $loader->addAction( "wp_ajax_wpstg_send_feedback", $this, "sendFeedback" );
119
 
120
 
121
  // Ajax hooks pro Version
122
- $loader->addAction( "wp_ajax_wpstg_scan", $this, "ajaxPushScan" );
123
- $loader->addAction( "wp_ajax_wpstg_push_processing", $this, "ajaxPushProcessing" );
124
  }
125
 
126
  /**
127
  * Load Feedback Form on plugins.php
128
  */
129
- public function loadFeedbackForm() {
 
130
  $form = new Feedback\Feedback();
131
  $load = $form->loadForm();
132
  }
@@ -134,7 +148,8 @@ class Administrator extends InjectionAware {
134
  /**
135
  * Send Feedback form via mail
136
  */
137
- public function sendFeedback() {
 
138
  $form = new Feedback\Feedback();
139
  $send = $form->sendMail();
140
  }
@@ -142,8 +157,9 @@ class Administrator extends InjectionAware {
142
  /**
143
  * Register options form elements
144
  */
145
- public function setOptionFormElements() {
146
- register_setting( "wpstg_settings", "wpstg_settings", array($this, "sanitizeOptions") );
 
147
  }
148
 
149
  /**
@@ -164,23 +180,32 @@ class Administrator extends InjectionAware {
164
  * @param array $data
165
  * @return array
166
  */
167
- public function sanitizeOptions( $data = array() ) {
168
- $sanitized = $this->sanitizeData( $data );
 
169
 
170
- add_settings_error( "wpstg-notices", '', __( "Settings updated.", "wp-staging" ), "updated" );
171
 
172
- return apply_filters( "wpstg-settings", $sanitized, $data );
173
  }
174
 
175
  /**
176
  * @param array $data
177
  * @return array
178
  */
179
- private function sanitizeData( $data = array() ) {
 
180
  $sanitized = array();
181
 
182
- foreach ( $data as $key => $value ) {
183
- $sanitized[$key] = (is_array( $value )) ? $this->sanitizeData( $value ) : htmlspecialchars( $value );
 
 
 
 
 
 
 
184
  }
185
 
186
  return $sanitized;
@@ -238,18 +263,20 @@ class Administrator extends InjectionAware {
238
  /**
239
  * Settings Page
240
  */
241
- public function getSettingsPage() {
 
242
  // Tabs
243
- $tabs = new Tabs( array(
244
- "general" => __( "General", "wp-staging" )
245
- ) );
246
 
247
 
248
  $this->di
249
- // Set tabs
250
- ->set( "tabs", $tabs )
251
- // Forms
252
- ->set( "forms", new FormSettings( $tabs ) );
 
253
 
254
 
255
  require_once "{$this->path}views/settings/main-settings.php";
@@ -258,9 +285,10 @@ class Administrator extends InjectionAware {
258
  /**
259
  * Clone Page
260
  */
261
- public function getClonePage() {
 
262
  // Existing clones
263
- $availableClones = get_option( "wpstg_existing_clones_beta", array() );
264
 
265
  require_once "{$this->path}views/clone/index.php";
266
  }
@@ -268,9 +296,9 @@ class Administrator extends InjectionAware {
268
  /**
269
  * Welcome Page
270
  */
271
- public function getWelcomePage() {
272
- if(defined('WPSTGPRO_VERSION'))
273
- {
274
  return;
275
  }
276
  require_once "{$this->path}views/welcome/welcome.php";
@@ -279,16 +307,17 @@ class Administrator extends InjectionAware {
279
  /**
280
  * Tools Page
281
  */
282
- public function getToolsPage() {
 
283
  // Tabs
284
- $tabs = new Tabs( array(
285
- "import_export" => __( "Import/Export", "wp-staging" ),
286
- "system_info" => __( "System Info", "wp-staging" )
287
- ) );
288
 
289
- $this->di->set( "tabs", $tabs );
290
 
291
- $this->di->set( "systemInfo", new SystemInfo( $this->di ) );
292
 
293
  require_once "{$this->path}views/tools/index.php";
294
  }
@@ -296,49 +325,51 @@ class Administrator extends InjectionAware {
296
  /**
297
  * System Information Download
298
  */
299
- public function systemInfoDownload() {
300
- if( !current_user_can( "update_plugins" ) ) {
 
301
  return;
302
  }
303
 
304
  nocache_headers();
305
- header( "Content-Type: text/plain" );
306
- header( 'Content-Disposition: attachment; filename="wpstg-system-info.txt"' );
307
- echo wp_strip_all_tags( new SystemInfo( $this->di ) );
308
  }
309
 
310
  /**
311
  * Import JSON settings file
312
  */
313
- public function import() {
314
- if( empty( $_POST["wpstg_import_nonce"] ) ) {
 
315
  return;
316
  }
317
 
318
- if( !wp_verify_nonce( $_POST["wpstg_import_nonce"], "wpstg_import_nonce" ) ) {
319
  return;
320
  }
321
 
322
- if( !current_user_can( "update_plugins" ) ) {
323
  return;
324
  }
325
 
326
- $fileExtension = explode( '.', $_FILES["import_file"]["name"] );
327
- $fileExtension = end( $fileExtension );
328
- if( "json" !== $fileExtension ) {
329
- wp_die( "Please upload a valid .json file", "wp-staging" );
330
  }
331
 
332
 
333
  $importFile = $_FILES["import_file"]["tmp_name"];
334
 
335
- if( empty( $importFile ) ) {
336
- wp_die( __( "Please upload a file to import", "wp-staging" ) );
337
  }
338
 
339
- update_option( "wpstg_settings", json_decode( file_get_contents( $importFile, true ) ) );
340
 
341
- wp_safe_redirect( admin_url( "admin.php?page=wpstg-tools&wpstg-message=settings-imported" ) );
342
 
343
  return;
344
  }
@@ -346,35 +377,36 @@ class Administrator extends InjectionAware {
346
  /**
347
  * Export settings to JSON file
348
  */
349
- public function export() {
350
- if( empty( $_POST["wpstg_export_nonce"] ) ) {
 
351
  return;
352
  }
353
 
354
- if( !wp_verify_nonce( $_POST["wpstg_export_nonce"], "wpstg_export_nonce" ) ) {
355
  return;
356
  }
357
 
358
- if( !current_user_can( "manage_options" ) ) {
359
  return;
360
  }
361
 
362
- $settings = get_option( "wpstg_settings", array() );
363
 
364
- ignore_user_abort( true );
365
 
366
- if( !in_array( "set_time_limit", explode( ',', ini_get( "disable_functions" ) ) ) && !@ini_get( "safe_mode" ) ) {
367
- set_time_limit( 0 );
368
  }
369
 
370
- $fileName = apply_filters( "wpstg_settings_export_filename", "wpstg-settings-export-" . date( "m-d-Y" ) ) . ".json";
371
 
372
  nocache_headers();
373
- header( "Content-Type: application/json; charset=utf-8" );
374
- header( "Content-Disposition: attachment; filename={$fileName}" );
375
- header( "Expires: 0" );
376
 
377
- echo json_encode( $settings );
378
  }
379
 
380
  /**
@@ -383,26 +415,27 @@ class Administrator extends InjectionAware {
383
  * @param array $vars
384
  * @return string
385
  */
386
- public function render( $file, $vars = array() ) {
 
387
  $fullPath = $this->path . "views" . DIRECTORY_SEPARATOR;
388
- $fullPath = str_replace( array('/', "\\"), DIRECTORY_SEPARATOR, $fullPath . $file . ".php" );
389
 
390
- if( !file_exists( $fullPath ) || !is_readable( $fullPath ) ) {
391
  return "Can't render : {$fullPath} either file doesn't exist or can't read it";
392
  }
393
 
394
- $contents = @file_get_contents( $fullPath );
395
 
396
  // Variables are set
397
- if( count( $vars ) > 0 ) {
398
  $vars = array_combine(
399
- array_map( function ($key) {
400
- return "{{" . $key . "}}";
401
- }, array_keys( $vars )
402
- ), $vars
403
  );
404
 
405
- $contents = str_replace( array_keys( $vars ), array_values( $vars ), $contents );
406
  }
407
 
408
  return $contents;
@@ -411,8 +444,9 @@ class Administrator extends InjectionAware {
411
  /**
412
  * Restart cloning process
413
  */
414
- public function ajaxRestart() {
415
- check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
 
416
 
417
  $process = new ProcessLock();
418
  $process->restart();
@@ -421,19 +455,20 @@ class Administrator extends InjectionAware {
421
  /**
422
  * Ajax Overview
423
  */
424
- public function ajaxOverview() {
425
- check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
 
426
 
427
  // Existing clones
428
- $availableClones = get_option( "wpstg_existing_clones_beta", array() );
429
 
430
  // Get license data
431
- $license = get_option( 'wpstg_license_status' );
432
 
433
  // Get db
434
- $db = WPStaging::getInstance()->get( 'wpdb' );
435
 
436
- if( \WPStaging\WPStaging::getSlug() === 'wp-staging-pro' ) {
437
  require_once "{$this->path}Pro/views/single-overview-pro.php";
438
  } else {
439
  require_once "{$this->path}views/clone/ajax/single-overview.php";
@@ -445,14 +480,15 @@ class Administrator extends InjectionAware {
445
  /**
446
  * Ajax Scan
447
  */
448
- public function ajaxScan() {
449
- check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
 
450
 
451
  // Check first if there is already a process running
452
  $processLock = new ProcessLock();
453
  $processLock->isRunning();
454
 
455
- $db = WPStaging::getInstance()->get( 'wpdb' );
456
 
457
  // Scan
458
  $scan = new Scan();
@@ -469,44 +505,46 @@ class Administrator extends InjectionAware {
469
  /**
470
  * Ajax Check Clone Name
471
  */
472
- public function ajaxCheckCloneName() {
473
- $cloneName = sanitize_key( $_POST["cloneID"] );
474
- $cloneNameLength = strlen( $cloneName );
475
- $clones = get_option( "wpstg_existing_clones_beta", array() );
 
476
 
477
- $clonePath = trailingslashit( get_home_path() ) . $cloneName;
478
 
479
  // Check clone name length
480
- if( $cloneNameLength < 1 || $cloneNameLength > 16 ) {
481
- echo wp_send_json( array(
482
- "status" => "failed",
483
  "message" => "Clone name must be between 1 - 16 characters"
484
- ) );
485
- } elseif( array_key_exists( $cloneName, $clones ) ) {
486
- echo wp_send_json( array(
487
- "status" => "failed",
488
  "message" => "Clone name is already in use, please choose another clone name."
489
- ) );
490
- } elseif( is_dir( $clonePath ) && !wpstg_is_empty_dir( $clonePath ) ) {
491
- echo wp_send_json( array(
492
- "status" => "failed",
493
  "message" => "Clone directory " . $clonePath . " already exists. Use another clone name."
494
- ) );
495
  }
496
 
497
- echo wp_send_json( array("status" => "success") );
498
  }
499
 
500
  /**
501
  * Ajax Start Updating Clone (Basically just layout and saving data)
502
  */
503
- public function ajaxUpdateProcess() {
504
- check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
 
505
 
506
  $cloning = new Updating();
507
 
508
- if( !$cloning->save() ) {
509
- wp_die( 'can not save clone data' );
510
  }
511
 
512
  require_once "{$this->path}views/clone/ajax/update.php";
@@ -517,8 +555,9 @@ class Administrator extends InjectionAware {
517
  /**
518
  * Ajax Start Clone (Basically just layout and saving data)
519
  */
520
- public function ajaxStartClone() {
521
- check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
 
522
 
523
  // Check first if there is already a process running
524
  $processLock = new ProcessLock();
@@ -526,8 +565,8 @@ class Administrator extends InjectionAware {
526
 
527
  $cloning = new Cloning();
528
 
529
- if( !$cloning->save() ) {
530
- wp_die( 'can not save clone data' );
531
  }
532
 
533
  require_once "{$this->path}views/clone/ajax/start.php";
@@ -538,8 +577,9 @@ class Administrator extends InjectionAware {
538
  /**
539
  * Ajax Clone Database
540
  */
541
- public function ajaxCloneDatabase() {
542
- check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
 
543
 
544
  $cloning = new Cloning();
545
 
@@ -547,58 +587,63 @@ class Administrator extends InjectionAware {
547
  //http_response_code(504);
548
  //wp_send_json( '<html><body><head></head><body>test</body></html>' );
549
 
550
- wp_send_json( $cloning->start() );
551
  }
552
 
553
  /**
554
  * Ajax Prepare Directories (get listing of files)
555
  */
556
- public function ajaxPrepareDirectories() {
557
- check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
 
558
 
559
  $cloning = new Cloning();
560
 
561
- wp_send_json( $cloning->start() );
562
  }
563
 
564
  /**
565
  * Ajax Clone Files
566
  */
567
- public function ajaxCopyFiles() {
568
- check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
 
569
 
570
  $cloning = new Cloning();
571
 
572
- wp_send_json( $cloning->start() );
573
  }
574
 
575
  /**
576
  * Ajax Replace Data
577
  */
578
- public function ajaxReplaceData() {
579
- check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
 
580
 
581
  $cloning = new Cloning();
582
 
583
- wp_send_json( $cloning->start() );
584
  }
585
 
586
  /**
587
  * Ajax Finish
588
  */
589
- public function ajaxFinish() {
590
- check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
 
591
 
592
  $cloning = new Cloning();
593
 
594
- wp_send_json( $cloning->start() );
595
  }
596
 
597
  /**
598
  * Ajax Delete Confirmation
599
  */
600
- public function ajaxDeleteConfirmation() {
601
- check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
 
602
 
603
  $delete = new Delete();
604
  $delete->setData();
@@ -615,40 +660,44 @@ class Administrator extends InjectionAware {
615
  /**
616
  * Delete clone
617
  */
618
- public function ajaxDeleteClone() {
619
- check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
 
620
 
621
  $delete = new Delete();
622
 
623
- wp_send_json( $delete->start() );
624
  }
625
 
626
  /**
627
  * Delete clone
628
  */
629
- public function ajaxCancelClone() {
630
- check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
 
631
 
632
  $cancel = new Cancel();
633
 
634
- wp_send_json( $cancel->start() );
635
  }
636
 
637
  /**
638
  * Cancel updating process / Do not delete clone!
639
  */
640
- public function ajaxCancelUpdate() {
641
- check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
 
642
 
643
  $cancel = new CancelUpdate();
644
- wp_send_json( $cancel->start() );
645
  }
646
 
647
  /**
648
  * Admin Messages
649
  */
650
- public function messages() {
651
- $notice = new Notices( $this->path, $this->url );
 
652
 
653
  $run = $notice->messages();
654
  }
@@ -657,9 +706,10 @@ class Administrator extends InjectionAware {
657
  * Ajax Hide Poll
658
  * @return mixed boolean | json
659
  */
660
- public function ajaxHidePoll() {
661
- if( false !== update_option( "wpstg_poll", "no" ) ) {
662
- wp_send_json( true );
 
663
  }
664
  return wp_send_json();
665
  }
@@ -668,9 +718,10 @@ class Administrator extends InjectionAware {
668
  * Ajax Hide Rating
669
  * @return mixed bool | json
670
  */
671
- public function ajaxHideRating() {
672
- if( false !== update_option( "wpstg_rating", "no" ) ) {
673
- wp_send_json( true );
 
674
  }
675
  return wp_send_json();
676
  }
@@ -679,36 +730,40 @@ class Administrator extends InjectionAware {
679
  * Ajax Hide Rating and show it again after one week
680
  * @return mixed bool | json
681
  */
682
- public function ajaxHideLaterRating() {
683
- $date = date('Y-m-d', strtotime(date('Y-m-d'). ' + 7 days'));
684
- if( false !== update_option( 'wpstg_rating',$date )) {
685
- wp_send_json( true );
 
686
  }
687
- return wp_send_json( false );
688
  }
689
 
690
  /**
691
  * Ajax Hide Beta
692
  */
693
- public function ajaxHideBeta() {
694
- wp_send_json( update_option( "wpstg_beta", "no" ) );
 
695
  }
696
 
697
  /**
698
  * Clone logs
699
  */
700
- public function ajaxLogs() {
701
- check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
 
702
 
703
  $logs = new Logs();
704
- wp_send_json( $logs->start() );
705
  }
706
 
707
  /**
708
  * Ajax Checks Free Disk Space
709
  */
710
- public function ajaxCheckFreeSpace() {
711
- check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
 
712
 
713
  $scan = new Scan();
714
  return $scan->hasFreeDiskSpace();
@@ -718,10 +773,11 @@ class Administrator extends InjectionAware {
718
  * Ajax Start Push Changes Process
719
  * Start with the module Scan
720
  */
721
- public function ajaxPushScan() {
722
- check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
 
723
 
724
- if( !class_exists( 'WPStaging\Backend\Pro\Modules\Jobs\Scan' ) ) {
725
  return false;
726
  }
727
 
@@ -741,99 +797,105 @@ class Administrator extends InjectionAware {
741
  /**
742
  * Ajax Start Pushing. Needs WP Staging Pro
743
  */
744
- public function ajaxPushProcessing() {
745
- check_ajax_referer( "wpstg_ajax_nonce", "nonce" );
 
746
 
747
- if( !class_exists( 'WPStaging\Backend\Pro\Modules\Jobs\Processing' ) ) {
748
  return false;
749
  }
750
 
751
  // Start the process
752
  $processing = new Processing();
753
- wp_send_json( $processing->start() );
754
  }
755
 
756
  /**
757
  * License Page
758
  */
759
- public function getLicensePage() {
 
760
 
761
  // Get license data
762
- $license = get_option( 'wpstg_license_status' );
763
 
764
  require_once "{$this->path}Pro/views/licensing.php";
765
  }
766
 
767
  /**
768
  * Send mail via ajax
769
- * @param type $args
770
  */
771
- public function ajaxSendReport( $args = array() ) {
 
772
  // Set params
773
- if( empty( $args ) ) {
774
- $args = stripslashes_deep( $_POST );
775
  }
776
  // Set e-mail
777
  $email = null;
778
- if( isset( $args['wpstg_email'] ) ) {
779
- $email = trim( $args['wpstg_email'] );
 
 
 
 
 
 
780
  }
781
 
782
  // Set message
783
  $message = null;
784
- if( isset( $args['wpstg_message'] ) ) {
785
- $message = trim( $args['wpstg_message'] );
786
  }
787
 
788
  // Set syslog
789
  $syslog = false;
790
- if( isset( $args['wpstg_syslog'] ) ) {
791
- $syslog = ( bool ) $args['wpstg_syslog'];
792
  }
793
 
794
  // Set terms
795
  $terms = false;
796
- if( isset( $args['wpstg_terms'] ) ) {
797
- $terms = ( bool ) $args['wpstg_terms'];
798
  }
799
 
800
- $report = new Report( $this->di );
801
- $errors = $report->send( $email, $message, $terms, $syslog );
802
 
803
- echo json_encode( array('errors' => $errors) );
804
  exit;
805
  }
806
 
807
  /**
808
  * Connect to external database for testing correct credentials
809
  */
810
- public function ajaxDatabaseConnect() {
811
- // Set params
812
- if( empty( $args ) ) {
813
- $args = stripslashes_deep( $_POST );
814
- }
815
-
816
- $user = !empty( $args['databaseUser'] ) ? $args['databaseUser'] : '';
817
- $password = !empty( $args['databasePassword'] ) ? $args['databasePassword'] : '';
818
- $database = !empty( $args['databaseDatabase'] ) ? $args['databaseDatabase'] : '';
819
- $server = !empty( $args['databaseServer'] ) ? $args['databaseServer'] : 'localhost';
820
 
821
- $db = new \wpdb( $user, $password, $database, $server );
822
 
823
  // Can not connect to mysql
824
- if( !empty( $db->error->errors['db_connect_fail']['0'] ) ) {
825
- echo json_encode( array('errors' => $db->error->errors['db_connect_fail']['0']) );
826
  exit;
827
  }
828
 
829
  // Can not connect to database
830
- $db->select( $database );
831
- if( !$db->ready ) {
832
  $error = isset($db->error->errors['db_select_fail']) ? $db->error->errors['db_select_fail'] : "Error: Can't select {database} Either it does not exist or you don't have privileges to access it.";
833
- echo json_encode( array('errors' => $error ) );
834
  exit;
835
  }
836
- echo json_encode( array('success' => 'true') );
837
  exit;
838
  }
839
 
3
  namespace WPStaging\Backend;
4
 
5
  // No Direct Access
6
+ if (!defined("WPINC")) {
7
  die;
8
  }
9
 
30
  * Class Administrator
31
  * @package WPStaging\Backend
32
  */
33
+ class Administrator extends InjectionAware
34
+ {
35
 
36
  /**
37
  * @var string
43
  */
44
  private $url;
45
 
46
+ /**
47
+ * @var array
48
+ * All options here will only be stored in the database as integers. Decimal points and separators will be removed
49
+ */
50
+ private static $integerOptions = [
51
+ 'queryLimit',
52
+ 'querySRLimit'
53
+ ];
54
+
55
  /**
56
  * Initialize class
57
  */
58
+ public function initialize()
59
+ {
60
  $this->defineHooks();
61
 
62
  // Path to backend
63
+ $this->path = plugin_dir_path(__FILE__);
64
 
65
  // URL to public backend folder
66
+ $this->url = plugin_dir_url(__FILE__) . "public/";
67
 
68
  // Load plugins meta data
69
  $this->loadMeta();
72
  /**
73
  * Load plugn meta data
74
  */
75
+ public function loadMeta()
76
+ {
77
  $run = new \WPStaging\Backend\Pluginmeta\Pluginmeta();
78
  }
79
 
80
  /**
81
  * Define Hooks
82
  */
83
+ private function defineHooks()
84
+ {
85
  // Get loader
86
+ $loader = $this->di->get("loader");
87
 
88
  $Activation = new \WPStaging\Backend\Activation\Activation();
89
 
90
+ if (!defined('WPSTGPRO_VERSION')) {
91
  $Welcome = new Activation\Welcome();
92
  }
93
 
94
+ $loader->addAction("activated_plugin", $Activation, 'deactivate_other_instances');
95
+ $loader->addAction("admin_menu", $this, "addMenu", 10);
96
+ $loader->addAction("admin_init", $this, "setOptionFormElements");
97
+ $loader->addAction("admin_init", $this, "upgrade");
98
+ $loader->addAction("admin_post_wpstg_download_sysinfo", $this, "systemInfoDownload");
99
+ $loader->addAction("admin_post_wpstg_export", $this, "export");
100
+ $loader->addAction("admin_post_wpstg_import_settings", $this, "import");
101
+ $loader->addAction("admin_notices", $this, "messages");
102
 
103
+ if (!defined('WPSTGPRO_VERSION')) {
104
+ add_filter('admin_footer', array($this, 'loadFeedbackForm'));
105
  }
106
 
107
  // Ajax Requests
108
+ $loader->addAction("wp_ajax_wpstg_overview", $this, "ajaxOverview");
109
+ $loader->addAction("wp_ajax_wpstg_scanning", $this, "ajaxScan");
110
+ $loader->addAction("wp_ajax_wpstg_check_clone", $this, "ajaxcheckCloneName");
111
+ $loader->addAction("wp_ajax_wpstg_restart", $this, "ajaxRestart");
112
+ $loader->addAction("wp_ajax_wpstg_update", $this, "ajaxUpdateProcess");
113
+ $loader->addAction("wp_ajax_wpstg_cloning", $this, "ajaxStartClone");
114
+ $loader->addAction("wp_ajax_wpstg_processing", $this, "ajaxCloneDatabase");
115
+ $loader->addAction("wp_ajax_wpstg_database_connect", $this, "ajaxDatabaseConnect");
116
+ $loader->addAction("wp_ajax_wpstg_clone_prepare_directories", $this, "ajaxPrepareDirectories");
117
+ $loader->addAction("wp_ajax_wpstg_clone_files", $this, "ajaxCopyFiles");
118
+ $loader->addAction("wp_ajax_wpstg_clone_replace_data", $this, "ajaxReplaceData");
119
+ $loader->addAction("wp_ajax_wpstg_clone_finish", $this, "ajaxFinish");
120
+ $loader->addAction("wp_ajax_wpstg_confirm_delete_clone", $this, "ajaxDeleteConfirmation");
121
+ $loader->addAction("wp_ajax_wpstg_delete_clone", $this, "ajaxDeleteClone");
122
+ $loader->addAction("wp_ajax_wpstg_cancel_clone", $this, "ajaxCancelClone");
123
+ $loader->addAction("wp_ajax_wpstg_cancel_update", $this, "ajaxCancelUpdate");
124
+ $loader->addAction("wp_ajax_wpstg_hide_poll", $this, "ajaxHidePoll");
125
+ $loader->addAction("wp_ajax_wpstg_hide_rating", $this, "ajaxHideRating");
126
+ $loader->addAction("wp_ajax_wpstg_hide_later", $this, "ajaxHideLaterRating");
127
+ $loader->addAction("wp_ajax_wpstg_hide_beta", $this, "ajaxHideBeta");
128
+ $loader->addAction("wp_ajax_wpstg_logs", $this, "ajaxLogs");
129
+ $loader->addAction("wp_ajax_wpstg_check_disk_space", $this, "ajaxCheckFreeSpace");
130
+ $loader->addAction("wp_ajax_wpstg_send_report", $this, "ajaxSendReport");
131
+ $loader->addAction("wp_ajax_wpstg_send_feedback", $this, "sendFeedback");
132
 
133
 
134
  // Ajax hooks pro Version
135
+ $loader->addAction("wp_ajax_wpstg_scan", $this, "ajaxPushScan");
136
+ $loader->addAction("wp_ajax_wpstg_push_processing", $this, "ajaxPushProcessing");
137
  }
138
 
139
  /**
140
  * Load Feedback Form on plugins.php
141
  */
142
+ public function loadFeedbackForm()
143
+ {
144
  $form = new Feedback\Feedback();
145
  $load = $form->loadForm();
146
  }
148
  /**
149
  * Send Feedback form via mail
150
  */
151
+ public function sendFeedback()
152
+ {
153
  $form = new Feedback\Feedback();
154
  $send = $form->sendMail();
155
  }
157
  /**
158
  * Register options form elements
159
  */
160
+ public function setOptionFormElements()
161
+ {
162
+ register_setting("wpstg_settings", "wpstg_settings", array($this, "sanitizeOptions"));
163
  }
164
 
165
  /**
180
  * @param array $data
181
  * @return array
182
  */
183
+ public function sanitizeOptions($data = array())
184
+ {
185
+ $sanitized = $this->sanitizeData($data);
186
 
187
+ add_settings_error("wpstg-notices", '', __("Settings updated.", "wp-staging"), "updated");
188
 
189
+ return apply_filters("wpstg-settings", $sanitized, $data);
190
  }
191
 
192
  /**
193
  * @param array $data
194
  * @return array
195
  */
196
+ private function sanitizeData($data = array())
197
+ {
198
  $sanitized = array();
199
 
200
+ foreach ($data as $key => $value) {
201
+ if (is_array($value)) {
202
+ $sanitized[$key] = $this->sanitizeData($value);
203
+ } //Removing comma separators and decimal points
204
+ else if (in_array($key, self::$integerOptions, true)) {
205
+ $sanitized[$key] = preg_replace('/\D/', '', htmlspecialchars($value));
206
+ } else {
207
+ $sanitized[$key] = htmlspecialchars($value);
208
+ }
209
  }
210
 
211
  return $sanitized;
263
  /**
264
  * Settings Page
265
  */
266
+ public function getSettingsPage()
267
+ {
268
  // Tabs
269
+ $tabs = new Tabs(array(
270
+ "general" => __("General", "wp-staging")
271
+ ));
272
 
273
 
274
  $this->di
275
+ // Set tabs
276
+ ->set("tabs", $tabs)
277
+ // Forms
278
+ ->set("forms", new FormSettings($tabs))
279
+ ;
280
 
281
 
282
  require_once "{$this->path}views/settings/main-settings.php";
285
  /**
286
  * Clone Page
287
  */
288
+ public function getClonePage()
289
+ {
290
  // Existing clones
291
+ $availableClones = get_option("wpstg_existing_clones_beta", array());
292
 
293
  require_once "{$this->path}views/clone/index.php";
294
  }
296
  /**
297
  * Welcome Page
298
  */
299
+ public function getWelcomePage()
300
+ {
301
+ if (defined('WPSTGPRO_VERSION')) {
302
  return;
303
  }
304
  require_once "{$this->path}views/welcome/welcome.php";
307
  /**
308
  * Tools Page
309
  */
310
+ public function getToolsPage()
311
+ {
312
  // Tabs
313
+ $tabs = new Tabs(array(
314
+ "import_export" => __("Import/Export", "wp-staging"),
315
+ "system_info" => __("System Info", "wp-staging")
316
+ ));
317
 
318
+ $this->di->set("tabs", $tabs);
319
 
320
+ $this->di->set("systemInfo", new SystemInfo($this->di));
321
 
322
  require_once "{$this->path}views/tools/index.php";
323
  }
325
  /**
326
  * System Information Download
327
  */
328
+ public function systemInfoDownload()
329
+ {
330
+ if (!current_user_can("update_plugins")) {
331
  return;
332
  }
333
 
334
  nocache_headers();
335
+ header("Content-Type: text/plain");
336
+ header('Content-Disposition: attachment; filename="wpstg-system-info.txt"');
337
+ echo wp_strip_all_tags(new SystemInfo($this->di));
338
  }
339
 
340
  /**
341
  * Import JSON settings file
342
  */
343
+ public function import()
344
+ {
345
+ if (empty($_POST["wpstg_import_nonce"])) {
346
  return;
347
  }
348
 
349
+ if (!wp_verify_nonce($_POST["wpstg_import_nonce"], "wpstg_import_nonce")) {
350
  return;
351
  }
352
 
353
+ if (!current_user_can("update_plugins")) {
354
  return;
355
  }
356
 
357
+ $fileExtension = explode('.', $_FILES["import_file"]["name"]);
358
+ $fileExtension = end($fileExtension);
359
+ if ("json" !== $fileExtension) {
360
+ wp_die("Please upload a valid .json file", "wp-staging");
361
  }
362
 
363
 
364
  $importFile = $_FILES["import_file"]["tmp_name"];
365
 
366
+ if (empty($importFile)) {
367
+ wp_die(__("Please upload a file to import", "wp-staging"));
368
  }
369
 
370
+ update_option("wpstg_settings", json_decode(file_get_contents($importFile, true)));
371
 
372
+ wp_safe_redirect(admin_url("admin.php?page=wpstg-tools&amp;wpstg-message=settings-imported"));
373
 
374
  return;
375
  }
377
  /**
378
  * Export settings to JSON file
379
  */
380
+ public function export()
381
+ {
382
+ if (empty($_POST["wpstg_export_nonce"])) {
383
  return;
384
  }
385
 
386
+ if (!wp_verify_nonce($_POST["wpstg_export_nonce"], "wpstg_export_nonce")) {
387
  return;
388
  }
389
 
390
+ if (!current_user_can("manage_options")) {
391
  return;
392
  }
393
 
394
+ $settings = get_option("wpstg_settings", array());
395
 
396
+ ignore_user_abort(true);
397
 
398
+ if (!in_array("set_time_limit", explode(',', ini_get("disable_functions"))) && !@ini_get("safe_mode")) {
399
+ set_time_limit(0);
400
  }
401
 
402
+ $fileName = apply_filters("wpstg_settings_export_filename", "wpstg-settings-export-" . date("m-d-Y")) . ".json";
403
 
404
  nocache_headers();
405
+ header("Content-Type: application/json; charset=utf-8");
406
+ header("Content-Disposition: attachment; filename={$fileName}");
407
+ header("Expires: 0");
408
 
409
+ echo json_encode($settings);
410
  }
411
 
412
  /**
415
  * @param array $vars
416
  * @return string
417
  */
418
+ public function render($file, $vars = array())
419
+ {
420
  $fullPath = $this->path . "views" . DIRECTORY_SEPARATOR;
421
+ $fullPath = str_replace(array('/', "\\"), DIRECTORY_SEPARATOR, $fullPath . $file . ".php");
422
 
423
+ if (!file_exists($fullPath) || !is_readable($fullPath)) {
424
  return "Can't render : {$fullPath} either file doesn't exist or can't read it";
425
  }
426
 
427
+ $contents = @file_get_contents($fullPath);
428
 
429
  // Variables are set
430
+ if (count($vars) > 0) {
431
  $vars = array_combine(
432
+ array_map(function ($key) {
433
+ return "{{" . $key . "}}";
434
+ }, array_keys($vars)
435
+ ), $vars
436
  );
437
 
438
+ $contents = str_replace(array_keys($vars), array_values($vars), $contents);
439
  }
440
 
441
  return $contents;
444
  /**
445
  * Restart cloning process
446
  */
447
+ public function ajaxRestart()
448
+ {
449
+ check_ajax_referer("wpstg_ajax_nonce", "nonce");
450
 
451
  $process = new ProcessLock();
452
  $process->restart();
455
  /**
456
  * Ajax Overview
457
  */
458
+ public function ajaxOverview()
459
+ {
460
+ check_ajax_referer("wpstg_ajax_nonce", "nonce");
461
 
462
  // Existing clones
463
+ $availableClones = get_option("wpstg_existing_clones_beta", array());
464
 
465
  // Get license data
466
+ $license = get_option('wpstg_license_status');
467
 
468
  // Get db
469
+ $db = WPStaging::getInstance()->get('wpdb');
470
 
471
+ if (\WPStaging\WPStaging::getSlug() === 'wp-staging-pro') {
472
  require_once "{$this->path}Pro/views/single-overview-pro.php";
473
  } else {
474
  require_once "{$this->path}views/clone/ajax/single-overview.php";
480
  /**
481
  * Ajax Scan
482
  */
483
+ public function ajaxScan()
484
+ {
485
+ check_ajax_referer("wpstg_ajax_nonce", "nonce");
486
 
487
  // Check first if there is already a process running
488
  $processLock = new ProcessLock();
489
  $processLock->isRunning();
490
 
491
+ $db = WPStaging::getInstance()->get('wpdb');
492
 
493
  // Scan
494
  $scan = new Scan();
505
  /**
506
  * Ajax Check Clone Name
507
  */
508
+ public function ajaxCheckCloneName()
509
+ {
510
+ $cloneName = sanitize_key($_POST["cloneID"]);
511
+ $cloneNameLength = strlen($cloneName);
512
+ $clones = get_option("wpstg_existing_clones_beta", array());
513
 
514
+ $clonePath = trailingslashit(get_home_path()) . $cloneName;
515
 
516
  // Check clone name length
517
+ if ($cloneNameLength < 1 || $cloneNameLength > 16) {
518
+ echo wp_send_json(array(
519
+ "status" => "failed",
520
  "message" => "Clone name must be between 1 - 16 characters"
521
+ ));
522
+ } elseif (array_key_exists($cloneName, $clones)) {
523
+ echo wp_send_json(array(
524
+ "status" => "failed",
525
  "message" => "Clone name is already in use, please choose another clone name."
526
+ ));
527
+ } elseif (is_dir($clonePath) && !wpstg_is_empty_dir($clonePath)) {
528
+ echo wp_send_json(array(
529
+ "status" => "failed",
530
  "message" => "Clone directory " . $clonePath . " already exists. Use another clone name."
531
+ ));
532
  }
533
 
534
+ echo wp_send_json(array("status" => "success"));
535
  }
536
 
537
  /**
538
  * Ajax Start Updating Clone (Basically just layout and saving data)
539
  */
540
+ public function ajaxUpdateProcess()
541
+ {
542
+ check_ajax_referer("wpstg_ajax_nonce", "nonce");
543
 
544
  $cloning = new Updating();
545
 
546
+ if (!$cloning->save()) {
547
+ wp_die('can not save clone data');
548
  }
549
 
550
  require_once "{$this->path}views/clone/ajax/update.php";
555
  /**
556
  * Ajax Start Clone (Basically just layout and saving data)
557
  */
558
+ public function ajaxStartClone()
559
+ {
560
+ check_ajax_referer("wpstg_ajax_nonce", "nonce");
561
 
562
  // Check first if there is already a process running
563
  $processLock = new ProcessLock();
565
 
566
  $cloning = new Cloning();
567
 
568
+ if (!$cloning->save()) {
569
+ wp_die('can not save clone data');
570
  }
571
 
572
  require_once "{$this->path}views/clone/ajax/start.php";
577
  /**
578
  * Ajax Clone Database
579
  */
580
+ public function ajaxCloneDatabase()
581
+ {
582
+ check_ajax_referer("wpstg_ajax_nonce", "nonce");
583
 
584
  $cloning = new Cloning();
585
 
587
  //http_response_code(504);
588
  //wp_send_json( '<html><body><head></head><body>test</body></html>' );
589
 
590
+ wp_send_json($cloning->start());
591
  }
592
 
593
  /**
594
  * Ajax Prepare Directories (get listing of files)
595
  */
596
+ public function ajaxPrepareDirectories()
597
+ {
598
+ check_ajax_referer("wpstg_ajax_nonce", "nonce");
599
 
600
  $cloning = new Cloning();
601
 
602
+ wp_send_json($cloning->start());
603
  }
604
 
605
  /**
606
  * Ajax Clone Files
607
  */
608
+ public function ajaxCopyFiles()
609
+ {
610
+ check_ajax_referer("wpstg_ajax_nonce", "nonce");
611
 
612
  $cloning = new Cloning();
613
 
614
+ wp_send_json($cloning->start());
615
  }
616
 
617
  /**
618
  * Ajax Replace Data
619
  */
620
+ public function ajaxReplaceData()
621
+ {
622
+ check_ajax_referer("wpstg_ajax_nonce", "nonce");
623
 
624
  $cloning = new Cloning();
625
 
626
+ wp_send_json($cloning->start());
627
  }
628
 
629
  /**
630
  * Ajax Finish
631
  */
632
+ public function ajaxFinish()
633
+ {
634
+ check_ajax_referer("wpstg_ajax_nonce", "nonce");
635
 
636
  $cloning = new Cloning();
637
 
638
+ wp_send_json($cloning->start());
639
  }
640
 
641
  /**
642
  * Ajax Delete Confirmation
643
  */
644
+ public function ajaxDeleteConfirmation()
645
+ {
646
+ check_ajax_referer("wpstg_ajax_nonce", "nonce");
647
 
648
  $delete = new Delete();
649
  $delete->setData();
660
  /**
661
  * Delete clone
662
  */
663
+ public function ajaxDeleteClone()
664
+ {
665
+ check_ajax_referer("wpstg_ajax_nonce", "nonce");
666
 
667
  $delete = new Delete();
668
 
669
+ wp_send_json($delete->start());
670
  }
671
 
672
  /**
673
  * Delete clone
674
  */
675
+ public function ajaxCancelClone()
676
+ {
677
+ check_ajax_referer("wpstg_ajax_nonce", "nonce");
678
 
679
  $cancel = new Cancel();
680
 
681
+ wp_send_json($cancel->start());
682
  }
683
 
684
  /**
685
  * Cancel updating process / Do not delete clone!
686
  */
687
+ public function ajaxCancelUpdate()
688
+ {
689
+ check_ajax_referer("wpstg_ajax_nonce", "nonce");
690
 
691
  $cancel = new CancelUpdate();
692
+ wp_send_json($cancel->start());
693
  }
694
 
695
  /**
696
  * Admin Messages
697
  */
698
+ public function messages()
699
+ {
700
+ $notice = new Notices($this->path, $this->url);
701
 
702
  $run = $notice->messages();
703
  }
706
  * Ajax Hide Poll
707
  * @return mixed boolean | json
708
  */
709
+ public function ajaxHidePoll()
710
+ {
711
+ if (false !== update_option("wpstg_poll", "no")) {
712
+ wp_send_json(true);
713
  }
714
  return wp_send_json();
715
  }
718
  * Ajax Hide Rating
719
  * @return mixed bool | json
720
  */
721
+ public function ajaxHideRating()
722
+ {
723
+ if (false !== update_option("wpstg_rating", "no")) {
724
+ wp_send_json(true);
725
  }
726
  return wp_send_json();
727
  }
730
  * Ajax Hide Rating and show it again after one week
731
  * @return mixed bool | json
732
  */
733
+ public function ajaxHideLaterRating()
734
+ {
735
+ $date = date('Y-m-d', strtotime(date('Y-m-d') . ' + 7 days'));
736
+ if (false !== update_option('wpstg_rating', $date)) {
737
+ wp_send_json(true);
738
  }
739
+ return wp_send_json(false);
740
  }
741
 
742
  /**
743
  * Ajax Hide Beta
744
  */
745
+ public function ajaxHideBeta()
746
+ {
747
+ wp_send_json(update_option("wpstg_beta", "no"));
748
  }
749
 
750
  /**
751
  * Clone logs
752
  */
753
+ public function ajaxLogs()
754
+ {
755
+ check_ajax_referer("wpstg_ajax_nonce", "nonce");
756
 
757
  $logs = new Logs();
758
+ wp_send_json($logs->start());
759
  }
760
 
761
  /**
762
  * Ajax Checks Free Disk Space
763
  */
764
+ public function ajaxCheckFreeSpace()
765
+ {
766
+ check_ajax_referer("wpstg_ajax_nonce", "nonce");
767
 
768
  $scan = new Scan();
769
  return $scan->hasFreeDiskSpace();
773
  * Ajax Start Push Changes Process
774
  * Start with the module Scan
775
  */
776
+ public function ajaxPushScan()
777
+ {
778
+ check_ajax_referer("wpstg_ajax_nonce", "nonce");
779
 
780
+ if (!class_exists('WPStaging\Backend\Pro\Modules\Jobs\Scan')) {
781
  return false;
782
  }
783
 
797
  /**
798
  * Ajax Start Pushing. Needs WP Staging Pro
799
  */
800
+ public function ajaxPushProcessing()
801
+ {
802
+ check_ajax_referer("wpstg_ajax_nonce", "nonce");
803
 
804
+ if (!class_exists('WPStaging\Backend\Pro\Modules\Jobs\Processing')) {
805
  return false;
806
  }
807
 
808
  // Start the process
809
  $processing = new Processing();
810
+ wp_send_json($processing->start());
811
  }
812
 
813
  /**
814
  * License Page
815
  */
816
+ public function getLicensePage()
817
+ {
818
 
819
  // Get license data
820
+ $license = get_option('wpstg_license_status');
821
 
822
  require_once "{$this->path}Pro/views/licensing.php";
823
  }
824
 
825
  /**
826
  * Send mail via ajax
827
+ * @param array $args
828
  */
829
+ public function ajaxSendReport($args = array())
830
+ {
831
  // Set params
832
+ if (empty($args)) {
833
+ $args = stripslashes_deep($_POST);
834
  }
835
  // Set e-mail
836
  $email = null;
837
+ if (isset($args['wpstg_email'])) {
838
+ $email = trim($args['wpstg_email']);
839
+ }
840
+
841
+ // Set hosting provider
842
+ $provider = null;
843
+ if (isset($args['wpstg_provider'])) {
844
+ $provider = trim($args['wpstg_provider']);
845
  }
846
 
847
  // Set message
848
  $message = null;
849
+ if (isset($args['wpstg_message'])) {
850
+ $message = trim($args['wpstg_message']);
851
  }
852
 
853
  // Set syslog
854
  $syslog = false;
855
+ if (isset($args['wpstg_syslog'])) {
856
+ $syslog = ( bool )$args['wpstg_syslog'];
857
  }
858
 
859
  // Set terms
860
  $terms = false;
861
+ if (isset($args['wpstg_terms'])) {
862
+ $terms = ( bool )$args['wpstg_terms'];
863
  }
864
 
865
+ $report = new Report($this->di);
866
+ $errors = $report->send($email, $message, $terms, $syslog, $provider);
867
 
868
+ echo json_encode(array('errors' => $errors));
869
  exit;
870
  }
871
 
872
  /**
873
  * Connect to external database for testing correct credentials
874
  */
875
+ public function ajaxDatabaseConnect()
876
+ {
877
+ $args = $_POST;
878
+ $user = !empty($args['databaseUser']) ? $args['databaseUser'] : '';
879
+ $password = !empty($args['databasePassword']) ? $args['databasePassword'] : '';
880
+ $database = !empty($args['databaseDatabase']) ? $args['databaseDatabase'] : '';
881
+ $server = !empty($args['databaseServer']) ? $args['databaseServer'] : 'localhost';
 
 
 
882
 
883
+ $db = new \wpdb($user, stripslashes($password), $database, $server);
884
 
885
  // Can not connect to mysql
886
+ if (!empty($db->error->errors['db_connect_fail']['0'])) {
887
+ echo json_encode(array('errors' => $db->error->errors['db_connect_fail']['0']));
888
  exit;
889
  }
890
 
891
  // Can not connect to database
892
+ $db->select($database);
893
+ if (!$db->ready) {
894
  $error = isset($db->error->errors['db_select_fail']) ? $db->error->errors['db_select_fail'] : "Error: Can't select {database} Either it does not exist or you don't have privileges to access it.";
895
+ echo json_encode(array('errors' => $error));
896
  exit;
897
  }
898
+ echo json_encode(array('success' => 'true'));
899
  exit;
900
  }
901
 
Backend/Modules/Jobs/Cloning.php CHANGED
@@ -14,7 +14,6 @@ use WPStaging\Backend\Modules\Jobs\Multisite\Finish as muFinish;
14
  use WPStaging\Backend\Modules\Jobs\Multisite\Directories as muDirectories;
15
  use WPStaging\Backend\Modules\Jobs\Multisite\Files as muFiles;
16
  use WPStaging\Utils\Helper;
17
-
18
  /**
19
  * Class Cloning
20
  * @package WPStaging\Backend\Modules\Jobs
@@ -22,6 +21,11 @@ use WPStaging\Utils\Helper;
22
  class Cloning extends Job
23
  {
24
 
 
 
 
 
 
25
  /**
26
  * Initialize is called in \Job
27
  */
@@ -105,16 +109,16 @@ class Cloning extends Job
105
  // Excluded Directories TOTAL
106
  // Do not copy these folders and plugins
107
  $excludedDirectories = array(
108
- \WPStaging\WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR . 'cache',
109
- \WPStaging\WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'wps-hide-login',
110
- \WPStaging\WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'wp-super-cache',
111
- \WPStaging\WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'peters-login-redirect',
112
- \WPStaging\WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'wp-spamshield',
113
  );
114
 
115
  $this->options->excludedDirectories = array_merge($excludedDirectories, wpstg_urldecode($this->options->excludedDirectories));
116
 
117
- array_unshift($this->options->directoriesToCopy, \WPStaging\WPStaging::getWPpath());
118
 
119
  // Included Directories
120
  if (isset($_POST["includedDirectories"]) && is_array($_POST["includedDirectories"])) {
@@ -142,7 +146,7 @@ class Cloning extends Job
142
  }
143
  $this->options->databasePassword = '';
144
  if (isset($_POST["databasePassword"]) && !empty($_POST["databasePassword"])) {
145
- $this->options->databasePassword = $_POST["databasePassword"];
146
  }
147
  $this->options->databaseDatabase = '';
148
  if (isset($_POST["databaseDatabase"]) && !empty($_POST["databaseDatabase"])) {
@@ -176,6 +180,13 @@ class Cloning extends Job
176
  return $this->saveOptions();
177
  }
178
 
 
 
 
 
 
 
 
179
 
180
  /**
181
  * Save clone data initially
@@ -192,7 +203,7 @@ class Cloning extends Job
192
  "url" => $this->getDestinationUrl(),
193
  "number" => $this->options->cloneNumber,
194
  "version" => WPStaging::getVersion(),
195
- "status" => "unfinished or broken",
196
  "prefix" => $this->options->prefix,
197
  "datetime" => time(),
198
  "databaseUser" => $this->options->databaseUser,
@@ -212,7 +223,7 @@ class Cloning extends Job
212
 
213
  /**
214
  * Get destination Hostname depending on wheather WP has been installed in sub dir or not
215
- * @return type
216
  */
217
  private function getDestinationUrl()
218
  {
@@ -249,19 +260,19 @@ class Cloning extends Job
249
 
250
  /**
251
  * Get Destination Directory including staging subdirectory
252
- * @return type
253
  */
254
  private function getDestinationDir()
255
  {
256
  // Throw fatal error
257
- if (!empty($this->options->cloneDir) & (trailingslashit($this->options->cloneDir) === ( string )trailingslashit(\WPStaging\WPStaging::getWPpath()))) {
258
  $this->returnException('Error: Target Directory must be different from the root of the production website.');
259
  die();
260
  }
261
 
262
  // No custom clone dir so clone path will be in subfolder of root
263
  if (empty($this->options->cloneDir)) {
264
- $this->options->cloneDir = trailingslashit(\WPStaging\WPStaging::getWPpath() . $this->options->cloneDirectoryName);
265
  return $this->options->cloneDir;
266
  }
267
  return trailingslashit($this->options->cloneDir);
@@ -283,12 +294,12 @@ class Cloning extends Job
283
  }
284
 
285
  /**
286
- * Create a new staging prefix which does not already exists in database
287
  */
288
  private function setStagingPrefix()
289
  {
290
 
291
- // Get & find a new prefix that does not already exist in database.
292
  // Loop through up to 1000 different possible prefixes should be enough here;)
293
  for ($i = 0; $i <= 10000; $i++) {
294
  $this->options->prefix = isset($this->options->existingClones) ?
@@ -307,22 +318,6 @@ class Cloning extends Job
307
  wp_die("Fatal Error: Can not create staging prefix. Prefix '{$this->options->prefix}' already exists! Stopping for security reasons. Contact support@wp-staging.com");
308
  }
309
 
310
- /**
311
- * Check if potential new prefix of staging site would be identical with live site.
312
- * @return boolean
313
- */
314
- private function isPrefixIdentical()
315
- {
316
- $db = WPStaging::getInstance()->get("wpdb");
317
-
318
- $livePrefix = $db->prefix;
319
- $stagingPrefix = $this->options->prefix;
320
-
321
- if ($livePrefix == $stagingPrefix) {
322
- return true;
323
- }
324
- return false;
325
- }
326
 
327
  /**
328
  * Start the cloning job
@@ -341,6 +336,10 @@ class Cloning extends Job
341
  throw new JobNotFoundException($methodName);
342
  }
343
 
 
 
 
 
344
  // Call the job
345
  return $this->{$methodName}();
346
  }
@@ -367,6 +366,15 @@ class Cloning extends Job
367
  return $response;
368
  }
369
 
 
 
 
 
 
 
 
 
 
370
  /**
371
  * Clone Database
372
  * @return object
@@ -412,7 +420,16 @@ class Cloning extends Job
412
  $searchReplace = new SearchReplaceExternal();
413
  }
414
  }
415
- return $this->handleJobResponse($searchReplace->start(), "directories");
 
 
 
 
 
 
 
 
 
416
  }
417
 
418
  /**
14
  use WPStaging\Backend\Modules\Jobs\Multisite\Directories as muDirectories;
15
  use WPStaging\Backend\Modules\Jobs\Multisite\Files as muFiles;
16
  use WPStaging\Utils\Helper;
 
17
  /**
18
  * Class Cloning
19
  * @package WPStaging\Backend\Modules\Jobs
21
  class Cloning extends Job
22
  {
23
 
24
+ /**
25
+ * @var object
26
+ */
27
+ private $db;
28
+
29
  /**
30
  * Initialize is called in \Job
31
  */
109
  // Excluded Directories TOTAL
110
  // Do not copy these folders and plugins
111
  $excludedDirectories = array(
112
+ WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR . 'cache',
113
+ WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'wps-hide-login',
114
+ WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'wp-super-cache',
115
+ WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'peters-login-redirect',
116
+ WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'wp-spamshield',
117
  );
118
 
119
  $this->options->excludedDirectories = array_merge($excludedDirectories, wpstg_urldecode($this->options->excludedDirectories));
120
 
121
+ array_unshift($this->options->directoriesToCopy, WPStaging::getWPpath());
122
 
123
  // Included Directories
124
  if (isset($_POST["includedDirectories"]) && is_array($_POST["includedDirectories"])) {
146
  }
147
  $this->options->databasePassword = '';
148
  if (isset($_POST["databasePassword"]) && !empty($_POST["databasePassword"])) {
149
+ $this->options->databasePassword = stripslashes($_POST["databasePassword"]);
150
  }
151
  $this->options->databaseDatabase = '';
152
  if (isset($_POST["databaseDatabase"]) && !empty($_POST["databaseDatabase"])) {
180
  return $this->saveOptions();
181
  }
182
 
183
+ /**
184
+ * @return bool
185
+ */
186
+ private function enteredDatabaseSameAsLiveDatabase()
187
+ {
188
+ return $this->options->databaseServer === DB_HOST && $this->options->databaseDatabase == DB_NAME;
189
+ }
190
 
191
  /**
192
  * Save clone data initially
203
  "url" => $this->getDestinationUrl(),
204
  "number" => $this->options->cloneNumber,
205
  "version" => WPStaging::getVersion(),
206
+ "status" => "unfinished or broken (?)",
207
  "prefix" => $this->options->prefix,
208
  "datetime" => time(),
209
  "databaseUser" => $this->options->databaseUser,
223
 
224
  /**
225
  * Get destination Hostname depending on wheather WP has been installed in sub dir or not
226
+ * @return string
227
  */
228
  private function getDestinationUrl()
229
  {
260
 
261
  /**
262
  * Get Destination Directory including staging subdirectory
263
+ * @return string
264
  */
265
  private function getDestinationDir()
266
  {
267
  // Throw fatal error
268
+ if (!empty($this->options->cloneDir) & (trailingslashit($this->options->cloneDir) === ( string )trailingslashit(WPStaging::getWPpath()))) {
269
  $this->returnException('Error: Target Directory must be different from the root of the production website.');
270
  die();
271
  }
272
 
273
  // No custom clone dir so clone path will be in subfolder of root
274
  if (empty($this->options->cloneDir)) {
275
+ $this->options->cloneDir = trailingslashit(WPStaging::getWPpath() . $this->options->cloneDirectoryName);
276
  return $this->options->cloneDir;
277
  }
278
  return trailingslashit($this->options->cloneDir);
294
  }
295
 
296
  /**
297
+ * Create a new staging prefix that does not already exists in database
298
  */
299
  private function setStagingPrefix()
300
  {
301
 
302
+ // Get & find a new prefix that does not already exist in database.
303
  // Loop through up to 1000 different possible prefixes should be enough here;)
304
  for ($i = 0; $i <= 10000; $i++) {
305
  $this->options->prefix = isset($this->options->existingClones) ?
318
  wp_die("Fatal Error: Can not create staging prefix. Prefix '{$this->options->prefix}' already exists! Stopping for security reasons. Contact support@wp-staging.com");
319
  }
320
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
 
322
  /**
323
  * Start the cloning job
336
  throw new JobNotFoundException($methodName);
337
  }
338
 
339
+ if ($this->enteredDatabaseSameAsLiveDatabase() && $this->options->databasePrefix === $this->db->prefix) {
340
+ $this->returnException('Entered table prefix for staging and production database can not be identical! Please start over and change the table prefix.');
341
+ }
342
+
343
  // Call the job
344
  return $this->{$methodName}();
345
  }
366
  return $response;
367
  }
368
 
369
+ /**
370
+ * Copy data from staging site to temporary column to use it later
371
+ * @return object
372
+ */
373
+ public function jobPreserveDataFirstStep(){
374
+ $preserve = new PreserveDataFirstStep();
375
+ return $this->handleJobResponse($preserve->start(), 'database');
376
+ }
377
+
378
  /**
379
  * Clone Database
380
  * @return object
420
  $searchReplace = new SearchReplaceExternal();
421
  }
422
  }
423
+ return $this->handleJobResponse($searchReplace->start(), "PreserveDataSecondStep");
424
+ }
425
+
426
+ /**
427
+ * Copy tmp data back to staging site
428
+ * @return object
429
+ */
430
+ public function jobPreserveDataSecondStep(){
431
+ $preserve = new PreserveDataSecondStep();
432
+ return $this->handleJobResponse($preserve->start(), 'directories');
433
  }
434
 
435
  /**
Backend/Modules/Jobs/Data.php CHANGED
@@ -79,7 +79,7 @@ class Data extends JobExecutable
79
  */
80
  protected function calculateTotalSteps()
81
  {
82
- $this->options->totalSteps = 19;
83
  }
84
 
85
  /**
@@ -181,7 +181,7 @@ class Data extends JobExecutable
181
  * @param string $table
182
  * @return boolean
183
  */
184
- protected function isTable($table)
185
  {
186
  if ($this->db->get_var("SHOW TABLES LIKE '{$table}'") != $table) {
187
  $this->log("Preparing Data: Table {$table} does not exist.", Logger::TYPE_INFO);
@@ -234,8 +234,8 @@ class Data extends JobExecutable
234
 
235
  /**
236
  * Copy files with symlink support
237
- * @param type $source
238
- * @param type $destination
239
  * @return boolean
240
  */
241
  protected function copy($source, $destination)
@@ -262,12 +262,11 @@ class Data extends JobExecutable
262
 
263
  /**
264
  * Make sure wp-config.php contains correct db credentials
265
- * @param type $source
266
  * @return boolean
267
  */
268
  protected function alterWpConfig($source)
269
  {
270
- $content = file_get_contents($source);
271
 
272
  if (false === ($content = file_get_contents($source))) {
273
  return false;
@@ -300,7 +299,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
300
 
301
  /**
302
  * Check if wp-config.php contains important constants
303
- * @param type $source
304
  * @return boolean
305
  */
306
  protected function isValidWpConfig($source)
@@ -310,8 +309,6 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
310
  return false;
311
  }
312
 
313
- $content = file_get_contents($source);
314
-
315
  if (false === ($content = file_get_contents($source))) {
316
  return false;
317
  }
@@ -355,7 +352,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
355
  $this->log("Preparing Data Step1: Updating siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_INFO);
356
 
357
  // Skip - Table does not exist
358
- if (false === $this->isTable($this->prefix . 'options')) {
359
  return true;
360
  }
361
  // Skip - Table is not selected or updated
@@ -392,7 +389,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
392
  $this->log("Preparing Data Step2: Updating row wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}");
393
 
394
  // Skip - Table does not exist
395
- if (false === $this->isTable($this->prefix . 'options')) {
396
  $this->log("Preparing Data Step2: Skipping");
397
  return true;
398
  }
@@ -402,7 +399,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
402
  return true;
403
  }
404
 
405
- $delete = $this->db->query(
406
  $this->db->prepare(
407
  "DELETE FROM `{$this->prefix}options` WHERE `option_name` = %s;", 'wpstg_is_staging_site'
408
  )
@@ -410,13 +407,12 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
410
 
411
 
412
  // No errors but no option name such as wpstg_is_staging_site
413
- //if( '' === $this->db->last_error && 0 == $result ) {
414
  $insert = $this->db->query(
415
  $this->db->prepare(
416
  "INSERT INTO {$this->prefix}options (option_name,option_value) VALUES ('wpstg_is_staging_site',%s)", "true"
417
  )
418
  );
419
- //}
420
  // All good
421
  if ($insert) {
422
  $this->log("Preparing Data Step2: Successful", Logger::TYPE_INFO);
@@ -443,7 +439,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
443
  }
444
 
445
  // Skip - Table does not exist
446
- if (false === $this->isTable($this->prefix . 'options')) {
447
  $this->log("Preparing Data Step3: Skipping");
448
  return true;
449
  }
@@ -478,7 +474,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
478
  $this->log("Preparing Data Step4: Updating db prefix in {$this->prefix}usermeta. ");
479
 
480
  // Skip - Table does not exist
481
- if (false === $this->isTable($this->prefix . 'usermeta')) {
482
  return true;
483
  }
484
 
@@ -534,7 +530,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
534
  $content = str_replace($this->homeUrl, $this->getStagingSiteUrl(), $content);
535
 
536
  if (false === @wpstg_put_contents($path, $content)) {
537
- $this->log("Preparing Data Step5: Failed to update $table_prefix in {$path} to " . $this->prefix . ". Can't save contents", Logger::TYPE_ERROR);
538
  return false;
539
  }
540
 
@@ -606,7 +602,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
606
  $this->log("Preparing Data Step7: Updating wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}");
607
 
608
  // Skip - Table does not exist
609
- if (false === $this->isTable($this->prefix . 'options')) {
610
  $this->log("Preparing Data Step7: Skipping Table {$this->prefix}'options' does not exist");
611
  return true;
612
  }
@@ -649,7 +645,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
649
  }
650
 
651
  // Skip - Table does not exist
652
- if (false === $this->isTable($this->prefix . 'options')) {
653
  $this->log("Preparing Data Step8: Skipping");
654
  return true;
655
  }
@@ -685,7 +681,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
685
 
686
  $this->log("Preparing Data Step9: Set staging site to noindex");
687
 
688
- if (false === $this->isTable($this->prefix . 'options')) {
689
  return true;
690
  }
691
 
@@ -736,7 +732,6 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
736
  $pattern = "/define\s*\(\s*['\"]WP_HOME['\"]\s*,\s*(.*)\s*\);.*/";
737
 
738
  $replace = "define('WP_HOME','" . $this->getStagingSiteUrl() . "'); // " . $matches[1] . " Changed by WP-Staging";
739
- //$replace.= " // Changed by WP-Staging";
740
 
741
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
742
  $this->log("Preparing Data: Failed to update WP_HOME", Logger::TYPE_ERROR);
@@ -779,7 +774,6 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
779
  $pattern = "/define\s*\(\s*['\"]WP_SITEURL['\"]\s*,\s*(.*)\s*\);.*/";
780
 
781
  $replace = "define('WP_SITEURL','" . $this->getStagingSiteUrl() . "'); // " . $matches[1] . " Changed by WP-Staging";
782
- //$replace.= " // Changed by WP-Staging";
783
 
784
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
785
  $this->log("Preparing Data Step11: Failed to update WP_SITEURL to " . $this->getStagingSiteUrl(), Logger::TYPE_ERROR);
@@ -807,7 +801,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
807
  $this->log("Preparing Data Step12: Updating db prefix in {$this->prefix}options.");
808
 
809
  // Skip - Table does not exist
810
- if (false === $this->isTable($this->prefix . 'options')) {
811
  return true;
812
  }
813
 
@@ -817,7 +811,6 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
817
  return true;
818
  }
819
 
820
- $notice = !empty($this->db->last_error) ? 'Last error: ' . $this->db->last_error : '';
821
 
822
  // Filter the rows below. Do not update them!
823
  $filters = array(
@@ -861,7 +854,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
861
  $this->log("Preparing Data Step13: Updating upload_path {$this->prefix}options.");
862
 
863
  // Skip - Table does not exist
864
- if (false === $this->isTable($this->prefix . 'options')) {
865
  return true;
866
  }
867
 
@@ -882,11 +875,6 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
882
 
883
  $this->log("Updating upload_path in {$this->prefix}options. {$error}");
884
 
885
- // $updateOptions = $this->db->query(
886
- // $this->db->prepare(
887
- // "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'upload_path'", $newUploadPath
888
- // )
889
- // );
890
  // remove upload_path value and use UPLOADS constant instead. (upload_path is deprecated and should not be used any longer)
891
  $updateOptions = $this->db->query(
892
  $this->db->prepare(
@@ -926,7 +914,6 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
926
  $pattern = "/define\s*\(\s*['\"]WP_CACHE['\"]\s*,\s*(.*)\s*\);.*/";
927
 
928
  $replace = "define('WP_CACHE',false); // " . $matches[1] . " Changed by WP-Staging";
929
- //$replace.= " // Changed by WP-Staging";
930
 
931
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
932
  $this->log("Preparing Data: Failed to change WP_CACHE", Logger::TYPE_ERROR);
@@ -1118,7 +1105,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1118
  $table = $this->prefix . 'options';
1119
 
1120
  // Skip - Table does not exist
1121
- if (false === $this->isTable($table)) {
1122
  return true;
1123
  }
1124
 
@@ -1136,76 +1123,31 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1136
  }
1137
 
1138
  /**
1139
- * Preserve data and prevents data in wp_options from beeing cloned to staging site
1140
  * @return bool
1141
  */
1142
- // protected function step20() {
1143
- // $this->log( "Preparing Data Step20: Preserve Data in " . $this->prefix . "options" );
1144
- //
1145
- // // Skip - Table does not exist
1146
- // if( false === $this->isTable( $this->prefix . 'options' ) ) {
1147
- // return true;
1148
- // }
1149
- //
1150
- // // Skip - Table is not selected or updated
1151
- // if( !in_array( $this->prefix . 'options', $this->tables ) ) {
1152
- // $this->log( "Preparing Data Step20: Skipped" );
1153
- // return true;
1154
- // }
1155
- //
1156
- // $sql = '';
1157
- //
1158
- // $preserved_option_names = array('wpstg_existing_clones_beta');
1159
- //
1160
- // $preserved_option_names = apply_filters( 'wpstg_preserved_options_cloning', $preserved_option_names );
1161
- // $preserved_options_escaped = esc_sql( $preserved_option_names );
1162
- //
1163
- // $preserved_options_data = array();
1164
- //
1165
- // // Get preserved data in wp_options tables
1166
- // $table = $this->db->prefix . 'options';
1167
- // $preserved_options_data[$this->prefix . 'options'] = $this->db->get_results(
1168
- // sprintf(
1169
- // "SELECT * FROM `{$table}` WHERE `option_name` IN ('%s')", implode( "','", $preserved_options_escaped )
1170
- // ), ARRAY_A
1171
- // );
1172
- //
1173
- // // Create preserved data queries for options tables
1174
- // foreach ( $preserved_options_data as $key => $value ) {
1175
- // if( false === empty( $value ) ) {
1176
- // foreach ( $value as $option ) {
1177
- // $sql .= $this->db->prepare(
1178
- // "DELETE FROM `{$key}` WHERE `option_name` = %s;\n", $option['option_name']
1179
- // );
1180
- //
1181
- // $sql .= $this->db->prepare(
1182
- // "INSERT INTO `{$key}` ( `option_id`, `option_name`, `option_value`, `autoload` ) VALUES ( NULL , %s, %s, %s );\n", $option['option_name'], $option['option_value'], $option['autoload']
1183
- // );
1184
- // }
1185
- // }
1186
- // }
1187
- //
1188
- // $this->debugLog( "Preparing Data Step20: Preserve values " . json_encode( $preserved_options_data ), Logger::TYPE_INFO );
1189
- //
1190
- // $this->executeSql( $sql );
1191
- //
1192
- // $this->log( "Preparing Data Step20: Successful!" );
1193
- // return true;
1194
- // }
1195
 
1196
- /**
1197
- * Execute a batch of sql queries
1198
- * @param string $sqlbatch
1199
- */
1200
- private function executeSql($sqlbatch)
1201
- {
1202
- $queries = array_filter(explode(";\n", $sqlbatch));
1203
 
1204
- foreach ($queries as $query) {
1205
- if (false === $this->db->query($query)) {
1206
- $this->log("Data Crunching Warning: Can not execute query {$query}", Logger::TYPE_WARNING);
1207
- }
1208
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
1209
  return true;
1210
  }
1211
 
@@ -1235,11 +1177,9 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1235
  return false;
1236
  }
1237
 
1238
- $customSlug = str_replace(wpstg_replace_windows_directory_separator(\WPStaging\WPStaging::getWPpath()), '', wpstg_replace_windows_directory_separator($uploadPath));
1239
-
1240
- $newUploadPath = $this->options->destinationDir . $customSlug;
1241
 
1242
- return $newUploadPath;
1243
  }
1244
 
1245
  /**
@@ -1269,10 +1209,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1269
  $siteurl = preg_replace('#^https?://#', '', rtrim(get_option('siteurl'), '/'));
1270
  $home = preg_replace('#^https?://#', '', rtrim(get_option('home'), '/'));
1271
 
1272
- if ($home !== $siteurl) {
1273
- return true;
1274
- }
1275
- return false;
1276
  }
1277
 
1278
  /**
@@ -1288,8 +1225,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1288
  return '';
1289
  }
1290
 
1291
- $dir = str_replace($home, '', $siteurl);
1292
- return str_replace('/', '', $dir);
1293
  }
1294
 
1295
  }
79
  */
80
  protected function calculateTotalSteps()
81
  {
82
+ $this->options->totalSteps = 20;
83
  }
84
 
85
  /**
181
  * @param string $table
182
  * @return boolean
183
  */
184
+ protected function tableExists($table)
185
  {
186
  if ($this->db->get_var("SHOW TABLES LIKE '{$table}'") != $table) {
187
  $this->log("Preparing Data: Table {$table} does not exist.", Logger::TYPE_INFO);
234
 
235
  /**
236
  * Copy files with symlink support
237
+ * @param string $source
238
+ * @param string $destination
239
  * @return boolean
240
  */
241
  protected function copy($source, $destination)
262
 
263
  /**
264
  * Make sure wp-config.php contains correct db credentials
265
+ * @param string $source
266
  * @return boolean
267
  */
268
  protected function alterWpConfig($source)
269
  {
 
270
 
271
  if (false === ($content = file_get_contents($source))) {
272
  return false;
299
 
300
  /**
301
  * Check if wp-config.php contains important constants
302
+ * @param string $source
303
  * @return boolean
304
  */
305
  protected function isValidWpConfig($source)
309
  return false;
310
  }
311
 
 
 
312
  if (false === ($content = file_get_contents($source))) {
313
  return false;
314
  }
352
  $this->log("Preparing Data Step1: Updating siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_INFO);
353
 
354
  // Skip - Table does not exist
355
+ if (false === $this->tableExists($this->prefix . 'options')) {
356
  return true;
357
  }
358
  // Skip - Table is not selected or updated
389
  $this->log("Preparing Data Step2: Updating row wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}");
390
 
391
  // Skip - Table does not exist
392
+ if (false === $this->tableExists($this->prefix . 'options')) {
393
  $this->log("Preparing Data Step2: Skipping");
394
  return true;
395
  }
399
  return true;
400
  }
401
 
402
+ $this->db->query(
403
  $this->db->prepare(
404
  "DELETE FROM `{$this->prefix}options` WHERE `option_name` = %s;", 'wpstg_is_staging_site'
405
  )
407
 
408
 
409
  // No errors but no option name such as wpstg_is_staging_site
 
410
  $insert = $this->db->query(
411
  $this->db->prepare(
412
  "INSERT INTO {$this->prefix}options (option_name,option_value) VALUES ('wpstg_is_staging_site',%s)", "true"
413
  )
414
  );
415
+
416
  // All good
417
  if ($insert) {
418
  $this->log("Preparing Data Step2: Successful", Logger::TYPE_INFO);
439
  }
440
 
441
  // Skip - Table does not exist
442
+ if (false === $this->tableExists($this->prefix . 'options')) {
443
  $this->log("Preparing Data Step3: Skipping");
444
  return true;
445
  }
474
  $this->log("Preparing Data Step4: Updating db prefix in {$this->prefix}usermeta. ");
475
 
476
  // Skip - Table does not exist
477
+ if (false === $this->tableExists($this->prefix . 'usermeta')) {
478
  return true;
479
  }
480
 
530
  $content = str_replace($this->homeUrl, $this->getStagingSiteUrl(), $content);
531
 
532
  if (false === @wpstg_put_contents($path, $content)) {
533
+ $this->log("Preparing Data Step5: Failed to update table_prefix in {$path} to " . $this->prefix . ". Can't save contents", Logger::TYPE_ERROR);
534
  return false;
535
  }
536
 
602
  $this->log("Preparing Data Step7: Updating wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}");
603
 
604
  // Skip - Table does not exist
605
+ if (false === $this->tableExists($this->prefix . 'options')) {
606
  $this->log("Preparing Data Step7: Skipping Table {$this->prefix}'options' does not exist");
607
  return true;
608
  }
645
  }
646
 
647
  // Skip - Table does not exist
648
+ if (false === $this->tableExists($this->prefix . 'options')) {
649
  $this->log("Preparing Data Step8: Skipping");
650
  return true;
651
  }
681
 
682
  $this->log("Preparing Data Step9: Set staging site to noindex");
683
 
684
+ if (false === $this->tableExists($this->prefix . 'options')) {
685
  return true;
686
  }
687
 
732
  $pattern = "/define\s*\(\s*['\"]WP_HOME['\"]\s*,\s*(.*)\s*\);.*/";
733
 
734
  $replace = "define('WP_HOME','" . $this->getStagingSiteUrl() . "'); // " . $matches[1] . " Changed by WP-Staging";
 
735
 
736
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
737
  $this->log("Preparing Data: Failed to update WP_HOME", Logger::TYPE_ERROR);
774
  $pattern = "/define\s*\(\s*['\"]WP_SITEURL['\"]\s*,\s*(.*)\s*\);.*/";
775
 
776
  $replace = "define('WP_SITEURL','" . $this->getStagingSiteUrl() . "'); // " . $matches[1] . " Changed by WP-Staging";
 
777
 
778
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
779
  $this->log("Preparing Data Step11: Failed to update WP_SITEURL to " . $this->getStagingSiteUrl(), Logger::TYPE_ERROR);
801
  $this->log("Preparing Data Step12: Updating db prefix in {$this->prefix}options.");
802
 
803
  // Skip - Table does not exist
804
+ if (false === $this->tableExists($this->prefix . 'options')) {
805
  return true;
806
  }
807
 
811
  return true;
812
  }
813
 
 
814
 
815
  // Filter the rows below. Do not update them!
816
  $filters = array(
854
  $this->log("Preparing Data Step13: Updating upload_path {$this->prefix}options.");
855
 
856
  // Skip - Table does not exist
857
+ if (false === $this->tableExists($this->prefix . 'options')) {
858
  return true;
859
  }
860
 
875
 
876
  $this->log("Updating upload_path in {$this->prefix}options. {$error}");
877
 
 
 
 
 
 
878
  // remove upload_path value and use UPLOADS constant instead. (upload_path is deprecated and should not be used any longer)
879
  $updateOptions = $this->db->query(
880
  $this->db->prepare(
914
  $pattern = "/define\s*\(\s*['\"]WP_CACHE['\"]\s*,\s*(.*)\s*\);.*/";
915
 
916
  $replace = "define('WP_CACHE',false); // " . $matches[1] . " Changed by WP-Staging";
 
917
 
918
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
919
  $this->log("Preparing Data: Failed to change WP_CACHE", Logger::TYPE_ERROR);
1105
  $table = $this->prefix . 'options';
1106
 
1107
  // Skip - Table does not exist
1108
+ if (false === $this->tableExists($table)) {
1109
  return true;
1110
  }
1111
 
1123
  }
1124
 
1125
  /**
1126
+ * Delete all listed staging sites from cloned site in initial cloning. Useful if a cloned site is cloned again. E.g. for dev > staging > production setup
1127
  * @return bool
1128
  */
1129
+ protected function step20(){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1130
 
1131
+ if ($this->options->mainJob === 'updating'){
1132
+ return true;
1133
+ }
 
 
 
 
1134
 
1135
+ if( false === $this->tableExists( $this->prefix . 'options' ) ) {
1136
+ return true;
 
 
1137
  }
1138
+
1139
+ $step = "Preparing Data Step20:";
1140
+ $this->log( $step . " Reset wp staging site data" );
1141
+
1142
+ $result = $this->db->query(
1143
+ $this->db->prepare( "UPDATE {$this->prefix}options SET `option_value` = %s WHERE `option_name` = %s", serialize(array()), 'wpstg_existing_clones_beta'
1144
+ )
1145
+ );
1146
+
1147
+ if (false === $result){
1148
+ $this->log( $step . "Failed to reset wp staging site data." );
1149
+ }
1150
+
1151
  return true;
1152
  }
1153
 
1177
  return false;
1178
  }
1179
 
1180
+ $customSlug = str_replace(wpstg_replace_windows_directory_separator(WPStaging::getWPpath()), '', wpstg_replace_windows_directory_separator($uploadPath));
 
 
1181
 
1182
+ return $this->options->destinationDir . $customSlug;
1183
  }
1184
 
1185
  /**
1209
  $siteurl = preg_replace('#^https?://#', '', rtrim(get_option('siteurl'), '/'));
1210
  $home = preg_replace('#^https?://#', '', rtrim(get_option('home'), '/'));
1211
 
1212
+ return $home !== $siteurl;
 
 
 
1213
  }
1214
 
1215
  /**
1225
  return '';
1226
  }
1227
 
1228
+ return str_replace(array($home, '/'), '', $siteurl);
 
1229
  }
1230
 
1231
  }
Backend/Modules/Jobs/DataExternal.php CHANGED
@@ -3,7 +3,7 @@
3
  namespace WPStaging\Backend\Modules\Jobs;
4
 
5
  // No Direct Access
6
- if( !defined( "WPINC" ) ) {
7
  die;
8
  }
9
 
@@ -16,7 +16,8 @@ use WPStaging\Utils\Strings;
16
  * Class Data
17
  * @package WPStaging\Backend\Modules\Jobs
18
  */
19
- class DataExternal extends JobExecutable {
 
20
 
21
  /**
22
  * @var \wpdb
@@ -43,19 +44,20 @@ class DataExternal extends JobExecutable {
43
  /**
44
  * Initialize
45
  */
46
- public function initialize() {
47
- $this->db = $this->getStagingDB();
48
- $this->productionDb = WPStaging::getInstance()->get( "wpdb" );
49
- $this->prefix = $this->options->prefix;
50
- $this->db->prefix = $this->options->databasePrefix;
 
51
 
52
  $this->getTables();
53
 
54
- $helper = new Helper();
55
  $this->homeUrl = $helper->get_home_url();
56
 
57
  // Fix current step
58
- if( 0 == $this->options->currentStep ) {
59
  $this->options->currentStep = 0;
60
  }
61
  }
@@ -63,18 +65,20 @@ class DataExternal extends JobExecutable {
63
  /**
64
  * Get database object to interact with
65
  */
66
- private function getStagingDB() {
67
- return new \wpdb( $this->options->databaseUser, str_replace( "\\\\", "\\", $this->options->databasePassword ), $this->options->databaseDatabase, $this->options->databaseServer );
 
68
  }
69
 
70
  /**
71
  * Get a list of tables to copy
72
  */
73
- private function getTables() {
74
- $strings = new Strings();
 
75
  $this->tables = array();
76
- foreach ( $this->options->tables as $table ) {
77
- $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->productionDb->prefix, null, $table );
78
  }
79
  }
80
 
@@ -82,7 +86,8 @@ class DataExternal extends JobExecutable {
82
  * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
83
  * @return void
84
  */
85
- protected function calculateTotalSteps() {
 
86
  $this->options->totalSteps = 20;
87
  }
88
 
@@ -90,14 +95,15 @@ class DataExternal extends JobExecutable {
90
  * Start Module
91
  * @return object
92
  */
93
- public function start() {
 
94
  // Execute steps
95
  $this->run();
96
 
97
  // Save option, progress
98
  $this->saveOptions();
99
 
100
- return ( object ) $this->response;
101
  }
102
 
103
  /**
@@ -105,30 +111,31 @@ class DataExternal extends JobExecutable {
105
  * Returns false when over threshold limits are hit or when the job is done, true otherwise
106
  * @return bool
107
  */
108
- protected function execute() {
 
109
  // Fatal error. Let this happen never and break here immediately
110
- if( $this->isRoot() ) {
111
  return false;
112
  }
113
 
114
  // Over limits threshold
115
- if( $this->isOverThreshold() ) {
116
  // Prepare response and save current progress
117
- $this->prepareResponse( false, false );
118
  $this->saveOptions();
119
  return false;
120
  }
121
 
122
  // No more steps, finished
123
- if( $this->isFinished() ) {
124
- $this->prepareResponse( true, false );
125
  return false;
126
  }
127
 
128
  // Execute step
129
  $stepMethodName = "step" . $this->options->currentStep;
130
- if( !$this->{$stepMethodName}() ) {
131
- $this->prepareResponse( false, false );
132
  return false;
133
  }
134
 
@@ -143,19 +150,20 @@ class DataExternal extends JobExecutable {
143
  * Checks Whether There is Any Job to Execute or Not
144
  * @return bool
145
  */
146
- protected function isFinished() {
 
147
  return
148
  !$this->isRunning() ||
149
  $this->options->currentStep > $this->options->totalSteps ||
150
- !method_exists($this, "step" . $this->options->currentStep)
151
- ;
152
  }
153
 
154
  /**
155
  * Check if current operation is done on the root folder or on the live DB
156
  * @return boolean
157
  */
158
- protected function isRoot() {
 
159
 
160
  // Prefix is the same as the one of live site
161
  // $wpdb = WPStaging::getInstance()->get( "wpdb" );
@@ -163,13 +171,13 @@ class DataExternal extends JobExecutable {
163
  // return true;
164
  // }
165
  // CloneName is empty
166
- $name = ( array ) $this->options->cloneDirectoryName;
167
- if( empty( $name ) ) {
168
  return true;
169
  }
170
 
171
  // Live Path === Staging path
172
- if( $this->homeUrl . $this->options->cloneDirectoryName === $this->homeUrl ) {
173
  return true;
174
  }
175
 
@@ -181,9 +189,10 @@ class DataExternal extends JobExecutable {
181
  * @param string $table
182
  * @return boolean
183
  */
184
- protected function isTable( $table ) {
185
- if( $this->db->get_var( "SHOW TABLES LIKE '{$table}'" ) != $table ) {
186
- $this->log( "Table {$table} does not exist", Logger::TYPE_ERROR );
 
187
  return false;
188
  }
189
  return true;
@@ -194,39 +203,40 @@ class DataExternal extends JobExecutable {
194
  * copy default wp-config.php if production site uses bedrock or any other boilerplate solution that stores wp default config data elsewhere.
195
  * @return boolean
196
  */
197
- protected function step0() {
198
- $this->log( "Preparing Data Step0: Copy wp-config.php file", Logger::TYPE_INFO );
 
199
 
200
- $dir = trailingslashit( dirname( ABSPATH ) );
201
 
202
  $source = $dir . 'wp-config.php';
203
 
204
  $destination = $this->options->destinationDir . 'wp-config.php';
205
 
206
  // Check if there is already a valid wp-config.php in root of staging site
207
- if( $this->isValidWpConfig( $destination ) ) {
208
  return true;
209
  }
210
 
211
  // Check if there is a valid wp-config.php outside root of wp production site
212
- if( $this->isValidWpConfig( $source ) ) {
213
  // Copy it to staging site
214
- if( $this->copy( $source, $destination ) ) {
215
  return true;
216
  }
217
  }
218
 
219
  // No valid wp-config.php found so let's copy wp stagings default wp-config.php to staging site
220
  $source = WPSTG_PLUGIN_DIR . "Backend/helpers/wp-config.php";
221
- if( $this->copy( $source, $destination ) ) {
222
  // add missing db credentials to wp-config.php
223
- if( !$this->alterWpConfig( $destination ) ) {
224
- $this->log( "Preparing Data Step0: Can not alter db credentials in wp-config.php", Logger::TYPE_INFO );
225
  return false;
226
  }
227
  }
228
 
229
- $this->log( "Preparing Data Step0: Successful", Logger::TYPE_INFO );
230
  return true;
231
  }
232
 
@@ -236,21 +246,22 @@ class DataExternal extends JobExecutable {
236
  * @param type $destination
237
  * @return boolean
238
  */
239
- protected function copy( $source, $destination ) {
 
240
  // Copy symbolic link
241
- if( is_link( $source ) ) {
242
- $this->log( "Preparing Data: Symbolic link found...", Logger::TYPE_INFO );
243
- if( !@copy( readlink( $source ), $destination ) ) {
244
  $errors = error_get_last();
245
- $this->log( "Preparing Data: Failed to copy {$source} Error: {$errors['message']} {$source} -> {$destination}", Logger::TYPE_ERROR );
246
  return false;
247
  }
248
  }
249
 
250
  // Copy file
251
- if( !@copy( $source, $destination ) ) {
252
  $errors = error_get_last();
253
- $this->log( "Preparing Data Step0: Failed to copy {$source}! Error: {$errors['message']} {$source} -> {$destination}", Logger::TYPE_ERROR );
254
  return false;
255
  }
256
 
@@ -262,10 +273,10 @@ class DataExternal extends JobExecutable {
262
  * @param type $source
263
  * @return boolean
264
  */
265
- protected function alterWpConfig( $source ) {
266
- $content = file_get_contents( $source );
267
 
268
- if( false === ($content = file_get_contents( $source )) ) {
269
  return false;
270
  }
271
 
@@ -284,10 +295,10 @@ define( 'DB_CHARSET', '" . DB_CHARSET . "' );\r\n
284
  /** The Database Collate type. Don't change this if in doubt. */\r\n
285
  define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
286
 
287
- $content = str_replace( $search, $replace, $content );
288
 
289
- if( false === @wpstg_put_contents( $source, $content ) ) {
290
- $this->log( "Preparing Data: Can't save wp-config.php", Logger::TYPE_ERROR );
291
  return false;
292
  }
293
 
@@ -299,43 +310,43 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
299
  * @param type $source
300
  * @return boolean
301
  */
302
- protected function isValidWpConfig( $source ) {
 
303
 
304
- if( !is_file( $source ) && !is_link( $source ) ) {
305
  return false;
306
  }
307
 
308
- $content = file_get_contents( $source );
309
 
310
- if( false === ($content = file_get_contents( $source )) ) {
311
  return false;
312
  }
313
 
314
  // Get DB_NAME from wp-config.php
315
- preg_match( "/define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
316
 
317
- if( empty( $matches[1] ) ) {
318
  return false;
319
  }
320
 
321
  // Get DB_USER from wp-config.php
322
- preg_match( "/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
323
 
324
- if( empty( $matches[1] ) ) {
325
  return false;
326
  }
327
 
328
  // Get DB_PASSWORD from wp-config.php
329
- preg_match( "/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
330
 
331
- if( empty( $matches[1] ) ) {
332
  return false;
333
  }
334
 
335
  // Get DB_HOST from wp-config.php
336
- preg_match( "/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
337
 
338
- if( empty( $matches[1] ) ) {
339
  return false;
340
  }
341
  return true;
@@ -345,35 +356,35 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
345
  * Replace "siteurl" and "home"
346
  * @return bool
347
  */
348
- protected function step1() {
349
- $this->log( "Preparing Data Step1: Updating siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_INFO );
 
350
 
351
  // Skip - Table does not exist
352
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
353
  return true;
354
  }
355
  // Skip - Table is not selected or updated
356
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
357
- $this->log( "Preparing Data Step1: Skipping" );
358
  return true;
359
  }
360
 
361
- $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . $this->getStagingSiteUrl() );
362
  // Replace URLs
363
  $result = $this->db->query(
364
- $this->db->prepare(
365
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", $this->getStagingSiteUrl()
366
- )
367
  );
368
 
369
 
370
-
371
  // All good
372
- if( $result ) {
373
  return true;
374
  }
375
 
376
- $this->log( "Preparing Data Step1: Skip updating siteurl and homeurl in {$this->prefix}options. Probably already did! {$this->db->last_error}", Logger::TYPE_WARNING );
377
  return true;
378
  }
379
 
@@ -381,42 +392,43 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
381
  * Update "wpstg_is_staging_site"
382
  * @return bool
383
  */
384
- protected function step2() {
 
385
 
386
- $this->log( "Preparing Data Step2: Updating row wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}" );
387
 
388
  // Skip - Table does not exist
389
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
390
- $this->log( "Preparing Data Step2: Skipping" );
391
  return true;
392
  }
393
  // Skip - Table is not selected or updated
394
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
395
- $this->log( "Preparing Data Step2: Skipping" );
396
  return true;
397
  }
398
 
399
- $delete = $this->db->query(
400
- $this->db->prepare(
401
- "DELETE FROM `{$this->prefix}options` WHERE `option_name` = %s;", 'wpstg_is_staging_site'
402
- )
403
  );
404
 
405
 
406
  // No errors but no option name such as wpstg_is_staging_site
407
  $insert = $this->db->query(
408
- $this->db->prepare(
409
- "INSERT INTO {$this->prefix}options (option_name,option_value) VALUES ('wpstg_is_staging_site',%s)", "true"
410
- )
411
  );
412
 
413
  // All good
414
- if( $insert ) {
415
- $this->log( "Preparing Data Step2: Successful", Logger::TYPE_INFO );
416
  return true;
417
  }
418
 
419
- $this->log( "Preparing Data Step2: Failed to update wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
420
  return false;
421
  }
422
 
@@ -424,36 +436,37 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
424
  * Update rewrite_rules
425
  * @return bool
426
  */
427
- protected function step3() {
 
428
 
429
- $this->log( "Preparing Data Step3: Updating rewrite_rules in {$this->prefix}options {$this->db->last_error}" );
430
 
431
  // Keep Permalinks
432
- if( isset( $this->settings->keepPermalinks ) && $this->settings->keepPermalinks === "1" ) {
433
- $this->log( "Preparing Data Step3: Skipping" );
434
  return true;
435
  }
436
 
437
  // Skip - Table does not exist
438
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
439
- $this->log( "Preparing Data Step3: Skipping" );
440
  return true;
441
  }
442
 
443
  // Skip - Table is not selected or updated
444
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
445
- $this->log( "Preparing Data Step3: Skipping" );
446
  return true;
447
  }
448
 
449
  $result = $this->db->query(
450
- $this->db->prepare(
451
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'rewrite_rules'", ' '
452
- )
453
  );
454
 
455
  // All good
456
- if( $result ) {
457
  return true;
458
  }
459
 
@@ -465,36 +478,35 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
465
  * Update Table Prefix in wp_usermeta and wp_options
466
  * @return bool
467
  */
468
- protected function step4() {
469
- $this->log( "Preparing Data Step4: Updating db prefix in {$this->prefix}usermeta. " );
 
470
 
471
  // Skip - Table does not exist
472
- if( false === $this->isTable( $this->prefix . 'usermeta' ) ) {
473
  return true;
474
  }
475
 
476
  // Skip - Table is not selected or updated
477
- if( !in_array( $this->prefix . 'usermeta', $this->tables ) ) {
478
- $this->log( "Preparing Data Step4: Skipping" );
479
  return true;
480
  }
481
 
482
 
483
-
484
  $update = $this->db->query(
485
- $this->db->prepare(
486
- "UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, %s, %s) WHERE meta_key LIKE %s", $this->productionDb->prefix, $this->prefix, $this->productionDb->prefix . "_%"
487
- )
488
  );
489
 
490
- if( false === $update ) {
491
- $this->log( "Preparing Data Step4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}", Logger::TYPE_ERROR );
492
- $this->returnException( "Preparing Data Step4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}" );
493
  return false;
494
  }
495
 
496
 
497
-
498
  return true;
499
  }
500
 
@@ -502,30 +514,31 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
502
  * Update Table prefix in wp-config.php
503
  * @return bool
504
  */
505
- protected function step5() {
 
506
  $path = $this->options->destinationDir . "wp-config.php";
507
 
508
- $this->log( "Preparing Data Step5: Updating table_prefix in {$path} to " . $this->prefix );
509
- if( false === ($content = file_get_contents( $path )) ) {
510
- $this->log( "Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR );
511
  return false;
512
  }
513
 
514
  // Replace table prefix
515
- $pattern = '/\$table_prefix\s*=\s*(.*).*/';
516
  $replacement = '$table_prefix = \'' . $this->prefix . '\'; // Changed by WP Staging';
517
- $content = preg_replace( $pattern, $replacement, $content );
518
 
519
- if( null === $content ) {
520
- $this->log( "Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR );
521
  return false;
522
  }
523
 
524
  // Replace URLs
525
- $content = str_replace( $this->homeUrl, $this->getStagingSiteUrl(), $content );
526
 
527
- if( false === @wpstg_put_contents( $path, $content ) ) {
528
- $this->log( "Preparing Data Step5: Failed to update $table_prefix in {$path} to " . $this->prefix . ". Can't save contents", Logger::TYPE_ERROR );
529
  return false;
530
  }
531
 
@@ -591,30 +604,31 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
591
  * Update wpstg_rmpermalinks_executed
592
  * @return bool
593
  */
594
- protected function step7() {
 
595
 
596
- $this->log( "Preparing Data Step7: Updating wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}" );
597
 
598
  // Skip - Table does not exist
599
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
600
- $this->log( "Preparing Data Step7: Skipping Table {$this->prefix}'options' does not exist" );
601
  return true;
602
  }
603
 
604
  // Skip - Table is not selected or updated
605
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
606
- $this->log( "Preparing Data Step7: Skipping" );
607
  return true;
608
  }
609
 
610
  $result = $this->db->query(
611
- $this->db->prepare(
612
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_rmpermalinks_executed'", ' '
613
- )
614
  );
615
 
616
 
617
- $this->Log( "Preparing Data Step7: Finished successfully" );
618
  return true;
619
  }
620
 
@@ -622,41 +636,42 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
622
  * Update permalink_structure
623
  * @return bool
624
  */
625
- protected function step8() {
 
626
 
627
- $this->log( "Preparing Data Step8: Updating permalink_structure in {$this->prefix}options {$this->db->last_error}" );
628
 
629
  // Keep Permalinks
630
- if( isset( $this->settings->keepPermalinks ) && $this->settings->keepPermalinks === "1" ) {
631
- $this->log( "Preparing Data Step8: Skipping" );
632
  return true;
633
  }
634
 
635
  // Skip - Table does not exist
636
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
637
- $this->log( "Preparing Data Step8: Skipping" );
638
  return true;
639
  }
640
 
641
  // Skip - Table is not selected or updated
642
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
643
- $this->log( "Preparing Data Step8: Skipping" );
644
  return true;
645
  }
646
 
647
  $result = $this->db->query(
648
- $this->db->prepare(
649
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'permalink_structure'", ' '
650
- )
651
  );
652
 
653
  // All good
654
- if( $result ) {
655
- $this->Log( "Preparing Data Step8: Finished successfully" );
656
  return true;
657
  }
658
 
659
- $this->log( "Failed to update permalink_structure in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
660
  return true;
661
  }
662
 
@@ -664,33 +679,34 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
664
  * Update blog_public option to not allow staging site to be indexed by search engines
665
  * @return bool
666
  */
667
- protected function step9() {
 
668
 
669
- $this->log( "Preparing Data Step9: Set staging site to noindex" );
670
 
671
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
672
  return true;
673
  }
674
 
675
  // Skip - Table is not selected or updated
676
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
677
- $this->log( "Preparing Data Step9: Skipping" );
678
  return true;
679
  }
680
 
681
  $result = $this->db->query(
682
- $this->db->prepare(
683
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'blog_public'", '0'
684
- )
685
  );
686
 
687
  // All good
688
- if( $result ) {
689
- $this->Log( "Preparing Data Step9: Finished successfully" );
690
  return true;
691
  }
692
 
693
- $this->log( "Can not update staging site to noindex. Possible already done!", Logger::TYPE_WARNING );
694
  return true;
695
  }
696
 
@@ -698,21 +714,22 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
698
  * Update WP_HOME in wp-config.php
699
  * @return bool
700
  */
701
- protected function step10() {
 
702
  $path = $this->options->destinationDir . "wp-config.php";
703
 
704
- $this->log( "Preparing Data Step10: Updating WP_HOME in wp-config.php to " . $this->getStagingSiteUrl() );
705
 
706
- if( false === ($content = file_get_contents( $path )) ) {
707
- $this->log( "Preparing Data Step10: Failed to update WP_HOME in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
708
  return false;
709
  }
710
 
711
 
712
  // Get WP_HOME from wp-config.php
713
- preg_match( "/define\s*\(\s*['\"]WP_HOME['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
714
 
715
- if( !empty( $matches[1] ) ) {
716
  $matches[1];
717
 
718
  $pattern = "/define\s*\(\s*['\"]WP_HOME['\"]\s*,\s*(.*)\s*\);.*/";
@@ -720,19 +737,19 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
720
  $replace = "define('WP_HOME','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
721
  //$replace.= " // Changed by WP-Staging";
722
 
723
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
724
- $this->log( "Preparing Data: Failed to update WP_HOME", Logger::TYPE_ERROR );
725
  return false;
726
  }
727
  } else {
728
- $this->log( "Preparing Data Step10: WP_HOME not defined in wp-config.php. Skipping this step." );
729
  }
730
 
731
- if( false === @wpstg_put_contents( $path, $content ) ) {
732
- $this->log( "Preparing Data Step10: Failed to update WP_HOME. Can't save contents", Logger::TYPE_ERROR );
733
  return false;
734
  }
735
- $this->Log( "Preparing Data Step10: Finished successfully" );
736
  return true;
737
  }
738
 
@@ -740,21 +757,22 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
740
  * Update WP_SITEURL in wp-config.php
741
  * @return bool
742
  */
743
- protected function step11() {
 
744
  $path = $this->options->destinationDir . "wp-config.php";
745
 
746
- $this->log( "Preparing Data Step11: Updating WP_SITEURL in wp-config.php to " . $this->getStagingSiteUrl() );
747
 
748
- if( false === ($content = file_get_contents( $path )) ) {
749
- $this->log( "Preparing Data Step11: Failed to update WP_SITEURL in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
750
  return false;
751
  }
752
 
753
 
754
  // Get WP_SITEURL from wp-config.php
755
- preg_match( "/define\s*\(\s*['\"]WP_SITEURL['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
756
 
757
- if( !empty( $matches[1] ) ) {
758
  $matches[1];
759
 
760
  $pattern = "/define\s*\(\s*['\"]WP_SITEURL['\"]\s*,\s*(.*)\s*\);.*/";
@@ -762,114 +780,50 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
762
  $replace = "define('WP_SITEURL','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
763
  //$replace.= " // Changed by WP-Staging";
764
 
765
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
766
- $this->log( "Preparing Data Step11: Failed to update WP_SITEURL", Logger::TYPE_ERROR );
767
  return false;
768
  }
769
  } else {
770
- $this->log( "Preparing Data Step11: WP_SITEURL not defined in wp-config.php. Skipping this step." );
771
  }
772
 
773
 
774
- if( false === @wpstg_put_contents( $path, $content ) ) {
775
- $this->log( "Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR );
776
  return false;
777
  }
778
- $this->Log( "Preparing Data Step11: Finished successfully" );
779
  return true;
780
  }
781
 
782
- /**
783
- * Set true WP_DEBUG & WP_DEBUG_DISPLAY in wp-config.php
784
- * @return bool
785
- */
786
- // protected function step12() {
787
- // $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
788
- //
789
- // $this->log( "Preparing Data Step12: Set WP_DEBUG to true in wp-config.php." );
790
- //
791
- // if( false === ($content = file_get_contents( $path )) ) {
792
- // $this->log( "Preparing Data Step12: Failed to update WP_DEBUG in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
793
- // return false;
794
- // }
795
- //
796
- //
797
- // // Get WP_DEBUG from wp-config.php
798
- // preg_match( "/define\s*\(\s*'WP_DEBUG'\s*,\s*(.*)\s*\);/", $content, $matches );
799
- //
800
- // // Found it!
801
- // if( !empty( $matches[1] ) ) {
802
- // $matches[1];
803
- //
804
- // $pattern = "/define\s*\(\s*'WP_DEBUG'\s*,\s*(.*)\s*\);/";
805
- //
806
- // $replace = "define('WP_DEBUG',true); // " . $matches[1];
807
- // $replace.= " // Changed by WP-Staging";
808
- //
809
- // if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
810
- // $this->log( "Preparing Data Step12: Failed to update WP_DEBUG", Logger::TYPE_ERROR );
811
- // return false;
812
- // }
813
- // } else {
814
- // $this->log( "Preparing Data Step12: WP_DEBUG not defined in wp-config.php. Skip it." );
815
- // }
816
- //
817
- //
818
- //
819
- // // Get WP_DEBUG_DISPLAY
820
- // preg_match( "/define\s*\(\s*'WP_DEBUG_DISPLAY'\s*,\s*(.*)\s*\);/", $content, $matches );
821
- //
822
- // // Found it!
823
- // if( !empty( $matches[1] ) ) {
824
- // $matches[1];
825
- //
826
- // $pattern = "/define\s*\(\s*'WP_DEBUG_DISPLAY'\s*,\s*(.*)\s*\);/";
827
- //
828
- // $replace = "define('WP_DEBUG_DISPLAY',true); // " . $matches[1];
829
- // $replace.= " // Changed by WP-Staging";
830
- //
831
- // if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
832
- // $this->log( "Preparing Data Step12: Failed to update WP_DEBUG", Logger::TYPE_ERROR );
833
- // return false;
834
- // }
835
- // } else {
836
- // $this->log( "Preparing Data Step12: WP_DEBUG not defined in wp-config.php. Skip it." );
837
- // }
838
- //
839
- //
840
- // if( false === @wpstg_put_contents( $path, $content ) ) {
841
- // $this->log( "Preparing Data Step12: Failed to update WP_DEBUG. Can't save content in wp-config.php", Logger::TYPE_ERROR );
842
- // return false;
843
- // }
844
- // $this->Log( "Preparing Data: Finished Step 12 successfully" );
845
- // return true;
846
- // }
847
 
848
  /**
849
  * Update Table Prefix in wp_options
850
  * @return bool
851
  */
852
- protected function step12() {
853
- $this->log( "Preparing Data Step12: Updating db prefix in {$this->prefix}options." );
 
854
 
855
  // Skip - Table does not exist
856
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
857
  return true;
858
  }
859
 
860
  // Skip, prefixes are identical. No change needed
861
- if( $this->productionDb->prefix === $this->prefix ) {
862
- $this->log( "Preparing Data Step12: Skipped" );
863
  return true;
864
  }
865
 
866
  // Skip - Table is not selected or updated
867
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
868
- $this->log( "Preparing Data Step12: Skipping" );
869
  return true;
870
  }
871
 
872
- $notice = !empty( $this->db->last_error ) ? 'Last error: ' . $this->db->last_error : '';
873
 
874
  //$this->log( "Updating option_name in {$this->prefix}options. {$notice}" );
875
  // Filter the rows below. Do not update them!
@@ -879,25 +833,25 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
879
  'wp_mail_smtp_debug',
880
  );
881
 
882
- $filters = apply_filters( 'wpstg_data_excl_rows', $filters );
883
 
884
  $where = "";
885
- foreach ( $filters as $filter ) {
886
  $where .= " AND option_name <> '" . $filter . "'";
887
  }
888
 
889
  $updateOptions = $this->db->query(
890
- $this->db->prepare(
891
- "UPDATE IGNORE {$this->prefix}options SET option_name= replace(option_name, %s, %s) WHERE option_name LIKE %s" . $where, $this->productionDb->prefix, $this->prefix, $this->productionDb->prefix . "_%"
892
- )
893
  );
894
 
895
- if( false === $updateOptions ) {
896
- $this->log( "Preparing Data Step12: Failed to update option_name in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_ERROR );
897
- $this->returnException( " Preparing Data Step12: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
898
  return false;
899
  }
900
- $this->Log( "Preparing Data Step12: Finished successfully" );
901
  return true;
902
  }
903
 
@@ -905,42 +859,43 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
905
  * Change upload_path in wp_options (if it is defined)
906
  * @return bool
907
  */
908
- protected function step13() {
909
- $this->log( "Preparing Data Step13: Updating upload_path {$this->prefix}options." );
 
910
 
911
  // Skip - Table does not exist
912
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
913
  return true;
914
  }
915
 
916
  $newUploadPath = $this->getNewUploadPath();
917
 
918
- if( false === $newUploadPath ) {
919
- $this->log( "Preparing Data Step13: Skipped" );
920
  return true;
921
  }
922
 
923
  // Skip - Table is not selected or updated
924
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
925
- $this->log( "Preparing Data Step13: Skipped" );
926
  return true;
927
  }
928
 
929
- $error = isset( $this->db->last_error ) ? 'Last error: ' . $this->db->last_error : '';
930
 
931
- $this->log( "Updating upload_path in {$this->prefix}options. {$error}" );
932
 
933
  $updateOptions = $this->db->query(
934
- $this->db->prepare(
935
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'upload_path'", $newUploadPath
936
- )
937
  );
938
 
939
- if( false === $updateOptions ) {
940
- $this->log( "Preparing Data Step13: Failed to update upload_path in {$this->prefix}options. {$error}", Logger::TYPE_ERROR );
941
  return true;
942
  }
943
- $this->Log( "Preparing Data Step 13: Finished successfully" );
944
  return true;
945
  }
946
 
@@ -948,40 +903,41 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
948
  * Update WP_CACHE in wp-config.php
949
  * @return bool
950
  */
951
- protected function step14() {
 
952
  $path = $this->options->destinationDir . "wp-config.php";
953
 
954
- $this->log( "Preparing Data Step14: Set WP_CACHE in wp-config.php to false" );
955
 
956
- if( false === ($content = file_get_contents( $path )) ) {
957
- $this->log( "Preparing Data Step14: Failed to update WP_CACHE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
958
  return false;
959
  }
960
 
961
 
962
  // Get WP_CACHE from wp-config.php
963
- preg_match( "/define\s*\(\s*['\"]WP_CACHE['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
964
 
965
- if( !empty( $matches[1] ) ) {
966
 
967
  $pattern = "/define\s*\(\s*['\"]WP_CACHE['\"]\s*,\s*(.*)\s*\);.*/";
968
 
969
  $replace = "define('WP_CACHE',false); // " . $matches[1];
970
  //$replace.= " // Changed by WP-Staging";
971
 
972
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
973
- $this->log( "Preparing Data: Failed to change WP_CACHE", Logger::TYPE_ERROR );
974
  return false;
975
  }
976
  } else {
977
- $this->log( "Preparing Data Step14: WP_CACHE not defined in wp-config.php. Skipping this step." );
978
  }
979
 
980
- if( false === @wpstg_put_contents( $path, $content ) ) {
981
- $this->log( "Preparing Data Step14: Failed to update WP_CACHE. Can't save contents", Logger::TYPE_ERROR );
982
  return false;
983
  }
984
- $this->Log( "Preparing Data Step14: Finished successfully" );
985
  return true;
986
  }
987
 
@@ -989,21 +945,22 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
989
  * Update database credentials in wp-config.php
990
  * @return bool
991
  */
992
- protected function step15() {
 
993
  $path = $this->options->destinationDir . "wp-config.php";
994
 
995
- $this->log( "Preparing Data Step15: Change database credentials in wp-config.php" );
996
 
997
- if( false === ($content = file_get_contents( $path )) ) {
998
- $this->log( "Preparing Data Step15: Failed to update database credentials in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
999
  return false;
1000
  }
1001
 
1002
 
1003
  // Get DB_NAME from wp-config.php
1004
- preg_match( "/define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
1005
 
1006
- if( !empty( $matches[1] ) ) {
1007
  $matches[1];
1008
 
1009
  $pattern = "/define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*(.*)\s*\);.*/";
@@ -1011,17 +968,17 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1011
  $replace = "define('DB_NAME','{$this->options->databaseDatabase}'); // " . $matches[1];
1012
  //$replace.= " // Changed by WP-Staging";
1013
 
1014
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1015
- $this->log( "Preparing Data: Failed to change DB_NAME", Logger::TYPE_ERROR );
1016
  return false;
1017
  }
1018
  } else {
1019
- $this->log( "Preparing Data Step15: DB_NAME not defined in wp-config.php. Skipped this step." );
1020
  }
1021
  // Get DB_USER from wp-config.php
1022
- preg_match( "/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
1023
 
1024
- if( !empty( $matches[1] ) ) {
1025
  $matches[1];
1026
 
1027
  $pattern = "/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*(.*)\s*\);.*/";
@@ -1029,35 +986,39 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1029
  $replace = "define('DB_USER','{$this->options->databaseUser}'); // " . $matches[1];
1030
  //$replace.= " // Changed by WP-Staging";
1031
 
1032
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1033
- $this->log( "Preparing Data: Failed to change DB_USER", Logger::TYPE_ERROR );
1034
  return false;
1035
  }
1036
  } else {
1037
- $this->log( "Preparing Data Step15: DB_USER not defined in wp-config.php. Skipped this step." );
1038
  }
1039
  // Get DB_PASSWORD from wp-config.php
1040
- preg_match( "/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
1041
 
1042
- if( !empty( $matches[1] ) ) {
1043
  $matches[1];
1044
 
1045
  $pattern = "/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*(.*)\s*\);.*/";
1046
 
1047
- $replace = "define('DB_PASSWORD','{$this->options->databasePassword}'); // Changed by WP Staging";
 
 
 
 
1048
  //$replace.= " // Changed by WP-Staging";
1049
 
1050
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1051
- $this->log( "Preparing Data: Failed to change DB_PASSWORD", Logger::TYPE_ERROR );
1052
  return false;
1053
  }
1054
  } else {
1055
- $this->log( "Preparing Data Step15: DB_PASSWORD not defined in wp-config.php. Skipped this step." );
1056
  }
1057
  // Get DB_HOST from wp-config.php
1058
- preg_match( "/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
1059
 
1060
- if( !empty( $matches[1] ) ) {
1061
  $matches[1];
1062
 
1063
  $pattern = "/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*(.*)\s*\);.*/";
@@ -1065,20 +1026,20 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1065
  $replace = "define('DB_HOST','{$this->options->databaseServer}'); // " . $matches[1];
1066
  //$replace.= " // Changed by WP-Staging";
1067
 
1068
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1069
- $this->log( "Preparing Data: Failed to change DB_HOST", Logger::TYPE_ERROR );
1070
  return false;
1071
  }
1072
  } else {
1073
- $this->log( "Preparing Data Step15: DB_HOST not defined in wp-config.php. Skipped this step." );
1074
  }
1075
 
1076
 
1077
- if( false === @wpstg_put_contents( $path, $content ) ) {
1078
- $this->log( "Preparing Data Step15: Failed to update database credentials in wp-config.php. Can't save contents", Logger::TYPE_ERROR );
1079
  return false;
1080
  }
1081
- $this->Log( "Preparing Data: Finished Step 15 successfully" );
1082
  return true;
1083
  }
1084
 
@@ -1086,39 +1047,40 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1086
  * Remove UPLOADS constant in wp-config.php to reset default image folder
1087
  * @return bool
1088
  */
1089
- protected function step16() {
 
1090
  $path = $this->options->destinationDir . "wp-config.php";
1091
 
1092
- $this->log( "Preparing Data Step16: Remove UPLOADS in wp-config.php" );
1093
 
1094
- if( false === ($content = file_get_contents( $path )) ) {
1095
- $this->log( "Preparing Data Step16: Failed to get UPLOADS in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
1096
  return false;
1097
  }
1098
 
1099
 
1100
  // Get UPLOADS from wp-config.php
1101
- preg_match( "/define\s*\(\s*['\"]UPLOADS['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
1102
 
1103
- if( !empty( $matches[0] ) ) {
1104
 
1105
  $pattern = "/define\s*\(\s*'UPLOADS'\s*,\s*(.*)\s*\);/";
1106
 
1107
  $replace = "";
1108
 
1109
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1110
- $this->log( "Preparing Data: Failed to change UPLOADS", Logger::TYPE_ERROR );
1111
  return false;
1112
  }
1113
  } else {
1114
- $this->log( "Preparing Data Step16: UPLOADS not defined in wp-config.php. Skipping this step." );
1115
  }
1116
 
1117
- if( false === @wpstg_put_contents( $path, $content ) ) {
1118
- $this->log( "Preparing Data Step16: Failed to update UPLOADS. Can't save contents", Logger::TYPE_ERROR );
1119
  return false;
1120
  }
1121
- $this->Log( "Preparing Data Step 16: Finished successfully" );
1122
  return true;
1123
  }
1124
 
@@ -1127,49 +1089,50 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1127
  * This is important when a custom uploads folder is used
1128
  * @return bool
1129
  */
1130
- protected function step17() {
1131
- $path = $this->options->destinationDir . "wp-config.php";
1132
- $this->log( "Preparing Data Step17: Update UPLOADS constant in wp-config.php" );
1133
- if( false === ($content = file_get_contents( $path )) ) {
1134
- $this->log( "Preparing Data Step17: Failed to get UPLOADS in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
 
1135
  return false;
1136
  }
1137
  // Get UPLOADS from wp-config.php if there is already one
1138
- preg_match( "/define\s*\(\s*['\"]UPLOADS['\"]\s*,\s*(.*)\s*\);/", $content, $matches );
1139
  // TODO RPoC; DRY
1140
  $uploadFolder = wpstg_get_rel_upload_dir();
1141
  $uploadFolder = ltrim($uploadFolder, '/');
1142
  $uploadFolder = rtrim($uploadFolder, '/');
1143
- if( !empty( $matches[0] ) ) {
1144
  $pattern = "/define\s*\(\s*'UPLOADS'\s*,\s*(.*)\s*\);/";
1145
 
1146
  $replace = "define('UPLOADS', '" . $uploadFolder . "');";
1147
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1148
- $this->log( "Preparing Data Step17: Failed to change UPLOADS", Logger::TYPE_ERROR );
1149
  return false;
1150
  }
1151
  } else {
1152
- $this->log( "Preparing Data Step17: UPLOADS not defined in wp-config.php. Creating new entry." );
1153
  // Find line with ABSPATH and add UPLOADS constant above
1154
- preg_match( "/if\s*\(\s*\s*!\s*defined\s*\(\s*['\"]ABSPATH['\"]\s*(.*)\s*\)\s*\)/", $content, $matches );
1155
- if( !empty( $matches[0] ) ) {
1156
  $matches[0];
1157
  $pattern = "/if\s*\(\s*\s*!\s*defined\s*\(\s*['\"]ABSPATH['\"]\s*(.*)\s*\)\s*\)/";
1158
  $replace = "define('UPLOADS', '" . $uploadFolder . "'); \n" .
1159
- "if ( ! defined( 'ABSPATH' ) )";
1160
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
1161
- $this->log( "Preparing Data Step 17: Failed to change UPLOADS", Logger::TYPE_ERROR );
1162
  return false;
1163
  }
1164
  } else {
1165
- $this->log( "Preparing Data Step 17: Can not add UPLOAD constant to wp-config.php. Can not find free position to add it.", Logger::TYPE_ERROR );
1166
  }
1167
  }
1168
- if( false === @wpstg_put_contents( $path, $content ) ) {
1169
- $this->log( "Preparing Data Step17: Failed to update UPLOADS. Can't save contents", Logger::TYPE_ERROR );
1170
  return false;
1171
  }
1172
- $this->Log( "Preparing Data Step17: Finished successfully" );
1173
  return true;
1174
  }
1175
 
@@ -1177,25 +1140,26 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1177
  * Save hostname of parent production site in option_name wpstg_connection
1178
  * @return boolean
1179
  */
1180
- protected function step18() {
 
1181
 
1182
  $table = $this->prefix . 'options';
1183
 
1184
  $siteurl = get_site_url();
1185
 
1186
- $connection = json_encode( array('prodHostname' => $siteurl) );
1187
 
1188
  $data = array(
1189
- 'option_name' => 'wpstg_connection',
1190
  'option_value' => $connection
1191
  );
1192
 
1193
  $format = array('%s', '%s');
1194
 
1195
- $result = $this->db->replace( $table, $data, $format );
1196
 
1197
- if( false === $result ) {
1198
- $this->Log( "Preparing Data Step18: Could not save {$siteurl} in {$table}", Logger::TYPE_ERROR );
1199
  }
1200
  return true;
1201
  }
@@ -1205,97 +1169,54 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1205
  * This option is used to determine if the staging website has not been loaded initiall for executing certain custom actions from \WPStaging\initActions()
1206
  * @return boolean
1207
  */
1208
- protected function step19() {
 
1209
 
1210
  $table = $this->prefix . 'options';
1211
 
1212
  // Skip - Table does not exist
1213
- if( false === $this->isTable( $table ) ) {
1214
  return true;
1215
  }
1216
 
1217
  $result = $this->db->query(
1218
- $this->db->prepare(
1219
- "INSERT INTO {$this->prefix}options (option_name,option_value) VALUES ('wpstg_execute',%s) ON DUPLICATE KEY UPDATE option_value = %s", "true", "true"
1220
- )
1221
  );
1222
 
1223
- if( false === $result ) {
1224
- $this->Log( "Preparing Data Step19: Could not save wpstg_execute in {$table}", Logger::TYPE_ERROR );
1225
  }
1226
  return true;
1227
  }
1228
 
1229
  /**
1230
- * Preserve data and prevents data in wp_options from beeing cloned to staging site
1231
  * @return bool
1232
  */
1233
- protected function step20() {
1234
- $this->log( "Preparing Data Step20: Preserve Data in " . $this->prefix . "options" );
1235
 
1236
- // Skip - Table does not exist
1237
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
1238
  return true;
1239
  }
1240
 
1241
- // Skip - Table is not selected or updated
1242
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
1243
- $this->log( "Preparing Data Step20: Skipped" );
1244
  return true;
1245
  }
1246
 
1247
- $sql = '';
1248
-
1249
- $preserved_option_names = array('wpstg_existing_clones_beta');
1250
 
1251
- $preserved_option_names = apply_filters( 'wpstg_preserved_options_cloning', $preserved_option_names );
1252
- $preserved_options_escaped = esc_sql( $preserved_option_names );
1253
-
1254
- $preserved_options_data = array();
1255
-
1256
- // Get preserved data in wp_options tables
1257
- $table = $this->db->prefix . 'options';
1258
- $preserved_options_data[$this->prefix . 'options'] = $this->db->get_results(
1259
- sprintf(
1260
- "SELECT * FROM `{$table}` WHERE `option_name` IN ('%s')", implode( "','", $preserved_options_escaped )
1261
- ), ARRAY_A
1262
  );
1263
 
1264
- // Create preserved data queries for options tables
1265
- foreach ( $preserved_options_data as $key => $value ) {
1266
- if( false === empty( $value ) ) {
1267
- foreach ( $value as $option ) {
1268
- $sql .= $this->db->prepare(
1269
- "DELETE FROM `{$key}` WHERE `option_name` = %s;\n", $option['option_name']
1270
- );
1271
-
1272
- $sql .= $this->db->prepare(
1273
- "INSERT INTO `{$key}` ( `option_id`, `option_name`, `option_value`, `autoload` ) VALUES ( NULL , %s, %s, %s );\n", $option['option_name'], $option['option_value'], $option['autoload']
1274
- );
1275
- }
1276
- }
1277
  }
1278
 
1279
- $this->debugLog( "Preparing Data Step20: Preserve values " . json_encode( $preserved_options_data ), Logger::TYPE_INFO );
1280
-
1281
- $this->executeSql( $sql );
1282
-
1283
- $this->log( "Preparing Data Step20: Successful!" );
1284
- return true;
1285
- }
1286
-
1287
- /**
1288
- * Execute a batch of sql queries
1289
- * @param string $sqlbatch
1290
- */
1291
- private function executeSql( $sqlbatch ) {
1292
- $queries = array_filter( explode( ";\n", $sqlbatch ) );
1293
-
1294
- foreach ( $queries as $query ) {
1295
- if( false === $this->db->query( $query ) ) {
1296
- $this->log( "Data Crunching Warning: Can not execute query {$query}", Logger::TYPE_WARNING );
1297
- }
1298
- }
1299
  return true;
1300
  }
1301
 
@@ -1303,14 +1224,15 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1303
  * Get upload path
1304
  * @return boolean|string
1305
  */
1306
- protected function getNewUploadPath() {
1307
- $uploadPath = get_option( 'upload_path' );
 
1308
 
1309
- if( !$uploadPath ) {
1310
  return false;
1311
  }
1312
 
1313
- $customSlug = str_replace( \WPStaging\WPStaging::getWPpath(), '', $uploadPath );
1314
 
1315
  $newUploadPath = $this->options->destinationDir . $customSlug;
1316
 
@@ -1321,28 +1243,30 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1321
  * Return URL to staging site
1322
  * @return string
1323
  */
1324
- protected function getStagingSiteUrl() {
1325
- if( !empty( $this->options->cloneHostname ) ) {
 
1326
  return $this->options->cloneHostname;
1327
  }
1328
- if( $this->isSubDir() ) {
1329
- return trailingslashit( $this->homeUrl ) . trailingslashit( $this->getSubDir() ) . $this->options->cloneDirectoryName;
1330
  }
1331
 
1332
- return trailingslashit( $this->homeUrl ) . $this->options->cloneDirectoryName;
1333
  }
1334
 
1335
  /**
1336
  * Check if WP is installed in subdir
1337
  * @return boolean
1338
  */
1339
- protected function isSubDir() {
 
1340
  // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
1341
  // This is happening much more often than you would expect
1342
- $siteurl = preg_replace( '#^https?://#', '', rtrim( get_option( 'siteurl' ), '/' ) );
1343
- $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
1344
 
1345
- if( $home !== $siteurl ) {
1346
  return true;
1347
  }
1348
  return false;
@@ -1352,16 +1276,17 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1352
  * Get the install sub directory if WP is installed in sub directory
1353
  * @return string
1354
  */
1355
- protected function getSubDir() {
1356
- $home = get_option( 'home' );
1357
- $siteurl = get_option( 'siteurl' );
 
1358
 
1359
- if( empty( $home ) || empty( $siteurl ) ) {
1360
  return '';
1361
  }
1362
 
1363
- $dir = str_replace( $home, '', $siteurl );
1364
- return str_replace( '/', '', $dir );
1365
  }
1366
 
1367
  }
3
  namespace WPStaging\Backend\Modules\Jobs;
4
 
5
  // No Direct Access
6
+ if (!defined("WPINC")) {
7
  die;
8
  }
9
 
16
  * Class Data
17
  * @package WPStaging\Backend\Modules\Jobs
18
  */
19
+ class DataExternal extends JobExecutable
20
+ {
21
 
22
  /**
23
  * @var \wpdb
44
  /**
45
  * Initialize
46
  */
47
+ public function initialize()
48
+ {
49
+ $this->db = $this->getStagingDB();
50
+ $this->productionDb = WPStaging::getInstance()->get("wpdb");
51
+ $this->prefix = $this->options->prefix;
52
+ $this->db->prefix = $this->options->databasePrefix;
53
 
54
  $this->getTables();
55
 
56
+ $helper = new Helper();
57
  $this->homeUrl = $helper->get_home_url();
58
 
59
  // Fix current step
60
+ if (0 == $this->options->currentStep) {
61
  $this->options->currentStep = 0;
62
  }
63
  }
65
  /**
66
  * Get database object to interact with
67
  */
68
+ private function getStagingDB()
69
+ {
70
+ return new \wpdb($this->options->databaseUser, $this->options->databasePassword, $this->options->databaseDatabase, $this->options->databaseServer);
71
  }
72
 
73
  /**
74
  * Get a list of tables to copy
75
  */
76
+ private function getTables()
77
+ {
78
+ $strings = new Strings();
79
  $this->tables = array();
80
+ foreach ($this->options->tables as $table) {
81
+ $this->tables[] = $this->options->prefix . $strings->str_replace_first($this->productionDb->prefix, null, $table);
82
  }
83
  }
84
 
86
  * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
87
  * @return void
88
  */
89
+ protected function calculateTotalSteps()
90
+ {
91
  $this->options->totalSteps = 20;
92
  }
93
 
95
  * Start Module
96
  * @return object
97
  */
98
+ public function start()
99
+ {
100
  // Execute steps
101
  $this->run();
102
 
103
  // Save option, progress
104
  $this->saveOptions();
105
 
106
+ return ( object )$this->response;
107
  }
108
 
109
  /**
111
  * Returns false when over threshold limits are hit or when the job is done, true otherwise
112
  * @return bool
113
  */
114
+ protected function execute()
115
+ {
116
  // Fatal error. Let this happen never and break here immediately
117
+ if ($this->isRoot()) {
118
  return false;
119
  }
120
 
121
  // Over limits threshold
122
+ if ($this->isOverThreshold()) {
123
  // Prepare response and save current progress
124
+ $this->prepareResponse(false, false);
125
  $this->saveOptions();
126
  return false;
127
  }
128
 
129
  // No more steps, finished
130
+ if ($this->isFinished()) {
131
+ $this->prepareResponse(true, false);
132
  return false;
133
  }
134
 
135
  // Execute step
136
  $stepMethodName = "step" . $this->options->currentStep;
137
+ if (!$this->{$stepMethodName}()) {
138
+ $this->prepareResponse(false, false);
139
  return false;
140
  }
141
 
150
  * Checks Whether There is Any Job to Execute or Not
151
  * @return bool
152
  */
153
+ protected function isFinished()
154
+ {
155
  return
156
  !$this->isRunning() ||
157
  $this->options->currentStep > $this->options->totalSteps ||
158
+ !method_exists($this, "step" . $this->options->currentStep);
 
159
  }
160
 
161
  /**
162
  * Check if current operation is done on the root folder or on the live DB
163
  * @return boolean
164
  */
165
+ protected function isRoot()
166
+ {
167
 
168
  // Prefix is the same as the one of live site
169
  // $wpdb = WPStaging::getInstance()->get( "wpdb" );
171
  // return true;
172
  // }
173
  // CloneName is empty
174
+ $name = ( array )$this->options->cloneDirectoryName;
175
+ if (empty($name)) {
176
  return true;
177
  }
178
 
179
  // Live Path === Staging path
180
+ if ($this->homeUrl . $this->options->cloneDirectoryName === $this->homeUrl) {
181
  return true;
182
  }
183
 
189
  * @param string $table
190
  * @return boolean
191
  */
192
+ protected function tableExists($table)
193
+ {
194
+ if ($this->db->get_var("SHOW TABLES LIKE '{$table}'") != $table) {
195
+ $this->log("Table {$table} does not exist", Logger::TYPE_ERROR);
196
  return false;
197
  }
198
  return true;
203
  * copy default wp-config.php if production site uses bedrock or any other boilerplate solution that stores wp default config data elsewhere.
204
  * @return boolean
205
  */
206
+ protected function step0()
207
+ {
208
+ $this->log("Preparing Data Step0: Copy wp-config.php file", Logger::TYPE_INFO);
209
 
210
+ $dir = trailingslashit(dirname(ABSPATH));
211
 
212
  $source = $dir . 'wp-config.php';
213
 
214
  $destination = $this->options->destinationDir . 'wp-config.php';
215
 
216
  // Check if there is already a valid wp-config.php in root of staging site
217
+ if ($this->isValidWpConfig($destination)) {
218
  return true;
219
  }
220
 
221
  // Check if there is a valid wp-config.php outside root of wp production site
222
+ if ($this->isValidWpConfig($source)) {
223
  // Copy it to staging site
224
+ if ($this->copy($source, $destination)) {
225
  return true;
226
  }
227
  }
228
 
229
  // No valid wp-config.php found so let's copy wp stagings default wp-config.php to staging site
230
  $source = WPSTG_PLUGIN_DIR . "Backend/helpers/wp-config.php";
231
+ if ($this->copy($source, $destination)) {
232
  // add missing db credentials to wp-config.php
233
+ if (!$this->alterWpConfig($destination)) {
234
+ $this->log("Preparing Data Step0: Can not alter db credentials in wp-config.php", Logger::TYPE_INFO);
235
  return false;
236
  }
237
  }
238
 
239
+ $this->log("Preparing Data Step0: Successful", Logger::TYPE_INFO);
240
  return true;
241
  }
242
 
246
  * @param type $destination
247
  * @return boolean
248
  */
249
+ protected function copy($source, $destination)
250
+ {
251
  // Copy symbolic link
252
+ if (is_link($source)) {
253
+ $this->log("Preparing Data: Symbolic link found...", Logger::TYPE_INFO);
254
+ if (!@copy(readlink($source), $destination)) {
255
  $errors = error_get_last();
256
+ $this->log("Preparing Data: Failed to copy {$source} Error: {$errors['message']} {$source} -> {$destination}", Logger::TYPE_ERROR);
257
  return false;
258
  }
259
  }
260
 
261
  // Copy file
262
+ if (!@copy($source, $destination)) {
263
  $errors = error_get_last();
264
+ $this->log("Preparing Data Step0: Failed to copy {$source}! Error: {$errors['message']} {$source} -> {$destination}", Logger::TYPE_ERROR);
265
  return false;
266
  }
267
 
273
  * @param type $source
274
  * @return boolean
275
  */
276
+ protected function alterWpConfig($source)
277
+ {
278
 
279
+ if (false === ($content = file_get_contents($source))) {
280
  return false;
281
  }
282
 
295
  /** The Database Collate type. Don't change this if in doubt. */\r\n
296
  define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
297
 
298
+ $content = str_replace($search, $replace, $content);
299
 
300
+ if (false === @wpstg_put_contents($source, $content)) {
301
+ $this->log("Preparing Data: Can't save wp-config.php", Logger::TYPE_ERROR);
302
  return false;
303
  }
304
 
310
  * @param type $source
311
  * @return boolean
312
  */
313
+ protected function isValidWpConfig($source)
314
+ {
315
 
316
+ if (!is_file($source) && !is_link($source)) {
317
  return false;
318
  }
319
 
 
320
 
321
+ if (false === ($content = file_get_contents($source))) {
322
  return false;
323
  }
324
 
325
  // Get DB_NAME from wp-config.php
326
+ preg_match("/define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*(.*)\s*\);/", $content, $matches);
327
 
328
+ if (empty($matches[1])) {
329
  return false;
330
  }
331
 
332
  // Get DB_USER from wp-config.php
333
+ preg_match("/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*(.*)\s*\);/", $content, $matches);
334
 
335
+ if (empty($matches[1])) {
336
  return false;
337
  }
338
 
339
  // Get DB_PASSWORD from wp-config.php
340
+ preg_match("/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*(.*)\s*\);/", $content, $matches);
341
 
342
+ if (empty($matches[1])) {
343
  return false;
344
  }
345
 
346
  // Get DB_HOST from wp-config.php
347
+ preg_match("/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*(.*)\s*\);/", $content, $matches);
348
 
349
+ if (empty($matches[1])) {
350
  return false;
351
  }
352
  return true;
356
  * Replace "siteurl" and "home"
357
  * @return bool
358
  */
359
+ protected function step1()
360
+ {
361
+ $this->log("Preparing Data Step1: Updating siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_INFO);
362
 
363
  // Skip - Table does not exist
364
+ if (false === $this->tableExists($this->prefix . 'options')) {
365
  return true;
366
  }
367
  // Skip - Table is not selected or updated
368
+ if (!in_array($this->prefix . 'options', $this->tables)) {
369
+ $this->log("Preparing Data Step1: Skipping");
370
  return true;
371
  }
372
 
373
+ $this->log("Preparing Data Step1: Updating siteurl and homeurl to " . $this->getStagingSiteUrl());
374
  // Replace URLs
375
  $result = $this->db->query(
376
+ $this->db->prepare(
377
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", $this->getStagingSiteUrl()
378
+ )
379
  );
380
 
381
 
 
382
  // All good
383
+ if ($result) {
384
  return true;
385
  }
386
 
387
+ $this->log("Preparing Data Step1: Skip updating siteurl and homeurl in {$this->prefix}options. Probably already did! {$this->db->last_error}", Logger::TYPE_WARNING);
388
  return true;
389
  }
390
 
392
  * Update "wpstg_is_staging_site"
393
  * @return bool
394
  */
395
+ protected function step2()
396
+ {
397
 
398
+ $this->log("Preparing Data Step2: Updating row wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}");
399
 
400
  // Skip - Table does not exist
401
+ if (false === $this->tableExists($this->prefix . 'options')) {
402
+ $this->log("Preparing Data Step2: Skipping");
403
  return true;
404
  }
405
  // Skip - Table is not selected or updated
406
+ if (!in_array($this->prefix . 'options', $this->tables)) {
407
+ $this->log("Preparing Data Step2: Skipping");
408
  return true;
409
  }
410
 
411
+ $this->db->query(
412
+ $this->db->prepare(
413
+ "DELETE FROM `{$this->prefix}options` WHERE `option_name` = %s;", 'wpstg_is_staging_site'
414
+ )
415
  );
416
 
417
 
418
  // No errors but no option name such as wpstg_is_staging_site
419
  $insert = $this->db->query(
420
+ $this->db->prepare(
421
+ "INSERT INTO {$this->prefix}options (option_name,option_value) VALUES ('wpstg_is_staging_site',%s)", "true"
422
+ )
423
  );
424
 
425
  // All good
426
+ if ($insert) {
427
+ $this->log("Preparing Data Step2: Successful", Logger::TYPE_INFO);
428
  return true;
429
  }
430
 
431
+ $this->log("Preparing Data Step2: Failed to update wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR);
432
  return false;
433
  }
434
 
436
  * Update rewrite_rules
437
  * @return bool
438
  */
439
+ protected function step3()
440
+ {
441
 
442
+ $this->log("Preparing Data Step3: Updating rewrite_rules in {$this->prefix}options {$this->db->last_error}");
443
 
444
  // Keep Permalinks
445
+ if (isset($this->settings->keepPermalinks) && $this->settings->keepPermalinks === "1") {
446
+ $this->log("Preparing Data Step3: Skipping");
447
  return true;
448
  }
449
 
450
  // Skip - Table does not exist
451
+ if (false === $this->tableExists($this->prefix . 'options')) {
452
+ $this->log("Preparing Data Step3: Skipping");
453
  return true;
454
  }
455
 
456
  // Skip - Table is not selected or updated
457
+ if (!in_array($this->prefix . 'options', $this->tables)) {
458
+ $this->log("Preparing Data Step3: Skipping");
459
  return true;
460
  }
461
 
462
  $result = $this->db->query(
463
+ $this->db->prepare(
464
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'rewrite_rules'", ' '
465
+ )
466
  );
467
 
468
  // All good
469
+ if ($result) {
470
  return true;
471
  }
472
 
478
  * Update Table Prefix in wp_usermeta and wp_options
479
  * @return bool
480
  */
481
+ protected function step4()
482
+ {
483
+ $this->log("Preparing Data Step4: Updating db prefix in {$this->prefix}usermeta. ");
484
 
485
  // Skip - Table does not exist
486
+ if (false === $this->tableExists($this->prefix . 'usermeta')) {
487
  return true;
488
  }
489
 
490
  // Skip - Table is not selected or updated
491
+ if (!in_array($this->prefix . 'usermeta', $this->tables)) {
492
+ $this->log("Preparing Data Step4: Skipping");
493
  return true;
494
  }
495
 
496
 
 
497
  $update = $this->db->query(
498
+ $this->db->prepare(
499
+ "UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, %s, %s) WHERE meta_key LIKE %s", $this->productionDb->prefix, $this->prefix, $this->productionDb->prefix . "_%"
500
+ )
501
  );
502
 
503
+ if (false === $update) {
504
+ $this->log("Preparing Data Step4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}", Logger::TYPE_ERROR);
505
+ $this->returnException("Preparing Data Step4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}");
506
  return false;
507
  }
508
 
509
 
 
510
  return true;
511
  }
512
 
514
  * Update Table prefix in wp-config.php
515
  * @return bool
516
  */
517
+ protected function step5()
518
+ {
519
  $path = $this->options->destinationDir . "wp-config.php";
520
 
521
+ $this->log("Preparing Data Step5: Updating table_prefix in {$path} to " . $this->prefix);
522
+ if (false === ($content = file_get_contents($path))) {
523
+ $this->log("Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR);
524
  return false;
525
  }
526
 
527
  // Replace table prefix
528
+ $pattern = '/\$table_prefix\s*=\s*(.*).*/';
529
  $replacement = '$table_prefix = \'' . $this->prefix . '\'; // Changed by WP Staging';
530
+ $content = preg_replace($pattern, $replacement, $content);
531
 
532
+ if (null === $content) {
533
+ $this->log("Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR);
534
  return false;
535
  }
536
 
537
  // Replace URLs
538
+ $content = str_replace($this->homeUrl, $this->getStagingSiteUrl(), $content);
539
 
540
+ if (false === @wpstg_put_contents($path, $content)) {
541
+ $this->log("Preparing Data Step5: Failed to update table_prefix in {$path} to " . $this->prefix . ". Can't save contents", Logger::TYPE_ERROR);
542
  return false;
543
  }
544
 
604
  * Update wpstg_rmpermalinks_executed
605
  * @return bool
606
  */
607
+ protected function step7()
608
+ {
609
 
610
+ $this->log("Preparing Data Step7: Updating wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}");
611
 
612
  // Skip - Table does not exist
613
+ if (false === $this->tableExists($this->prefix . 'options')) {
614
+ $this->log("Preparing Data Step7: Skipping Table {$this->prefix}'options' does not exist");
615
  return true;
616
  }
617
 
618
  // Skip - Table is not selected or updated
619
+ if (!in_array($this->prefix . 'options', $this->tables)) {
620
+ $this->log("Preparing Data Step7: Skipping");
621
  return true;
622
  }
623
 
624
  $result = $this->db->query(
625
+ $this->db->prepare(
626
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_rmpermalinks_executed'", ' '
627
+ )
628
  );
629
 
630
 
631
+ $this->Log("Preparing Data Step7: Finished successfully");
632
  return true;
633
  }
634
 
636
  * Update permalink_structure
637
  * @return bool
638
  */
639
+ protected function step8()
640
+ {
641
 
642
+ $this->log("Preparing Data Step8: Updating permalink_structure in {$this->prefix}options {$this->db->last_error}");
643
 
644
  // Keep Permalinks
645
+ if (isset($this->settings->keepPermalinks) && $this->settings->keepPermalinks === "1") {
646
+ $this->log("Preparing Data Step8: Skipping");
647
  return true;
648
  }
649
 
650
  // Skip - Table does not exist
651
+ if (false === $this->tableExists($this->prefix . 'options')) {
652
+ $this->log("Preparing Data Step8: Skipping");
653
  return true;
654
  }
655
 
656
  // Skip - Table is not selected or updated
657
+ if (!in_array($this->prefix . 'options', $this->tables)) {
658
+ $this->log("Preparing Data Step8: Skipping");
659
  return true;
660
  }
661
 
662
  $result = $this->db->query(
663
+ $this->db->prepare(
664
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'permalink_structure'", ' '
665
+ )
666
  );
667
 
668
  // All good
669
+ if ($result) {
670
+ $this->Log("Preparing Data Step8: Finished successfully");
671
  return true;
672
  }
673
 
674
+ $this->log("Failed to update permalink_structure in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR);
675
  return true;
676
  }
677
 
679
  * Update blog_public option to not allow staging site to be indexed by search engines
680
  * @return bool
681
  */
682
+ protected function step9()
683
+ {
684
 
685
+ $this->log("Preparing Data Step9: Set staging site to noindex");
686
 
687
+ if (false === $this->tableExists($this->prefix . 'options')) {
688
  return true;
689
  }
690
 
691
  // Skip - Table is not selected or updated
692
+ if (!in_array($this->prefix . 'options', $this->tables)) {
693
+ $this->log("Preparing Data Step9: Skipping");
694
  return true;
695
  }
696
 
697
  $result = $this->db->query(
698
+ $this->db->prepare(
699
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'blog_public'", '0'
700
+ )
701
  );
702
 
703
  // All good
704
+ if ($result) {
705
+ $this->Log("Preparing Data Step9: Finished successfully");
706
  return true;
707
  }
708
 
709
+ $this->log("Can not update staging site to noindex. Possible already done!", Logger::TYPE_WARNING);
710
  return true;
711
  }
712
 
714
  * Update WP_HOME in wp-config.php
715
  * @return bool
716
  */
717
+ protected function step10()
718
+ {
719
  $path = $this->options->destinationDir . "wp-config.php";
720
 
721
+ $this->log("Preparing Data Step10: Updating WP_HOME in wp-config.php to " . $this->getStagingSiteUrl());
722
 
723
+ if (false === ($content = file_get_contents($path))) {
724
+ $this->log("Preparing Data Step10: Failed to update WP_HOME in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR);
725
  return false;
726
  }
727
 
728
 
729
  // Get WP_HOME from wp-config.php
730
+ preg_match("/define\s*\(\s*['\"]WP_HOME['\"]\s*,\s*(.*)\s*\);/", $content, $matches);
731
 
732
+ if (!empty($matches[1])) {
733
  $matches[1];
734
 
735
  $pattern = "/define\s*\(\s*['\"]WP_HOME['\"]\s*,\s*(.*)\s*\);.*/";
737
  $replace = "define('WP_HOME','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
738
  //$replace.= " // Changed by WP-Staging";
739
 
740
+ if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
741
+ $this->log("Preparing Data: Failed to update WP_HOME", Logger::TYPE_ERROR);
742
  return false;
743
  }
744
  } else {
745
+ $this->log("Preparing Data Step10: WP_HOME not defined in wp-config.php. Skipping this step.");
746
  }
747
 
748
+ if (false === @wpstg_put_contents($path, $content)) {
749
+ $this->log("Preparing Data Step10: Failed to update WP_HOME. Can't save contents", Logger::TYPE_ERROR);
750
  return false;
751
  }
752
+ $this->Log("Preparing Data Step10: Finished successfully");
753
  return true;
754
  }
755
 
757
  * Update WP_SITEURL in wp-config.php
758
  * @return bool
759
  */
760
+ protected function step11()
761
+ {
762
  $path = $this->options->destinationDir . "wp-config.php";
763
 
764
+ $this->log("Preparing Data Step11: Updating WP_SITEURL in wp-config.php to " . $this->getStagingSiteUrl());
765
 
766
+ if (false === ($content = file_get_contents($path))) {
767
+ $this->log("Preparing Data Step11: Failed to update WP_SITEURL in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR);
768
  return false;
769
  }
770
 
771
 
772
  // Get WP_SITEURL from wp-config.php
773
+ preg_match("/define\s*\(\s*['\"]WP_SITEURL['\"]\s*,\s*(.*)\s*\);/", $content, $matches);
774
 
775
+ if (!empty($matches[1])) {
776
  $matches[1];
777
 
778
  $pattern = "/define\s*\(\s*['\"]WP_SITEURL['\"]\s*,\s*(.*)\s*\);.*/";
780
  $replace = "define('WP_SITEURL','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
781
  //$replace.= " // Changed by WP-Staging";
782
 
783
+ if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
784
+ $this->log("Preparing Data Step11: Failed to update WP_SITEURL", Logger::TYPE_ERROR);
785
  return false;
786
  }
787
  } else {
788
+ $this->log("Preparing Data Step11: WP_SITEURL not defined in wp-config.php. Skipping this step.");
789
  }
790
 
791
 
792
+ if (false === @wpstg_put_contents($path, $content)) {
793
+ $this->log("Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR);
794
  return false;
795
  }
796
+ $this->Log("Preparing Data Step11: Finished successfully");
797
  return true;
798
  }
799
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
800
 
801
  /**
802
  * Update Table Prefix in wp_options
803
  * @return bool
804
  */
805
+ protected function step12()
806
+ {
807
+ $this->log("Preparing Data Step12: Updating db prefix in {$this->prefix}options.");
808
 
809
  // Skip - Table does not exist
810
+ if (false === $this->tableExists($this->prefix . 'options')) {
811
  return true;
812
  }
813
 
814
  // Skip, prefixes are identical. No change needed
815
+ if ($this->productionDb->prefix === $this->prefix) {
816
+ $this->log("Preparing Data Step12: Skipped");
817
  return true;
818
  }
819
 
820
  // Skip - Table is not selected or updated
821
+ if (!in_array($this->prefix . 'options', $this->tables)) {
822
+ $this->log("Preparing Data Step12: Skipping");
823
  return true;
824
  }
825
 
826
+ $notice = !empty($this->db->last_error) ? 'Last error: ' . $this->db->last_error : '';
827
 
828
  //$this->log( "Updating option_name in {$this->prefix}options. {$notice}" );
829
  // Filter the rows below. Do not update them!
833
  'wp_mail_smtp_debug',
834
  );
835
 
836
+ $filters = apply_filters('wpstg_data_excl_rows', $filters);
837
 
838
  $where = "";
839
+ foreach ($filters as $filter) {
840
  $where .= " AND option_name <> '" . $filter . "'";
841
  }
842
 
843
  $updateOptions = $this->db->query(
844
+ $this->db->prepare(
845
+ "UPDATE IGNORE {$this->prefix}options SET option_name= replace(option_name, %s, %s) WHERE option_name LIKE %s" . $where, $this->productionDb->prefix, $this->prefix, $this->productionDb->prefix . "_%"
846
+ )
847
  );
848
 
849
+ if (false === $updateOptions) {
850
+ $this->log("Preparing Data Step12: Failed to update option_name in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_ERROR);
851
+ $this->returnException(" Preparing Data Step12: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}");
852
  return false;
853
  }
854
+ $this->Log("Preparing Data Step12: Finished successfully");
855
  return true;
856
  }
857
 
859
  * Change upload_path in wp_options (if it is defined)
860
  * @return bool
861
  */
862
+ protected function step13()
863
+ {
864
+ $this->log("Preparing Data Step13: Updating upload_path {$this->prefix}options.");
865
 
866
  // Skip - Table does not exist
867
+ if (false === $this->tableExists($this->prefix . 'options')) {
868
  return true;
869
  }
870
 
871
  $newUploadPath = $this->getNewUploadPath();
872
 
873
+ if (false === $newUploadPath) {
874
+ $this->log("Preparing Data Step13: Skipped");
875
  return true;
876
  }
877
 
878
  // Skip - Table is not selected or updated
879
+ if (!in_array($this->prefix . 'options', $this->tables)) {
880
+ $this->log("Preparing Data Step13: Skipped");
881
  return true;
882
  }
883
 
884
+ $error = isset($this->db->last_error) ? 'Last error: ' . $this->db->last_error : '';
885
 
886
+ $this->log("Updating upload_path in {$this->prefix}options. {$error}");
887
 
888
  $updateOptions = $this->db->query(
889
+ $this->db->prepare(
890
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'upload_path'", $newUploadPath
891
+ )
892
  );
893
 
894
+ if (false === $updateOptions) {
895
+ $this->log("Preparing Data Step13: Failed to update upload_path in {$this->prefix}options. {$error}", Logger::TYPE_ERROR);
896
  return true;
897
  }
898
+ $this->Log("Preparing Data Step 13: Finished successfully");
899
  return true;
900
  }
901
 
903
  * Update WP_CACHE in wp-config.php
904
  * @return bool
905
  */
906
+ protected function step14()
907
+ {
908
  $path = $this->options->destinationDir . "wp-config.php";
909
 
910
+ $this->log("Preparing Data Step14: Set WP_CACHE in wp-config.php to false");
911
 
912
+ if (false === ($content = file_get_contents($path))) {
913
+ $this->log("Preparing Data Step14: Failed to update WP_CACHE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR);
914
  return false;
915
  }
916
 
917
 
918
  // Get WP_CACHE from wp-config.php
919
+ preg_match("/define\s*\(\s*['\"]WP_CACHE['\"]\s*,\s*(.*)\s*\);/", $content, $matches);
920
 
921
+ if (!empty($matches[1])) {
922
 
923
  $pattern = "/define\s*\(\s*['\"]WP_CACHE['\"]\s*,\s*(.*)\s*\);.*/";
924
 
925
  $replace = "define('WP_CACHE',false); // " . $matches[1];
926
  //$replace.= " // Changed by WP-Staging";
927
 
928
+ if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
929
+ $this->log("Preparing Data: Failed to change WP_CACHE", Logger::TYPE_ERROR);
930
  return false;
931
  }
932
  } else {
933
+ $this->log("Preparing Data Step14: WP_CACHE not defined in wp-config.php. Skipping this step.");
934
  }
935
 
936
+ if (false === @wpstg_put_contents($path, $content)) {
937
+ $this->log("Preparing Data Step14: Failed to update WP_CACHE. Can't save contents", Logger::TYPE_ERROR);
938
  return false;
939
  }
940
+ $this->Log("Preparing Data Step14: Finished successfully");
941
  return true;
942
  }
943
 
945
  * Update database credentials in wp-config.php
946
  * @return bool
947
  */
948
+ protected function step15()
949
+ {
950
  $path = $this->options->destinationDir . "wp-config.php";
951
 
952
+ $this->log("Preparing Data Step15: Change database credentials in wp-config.php");
953
 
954
+ if (false === ($content = file_get_contents($path))) {
955
+ $this->log("Preparing Data Step15: Failed to update database credentials in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR);
956
  return false;
957
  }
958
 
959
 
960
  // Get DB_NAME from wp-config.php
961
+ preg_match("/define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*(.*)\s*\);/", $content, $matches);
962
 
963
+ if (!empty($matches[1])) {
964
  $matches[1];
965
 
966
  $pattern = "/define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*(.*)\s*\);.*/";
968
  $replace = "define('DB_NAME','{$this->options->databaseDatabase}'); // " . $matches[1];
969
  //$replace.= " // Changed by WP-Staging";
970
 
971
+ if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
972
+ $this->log("Preparing Data: Failed to change DB_NAME", Logger::TYPE_ERROR);
973
  return false;
974
  }
975
  } else {
976
+ $this->log("Preparing Data Step15: DB_NAME not defined in wp-config.php. Skipped this step.");
977
  }
978
  // Get DB_USER from wp-config.php
979
+ preg_match("/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*(.*)\s*\);/", $content, $matches);
980
 
981
+ if (!empty($matches[1])) {
982
  $matches[1];
983
 
984
  $pattern = "/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*(.*)\s*\);.*/";
986
  $replace = "define('DB_USER','{$this->options->databaseUser}'); // " . $matches[1];
987
  //$replace.= " // Changed by WP-Staging";
988
 
989
+ if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
990
+ $this->log("Preparing Data: Failed to change DB_USER", Logger::TYPE_ERROR);
991
  return false;
992
  }
993
  } else {
994
+ $this->log("Preparing Data Step15: DB_USER not defined in wp-config.php. Skipped this step.");
995
  }
996
  // Get DB_PASSWORD from wp-config.php
997
+ preg_match("/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*(.*)\s*\);/", $content, $matches);
998
 
999
+ if (!empty($matches[1])) {
1000
  $matches[1];
1001
 
1002
  $pattern = "/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*(.*)\s*\);.*/";
1003
 
1004
+ //We need to escape ',$ and \ for preg_replace. As the string will later be interpreted as a PHP string, we
1005
+ //need to escape \ twice.
1006
+ $escapedPassword = addcslashes(addcslashes($this->options->databasePassword, "\\"), "'$\\");
1007
+
1008
+ $replace = "define('DB_PASSWORD','{$escapedPassword}'); // Changed by WP Staging";
1009
  //$replace.= " // Changed by WP-Staging";
1010
 
1011
+ if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
1012
+ $this->log("Preparing Data: Failed to change DB_PASSWORD", Logger::TYPE_ERROR);
1013
  return false;
1014
  }
1015
  } else {
1016
+ $this->log("Preparing Data Step15: DB_PASSWORD not defined in wp-config.php. Skipped this step.");
1017
  }
1018
  // Get DB_HOST from wp-config.php
1019
+ preg_match("/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*(.*)\s*\);/", $content, $matches);
1020
 
1021
+ if (!empty($matches[1])) {
1022
  $matches[1];
1023
 
1024
  $pattern = "/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*(.*)\s*\);.*/";
1026
  $replace = "define('DB_HOST','{$this->options->databaseServer}'); // " . $matches[1];
1027
  //$replace.= " // Changed by WP-Staging";
1028
 
1029
+ if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
1030
+ $this->log("Preparing Data: Failed to change DB_HOST", Logger::TYPE_ERROR);
1031
  return false;
1032
  }
1033
  } else {
1034
+ $this->log("Preparing Data Step15: DB_HOST not defined in wp-config.php. Skipped this step.");
1035
  }
1036
 
1037
 
1038
+ if (false === @wpstg_put_contents($path, $content)) {
1039
+ $this->log("Preparing Data Step15: Failed to update database credentials in wp-config.php. Can't save contents", Logger::TYPE_ERROR);
1040
  return false;
1041
  }
1042
+ $this->Log("Preparing Data: Finished Step 15 successfully");
1043
  return true;
1044
  }
1045
 
1047
  * Remove UPLOADS constant in wp-config.php to reset default image folder
1048
  * @return bool
1049
  */
1050
+ protected function step16()
1051
+ {
1052
  $path = $this->options->destinationDir . "wp-config.php";
1053
 
1054
+ $this->log("Preparing Data Step16: Remove UPLOADS in wp-config.php");
1055
 
1056
+ if (false === ($content = file_get_contents($path))) {
1057
+ $this->log("Preparing Data Step16: Failed to get UPLOADS in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR);
1058
  return false;
1059
  }
1060
 
1061
 
1062
  // Get UPLOADS from wp-config.php
1063
+ preg_match("/define\s*\(\s*['\"]UPLOADS['\"]\s*,\s*(.*)\s*\);/", $content, $matches);
1064
 
1065
+ if (!empty($matches[0])) {
1066
 
1067
  $pattern = "/define\s*\(\s*'UPLOADS'\s*,\s*(.*)\s*\);/";
1068
 
1069
  $replace = "";
1070
 
1071
+ if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
1072
+ $this->log("Preparing Data: Failed to change UPLOADS", Logger::TYPE_ERROR);
1073
  return false;
1074
  }
1075
  } else {
1076
+ $this->log("Preparing Data Step16: UPLOADS not defined in wp-config.php. Skipping this step.");
1077
  }
1078
 
1079
+ if (false === @wpstg_put_contents($path, $content)) {
1080
+ $this->log("Preparing Data Step16: Failed to update UPLOADS. Can't save contents", Logger::TYPE_ERROR);
1081
  return false;
1082
  }
1083
+ $this->Log("Preparing Data Step16: Finished successfully");
1084
  return true;
1085
  }
1086
 
1089
  * This is important when a custom uploads folder is used
1090
  * @return bool
1091
  */
1092
+ protected function step17()
1093
+ {
1094
+ $path = $this->options->destinationDir . "wp-config.php";
1095
+ $this->log("Preparing Data Step17: Update UPLOADS constant in wp-config.php");
1096
+ if (false === ($content = file_get_contents($path))) {
1097
+ $this->log("Preparing Data Step17: Failed to get UPLOADS in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR);
1098
  return false;
1099
  }
1100
  // Get UPLOADS from wp-config.php if there is already one
1101
+ preg_match("/define\s*\(\s*['\"]UPLOADS['\"]\s*,\s*(.*)\s*\);/", $content, $matches);
1102
  // TODO RPoC; DRY
1103
  $uploadFolder = wpstg_get_rel_upload_dir();
1104
  $uploadFolder = ltrim($uploadFolder, '/');
1105
  $uploadFolder = rtrim($uploadFolder, '/');
1106
+ if (!empty($matches[0])) {
1107
  $pattern = "/define\s*\(\s*'UPLOADS'\s*,\s*(.*)\s*\);/";
1108
 
1109
  $replace = "define('UPLOADS', '" . $uploadFolder . "');";
1110
+ if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
1111
+ $this->log("Preparing Data Step17: Failed to change UPLOADS", Logger::TYPE_ERROR);
1112
  return false;
1113
  }
1114
  } else {
1115
+ $this->log("Preparing Data Step17: UPLOADS not defined in wp-config.php. Creating new entry.");
1116
  // Find line with ABSPATH and add UPLOADS constant above
1117
+ preg_match("/if\s*\(\s*\s*!\s*defined\s*\(\s*['\"]ABSPATH['\"]\s*(.*)\s*\)\s*\)/", $content, $matches);
1118
+ if (!empty($matches[0])) {
1119
  $matches[0];
1120
  $pattern = "/if\s*\(\s*\s*!\s*defined\s*\(\s*['\"]ABSPATH['\"]\s*(.*)\s*\)\s*\)/";
1121
  $replace = "define('UPLOADS', '" . $uploadFolder . "'); \n" .
1122
+ "if ( ! defined( 'ABSPATH' ) )";
1123
+ if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
1124
+ $this->log("Preparing Data Step 17: Failed to change UPLOADS", Logger::TYPE_ERROR);
1125
  return false;
1126
  }
1127
  } else {
1128
+ $this->log("Preparing Data Step 17: Can not add UPLOAD constant to wp-config.php. Can not find free position to add it.", Logger::TYPE_ERROR);
1129
  }
1130
  }
1131
+ if (false === @wpstg_put_contents($path, $content)) {
1132
+ $this->log("Preparing Data Step17: Failed to update UPLOADS. Can't save contents", Logger::TYPE_ERROR);
1133
  return false;
1134
  }
1135
+ $this->Log("Preparing Data Step17: Finished successfully");
1136
  return true;
1137
  }
1138
 
1140
  * Save hostname of parent production site in option_name wpstg_connection
1141
  * @return boolean
1142
  */
1143
+ protected function step18()
1144
+ {
1145
 
1146
  $table = $this->prefix . 'options';
1147
 
1148
  $siteurl = get_site_url();
1149
 
1150
+ $connection = json_encode(array('prodHostname' => $siteurl));
1151
 
1152
  $data = array(
1153
+ 'option_name' => 'wpstg_connection',
1154
  'option_value' => $connection
1155
  );
1156
 
1157
  $format = array('%s', '%s');
1158
 
1159
+ $result = $this->db->replace($table, $data, $format);
1160
 
1161
+ if (false === $result) {
1162
+ $this->Log("Preparing Data Step18: Could not save {$siteurl} in {$table}", Logger::TYPE_ERROR);
1163
  }
1164
  return true;
1165
  }
1169
  * This option is used to determine if the staging website has not been loaded initiall for executing certain custom actions from \WPStaging\initActions()
1170
  * @return boolean
1171
  */
1172
+ protected function step19()
1173
+ {
1174
 
1175
  $table = $this->prefix . 'options';
1176
 
1177
  // Skip - Table does not exist
1178
+ if (false === $this->tableExists($table)) {
1179
  return true;
1180
  }
1181
 
1182
  $result = $this->db->query(
1183
+ $this->db->prepare(
1184
+ "INSERT INTO {$this->prefix}options (option_name,option_value) VALUES ('wpstg_execute',%s) ON DUPLICATE KEY UPDATE option_value = %s", "true", "true"
1185
+ )
1186
  );
1187
 
1188
+ if (false === $result) {
1189
+ $this->Log("Preparing Data Step19: Could not save wpstg_execute in {$table}", Logger::TYPE_ERROR);
1190
  }
1191
  return true;
1192
  }
1193
 
1194
  /**
1195
+ * Delete all listed staging sites from cloned site in initial cloning. Useful if a cloned site is cloned again. E.g. for dev > staging > production setup
1196
  * @return bool
1197
  */
1198
+ protected function step20(){
 
1199
 
1200
+ if ($this->options->mainJob === 'updating'){
 
1201
  return true;
1202
  }
1203
 
1204
+ if( false === $this->tableExists( $this->prefix . 'options' ) ) {
 
 
1205
  return true;
1206
  }
1207
 
1208
+ $step = "Preparing Data Step20:";
1209
+ $this->log( $step . " Reset wp staging site data" );
 
1210
 
1211
+ $result = $this->db->query(
1212
+ $this->db->prepare( "UPDATE {$this->prefix}options SET `option_value` = %s WHERE `option_name` = %s", serialize(array()), 'wpstg_existing_clones_beta'
1213
+ )
 
 
 
 
 
 
 
 
1214
  );
1215
 
1216
+ if (false === $result){
1217
+ $this->log( $step . "Failed to reset wp staging site data." );
 
 
 
 
 
 
 
 
 
 
 
1218
  }
1219
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1220
  return true;
1221
  }
1222
 
1224
  * Get upload path
1225
  * @return boolean|string
1226
  */
1227
+ protected function getNewUploadPath()
1228
+ {
1229
+ $uploadPath = get_option('upload_path');
1230
 
1231
+ if (!$uploadPath) {
1232
  return false;
1233
  }
1234
 
1235
+ $customSlug = str_replace(\WPStaging\WPStaging::getWPpath(), '', $uploadPath);
1236
 
1237
  $newUploadPath = $this->options->destinationDir . $customSlug;
1238
 
1243
  * Return URL to staging site
1244
  * @return string
1245
  */
1246
+ protected function getStagingSiteUrl()
1247
+ {
1248
+ if (!empty($this->options->cloneHostname)) {
1249
  return $this->options->cloneHostname;
1250
  }
1251
+ if ($this->isSubDir()) {
1252
+ return trailingslashit($this->homeUrl) . trailingslashit($this->getSubDir()) . $this->options->cloneDirectoryName;
1253
  }
1254
 
1255
+ return trailingslashit($this->homeUrl) . $this->options->cloneDirectoryName;
1256
  }
1257
 
1258
  /**
1259
  * Check if WP is installed in subdir
1260
  * @return boolean
1261
  */
1262
+ protected function isSubDir()
1263
+ {
1264
  // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
1265
  // This is happening much more often than you would expect
1266
+ $siteurl = preg_replace('#^https?://#', '', rtrim(get_option('siteurl'), '/'));
1267
+ $home = preg_replace('#^https?://#', '', rtrim(get_option('home'), '/'));
1268
 
1269
+ if ($home !== $siteurl) {
1270
  return true;
1271
  }
1272
  return false;
1276
  * Get the install sub directory if WP is installed in sub directory
1277
  * @return string
1278
  */
1279
+ protected function getSubDir()
1280
+ {
1281
+ $home = get_option('home');
1282
+ $siteurl = get_option('siteurl');
1283
 
1284
+ if (empty($home) || empty($siteurl)) {
1285
  return '';
1286
  }
1287
 
1288
+ $dir = str_replace($home, '', $siteurl);
1289
+ return str_replace('/', '', $dir);
1290
  }
1291
 
1292
  }
Backend/Modules/Jobs/Database.php CHANGED
@@ -92,14 +92,6 @@ class Database extends JobExecutable {
92
  return false;
93
  }
94
 
95
- // Table is excluded
96
- // if (in_array($this->options->tables[$this->options->currentStep]->name, $this->options->excludedTables))
97
- // {
98
- // $this->prepareResponse();
99
- // return true;
100
- // }
101
- // Copy table
102
- //if (!$this->copyTable($this->options->tables[$this->options->currentStep]->name))
103
  if( !$this->copyTable( $this->options->tables[$this->options->currentStep] ) ) {
104
  // Prepare Response
105
  $this->prepareResponse( false, false );
92
  return false;
93
  }
94
 
 
 
 
 
 
 
 
 
95
  if( !$this->copyTable( $this->options->tables[$this->options->currentStep] ) ) {
96
  // Prepare Response
97
  $this->prepareResponse( false, false );
Backend/Modules/Jobs/DatabaseExternal.php CHANGED
@@ -3,22 +3,19 @@
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\Backend\Modules\Jobs\JobExecutable;
13
-
14
- //use WPStaging\Utils\MySQL;
15
- //use WPStaging\Utils\MySQLi;
16
 
17
  /**
18
  * Class Database
19
  * @package WPStaging\Backend\Modules\Jobs
20
  */
21
- class DatabaseExternal extends JobExecutable {
 
22
 
23
  /**
24
  * @var int
@@ -36,27 +33,15 @@ class DatabaseExternal extends JobExecutable {
36
  */
37
  private $stagingDb;
38
 
39
- /**
40
- * The database client
41
- * @var obj
42
- */
43
- //private $mysql;
44
-
45
  /**
46
  * Initialize
47
  */
48
- public function initialize() {
49
- $this->db = WPStaging::getInstance()->get( "wpdb" );
 
50
  $this->stagingDb = $this->getExternalDBConnection();
51
- $this->total = count( $this->options->tables );
52
  $this->isFatalError();
53
-
54
- // Get database client
55
- // if( empty( $this->db->use_mysqli ) ) {
56
- // $this->mysql = new MySQL( $this->db );
57
- // } else {
58
- // $this->mysql = new MySQLi( $this->db );
59
- // }
60
  }
61
 
62
  /**
@@ -65,23 +50,22 @@ class DatabaseExternal extends JobExecutable {
65
  */
66
  private function getExternalDBConnection() {
67
 
68
- $db = new \wpdb( $this->options->databaseUser, str_replace( "\\\\", "\\", $this->options->databasePassword ), $this->options->databaseDatabase, $this->options->databaseServer );
69
 
70
  // Can not connect to mysql
71
- if( !empty( $db->error->errors['db_connect_fail']['0'] ) ) {
72
- $this->returnException( "Can not connect to external database {$this->options->databaseDatabase}" );
73
  return false;
74
  }
75
 
76
  // Can not connect to database
77
- $db->select( $this->options->databaseDatabase );
78
- if( !$db->ready ) {
79
- $error = isset( $db->error->errors['db_select_fail'] ) ? $db->error->errors['db_select_fail'] : "Error: Can't select {database} Either it does not exist or you don't have privileges to access it.";
80
- $this->returnException( $error );
81
  exit;
82
  }
83
 
84
-
85
  return $db;
86
  }
87
 
@@ -90,9 +74,10 @@ class DatabaseExternal extends JobExecutable {
90
  * and mainJob is not updating the clone
91
  * @return boolean
92
  */
93
- private function isFatalError() {
 
94
  $path = trailingslashit($this->options->cloneDir);
95
- if( isset( $this->options->mainJob ) && $this->options->mainJob !== 'updating' && (is_dir( $path ) && !wpstg_is_empty_dir( $path ) ) ) {
96
  $this->returnException(" Can not continue for security purposes. Directory {$path} is not empty! Use FTP or a file manager plugin and make sure it does not contain any files. ");
97
  }
98
  return false;
@@ -102,7 +87,8 @@ class DatabaseExternal extends JobExecutable {
102
  * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
103
  * @return void
104
  */
105
- protected function calculateTotalSteps() {
 
106
  $this->options->totalSteps = ($this->total === 0) ? 1 : $this->total;
107
  }
108
 
@@ -111,36 +97,33 @@ class DatabaseExternal extends JobExecutable {
111
  * Returns false when over threshold limits are hit or when the job is done, true otherwise
112
  * @return bool
113
  */
114
- protected function execute() {
115
-
116
-
117
  // Over limits threshold
118
- if( $this->isOverThreshold() ) {
119
  // Prepare response and save current progress
120
- $this->prepareResponse( false, false );
121
  $this->saveOptions();
122
  return false;
123
  }
124
 
125
  // No more steps, finished
126
  if (!$this->isRunning() || $this->options->currentStep > $this->total) {
127
- $this->prepareResponse( true, false );
128
  return false;
129
  }
130
 
131
  // Copy table
132
- if( isset( $this->options->tables[$this->options->currentStep] ) && !$this->copyTable( $this->options->tables[$this->options->currentStep] ) ) {
133
- // Prepare Response
134
- $this->prepareResponse( false, false );
135
 
136
- // Not finished
137
  return true;
138
  }
139
 
140
- // Prepare Response
141
  $this->prepareResponse();
142
 
143
- // Not finished
144
  return true;
145
  }
146
 
@@ -148,10 +131,9 @@ class DatabaseExternal extends JobExecutable {
148
  * Get new prefix for the staging site
149
  * @return string
150
  */
151
- private function getStagingPrefix() {
152
-
153
- $this->options->prefix = !empty( $this->options->databasePrefix ) ? $this->options->databasePrefix : $this->db->prefix;
154
-
155
  return $this->options->prefix;
156
  }
157
 
@@ -160,25 +142,26 @@ class DatabaseExternal extends JobExecutable {
160
  * @param string $tableName
161
  * @return bool
162
  */
163
- private function copyTable( $tableName ) {
 
164
 
165
- $strings = new Strings();
166
- $tableName = is_object( $tableName ) ? $tableName->name : $tableName;
167
- $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->prefix, null, $tableName );
168
 
169
  // Drop table if necessary
170
- $this->dropTable( $newTableName );
171
 
172
  // Save current job
173
- $this->setJob( $newTableName );
174
 
175
  // Beginning of the job
176
- if( !$this->startJob( $newTableName, $tableName ) ) {
177
  return true;
178
  }
179
 
180
  // Copy data
181
- $this->copyData( $newTableName, $tableName );
182
 
183
  // Finish the step
184
  return $this->finishStep();
@@ -188,13 +171,14 @@ class DatabaseExternal extends JobExecutable {
188
  * Set the job
189
  * @param string $table
190
  */
191
- private function setJob( $table ) {
192
- if( isset( $this->options->job->current ) ) {
 
193
  return;
194
  }
195
 
196
  $this->options->job->current = $table;
197
- $this->options->job->start = 0;
198
  }
199
 
200
  /**
@@ -203,43 +187,43 @@ class DatabaseExternal extends JobExecutable {
203
  * @param string $old
204
  * @return bool
205
  */
206
- private function startJob( $new, $old ) {
207
-
208
- if( $this->isExcludedTable( $new ) ) {
209
  return false;
210
  }
211
 
212
- if( 0 != $this->options->job->start ) {
213
  return true;
214
  }
215
 
216
  // Table does not exist
217
- //$table = $old;
218
- $result = $this->db->query( "SHOW TABLES LIKE '{$old}'" );
219
- if( !$result || 0 === $result ) {
220
  return true;
221
  }
222
 
223
- $this->log( "DB External Copy: CREATE table {$this->options->databaseDatabase}.{$new}" );
224
- $sql = $this->getCreateStatement( $old );
225
 
226
  // Replace table prefix to the new one
227
- $sql = str_replace( "CREATE TABLE `{$old}`", "CREATE TABLE `{$new}`", $sql );
 
228
 
229
  // Make constraint unique to prevent error:(errno: 121 "Duplicate key on write or update")
230
  $sql = wpstg_unique_constraint($sql);
231
 
232
  $this->stagingDb->query('SET FOREIGN_KEY_CHECKS=0;');
233
 
234
- if( false === $this->stagingDb->query( $sql ) ) {
235
- $this->returnException( "DB External Copy - Fatal Error: {$this->stagingDb->last_error} Query: {$sql}" );
236
  }
237
 
238
  // Count amount of tables to insert with next step
239
  $this->options->job->total = 0;
240
- $this->options->job->total = ( int ) $this->db->get_var( "SELECT COUNT(1) FROM `{$this->db->dbname}`.`{$old}`" );
241
 
242
- if( 0 == $this->options->job->total ) {
243
  $this->finishStep();
244
  return false;
245
  }
@@ -247,42 +231,18 @@ class DatabaseExternal extends JobExecutable {
247
  return true;
248
  }
249
 
250
- /**
251
- * Change create statements according to MySQL version
252
- * @since 1.0.0
253
- */
254
- // private function adaptCreateStatement( $create ) {
255
- //
256
- // $fromDbVersion = $this->stagingDb->get_var("SELECT VERSION()");
257
- //
258
- // $toDbVersion = $this->db->get_var("SELECT VERSION()");
259
- //
260
- // // If same version, all is good
261
- // if( version_compare( $fromDbVersion, $toDbVersion ) == 0 ) {
262
- // return $create;
263
- // }
264
- //
265
- // // Change from unicode 5.2 (520) to "normal" utf8mb4 unicode on MySQL versions before 5.6
266
- // if( version_compare( $toDbVersion, '5.6', '<' ) ) {
267
- // $create = str_replace( 'utf8mb4_unicode_520_ci', 'utf8mb4_unicode_ci', $create );
268
- // $create = str_replace( 'utf8_unicode_520_ci', 'utf8_unicode_ci', $create );
269
- // }
270
- //
271
- // return $create;
272
- // }
273
-
274
  /**
275
  * Get MySQL query create table
276
  *
277
- * @param string $table_name Table name
278
  * @return array
279
  */
280
- private function getCreateStatement( $tableName ) {
281
-
282
- $row = $this->db->get_results( "SHOW CREATE TABLE `{$tableName}`", ARRAY_A );
283
 
284
  // Get create table
285
- if( isset( $row[0]['Create Table'] ) ) {
286
  return $row[0]['Create Table'];
287
  }
288
  return array();
@@ -293,37 +253,37 @@ class DatabaseExternal extends JobExecutable {
293
  * @param string $new
294
  * @param string $old
295
  */
296
- private function copyData( $new, $old ) {
 
297
 
298
  $rows = $this->options->job->start + $this->settings->queryLimit;
299
  $this->log(
300
- "DB External Copy: INSERT {$this->db->dbname}.{$old} as {$this->options->databaseDatabase}.{$new} from {$this->options->job->start} to {$rows} records"
301
  );
302
 
303
  $limitation = '';
304
 
305
- if( 0 < ( int ) $this->settings->queryLimit ) {
306
  $limitation = " LIMIT {$this->settings->queryLimit} OFFSET {$this->options->job->start}";
307
  }
308
 
309
  // Get data from production site
310
- $rows = $this->db->get_results( "SELECT * FROM `{$old}` {$limitation}", ARRAY_A );
311
 
312
  // Start transaction
313
- $this->stagingDb->query( 'SET autocommit=0;' );
314
- $this->stagingDb->query( 'SET FOREIGN_KEY_CHECKS=0;');
315
- $this->stagingDb->query( 'START TRANSACTION;' );
316
 
317
  // Copy into staging site
318
- foreach ( $rows as $row ) {
319
- $escaped_values = $this->mysqlEscapeMimic( array_values( $row ) );
320
- $values = implode( "', '", $escaped_values );
321
- $this->stagingDb->query( "INSERT INTO `{$new}` VALUES ('{$values}')" );
322
  }
323
  // Commit transaction
324
- $this->stagingDb->query( 'COMMIT;' );
325
- $this->stagingDb->query( 'SET autocommit=1;' );
326
-
327
 
328
  // Set new offset
329
  $this->options->job->start += $this->settings->queryLimit;
@@ -333,15 +293,16 @@ class DatabaseExternal extends JobExecutable {
333
  * Mimics the mysql_real_escape_string function. Adapted from a post by 'feedr' on php.net.
334
  * @link http://php.net/manual/en/function.mysql-real-escape-string.php#101248
335
  * @access public
336
- * @param string $input The string to escape.
337
  * @return string
338
  */
339
- private function mysqlEscapeMimic( $input ) {
340
- if( is_array( $input ) ) {
341
- return array_map( __METHOD__, $input );
 
342
  }
343
- if( !empty( $input ) && is_string( $input ) ) {
344
- return str_replace( array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $input );
345
  }
346
 
347
  return $input;
@@ -352,19 +313,20 @@ class DatabaseExternal extends JobExecutable {
352
  * @param string $table
353
  * @return boolean
354
  */
355
- private function isExcludedTable( $table ) {
 
356
 
357
- $customTables = apply_filters( 'wpstg_clone_database_tables_exclude', array() );
358
  $defaultTables = array('blogs', 'blog_versions');
359
 
360
- $tables = array_merge( $customTables, $defaultTables );
361
 
362
  $excludedTables = array();
363
- foreach ( $tables as $key => $value ) {
364
  $excludedTables[] = $this->options->prefix . $value;
365
  }
366
 
367
- if( in_array( $table, $excludedTables ) ) {
368
  return true;
369
  }
370
  return false;
@@ -373,14 +335,15 @@ class DatabaseExternal extends JobExecutable {
373
  /**
374
  * Finish the step
375
  */
376
- private function finishStep() {
 
377
  // This job is not finished yet
378
- if( $this->options->job->total > $this->options->job->start ) {
379
  return false;
380
  }
381
 
382
  // Add it to cloned tables listing
383
- $this->options->clonedTables[] = isset( $this->options->tables[$this->options->currentStep] ) ? $this->options->tables[$this->options->currentStep] : false;
384
 
385
  // Reset job
386
  $this->options->job = new \stdClass();
@@ -392,17 +355,18 @@ class DatabaseExternal extends JobExecutable {
392
  * Drop table if necessary
393
  * @param string $new
394
  */
395
- private function dropTable( $new ) {
396
- $old = $this->stagingDb->get_var( $this->stagingDb->prepare( "SHOW TABLES LIKE %s", $new ) );
 
397
 
398
- if( !$this->shouldDropTable( $new, $old ) ) {
399
  return;
400
  }
401
 
402
- $this->log( "DB External Copy: {$new} already exists, dropping it first" );
403
- $this->stagingDb->query( "SET FOREIGN_KEY_CHECKS=0" );
404
- $this->stagingDb->query( "DROP TABLE {$new}" );
405
- $this->stagingDb->query( "SET FOREIGN_KEY_CHECKS=1" );
406
  }
407
 
408
  /**
@@ -411,16 +375,14 @@ class DatabaseExternal extends JobExecutable {
411
  * @param string $old
412
  * @return bool
413
  */
414
- private function shouldDropTable( $new, $old ) {
415
-
416
-
417
-
418
- if( $old === $new &&
419
- (
420
- !isset( $this->options->job->current ) ||
421
- !isset( $this->options->job->start ) ||
422
  0 == $this->options->job->start
423
- ) ) {
424
  return true;
425
  }
426
  return false;
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
 
13
  /**
14
  * Class Database
15
  * @package WPStaging\Backend\Modules\Jobs
16
  */
17
+ class DatabaseExternal extends JobExecutable
18
+ {
19
 
20
  /**
21
  * @var int
33
  */
34
  private $stagingDb;
35
 
 
 
 
 
 
 
36
  /**
37
  * Initialize
38
  */
39
+ public function initialize()
40
+ {
41
+ $this->db = WPStaging::getInstance()->get("wpdb");
42
  $this->stagingDb = $this->getExternalDBConnection();
43
+ $this->total = count($this->options->tables);
44
  $this->isFatalError();
 
 
 
 
 
 
 
45
  }
46
 
47
  /**
50
  */
51
  private function getExternalDBConnection() {
52
 
53
+ $db = new \wpdb( $this->options->databaseUser, $this->options->databasePassword, $this->options->databaseDatabase, $this->options->databaseServer );
54
 
55
  // Can not connect to mysql
56
+ if (!empty($db->error->errors['db_connect_fail']['0'])) {
57
+ $this->returnException("Can not connect to external database {$this->options->databaseDatabase}");
58
  return false;
59
  }
60
 
61
  // Can not connect to database
62
+ $db->select($this->options->databaseDatabase);
63
+ if (!$db->ready) {
64
+ $error = isset($db->error->errors['db_select_fail']) ? $db->error->errors['db_select_fail'] : "Error: Can't select {database} Either it does not exist or you don't have privileges to access it.";
65
+ $this->returnException($error);
66
  exit;
67
  }
68
 
 
69
  return $db;
70
  }
71
 
74
  * and mainJob is not updating the clone
75
  * @return boolean
76
  */
77
+ private function isFatalError()
78
+ {
79
  $path = trailingslashit($this->options->cloneDir);
80
+ if (isset($this->options->mainJob) && $this->options->mainJob !== 'updating' && (is_dir($path) && !wpstg_is_empty_dir($path))) {
81
  $this->returnException(" Can not continue for security purposes. Directory {$path} is not empty! Use FTP or a file manager plugin and make sure it does not contain any files. ");
82
  }
83
  return false;
87
  * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
88
  * @return void
89
  */
90
+ protected function calculateTotalSteps()
91
+ {
92
  $this->options->totalSteps = ($this->total === 0) ? 1 : $this->total;
93
  }
94
 
97
  * Returns false when over threshold limits are hit or when the job is done, true otherwise
98
  * @return bool
99
  */
100
+ protected function execute()
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->isRunning() || $this->options->currentStep > $this->total) {
112
+ $this->prepareResponse(true, false);
113
  return false;
114
  }
115
 
116
  // Copy table
117
+ if (isset($this->options->tables[$this->options->currentStep]) && !$this->copyTable($this->options->tables[$this->options->currentStep])) {
118
+ $this->prepareResponse(false, false);
 
119
 
120
+ // Continue execution
121
  return true;
122
  }
123
 
 
124
  $this->prepareResponse();
125
 
126
+ // Continue execution
127
  return true;
128
  }
129
 
131
  * Get new prefix for the staging site
132
  * @return string
133
  */
134
+ private function getStagingPrefix()
135
+ {
136
+ $this->options->prefix = !empty($this->options->databasePrefix) ? $this->options->databasePrefix : $this->db->prefix;
 
137
  return $this->options->prefix;
138
  }
139
 
142
  * @param string $tableName
143
  * @return bool
144
  */
145
+ private function copyTable($tableName)
146
+ {
147
 
148
+ $strings = new Strings();
149
+ $tableName = is_object($tableName) ? $tableName->name : $tableName;
150
+ $newTableName = $this->getStagingPrefix() . $strings->str_replace_first($this->db->prefix, null, $tableName);
151
 
152
  // Drop table if necessary
153
+ $this->dropTable($newTableName);
154
 
155
  // Save current job
156
+ $this->setJob($newTableName);
157
 
158
  // Beginning of the job
159
+ if (!$this->startJob($newTableName, $tableName)) {
160
  return true;
161
  }
162
 
163
  // Copy data
164
+ $this->copyData($newTableName, $tableName);
165
 
166
  // Finish the step
167
  return $this->finishStep();
171
  * Set the job
172
  * @param string $table
173
  */
174
+ private function setJob($table)
175
+ {
176
+ if (isset($this->options->job->current)) {
177
  return;
178
  }
179
 
180
  $this->options->job->current = $table;
181
+ $this->options->job->start = 0;
182
  }
183
 
184
  /**
187
  * @param string $old
188
  * @return bool
189
  */
190
+ private function startJob($new, $old)
191
+ {
192
+ if ($this->isExcludedTable($new)) {
193
  return false;
194
  }
195
 
196
+ if (0 != $this->options->job->start) {
197
  return true;
198
  }
199
 
200
  // Table does not exist
201
+ $result = $this->db->query("SHOW TABLES LIKE '{$old}'");
202
+ if (!$result || 0 === $result) {
 
203
  return true;
204
  }
205
 
206
+ $this->log("DB External Copy: CREATE table {$this->options->databaseDatabase}.{$new}");
207
+ $sql = $this->getTableCreateStatement($old);
208
 
209
  // Replace table prefix to the new one
210
+ $sql = str_replace("CREATE TABLE `{$old}`", "CREATE TABLE `{$new}`", $sql);
211
+
212
 
213
  // Make constraint unique to prevent error:(errno: 121 "Duplicate key on write or update")
214
  $sql = wpstg_unique_constraint($sql);
215
 
216
  $this->stagingDb->query('SET FOREIGN_KEY_CHECKS=0;');
217
 
218
+ if (false === $this->stagingDb->query($sql)) {
219
+ $this->returnException("DB External Copy - Fatal Error: {$this->stagingDb->last_error} Query: {$sql}");
220
  }
221
 
222
  // Count amount of tables to insert with next step
223
  $this->options->job->total = 0;
224
+ $this->options->job->total = ( int )$this->db->get_var("SELECT COUNT(1) FROM `{$this->db->dbname}`.`{$old}`");
225
 
226
+ if (0 == $this->options->job->total) {
227
  $this->finishStep();
228
  return false;
229
  }
231
  return true;
232
  }
233
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  /**
235
  * Get MySQL query create table
236
  *
237
+ * @param string $table_name Table name
238
  * @return array
239
  */
240
+ private function getTableCreateStatement($tableName)
241
+ {
242
+ $row = $this->db->get_results("SHOW CREATE TABLE `{$tableName}`", ARRAY_A);
243
 
244
  // Get create table
245
+ if (isset($row[0]['Create Table'])) {
246
  return $row[0]['Create Table'];
247
  }
248
  return array();
253
  * @param string $new
254
  * @param string $old
255
  */
256
+ private function copyData($new, $old)
257
+ {
258
 
259
  $rows = $this->options->job->start + $this->settings->queryLimit;
260
  $this->log(
261
+ "DB External Copy: INSERT {$this->db->dbname}.{$old} as {$this->options->databaseDatabase}.{$new} from {$this->options->job->start} to {$rows} records"
262
  );
263
 
264
  $limitation = '';
265
 
266
+ if (0 < ( int )$this->settings->queryLimit) {
267
  $limitation = " LIMIT {$this->settings->queryLimit} OFFSET {$this->options->job->start}";
268
  }
269
 
270
  // Get data from production site
271
+ $rows = $this->db->get_results("SELECT * FROM `{$old}` {$limitation}", ARRAY_A);
272
 
273
  // Start transaction
274
+ $this->stagingDb->query('SET autocommit=0;');
275
+ $this->stagingDb->query('SET FOREIGN_KEY_CHECKS=0;');
276
+ $this->stagingDb->query('START TRANSACTION;');
277
 
278
  // Copy into staging site
279
+ foreach ($rows as $row) {
280
+ $escaped_values = $this->mysqlEscapeMimic(array_values($row));
281
+ $values = implode("', '", $escaped_values);
282
+ $this->stagingDb->query("INSERT INTO `{$new}` VALUES ('{$values}')");
283
  }
284
  // Commit transaction
285
+ $this->stagingDb->query('COMMIT;');
286
+ $this->stagingDb->query('SET autocommit=1;');
 
287
 
288
  // Set new offset
289
  $this->options->job->start += $this->settings->queryLimit;
293
  * Mimics the mysql_real_escape_string function. Adapted from a post by 'feedr' on php.net.
294
  * @link http://php.net/manual/en/function.mysql-real-escape-string.php#101248
295
  * @access public
296
+ * @param string $input The string to escape.
297
  * @return string
298
  */
299
+ private function mysqlEscapeMimic($input)
300
+ {
301
+ if (is_array($input)) {
302
+ return array_map(__METHOD__, $input);
303
  }
304
+ if (!empty($input) && is_string($input)) {
305
+ return str_replace(array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $input);
306
  }
307
 
308
  return $input;
313
  * @param string $table
314
  * @return boolean
315
  */
316
+ private function isExcludedTable($table)
317
+ {
318
 
319
+ $customTables = apply_filters('wpstg_clone_database_tables_exclude', array());
320
  $defaultTables = array('blogs', 'blog_versions');
321
 
322
+ $tables = array_merge($customTables, $defaultTables);
323
 
324
  $excludedTables = array();
325
+ foreach ($tables as $key => $value) {
326
  $excludedTables[] = $this->options->prefix . $value;
327
  }
328
 
329
+ if (in_array($table, $excludedTables)) {
330
  return true;
331
  }
332
  return false;
335
  /**
336
  * Finish the step
337
  */
338
+ private function finishStep()
339
+ {
340
  // This job is not finished yet
341
+ if ($this->options->job->total > $this->options->job->start) {
342
  return false;
343
  }
344
 
345
  // Add it to cloned tables listing
346
+ $this->options->clonedTables[] = isset($this->options->tables[$this->options->currentStep]) ? $this->options->tables[$this->options->currentStep] : false;
347
 
348
  // Reset job
349
  $this->options->job = new \stdClass();
355
  * Drop table if necessary
356
  * @param string $new
357
  */
358
+ private function dropTable($new)
359
+ {
360
+ $old = $this->stagingDb->get_var($this->stagingDb->prepare("SHOW TABLES LIKE %s", $new));
361
 
362
+ if (!$this->shouldDropTable($new, $old)) {
363
  return;
364
  }
365
 
366
+ $this->log("DB External Copy: {$new} already exists, dropping it first");
367
+ $this->stagingDb->query("SET FOREIGN_KEY_CHECKS=0");
368
+ $this->stagingDb->query("DROP TABLE {$new}");
369
+ $this->stagingDb->query("SET FOREIGN_KEY_CHECKS=1");
370
  }
371
 
372
  /**
375
  * @param string $old
376
  * @return bool
377
  */
378
+ private function shouldDropTable($new, $old)
379
+ {
380
+ if ($old === $new &&
381
+ (
382
+ !isset($this->options->job->current) ||
383
+ !isset($this->options->job->start) ||
 
 
384
  0 == $this->options->job->start
385
+ )) {
386
  return true;
387
  }
388
  return false;
Backend/Modules/Jobs/Files.php CHANGED
@@ -310,6 +310,10 @@ class Files extends JobExecutable
310
  $src = fopen($src, 'r');
311
  $dest = fopen($dst, 'w');
312
 
 
 
 
 
313
  // Try first method:
314
  while (!feof($src)) {
315
  if (false === fwrite($dest, fread($src, $buffersize))) {
310
  $src = fopen($src, 'r');
311
  $dest = fopen($dst, 'w');
312
 
313
+ if (!$src || !$dest) {
314
+ return false;
315
+ }
316
+
317
  // Try first method:
318
  while (!feof($src)) {
319
  if (false === fwrite($dest, fread($src, $buffersize))) {
Backend/Modules/Jobs/Multisite/Data.php CHANGED
@@ -149,7 +149,6 @@ class Data extends JobExecutable
149
  {
150
 
151
  // Prefix is the same as the one of live site
152
- //$wpdb = WPStaging::getInstance()->get( "wpdb" );
153
  if ($this->db->prefix === $this->prefix) {
154
  return true;
155
  }
@@ -173,7 +172,7 @@ class Data extends JobExecutable
173
  * @param string $table
174
  * @return boolean
175
  */
176
- protected function isTable($table)
177
  {
178
  if ($this->db->get_var("SHOW TABLES LIKE '{$table}'") != $table) {
179
  $this->log("Table {$table} does not exist", Logger::TYPE_ERROR);
@@ -299,7 +298,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
299
 
300
  /**
301
  * Check if wp-config.php contains important constants
302
- * @param type $source
303
  * @return boolean
304
  */
305
  protected function isValidWpConfig($source)
@@ -310,7 +309,6 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
310
  return false;
311
  }
312
 
313
- $content = file_get_contents($source);
314
 
315
  if (false === ($content = file_get_contents($source))) {
316
  $this->log("Preparing Data Step0: Can not read {$source}", Logger::TYPE_INFO);
@@ -360,7 +358,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
360
  $this->log("Preparing Data Step1: Updating siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_INFO);
361
 
362
  // Skip - Table does not exist
363
- if (false === $this->isTable($this->prefix . 'options')) {
364
  return true;
365
  }
366
  // Skip - Table is not selected or updated
@@ -397,7 +395,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
397
  $this->log("Preparing Data Step2: Updating row wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}");
398
 
399
  // Skip - Table does not exist
400
- if (false === $this->isTable($this->prefix . 'options')) {
401
  $this->log("Preparing Data Step2: Skipping");
402
  return true;
403
  }
@@ -448,7 +446,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
448
  }
449
 
450
  // Skip - Table does not exist
451
- if (false === $this->isTable($this->prefix . 'options')) {
452
  return true;
453
  }
454
 
@@ -482,7 +480,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
482
  $this->log("Preparing Data Step4: Updating db prefix in {$this->prefix}usermeta. ");
483
 
484
  // Skip - Table does not exist
485
- if (false === $this->isTable($this->prefix . 'usermeta')) {
486
  return true;
487
  }
488
 
@@ -548,7 +546,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
548
  $content = str_replace($this->multisiteHomeDomain, $this->getStagingSiteUrl(), $content);
549
 
550
  if (false === @wpstg_put_contents($path, $content)) {
551
- $this->log("Preparing Data Step5: Failed to update $table_prefix in {$path} to " . $this->prefix . ". Can't save contents", Logger::TYPE_ERROR);
552
  return false;
553
  }
554
 
@@ -620,7 +618,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
620
  $this->log("Preparing Data Step7: Updating wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}");
621
 
622
  // Skip - Table does not exist
623
- if (false === $this->isTable($this->prefix . 'options')) {
624
  $this->log("Preparing Data Step7: Skipping Table {$this->prefix}'options' does not exist");
625
  return true;
626
  }
@@ -663,7 +661,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
663
  }
664
 
665
  // Skip - Table does not exist
666
- if (false === $this->isTable($this->prefix . 'options')) {
667
  $this->log("Preparing Data Step8: Skipping");
668
  return true;
669
  }
@@ -699,7 +697,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
699
 
700
  $this->log("Preparing Data Step9: Set staging site to noindex");
701
 
702
- if (false === $this->isTable($this->prefix . 'options')) {
703
  return true;
704
  }
705
 
@@ -750,7 +748,6 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
750
  $pattern = "/define\s*\(\s*['\"]WP_HOME['\"]\s*,\s*(.*)\s*\);.*/";
751
 
752
  $replace = "define('WP_HOME','" . $this->getStagingSiteUrl() . "'); // " . $matches[1] . " Changed by WP-Staging";
753
- //$replace.= " // Changed by WP-Staging";
754
 
755
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
756
  $this->log("Preparing Data: Failed to update WP_HOME", Logger::TYPE_ERROR);
@@ -793,7 +790,6 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
793
  $pattern = "/define\s*\(\s*['\"]WP_SITEURL['\"]\s*,\s*(.*)\s*\);.*/";
794
 
795
  $replace = "define('WP_SITEURL','" . $this->getStagingSiteUrl() . "'); // " . $matches[1] . " Changed by WP-Staging";
796
- //$replace.= " // Changed by WP-Staging";
797
 
798
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
799
  $this->log("Preparing Data Step11: Failed to update WP_SITEURL to " . $this->getStagingSiteUrl(), Logger::TYPE_ERROR);
@@ -910,7 +906,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
910
 
911
  $this->log("Data Crunching Step14: Updating active_plugins");
912
 
913
- if (false === $this->isTable($this->prefix . 'options')) {
914
  $this->log('Data Crunching Step14: Fatal Error ' . $this->prefix . 'options does not exist');
915
  $this->returnException('Data Crunching Step14: Fatal Error ' . $this->prefix . 'options does not exist');
916
  return false;
@@ -968,7 +964,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
968
  $this->log("Preparing Data Step15: Updating db prefix in {$this->prefix}options.");
969
 
970
  // Skip - Table does not exist
971
- if (false === $this->isTable($this->prefix . 'options')) {
972
  return true;
973
  }
974
 
@@ -1020,7 +1016,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1020
  $this->log("Preparing Data Step16: Updating upload_path {$this->prefix}options.");
1021
 
1022
  // Skip - Table does not exist
1023
- if (false === $this->isTable($this->prefix . 'options')) {
1024
  return true;
1025
  }
1026
 
@@ -1184,11 +1180,13 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1184
  */
1185
  protected function step20()
1186
  {
 
1187
 
1188
  $table = $this->prefix . 'options';
1189
 
1190
  // Skip - Table does not exist
1191
- if (false === $this->isTable($table)) {
 
1192
  return true;
1193
  }
1194
 
@@ -1205,82 +1203,24 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1205
  return true;
1206
  }
1207
 
1208
- /**
1209
- * Preserve data and prevents data in wp_options from beeing cloned to staging site
1210
- * @return bool
1211
- */
1212
- protected function step21()
1213
- {
1214
- $this->log("Preparing Data Step21: Preserve Data in " . $this->prefix . "options");
1215
-
1216
- // Skip - Table does not exist
1217
- if (false === $this->isTable($this->prefix . 'options')) {
1218
- return true;
1219
- }
1220
-
1221
- // Skip - Table is not selected or updated
1222
- if (!in_array($this->prefix . 'options', $this->tables)) {
1223
- $this->log("Preparing Data Step21: Skipped");
1224
- return true;
1225
- }
1226
-
1227
- $sql = '';
1228
-
1229
- $preserved_option_names = array('wpstg_existing_clones_beta');
1230
-
1231
- $preserved_option_names = apply_filters('wpstg_preserved_options_cloning', $preserved_option_names);
1232
- $preserved_options_escaped = esc_sql($preserved_option_names);
1233
-
1234
- $preserved_options_data = array();
1235
-
1236
- // Get preserved data in wp_options tables
1237
- $table = $this->db->prefix . 'options';
1238
- $preserved_options_data[$this->prefix . 'options'] = $this->db->get_results(
1239
- sprintf(
1240
- "SELECT * FROM `{$table}` WHERE `option_name` IN ('%s')", implode("','", $preserved_options_escaped)
1241
- ), ARRAY_A
1242
- );
1243
-
1244
- // Create preserved data queries for options tables
1245
- foreach ($preserved_options_data as $key => $value) {
1246
- if (false === empty($value)) {
1247
- foreach ($value as $option) {
1248
- $sql .= $this->db->prepare(
1249
- "DELETE FROM `{$key}` WHERE `option_name` = %s;\n", $option['option_name']
1250
- );
1251
-
1252
- $sql .= $this->db->prepare(
1253
- "INSERT INTO `{$key}` ( `option_id`, `option_name`, `option_value`, `autoload` ) VALUES ( NULL , %s, %s, %s );\n", $option['option_name'], $option['option_value'], $option['autoload']
1254
- );
1255
- }
1256
- }
1257
- }
1258
-
1259
- $this->debugLog("Preparing Data Step21: Preserve values " . json_encode($preserved_options_data), Logger::TYPE_INFO);
1260
-
1261
- $this->executeSql($sql);
1262
-
1263
- $this->log("Preparing Data Step21: Successful!");
1264
- return true;
1265
- }
1266
-
1267
  /**
1268
  * Check if there is a multisite super administrator.
1269
  * If not add it to _usermeta
1270
  * @return bool
1271
  */
1272
- protected function step22()
1273
  {
1274
- $this->log("Preparing Data Step22: Add network administrators");
 
1275
 
1276
  // Skip - Table does not exist
1277
- if (false === $this->isTable($this->prefix . 'usermeta')) {
1278
  return true;
1279
  }
1280
 
1281
  // Skip - Table is not selected or updated
1282
  if (!in_array($this->prefix . 'usermeta', $this->tables)) {
1283
- $this->log("Preparing Data Step22: Skipping");
1284
  return true;
1285
  }
1286
 
@@ -1314,6 +1254,7 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1314
  return true;
1315
  }
1316
 
 
1317
  /**
1318
  * Execute a batch of sql queries
1319
  * @param string $sqlbatch
@@ -1330,6 +1271,35 @@ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
1330
  return true;
1331
  }
1332
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1333
  /**
1334
  * Get relative path to the uploads media folder of multisite e.g.
1335
  * wp-content/uploads/sites/SITEID or old wordpress structure wp-content/blogs.dir/SITEID/files
149
  {
150
 
151
  // Prefix is the same as the one of live site
 
152
  if ($this->db->prefix === $this->prefix) {
153
  return true;
154
  }
172
  * @param string $table
173
  * @return boolean
174
  */
175
+ protected function tableExists($table)
176
  {
177
  if ($this->db->get_var("SHOW TABLES LIKE '{$table}'") != $table) {
178
  $this->log("Table {$table} does not exist", Logger::TYPE_ERROR);
298
 
299
  /**
300
  * Check if wp-config.php contains important constants
301
+ * @param string $source
302
  * @return boolean
303
  */
304
  protected function isValidWpConfig($source)
309
  return false;
310
  }
311
 
 
312
 
313
  if (false === ($content = file_get_contents($source))) {
314
  $this->log("Preparing Data Step0: Can not read {$source}", Logger::TYPE_INFO);
358
  $this->log("Preparing Data Step1: Updating siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_INFO);
359
 
360
  // Skip - Table does not exist
361
+ if (false === $this->tableExists($this->prefix . 'options')) {
362
  return true;
363
  }
364
  // Skip - Table is not selected or updated
395
  $this->log("Preparing Data Step2: Updating row wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}");
396
 
397
  // Skip - Table does not exist
398
+ if (false === $this->tableExists($this->prefix . 'options')) {
399
  $this->log("Preparing Data Step2: Skipping");
400
  return true;
401
  }
446
  }
447
 
448
  // Skip - Table does not exist
449
+ if (false === $this->tableExists($this->prefix . 'options')) {
450
  return true;
451
  }
452
 
480
  $this->log("Preparing Data Step4: Updating db prefix in {$this->prefix}usermeta. ");
481
 
482
  // Skip - Table does not exist
483
+ if (false === $this->tableExists($this->prefix . 'usermeta')) {
484
  return true;
485
  }
486
 
546
  $content = str_replace($this->multisiteHomeDomain, $this->getStagingSiteUrl(), $content);
547
 
548
  if (false === @wpstg_put_contents($path, $content)) {
549
+ $this->log("Preparing Data Step5: Failed to update table_prefix in {$path} to " . $this->prefix . ". Can't save contents", Logger::TYPE_ERROR);
550
  return false;
551
  }
552
 
618
  $this->log("Preparing Data Step7: Updating wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}");
619
 
620
  // Skip - Table does not exist
621
+ if (false === $this->tableExists($this->prefix . 'options')) {
622
  $this->log("Preparing Data Step7: Skipping Table {$this->prefix}'options' does not exist");
623
  return true;
624
  }
661
  }
662
 
663
  // Skip - Table does not exist
664
+ if (false === $this->tableExists($this->prefix . 'options')) {
665
  $this->log("Preparing Data Step8: Skipping");
666
  return true;
667
  }
697
 
698
  $this->log("Preparing Data Step9: Set staging site to noindex");
699
 
700
+ if (false === $this->tableExists($this->prefix . 'options')) {
701
  return true;
702
  }
703
 
748
  $pattern = "/define\s*\(\s*['\"]WP_HOME['\"]\s*,\s*(.*)\s*\);.*/";
749
 
750
  $replace = "define('WP_HOME','" . $this->getStagingSiteUrl() . "'); // " . $matches[1] . " Changed by WP-Staging";
 
751
 
752
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
753
  $this->log("Preparing Data: Failed to update WP_HOME", Logger::TYPE_ERROR);
790
  $pattern = "/define\s*\(\s*['\"]WP_SITEURL['\"]\s*,\s*(.*)\s*\);.*/";
791
 
792
  $replace = "define('WP_SITEURL','" . $this->getStagingSiteUrl() . "'); // " . $matches[1] . " Changed by WP-Staging";
 
793
 
794
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
795
  $this->log("Preparing Data Step11: Failed to update WP_SITEURL to " . $this->getStagingSiteUrl(), Logger::TYPE_ERROR);
906
 
907
  $this->log("Data Crunching Step14: Updating active_plugins");
908
 
909
+ if (false === $this->tableExists($this->prefix . 'options')) {
910
  $this->log('Data Crunching Step14: Fatal Error ' . $this->prefix . 'options does not exist');
911
  $this->returnException('Data Crunching Step14: Fatal Error ' . $this->prefix . 'options does not exist');
912
  return false;
964
  $this->log("Preparing Data Step15: Updating db prefix in {$this->prefix}options.");
965
 
966
  // Skip - Table does not exist
967
+ if (false === $this->tableExists($this->prefix . 'options')) {
968
  return true;
969
  }
970
 
1016
  $this->log("Preparing Data Step16: Updating upload_path {$this->prefix}options.");
1017
 
1018
  // Skip - Table does not exist
1019
+ if (false === $this->tableExists($this->prefix . 'options')) {
1020
  return true;
1021
  }
1022
 
1180
  */
1181
  protected function step20()
1182
  {
1183
+ $this->log("Preparing Data Step20: Preserve Data in " . $this->prefix . "options");
1184
 
1185
  $table = $this->prefix . 'options';
1186
 
1187
  // Skip - Table does not exist
1188
+ if (false === $this->tableExists($table)) {
1189
+ $this->log("Preparing Data Step21: Preserve Data in " . $this->prefix . "options");
1190
  return true;
1191
  }
1192
 
1203
  return true;
1204
  }
1205
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1206
  /**
1207
  * Check if there is a multisite super administrator.
1208
  * If not add it to _usermeta
1209
  * @return bool
1210
  */
1211
+ protected function step21()
1212
  {
1213
+ $step = "Preparing Data Step21: ";
1214
+ $this->log($step . "Add network administrators");
1215
 
1216
  // Skip - Table does not exist
1217
+ if (false === $this->tableExists($this->prefix . 'usermeta')) {
1218
  return true;
1219
  }
1220
 
1221
  // Skip - Table is not selected or updated
1222
  if (!in_array($this->prefix . 'usermeta', $this->tables)) {
1223
+ $this->log($step . "Skipping");
1224
  return true;
1225
  }
1226
 
1254
  return true;
1255
  }
1256
 
1257
+
1258
  /**
1259
  * Execute a batch of sql queries
1260
  * @param string $sqlbatch
1271
  return true;
1272
  }
1273
 
1274
+ /**
1275
+ * Delete all listed staging sites from cloned site on initial cloning. Useful if a cloned site is cloned again. E.g. for dev > staging > production setup
1276
+ * @return bool
1277
+ */
1278
+ protected function step22(){
1279
+
1280
+ if ($this->options->mainJob === 'updating'){
1281
+ return true;
1282
+ }
1283
+
1284
+ if( false === $this->tableExists( $this->prefix . 'options' ) ) {
1285
+ return true;
1286
+ }
1287
+
1288
+ $step = "Preparing Data Step22:";
1289
+ $this->log( $step . " Reset wp staging site data" );
1290
+
1291
+ $result = $this->db->query(
1292
+ $this->db->prepare( "UPDATE {$this->prefix}options SET `option_value` = %s WHERE `option_name` = %s", serialize(array()), 'wpstg_existing_clones_beta'
1293
+ )
1294
+ );
1295
+
1296
+ if (false === $result){
1297
+ $this->log( $step . "Failed to reset wp staging site data." );
1298
+ }
1299
+
1300
+ return true;
1301
+ }
1302
+
1303
  /**
1304
  * Get relative path to the uploads media folder of multisite e.g.
1305
  * wp-content/uploads/sites/SITEID or old wordpress structure wp-content/blogs.dir/SITEID/files
Backend/Modules/Jobs/Multisite/DataExternal.php CHANGED
@@ -9,8 +9,6 @@ if (!defined("WPINC")) {
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
 
@@ -41,10 +39,10 @@ class DataExternal extends JobExecutable
41
  */
42
  public function initialize()
43
  {
44
- $this->db = $this->getStagingDB();
45
  $this->productionDb = WPStaging::getInstance()->get("wpdb");
46
- $this->prefix = $this->options->prefix;
47
- $this->db->prefix = $this->options->databasePrefix;
48
 
49
  $this->getTables();
50
 
@@ -59,7 +57,7 @@ class DataExternal extends JobExecutable
59
  */
60
  private function getStagingDB()
61
  {
62
- return new \wpdb($this->options->databaseUser, str_replace("\\\\", "\\", $this->options->databasePassword), $this->options->databaseDatabase, $this->options->databaseServer);
63
  }
64
 
65
  /**
@@ -67,14 +65,14 @@ class DataExternal extends JobExecutable
67
  */
68
  private function getTables()
69
  {
70
- $strings = new Strings();
71
  $this->tables = array();
72
  foreach ($this->options->tables as $table) {
73
- $this->tables[] = $this->options->prefix.$strings->str_replace_first($this->productionDb->prefix, null, $table);
74
  }
75
  // Add extra global tables from main multisite (wpstgx_users and wpstgx_usermeta)
76
- $this->tables[] = $this->options->prefix.$strings->str_replace_first($this->productionDb->prefix, null, 'users');
77
- $this->tables[] = $this->options->prefix.$strings->str_replace_first($this->productionDb->prefix, null, 'usermeta');
78
  }
79
 
80
  /**
@@ -128,7 +126,7 @@ class DataExternal extends JobExecutable
128
  }
129
 
130
  // Execute step
131
- $stepMethodName = "step".$this->options->currentStep;
132
  if (!$this->{$stepMethodName}()) {
133
  $this->prepareResponse(false, false);
134
  return false;
@@ -150,8 +148,7 @@ class DataExternal extends JobExecutable
150
  return
151
  !$this->isRunning() ||
152
  $this->options->currentStep > $this->options->totalSteps ||
153
- !method_exists($this, "step".$this->options->currentStep)
154
- ;
155
  }
156
 
157
  /**
@@ -185,7 +182,7 @@ class DataExternal extends JobExecutable
185
  * @param string $table
186
  * @return boolean
187
  */
188
- protected function isTable($table)
189
  {
190
  if ($this->db->get_var("SHOW TABLES LIKE '{$table}'") != $table) {
191
  $this->log("Table {$table} does not exist", Logger::TYPE_ERROR);
@@ -205,9 +202,9 @@ class DataExternal extends JobExecutable
205
 
206
  $dir = trailingslashit(dirname(ABSPATH));
207
 
208
- $source = $dir.'wp-config.php';
209
 
210
- $destination = $this->options->destinationDir.'wp-config.php';
211
 
212
  // Check if there is already a valid wp-config.php in root of staging site
213
  if ($this->isValidWpConfig($destination)) {
@@ -225,7 +222,7 @@ class DataExternal extends JobExecutable
225
  }
226
 
227
  // No valid wp-config.php found so let's copy wp stagings default wp-config.php to staging site
228
- $source = WPSTG_PLUGIN_DIR."Backend/helpers/wp-config.php";
229
 
230
  $this->log("Preparing Data Step0: Copy default wp-config.php file from source {$source} to {$destination}", Logger::TYPE_INFO);
231
 
@@ -287,17 +284,17 @@ class DataExternal extends JobExecutable
287
  $search = "// ** MySQL settings ** //";
288
 
289
  $replace = "// ** MySQL settings ** //\r\n
290
- define( 'DB_NAME', '".DB_NAME."' );\r\n
291
  /** MySQL database username */\r\n
292
- define( 'DB_USER', '".DB_USER."' );\r\n
293
  /** MySQL database password */\r\n
294
- define( 'DB_PASSWORD', '".DB_PASSWORD."' );\r\n
295
  /** MySQL hostname */\r\n
296
- define( 'DB_HOST', '".DB_HOST."' );\r\n
297
  /** Database Charset to use in creating database tables. */\r\n
298
- define( 'DB_CHARSET', '".DB_CHARSET."' );\r\n
299
  /** The Database Collate type. Don't change this if in doubt. */\r\n
300
- define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
301
 
302
  $content = str_replace($search, $replace, $content);
303
 
@@ -372,16 +369,16 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
372
  $this->log("Preparing Data Step1: Updating siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_INFO);
373
 
374
  // Skip - Table does not exist
375
- if (false === $this->isTable($this->prefix.'options')) {
376
  return true;
377
  }
378
  // Skip - Table is not selected or updated
379
- if (!in_array($this->prefix.'options', $this->tables)) {
380
  $this->log("Preparing Data Step1: Skipping");
381
  return true;
382
  }
383
 
384
- $this->log("Preparing Data Step1: Updating siteurl and homeurl to ".$this->getStagingSiteUrl());
385
  // Replace URLs
386
  $result = $this->db->query(
387
  $this->db->prepare(
@@ -390,7 +387,6 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
390
  );
391
 
392
 
393
-
394
  // All good
395
  if ($result) {
396
  return true;
@@ -410,12 +406,12 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
410
  $this->log("Preparing Data Step2: Updating row wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}");
411
 
412
  // Skip - Table does not exist
413
- if (false === $this->isTable($this->prefix.'options')) {
414
  $this->log("Preparing Data Step2: Skipping");
415
  return true;
416
  }
417
  // Skip - Table is not selected or updated
418
- if (!in_array($this->prefix.'options', $this->tables)) {
419
  $this->log("Preparing Data Step2: Skipping");
420
  return true;
421
  }
@@ -461,12 +457,12 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
461
  }
462
 
463
  // Skip - Table does not exist
464
- if (false === $this->isTable($this->prefix.'options')) {
465
  return true;
466
  }
467
 
468
  // Skip - Table is not selected or updated
469
- if (!in_array($this->prefix.'options', $this->tables)) {
470
  $this->log("Preparing Data Step3: Skipping");
471
  return true;
472
  }
@@ -495,18 +491,17 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
495
  $this->log("Preparing Data Step4: Updating db prefix in {$this->prefix}usermeta. ");
496
 
497
  // Skip - Table does not exist
498
- if (false === $this->isTable($this->prefix.'usermeta')) {
499
  return true;
500
  }
501
 
502
  // Skip - Table is not selected or updated
503
- if (!in_array($this->prefix.'usermeta', $this->tables)) {
504
  $this->log("Preparing Data Step4: Skipping");
505
  return true;
506
  }
507
 
508
 
509
-
510
  // Skip - Target prefix is the same as production site prefix
511
  if ($this->db->prefix === $this->prefix) {
512
  $this->log("Preparing Data Step4: Target prefix equals production site prefix. Skipping");
@@ -517,7 +512,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
517
  $this->debugLog("Preparing Data Step4: UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, {$this->productionDb->base_prefix}, {$this->prefix}) WHERE meta_key LIKE {$this->productionDb->base_prefix}_%");
518
  $update = $this->db->query(
519
  $this->db->prepare(
520
- "UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, %s, %s) WHERE meta_key LIKE %s", $this->productionDb->base_prefix, $this->prefix, $this->productionDb->base_prefix."_%"
521
  )
522
  );
523
 
@@ -529,7 +524,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
529
  $this->debugLog("Preparing Data Step4: UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, {$this->db->prefix}, {$this->prefix}) WHERE meta_key LIKE {$this->db->prefix}_%");
530
  $update = $this->db->query(
531
  $this->db->prepare(
532
- "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."_%"
533
  )
534
  );
535
 
@@ -547,18 +542,18 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
547
  */
548
  protected function step5()
549
  {
550
- $path = $this->options->destinationDir."wp-config.php";
551
 
552
- $this->log("Preparing Data Step5: Updating table_prefix in {$path} to ".$this->prefix);
553
  if (false === ($content = file_get_contents($path))) {
554
  $this->log("Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR);
555
  return false;
556
  }
557
 
558
  // Replace table prefix
559
- $pattern = '/\$table_prefix\s*=\s*(.*).*/';
560
- $replacement = '$table_prefix = \''.$this->prefix.'\'; // Changed by WP Staging';
561
- $content = preg_replace($pattern, $replacement, $content);
562
 
563
  if (null === $content) {
564
  $this->log("Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR);
@@ -569,7 +564,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
569
  $content = str_replace($this->multisiteHomeDomain, $this->getStagingSiteUrl(), $content);
570
 
571
  if (false === @wpstg_put_contents($path, $content)) {
572
- $this->log("Preparing Data Step5: Failed to update $table_prefix in {$path} to ".$this->prefix.". Can't save contents", Logger::TYPE_ERROR);
573
  return false;
574
  }
575
 
@@ -591,7 +586,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
591
  return true;
592
  }
593
 
594
- $path = $this->options->destinationDir."index.php";
595
 
596
  if (false === ($content = file_get_contents($path))) {
597
  $this->log("Preparing Data Step6: Failed to reset {$path} for sub directory; can't read contents", Logger::TYPE_ERROR);
@@ -609,11 +604,10 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
609
 
610
  $pattern = "/require(.*) dirname(.*) __FILE__ (.*) \. '(.*)wp-blog-header.php'(.*);.*/";
611
 
612
- $replace = "require( dirname( __FILE__ ) . '/wp-blog-header.php' ); // ".$matches[0];
613
  //$replace.= " // Changed by WP-Staging";
614
 
615
 
616
-
617
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
618
  $this->log("Preparing Data: Failed to reset index.php for sub directory; replacement failed", Logger::TYPE_ERROR);
619
  return false;
@@ -637,12 +631,12 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
637
  $this->log("Preparing Data Step7: Updating wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}");
638
 
639
  // Skip - Table does not exist
640
- if (false === $this->isTable($this->prefix.'options')) {
641
  return true;
642
  }
643
 
644
  // Skip - Table is not selected or updated
645
- if (!in_array($this->prefix.'options', $this->tables)) {
646
  $this->log("Preparing Data Step7: Skipping");
647
  return true;
648
  }
@@ -674,12 +668,12 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
674
  }
675
 
676
  // Skip - Table does not exist
677
- if (false === $this->isTable($this->prefix.'options')) {
678
  return true;
679
  }
680
 
681
  // Skip - Table is not selected or updated
682
- if (!in_array($this->prefix.'options', $this->tables)) {
683
  $this->log("Preparing Data Step8: Skipping");
684
  return true;
685
  }
@@ -709,12 +703,12 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
709
 
710
  $this->log("Preparing Data Step9: Set staging site to noindex");
711
 
712
- if (false === $this->isTable($this->prefix.'options')) {
713
  return true;
714
  }
715
 
716
  // Skip - Table is not selected or updated
717
- if (!in_array($this->prefix.'options', $this->tables)) {
718
  $this->log("Preparing Data Step9: Skipping");
719
  return true;
720
  }
@@ -741,9 +735,9 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
741
  */
742
  protected function step10()
743
  {
744
- $path = $this->options->destinationDir."wp-config.php";
745
 
746
- $this->log("Preparing Data Step10: Updating WP_HOME in wp-config.php to ".$this->getStagingSiteUrl());
747
 
748
  if (false === ($content = file_get_contents($path))) {
749
  $this->log("Preparing Data Step10: Failed to update WP_HOME in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR);
@@ -759,7 +753,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
759
 
760
  $pattern = "/define\s*\(\s*['\"]WP_HOME['\"]\s*,\s*(.*)\s*\);.*/";
761
 
762
- $replace = "define('WP_HOME','".$this->getStagingSiteUrl()."'); // ".$matches[1];
763
  //$replace .= " // Changed by WP-Staging";
764
 
765
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
@@ -784,9 +778,9 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
784
  */
785
  protected function step11()
786
  {
787
- $path = $this->options->destinationDir."wp-config.php";
788
 
789
- $this->log("Preparing Data Step11: Updating WP_SITEURL in wp-config.php to ".$this->getStagingSiteUrl());
790
 
791
  if (false === ($content = file_get_contents($path))) {
792
  $this->log("Preparing Data Step11: Failed to update WP_SITEURL in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR);
@@ -802,7 +796,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
802
 
803
  $pattern = "/define\s*\(\s*['\"]WP_SITEURL['\"]\s*,\s*(.*)\s*\);.*/";
804
 
805
- $replace = "define('WP_SITEURL','".$this->getStagingSiteUrl()."'); // ".$matches[1];
806
  //$replace .= " // Changed by WP-Staging";
807
 
808
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
@@ -828,7 +822,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
828
  */
829
  protected function step12()
830
  {
831
- $path = $this->options->destinationDir."wp-config.php";
832
 
833
  $this->log("Preparing Data Step12: Updating WP_ALLOW_MULTISITE in wp-config.php to false");
834
 
@@ -846,7 +840,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
846
 
847
  $pattern = "/define\s*\(\s*['\"]WP_ALLOW_MULTISITE['\"]\s*,\s*(.*)\s*\);.*/";
848
 
849
- $replace = "define('WP_ALLOW_MULTISITE',false); // ".$matches[1];
850
  //$replace .= " // Changed by WP-Staging";
851
 
852
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
@@ -872,7 +866,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
872
  */
873
  protected function step13()
874
  {
875
- $path = $this->options->destinationDir."wp-config.php";
876
 
877
  $this->log("Preparing Data Step13: Updating MULTISITE in wp-config.php to false");
878
 
@@ -890,7 +884,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
890
 
891
  $pattern = "/define\s*\(\s*['\"]MULTISITE['\"]\s*,\s*(.*)\s*\);.*/";
892
 
893
- $replace = "define('MULTISITE',false); // ".$matches[1];
894
  //$replace .= " // Changed by WP-Staging";
895
 
896
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
@@ -920,14 +914,14 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
920
 
921
  $this->log("Data Crunching Step14: Updating active_plugins");
922
 
923
- if (false === $this->isTable($this->prefix.'options')) {
924
- $this->log('Data Crunching Step14: Fatal Error '.$this->prefix.'options does not exist');
925
- $this->returnException('Data Crunching Step14: Fatal Error '.$this->prefix.'options does not exist');
926
  return false;
927
  }
928
 
929
  // Skip - Table is not selected or updated
930
- if (!in_array($this->prefix.'options', $this->tables)) {
931
  $this->log("Preparing Data Step14: Skipping");
932
  return true;
933
  }
@@ -948,7 +942,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
948
  }
949
 
950
  $active_sitewide_plugins = unserialize($active_sitewide_plugins);
951
- $active_plugins = unserialize($active_plugins);
952
 
953
  $all_plugins = array_merge($active_plugins, array_keys($active_sitewide_plugins));
954
 
@@ -957,7 +951,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
957
 
958
  // Update active_plugins
959
  $update = $this->db->query(
960
- "UPDATE {$this->prefix}options SET option_value = '".serialize($all_plugins)."' WHERE option_name = 'active_plugins'"
961
  );
962
 
963
  if (false === $update) {
@@ -978,12 +972,12 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
978
  $this->log("Preparing Data Step15: Updating db prefix in {$this->prefix}options.");
979
 
980
  // Skip - Table does not exist
981
- if (false === $this->isTable($this->prefix.'options')) {
982
  return true;
983
  }
984
 
985
  // Skip - Table is not selected or updated
986
- if (!in_array($this->prefix.'options', $this->tables)) {
987
  $this->log("Preparing Data Step4: Skipping");
988
  return true;
989
  }
@@ -1002,12 +996,12 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1002
 
1003
  $where = "";
1004
  foreach ($filters as $filter) {
1005
- $where .= " AND option_name <> '".$filter."'";
1006
  }
1007
 
1008
  $updateOptions = $this->db->query(
1009
  $this->db->prepare(
1010
- "UPDATE IGNORE {$this->prefix}options SET option_name= replace(option_name, %s, %s) WHERE option_name LIKE %s".$where, $this->productionDb->prefix, $this->prefix, $this->productionDb->prefix."_%"
1011
  )
1012
  );
1013
 
@@ -1030,7 +1024,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1030
  $this->log("Preparing Data Step16: Updating upload_path {$this->prefix}options.");
1031
 
1032
  // Skip - Table does not exist
1033
- if (false === $this->isTable($this->prefix.'options')) {
1034
  return true;
1035
  }
1036
 
@@ -1042,13 +1036,13 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1042
  }
1043
 
1044
  // Skip - Table is not selected or updated
1045
- if (!in_array($this->prefix.'options', $this->tables)) {
1046
  $this->log("Preparing Data Step16: Skipping");
1047
  return true;
1048
  }
1049
 
1050
- $error = isset($this->db->last_error) ? 'Last error: '.$this->db->last_error
1051
- : '';
1052
 
1053
  $this->log("Updating upload_path in {$this->prefix}options. {$error}");
1054
 
@@ -1072,7 +1066,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1072
  */
1073
  protected function step17()
1074
  {
1075
- $path = $this->options->destinationDir."wp-config.php";
1076
 
1077
  $this->log("Preparing Data Step17: Set WP_CACHE in wp-config.php to false");
1078
 
@@ -1090,7 +1084,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1090
 
1091
  $pattern = "/define\s*\(\s*['\"]WP_CACHE['\"]\s*,\s*(.*)\s*\);.*/";
1092
 
1093
- $replace = "define('WP_CACHE',false); // ".$matches[1];
1094
  //$replace.= " // Changed by WP-Staging";
1095
 
1096
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
@@ -1115,7 +1109,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1115
  */
1116
  protected function step18()
1117
  {
1118
- $path = $this->options->destinationDir."wp-config.php";
1119
  $this->log("Preparing Data Step18: Update UPLOADS constant in wp-config.php");
1120
  if (false === ($content = file_get_contents($path))) {
1121
  $this->log("Preparing Data Step18: Failed to get UPLOADS in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR);
@@ -1127,7 +1121,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1127
  if (!empty($matches[0])) {
1128
  $pattern = "/define\s*\(\s*'UPLOADS'\s*,\s*(.*)\s*\);/";
1129
 
1130
- $replace = "define('UPLOADS', '".$uploadFolder."');";
1131
  $this->log("Preparing Data Step18: Change UPLOADS constant in wp-config.php to {$uploadFolder}.");
1132
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
1133
  $this->log("Preparing Data Step 18: Failed to change UPLOADS", Logger::TYPE_ERROR);
@@ -1140,7 +1134,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1140
  if (!empty($matches[0])) {
1141
  $matches[0];
1142
  $pattern = "/if\s*\(\s*\s*!\s*defined\s*\(\s*['\"]ABSPATH['\"]\s*(.*)\s*\)\s*\)/";
1143
- $replace = "define('UPLOADS', '".$uploadFolder."'); \n".
1144
  "if ( ! defined( 'ABSPATH' ) )";
1145
  $this->log("Preparing Data Step18: Change UPLOADS constant in wp-config.php to {$uploadFolder}.");
1146
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
@@ -1165,7 +1159,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1165
  */
1166
  protected function step19()
1167
  {
1168
- $path = $this->options->destinationDir."wp-config.php";
1169
 
1170
  $this->log("Preparing Data Step19: Change database credentials in wp-config.php");
1171
 
@@ -1183,8 +1177,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1183
 
1184
  $pattern = "/define\s*\(\s*'DB_NAME'\s*,\s*(.*)\s*\);.*/";
1185
 
1186
- $replace = "define('DB_NAME','{$this->options->databaseDatabase}'); // ".$matches[1];
1187
- //$replace.= " // Changed by WP-Staging";
1188
 
1189
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
1190
  $this->log("Preparing Data: Failed to change DB_NAME", Logger::TYPE_ERROR);
@@ -1201,7 +1194,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1201
 
1202
  $pattern = "/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*(.*)\s*\);.*/";
1203
 
1204
- $replace = "define('DB_USER','{$this->options->databaseUser}'); // ".$matches[1];
1205
  //$replace.= " // Changed by WP-Staging";
1206
 
1207
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
@@ -1219,8 +1212,10 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1219
 
1220
  $pattern = "/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*(.*)\s*\);.*/";
1221
 
1222
- $replace = "define('DB_PASSWORD','{$this->options->databasePassword}'); // Changed by WP Staging";
1223
- //$replace.= " // Changed by WP-Staging";
 
 
1224
 
1225
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
1226
  $this->log("Preparing Data: Failed to change DB_PASSWORD", Logger::TYPE_ERROR);
@@ -1237,7 +1232,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1237
 
1238
  $pattern = "/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*(.*)\s*\);.*/";
1239
 
1240
- $replace = "define('DB_HOST','{$this->options->databaseServer}'); // ".$matches[1];
1241
  //$replace.= " // Changed by WP-Staging";
1242
 
1243
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
@@ -1264,7 +1259,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1264
  protected function step20()
1265
  {
1266
 
1267
- $table = $this->prefix.'options';
1268
 
1269
  $siteurl = get_site_url();
1270
 
@@ -1287,16 +1282,16 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1287
 
1288
  /**
1289
  * Add option_name wpstg_execute and set it to true
1290
- * This option is used to determine if the staging website has not been loaded initiall for executing certain custom actions from \WPStaging\initActions()
1291
  * @return boolean
1292
  */
1293
  protected function step21()
1294
  {
1295
 
1296
- $table = $this->prefix.'options';
1297
 
1298
  // Skip - Table does not exist
1299
- if (false === $this->isTable($table)) {
1300
  return true;
1301
  }
1302
 
@@ -1313,80 +1308,22 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1313
  return true;
1314
  }
1315
 
1316
- /**
1317
- * Preserve data and prevents data in wp_options from beeing cloned to staging site
1318
- * @return bool
1319
- */
1320
- protected function step22()
1321
- {
1322
- $this->log("Preparing Data Step22: Preserve Data in ".$this->prefix."options");
1323
-
1324
- // Skip - Table does not exist
1325
- if (false === $this->isTable($this->prefix.'options')) {
1326
- return true;
1327
- }
1328
-
1329
- // Skip - Table is not selected or updated
1330
- if (!in_array($this->prefix.'options', $this->tables)) {
1331
- $this->log("Preparing Data Step22: Skipped");
1332
- return true;
1333
- }
1334
-
1335
- $sql = '';
1336
-
1337
- $preserved_option_names = array('wpstg_existing_clones_beta');
1338
-
1339
- $preserved_option_names = apply_filters('wpstg_preserved_options_cloning', $preserved_option_names);
1340
- $preserved_options_escaped = esc_sql($preserved_option_names);
1341
-
1342
- $preserved_options_data = array();
1343
-
1344
- // Get preserved data in wp_options tables
1345
- $table = $this->db->prefix.'options';
1346
- $preserved_options_data[$this->prefix.'options'] = $this->db->get_results(
1347
- sprintf(
1348
- "SELECT * FROM `{$table}` WHERE `option_name` IN ('%s')", implode("','", $preserved_options_escaped)
1349
- ), ARRAY_A
1350
- );
1351
-
1352
- // Create preserved data queries for options tables
1353
- foreach ($preserved_options_data as $key => $value) {
1354
- if (false === empty($value)) {
1355
- foreach ($value as $option) {
1356
- $sql .= $this->db->prepare(
1357
- "DELETE FROM `{$key}` WHERE `option_name` = %s;\n", $option['option_name']
1358
- );
1359
-
1360
- $sql .= $this->db->prepare(
1361
- "INSERT INTO `{$key}` ( `option_id`, `option_name`, `option_value`, `autoload` ) VALUES ( NULL , %s, %s, %s );\n", $option['option_name'], $option['option_value'], $option['autoload']
1362
- );
1363
- }
1364
- }
1365
- }
1366
-
1367
- $this->debugLog("Preparing Data Step22: Preserve values ".json_encode($preserved_options_data), Logger::TYPE_INFO);
1368
-
1369
- $this->executeSql($sql);
1370
-
1371
- $this->log("Preparing Data Step22: Successful!");
1372
- return true;
1373
- }
1374
 
1375
  /**
1376
  * Check if there is a multisite super administrator and add it to usermeta
1377
  * @return bool
1378
  */
1379
- protected function step23()
1380
  {
1381
  $this->log("Preparing Data Step23: Add network administrators");
1382
 
1383
  // Skip - Table does not exist
1384
- if (false === $this->isTable($this->prefix.'usermeta')) {
1385
  return true;
1386
  }
1387
 
1388
  // Skip - Table is not selected or updated
1389
- if (!in_array($this->prefix.'usermeta', $this->tables)) {
1390
  $this->log("Preparing Data Step23: Skipping");
1391
  return true;
1392
  }
@@ -1411,8 +1348,8 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1411
 
1412
  // Add new capability
1413
  $sql .= $this->db->prepare(
1414
- "INSERT INTO `{$this->prefix}usermeta` ( `umeta_id`, `user_id`, `meta_key`, `meta_value` ) VALUES ( NULL , %s, %s, %s );\n", $userId, $this->prefix.'capabilities', serialize(array(
1415
- 'administrator' => true))
1416
  );
1417
  }
1418
  if (!empty($sql)) {
@@ -1437,6 +1374,35 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1437
  return true;
1438
  }
1439
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1440
  /**
1441
  * Get relative path to the uploads media folder of multisite e.g.
1442
  * wp-content/uploads/sites/SITEID or old wordpress structure wp-content/blogs.dir/SITEID/files
@@ -1449,7 +1415,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1449
  $uploads = wp_upload_dir();
1450
  $basedir = $uploads['basedir'];
1451
  // Get relative upload path
1452
- $relDir = str_replace(wpstg_replace_windows_directory_separator(ABSPATH), null, wpstg_replace_windows_directory_separator($basedir));
1453
  return $relDir;
1454
  }
1455
 
@@ -1467,7 +1433,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1467
 
1468
  $customSlug = str_replace(wpstg_replace_windows_directory_separator(\WPStaging\WPStaging::getWPpath()), '', wpstg_replace_windows_directory_separator($uploadPath));
1469
 
1470
- $newUploadPath = wpstg_replace_windows_directory_separator(\WPStaging\WPStaging::getWPpath()).$this->options->cloneDirectoryName.'/'.$customSlug;
1471
 
1472
  return $newUploadPath;
1473
  }
@@ -1484,12 +1450,12 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1484
  }
1485
 
1486
  if ($this->isSubDir()) {
1487
- return trailingslashit($this->multisiteHomeDomain).trailingslashit($this->getSubDir()).$this->options->cloneDirectoryName;
1488
  }
1489
 
1490
  // Get the path to the main multisite without appending and trailingslash e.g. wordpress
1491
  $multisitePath = defined('PATH_CURRENT_SITE') ? PATH_CURRENT_SITE : '/';
1492
- $url = rtrim($this->multisiteHomeDomain, '/\\').$multisitePath.$this->options->cloneDirectoryName;
1493
 
1494
  return $url;
1495
  }
@@ -1503,7 +1469,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1503
  // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
1504
  // This is happening much more often than you would expect
1505
  $siteurl = preg_replace('#^https?://#', '', rtrim(get_option('siteurl'), '/'));
1506
- $home = preg_replace('#^https?://#', '', rtrim(get_option('home'), '/'));
1507
 
1508
  if ($home !== $siteurl) {
1509
  return true;
@@ -1517,7 +1483,7 @@ define( 'DB_COLLATE', '".DB_COLLATE."' );\r\n";
1517
  */
1518
  protected function getSubDir()
1519
  {
1520
- $home = get_option('home');
1521
  $siteurl = get_option('siteurl');
1522
 
1523
  if (empty($home) || empty($siteurl)) {
9
 
10
  use WPStaging\Utils\Logger;
11
  use WPStaging\WPStaging;
 
 
12
  use WPStaging\Utils\Strings;
13
  use WPStaging\Backend\Modules\Jobs\JobExecutable;
14
 
39
  */
40
  public function initialize()
41
  {
42
+ $this->db = $this->getStagingDB();
43
  $this->productionDb = WPStaging::getInstance()->get("wpdb");
44
+ $this->prefix = $this->options->prefix;
45
+ $this->db->prefix = $this->options->databasePrefix;
46
 
47
  $this->getTables();
48
 
57
  */
58
  private function getStagingDB()
59
  {
60
+ return new \wpdb($this->options->databaseUser, $this->options->databasePassword, $this->options->databaseDatabase, $this->options->databaseServer);
61
  }
62
 
63
  /**
65
  */
66
  private function getTables()
67
  {
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->productionDb->prefix, null, $table);
72
  }
73
  // Add extra global tables from main multisite (wpstgx_users and wpstgx_usermeta)
74
+ $this->tables[] = $this->options->prefix . $strings->str_replace_first($this->productionDb->prefix, null, 'users');
75
+ $this->tables[] = $this->options->prefix . $strings->str_replace_first($this->productionDb->prefix, null, 'usermeta');
76
  }
77
 
78
  /**
126
  }
127
 
128
  // Execute step
129
+ $stepMethodName = "step" . $this->options->currentStep;
130
  if (!$this->{$stepMethodName}()) {
131
  $this->prepareResponse(false, false);
132
  return false;
148
  return
149
  !$this->isRunning() ||
150
  $this->options->currentStep > $this->options->totalSteps ||
151
+ !method_exists($this, "step" . $this->options->currentStep);
 
152
  }
153
 
154
  /**
182
  * @param string $table
183
  * @return boolean
184
  */
185
+ protected function tableExists($table)
186
  {
187
  if ($this->db->get_var("SHOW TABLES LIKE '{$table}'") != $table) {
188
  $this->log("Table {$table} does not exist", Logger::TYPE_ERROR);
202
 
203
  $dir = trailingslashit(dirname(ABSPATH));
204
 
205
+ $source = $dir . 'wp-config.php';
206
 
207
+ $destination = $this->options->destinationDir . 'wp-config.php';
208
 
209
  // Check if there is already a valid wp-config.php in root of staging site
210
  if ($this->isValidWpConfig($destination)) {
222
  }
223
 
224
  // No valid wp-config.php found so let's copy wp stagings default wp-config.php to staging site
225
+ $source = WPSTG_PLUGIN_DIR . "Backend/helpers/wp-config.php";
226
 
227
  $this->log("Preparing Data Step0: Copy default wp-config.php file from source {$source} to {$destination}", Logger::TYPE_INFO);
228
 
284
  $search = "// ** MySQL settings ** //";
285
 
286
  $replace = "// ** MySQL settings ** //\r\n
287
+ define( 'DB_NAME', '" . DB_NAME . "' );\r\n
288
  /** MySQL database username */\r\n
289
+ define( 'DB_USER', '" . DB_USER . "' );\r\n
290
  /** MySQL database password */\r\n
291
+ define( 'DB_PASSWORD', '" . DB_PASSWORD . "' );\r\n
292
  /** MySQL hostname */\r\n
293
+ define( 'DB_HOST', '" . DB_HOST . "' );\r\n
294
  /** Database Charset to use in creating database tables. */\r\n
295
+ define( 'DB_CHARSET', '" . DB_CHARSET . "' );\r\n
296
  /** The Database Collate type. Don't change this if in doubt. */\r\n
297
+ define( 'DB_COLLATE', '" . DB_COLLATE . "' );\r\n";
298
 
299
  $content = str_replace($search, $replace, $content);
300
 
369
  $this->log("Preparing Data Step1: Updating siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_INFO);
370
 
371
  // Skip - Table does not exist
372
+ if (false === $this->tableExists($this->prefix . 'options')) {
373
  return true;
374
  }
375
  // Skip - Table is not selected or updated
376
+ if (!in_array($this->prefix . 'options', $this->tables)) {
377
  $this->log("Preparing Data Step1: Skipping");
378
  return true;
379
  }
380
 
381
+ $this->log("Preparing Data Step1: Updating siteurl and homeurl to " . $this->getStagingSiteUrl());
382
  // Replace URLs
383
  $result = $this->db->query(
384
  $this->db->prepare(
387
  );
388
 
389
 
 
390
  // All good
391
  if ($result) {
392
  return true;
406
  $this->log("Preparing Data Step2: Updating row wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}");
407
 
408
  // Skip - Table does not exist
409
+ if (false === $this->tableExists($this->prefix . 'options')) {
410
  $this->log("Preparing Data Step2: Skipping");
411
  return true;
412
  }
413
  // Skip - Table is not selected or updated
414
+ if (!in_array($this->prefix . 'options', $this->tables)) {
415
  $this->log("Preparing Data Step2: Skipping");
416
  return true;
417
  }
457
  }
458
 
459
  // Skip - Table does not exist
460
+ if (false === $this->tableExists($this->prefix . 'options')) {
461
  return true;
462
  }
463
 
464
  // Skip - Table is not selected or updated
465
+ if (!in_array($this->prefix . 'options', $this->tables)) {
466
  $this->log("Preparing Data Step3: Skipping");
467
  return true;
468
  }
491
  $this->log("Preparing Data Step4: Updating db prefix in {$this->prefix}usermeta. ");
492
 
493
  // Skip - Table does not exist
494
+ if (false === $this->tableExists($this->prefix . 'usermeta')) {
495
  return true;
496
  }
497
 
498
  // Skip - Table is not selected or updated
499
+ if (!in_array($this->prefix . 'usermeta', $this->tables)) {
500
  $this->log("Preparing Data Step4: Skipping");
501
  return true;
502
  }
503
 
504
 
 
505
  // Skip - Target prefix is the same as production site prefix
506
  if ($this->db->prefix === $this->prefix) {
507
  $this->log("Preparing Data Step4: Target prefix equals production site prefix. Skipping");
512
  $this->debugLog("Preparing Data Step4: UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, {$this->productionDb->base_prefix}, {$this->prefix}) WHERE meta_key LIKE {$this->productionDb->base_prefix}_%");
513
  $update = $this->db->query(
514
  $this->db->prepare(
515
+ "UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, %s, %s) WHERE meta_key LIKE %s", $this->productionDb->base_prefix, $this->prefix, $this->productionDb->base_prefix . "_%"
516
  )
517
  );
518
 
524
  $this->debugLog("Preparing Data Step4: UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, {$this->db->prefix}, {$this->prefix}) WHERE meta_key LIKE {$this->db->prefix}_%");
525
  $update = $this->db->query(
526
  $this->db->prepare(
527
+ "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 . "_%"
528
  )
529
  );
530
 
542
  */
543
  protected function step5()
544
  {
545
+ $path = $this->options->destinationDir . "wp-config.php";
546
 
547
+ $this->log("Preparing Data Step5: Updating table_prefix in {$path} to " . $this->prefix);
548
  if (false === ($content = file_get_contents($path))) {
549
  $this->log("Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR);
550
  return false;
551
  }
552
 
553
  // Replace table prefix
554
+ $pattern = '/\$table_prefix\s*=\s*(.*).*/';
555
+ $replacement = '$table_prefix = \'' . $this->prefix . '\'; // Changed by WP Staging';
556
+ $content = preg_replace($pattern, $replacement, $content);
557
 
558
  if (null === $content) {
559
  $this->log("Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR);
564
  $content = str_replace($this->multisiteHomeDomain, $this->getStagingSiteUrl(), $content);
565
 
566
  if (false === @wpstg_put_contents($path, $content)) {
567
+ $this->log("Preparing Data Step5: Failed to update $table_prefix in {$path} to " . $this->prefix . ". Can't save contents", Logger::TYPE_ERROR);
568
  return false;
569
  }
570
 
586
  return true;
587
  }
588
 
589
+ $path = $this->options->destinationDir . "index.php";
590
 
591
  if (false === ($content = file_get_contents($path))) {
592
  $this->log("Preparing Data Step6: Failed to reset {$path} for sub directory; can't read contents", Logger::TYPE_ERROR);
604
 
605
  $pattern = "/require(.*) dirname(.*) __FILE__ (.*) \. '(.*)wp-blog-header.php'(.*);.*/";
606
 
607
+ $replace = "require( dirname( __FILE__ ) . '/wp-blog-header.php' ); // " . $matches[0];
608
  //$replace.= " // Changed by WP-Staging";
609
 
610
 
 
611
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
612
  $this->log("Preparing Data: Failed to reset index.php for sub directory; replacement failed", Logger::TYPE_ERROR);
613
  return false;
631
  $this->log("Preparing Data Step7: Updating wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}");
632
 
633
  // Skip - Table does not exist
634
+ if (false === $this->tableExists($this->prefix . 'options')) {
635
  return true;
636
  }
637
 
638
  // Skip - Table is not selected or updated
639
+ if (!in_array($this->prefix . 'options', $this->tables)) {
640
  $this->log("Preparing Data Step7: Skipping");
641
  return true;
642
  }
668
  }
669
 
670
  // Skip - Table does not exist
671
+ if (false === $this->tableExists($this->prefix . 'options')) {
672
  return true;
673
  }
674
 
675
  // Skip - Table is not selected or updated
676
+ if (!in_array($this->prefix . 'options', $this->tables)) {
677
  $this->log("Preparing Data Step8: Skipping");
678
  return true;
679
  }
703
 
704
  $this->log("Preparing Data Step9: Set staging site to noindex");
705
 
706
+ if (false === $this->tableExists($this->prefix . 'options')) {
707
  return true;
708
  }
709
 
710
  // Skip - Table is not selected or updated
711
+ if (!in_array($this->prefix . 'options', $this->tables)) {
712
  $this->log("Preparing Data Step9: Skipping");
713
  return true;
714
  }
735
  */
736
  protected function step10()
737
  {
738
+ $path = $this->options->destinationDir . "wp-config.php";
739
 
740
+ $this->log("Preparing Data Step10: Updating WP_HOME in wp-config.php to " . $this->getStagingSiteUrl());
741
 
742
  if (false === ($content = file_get_contents($path))) {
743
  $this->log("Preparing Data Step10: Failed to update WP_HOME in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR);
753
 
754
  $pattern = "/define\s*\(\s*['\"]WP_HOME['\"]\s*,\s*(.*)\s*\);.*/";
755
 
756
+ $replace = "define('WP_HOME','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
757
  //$replace .= " // Changed by WP-Staging";
758
 
759
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
778
  */
779
  protected function step11()
780
  {
781
+ $path = $this->options->destinationDir . "wp-config.php";
782
 
783
+ $this->log("Preparing Data Step11: Updating WP_SITEURL in wp-config.php to " . $this->getStagingSiteUrl());
784
 
785
  if (false === ($content = file_get_contents($path))) {
786
  $this->log("Preparing Data Step11: Failed to update WP_SITEURL in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR);
796
 
797
  $pattern = "/define\s*\(\s*['\"]WP_SITEURL['\"]\s*,\s*(.*)\s*\);.*/";
798
 
799
+ $replace = "define('WP_SITEURL','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
800
  //$replace .= " // Changed by WP-Staging";
801
 
802
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
822
  */
823
  protected function step12()
824
  {
825
+ $path = $this->options->destinationDir . "wp-config.php";
826
 
827
  $this->log("Preparing Data Step12: Updating WP_ALLOW_MULTISITE in wp-config.php to false");
828
 
840
 
841
  $pattern = "/define\s*\(\s*['\"]WP_ALLOW_MULTISITE['\"]\s*,\s*(.*)\s*\);.*/";
842
 
843
+ $replace = "define('WP_ALLOW_MULTISITE',false); // " . $matches[1];
844
  //$replace .= " // Changed by WP-Staging";
845
 
846
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
866
  */
867
  protected function step13()
868
  {
869
+ $path = $this->options->destinationDir . "wp-config.php";
870
 
871
  $this->log("Preparing Data Step13: Updating MULTISITE in wp-config.php to false");
872
 
884
 
885
  $pattern = "/define\s*\(\s*['\"]MULTISITE['\"]\s*,\s*(.*)\s*\);.*/";
886
 
887
+ $replace = "define('MULTISITE',false); // " . $matches[1];
888
  //$replace .= " // Changed by WP-Staging";
889
 
890
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
914
 
915
  $this->log("Data Crunching Step14: Updating active_plugins");
916
 
917
+ if (false === $this->tableExists($this->prefix . 'options')) {
918
+ $this->log('Data Crunching Step14: Fatal Error ' . $this->prefix . 'options does not exist');
919
+ $this->returnException('Data Crunching Step14: Fatal Error ' . $this->prefix . 'options does not exist');
920
  return false;
921
  }
922
 
923
  // Skip - Table is not selected or updated
924
+ if (!in_array($this->prefix . 'options', $this->tables)) {
925
  $this->log("Preparing Data Step14: Skipping");
926
  return true;
927
  }
942
  }
943
 
944
  $active_sitewide_plugins = unserialize($active_sitewide_plugins);
945
+ $active_plugins = unserialize($active_plugins);
946
 
947
  $all_plugins = array_merge($active_plugins, array_keys($active_sitewide_plugins));
948
 
951
 
952
  // Update active_plugins
953
  $update = $this->db->query(
954
+ "UPDATE {$this->prefix}options SET option_value = '" . serialize($all_plugins) . "' WHERE option_name = 'active_plugins'"
955
  );
956
 
957
  if (false === $update) {
972
  $this->log("Preparing Data Step15: Updating db prefix in {$this->prefix}options.");
973
 
974
  // Skip - Table does not exist
975
+ if (false === $this->tableExists($this->prefix . 'options')) {
976
  return true;
977
  }
978
 
979
  // Skip - Table is not selected or updated
980
+ if (!in_array($this->prefix . 'options', $this->tables)) {
981
  $this->log("Preparing Data Step4: Skipping");
982
  return true;
983
  }
996
 
997
  $where = "";
998
  foreach ($filters as $filter) {
999
+ $where .= " AND option_name <> '" . $filter . "'";
1000
  }
1001
 
1002
  $updateOptions = $this->db->query(
1003
  $this->db->prepare(
1004
+ "UPDATE IGNORE {$this->prefix}options SET option_name= replace(option_name, %s, %s) WHERE option_name LIKE %s" . $where, $this->productionDb->prefix, $this->prefix, $this->productionDb->prefix . "_%"
1005
  )
1006
  );
1007
 
1024
  $this->log("Preparing Data Step16: Updating upload_path {$this->prefix}options.");
1025
 
1026
  // Skip - Table does not exist
1027
+ if (false === $this->tableExists($this->prefix . 'options')) {
1028
  return true;
1029
  }
1030
 
1036
  }
1037
 
1038
  // Skip - Table is not selected or updated
1039
+ if (!in_array($this->prefix . 'options', $this->tables)) {
1040
  $this->log("Preparing Data Step16: Skipping");
1041
  return true;
1042
  }
1043
 
1044
+ $error = isset($this->db->last_error) ? 'Last error: ' . $this->db->last_error
1045
+ : '';
1046
 
1047
  $this->log("Updating upload_path in {$this->prefix}options. {$error}");
1048
 
1066
  */
1067
  protected function step17()
1068
  {
1069
+ $path = $this->options->destinationDir . "wp-config.php";
1070
 
1071
  $this->log("Preparing Data Step17: Set WP_CACHE in wp-config.php to false");
1072
 
1084
 
1085
  $pattern = "/define\s*\(\s*['\"]WP_CACHE['\"]\s*,\s*(.*)\s*\);.*/";
1086
 
1087
+ $replace = "define('WP_CACHE',false); // " . $matches[1];
1088
  //$replace.= " // Changed by WP-Staging";
1089
 
1090
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
1109
  */
1110
  protected function step18()
1111
  {
1112
+ $path = $this->options->destinationDir . "wp-config.php";
1113
  $this->log("Preparing Data Step18: Update UPLOADS constant in wp-config.php");
1114
  if (false === ($content = file_get_contents($path))) {
1115
  $this->log("Preparing Data Step18: Failed to get UPLOADS in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR);
1121
  if (!empty($matches[0])) {
1122
  $pattern = "/define\s*\(\s*'UPLOADS'\s*,\s*(.*)\s*\);/";
1123
 
1124
+ $replace = "define('UPLOADS', '" . $uploadFolder . "');";
1125
  $this->log("Preparing Data Step18: Change UPLOADS constant in wp-config.php to {$uploadFolder}.");
1126
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
1127
  $this->log("Preparing Data Step 18: Failed to change UPLOADS", Logger::TYPE_ERROR);
1134
  if (!empty($matches[0])) {
1135
  $matches[0];
1136
  $pattern = "/if\s*\(\s*\s*!\s*defined\s*\(\s*['\"]ABSPATH['\"]\s*(.*)\s*\)\s*\)/";
1137
+ $replace = "define('UPLOADS', '" . $uploadFolder . "'); \n" .
1138
  "if ( ! defined( 'ABSPATH' ) )";
1139
  $this->log("Preparing Data Step18: Change UPLOADS constant in wp-config.php to {$uploadFolder}.");
1140
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
1159
  */
1160
  protected function step19()
1161
  {
1162
+ $path = $this->options->destinationDir . "wp-config.php";
1163
 
1164
  $this->log("Preparing Data Step19: Change database credentials in wp-config.php");
1165
 
1177
 
1178
  $pattern = "/define\s*\(\s*'DB_NAME'\s*,\s*(.*)\s*\);.*/";
1179
 
1180
+ $replace = "define('DB_NAME','{$this->options->databaseDatabase}'); // " . $matches[1];
 
1181
 
1182
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
1183
  $this->log("Preparing Data: Failed to change DB_NAME", Logger::TYPE_ERROR);
1194
 
1195
  $pattern = "/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*(.*)\s*\);.*/";
1196
 
1197
+ $replace = "define('DB_USER','{$this->options->databaseUser}'); // " . $matches[1];
1198
  //$replace.= " // Changed by WP-Staging";
1199
 
1200
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
1212
 
1213
  $pattern = "/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*(.*)\s*\);.*/";
1214
 
1215
+ //We need to escape ',$ and \ for preg_replace. As the string will later be interpreted as a PHP string, we
1216
+ //need to escape \ twice.
1217
+ $escapedPassword = addcslashes(addcslashes($this->options->databasePassword, "\\"), "'$\\");
1218
+ $replace = "define('DB_PASSWORD','{$escapedPassword}');";
1219
 
1220
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
1221
  $this->log("Preparing Data: Failed to change DB_PASSWORD", Logger::TYPE_ERROR);
1232
 
1233
  $pattern = "/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*(.*)\s*\);.*/";
1234
 
1235
+ $replace = "define('DB_HOST','{$this->options->databaseServer}'); // " . $matches[1];
1236
  //$replace.= " // Changed by WP-Staging";
1237
 
1238
  if (null === ($content = preg_replace(array($pattern), $replace, $content))) {
1259
  protected function step20()
1260
  {
1261
 
1262
+ $table = $this->prefix . 'options';
1263
 
1264
  $siteurl = get_site_url();
1265
 
1282
 
1283
  /**
1284
  * Add option_name wpstg_execute and set it to true
1285
+ * This option is used to determine if the staging website has not been loaded initially for executing certain custom actions from \WPStaging\initActions()
1286
  * @return boolean
1287
  */
1288
  protected function step21()
1289
  {
1290
 
1291
+ $table = $this->prefix . 'options';
1292
 
1293
  // Skip - Table does not exist
1294
+ if (false === $this->tableExists($table)) {
1295
  return true;
1296
  }
1297
 
1308
  return true;
1309
  }
1310
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1311
 
1312
  /**
1313
  * Check if there is a multisite super administrator and add it to usermeta
1314
  * @return bool
1315
  */
1316
+ protected function step22()
1317
  {
1318
  $this->log("Preparing Data Step23: Add network administrators");
1319
 
1320
  // Skip - Table does not exist
1321
+ if (false === $this->tableExists($this->prefix . 'usermeta')) {
1322
  return true;
1323
  }
1324
 
1325
  // Skip - Table is not selected or updated
1326
+ if (!in_array($this->prefix . 'usermeta', $this->tables)) {
1327
  $this->log("Preparing Data Step23: Skipping");
1328
  return true;
1329
  }
1348
 
1349
  // Add new capability
1350
  $sql .= $this->db->prepare(
1351
+ "INSERT INTO `{$this->prefix}usermeta` ( `umeta_id`, `user_id`, `meta_key`, `meta_value` ) VALUES ( NULL , %s, %s, %s );\n", $userId, $this->prefix . 'capabilities', serialize(array(
1352
+ 'administrator' => true))
1353
  );
1354
  }
1355
  if (!empty($sql)) {
1374
  return true;
1375
  }
1376
 
1377
+ /**
1378
+ * Delete all listed staging sites from cloned site on initial cloning. Useful if a cloned site is cloned again. E.g. for dev > staging > production setup
1379
+ * @return bool
1380
+ */
1381
+ protected function step23(){
1382
+
1383
+ if ($this->options->mainJob === 'updating'){
1384
+ return true;
1385
+ }
1386
+
1387
+ if( false === $this->tableExists( $this->prefix . 'options' ) ) {
1388
+ return true;
1389
+ }
1390
+
1391
+ $step = "Preparing Data Step23:";
1392
+ $this->log( $step . " Reset wp staging site data" );
1393
+
1394
+ $result = $this->db->query(
1395
+ $this->db->prepare( "UPDATE {$this->prefix}options SET `option_value` = %s WHERE `option_name` = %s", serialize(array()), 'wpstg_existing_clones_beta'
1396
+ )
1397
+ );
1398
+
1399
+ if (false === $result){
1400
+ $this->log( $step . "Failed to reset wp staging site data." );
1401
+ }
1402
+
1403
+ return true;
1404
+ }
1405
+
1406
  /**
1407
  * Get relative path to the uploads media folder of multisite e.g.
1408
  * wp-content/uploads/sites/SITEID or old wordpress structure wp-content/blogs.dir/SITEID/files
1415
  $uploads = wp_upload_dir();
1416
  $basedir = $uploads['basedir'];
1417
  // Get relative upload path
1418
+ $relDir = str_replace(wpstg_replace_windows_directory_separator(ABSPATH), null, wpstg_replace_windows_directory_separator($basedir));
1419
  return $relDir;
1420
  }
1421
 
1433
 
1434
  $customSlug = str_replace(wpstg_replace_windows_directory_separator(\WPStaging\WPStaging::getWPpath()), '', wpstg_replace_windows_directory_separator($uploadPath));
1435
 
1436
+ $newUploadPath = wpstg_replace_windows_directory_separator(\WPStaging\WPStaging::getWPpath()) . $this->options->cloneDirectoryName . '/' . $customSlug;
1437
 
1438
  return $newUploadPath;
1439
  }
1450
  }
1451
 
1452
  if ($this->isSubDir()) {
1453
+ return trailingslashit($this->multisiteHomeDomain) . trailingslashit($this->getSubDir()) . $this->options->cloneDirectoryName;
1454
  }
1455
 
1456
  // Get the path to the main multisite without appending and trailingslash e.g. wordpress
1457
  $multisitePath = defined('PATH_CURRENT_SITE') ? PATH_CURRENT_SITE : '/';
1458
+ $url = rtrim($this->multisiteHomeDomain, '/\\') . $multisitePath . $this->options->cloneDirectoryName;
1459
 
1460
  return $url;
1461
  }
1469
  // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
1470
  // This is happening much more often than you would expect
1471
  $siteurl = preg_replace('#^https?://#', '', rtrim(get_option('siteurl'), '/'));
1472
+ $home = preg_replace('#^https?://#', '', rtrim(get_option('home'), '/'));
1473
 
1474
  if ($home !== $siteurl) {
1475
  return true;
1483
  */
1484
  protected function getSubDir()
1485
  {
1486
+ $home = get_option('home');
1487
  $siteurl = get_option('siteurl');
1488
 
1489
  if (empty($home) || empty($siteurl)) {
Backend/Modules/Jobs/Multisite/Database.php CHANGED
@@ -171,69 +171,6 @@ class Database extends JobExecutable {
171
  return $this->finishStep();
172
  }
173
 
174
- /**
175
- * Copy multisite global user table wp_users to wpstgX_users
176
- * @return bool
177
- */
178
- // private function copyWpUsers() {
179
- //// $strings = new Strings();
180
- //// $tableName = $this->db->base_prefix . 'users';
181
- //// $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->base_prefix, null, $tableName );
182
- //
183
- // $tableName = $this->db->base_prefix . 'users';
184
- // $newTableName = $this->getStagingPrefix() . 'users';
185
- //
186
- // $this->log( "DB Copy: Try to create table {$newTableName}" );
187
- //
188
- // // Drop table if necessary
189
- // $this->dropTable( $newTableName );
190
- //
191
- // // Save current job
192
- // $this->setJob( $newTableName );
193
- //
194
- // // Beginning of the job
195
- // if( !$this->startJob( $newTableName, $tableName ) ) {
196
- // return true;
197
- // }
198
- //
199
- // // Copy data
200
- // $this->copyData( $newTableName, $tableName );
201
- //
202
- // // Finish the step
203
- // return $this->finishStep();
204
- // }
205
-
206
- /**
207
- * Copy multisite global user table wp_usermeta to wpstgX_users
208
- * @return bool
209
- */
210
- // private function copyWpUsermeta() {
211
- //// $strings = new Strings();
212
- //// $tableName = $this->db->base_prefix . 'usermeta';
213
- //// $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->base_prefix, null, $tableName );
214
- // $tableName = $this->db->base_prefix . 'usermeta';
215
- // $newTableName = $this->getStagingPrefix() . 'usermeta';
216
- //
217
- // $this->log( "DB Copy: Try to create table {$newTableName}" );
218
- //
219
- //
220
- // // Drop table if necessary
221
- // $this->dropTable( $newTableName );
222
- //
223
- // // Save current job
224
- // $this->setJob( $newTableName );
225
- //
226
- // // Beginning of the job
227
- // if( !$this->startJob( $newTableName, $tableName ) ) {
228
- // return true;
229
- // }
230
- // // Copy data
231
- // $this->copyData( $newTableName, $tableName );
232
- //
233
- // // Finish the step
234
- // return $this->finishStep();
235
- // }
236
-
237
  /**
238
  * Copy data from old table to new table
239
  * @param string $new
171
  return $this->finishStep();
172
  }
173
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  /**
175
  * Copy data from old table to new table
176
  * @param string $new
Backend/Modules/Jobs/Multisite/DatabaseExternal.php CHANGED
@@ -3,7 +3,7 @@
3
  namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
 
5
  // No Direct Access
6
- if( !defined( "WPINC" ) ) {
7
  die;
8
  }
9
 
@@ -15,7 +15,8 @@ use WPStaging\Backend\Modules\Jobs\JobExecutable;
15
  * Class Database
16
  * @package WPStaging\Backend\Modules\Jobs
17
  */
18
- class DatabaseExternal extends JobExecutable {
 
19
 
20
  /**
21
  * @var int
@@ -42,35 +43,36 @@ class DatabaseExternal extends JobExecutable {
42
  /**
43
  * Initialize
44
  */
45
- public function initialize() {
46
- $this->db = WPStaging::getInstance()->get( "wpdb" );
 
47
  $this->stagingDb = $this->getExternalDBConnection();
48
  $this->getTables();
49
  // Add 2 more tables to total count because we need to copy two more tables from the main multisite installation wp_users and wp_usermeta
50
- $this->total = count( $this->options->tables ) + 2;
51
  $this->isFatalError();
52
- $this->strings = new Strings();
53
  }
54
 
55
  /**
56
  * Get external db object
57
  * @return mixed db | false
58
  */
59
- private function getExternalDBConnection() {
60
-
61
- $db = new \wpdb( $this->options->databaseUser, str_replace( "\\\\", "\\", $this->options->databasePassword ), $this->options->databaseDatabase, $this->options->databaseServer );
62
 
63
  // Can not connect to mysql
64
- if( !empty( $db->error->errors['db_connect_fail']['0'] ) ) {
65
- $this->returnException( "Can not connect to external database {$this->options->databaseDatabase}" );
66
  return false;
67
  }
68
 
69
  // Can not connect to database
70
- $db->select( $this->options->databaseDatabase );
71
- if( !$db->ready ) {
72
- $error = isset( $db->error->errors['db_select_fail'] ) ? $db->error->errors['db_select_fail'] : "Error: Can't select {database} Either it does not exist or you don't have privileges to access it.";
73
- $this->returnException( $error );
74
  exit;
75
  }
76
  return $db;
@@ -82,14 +84,14 @@ class DatabaseExternal extends JobExecutable {
82
  *
83
  * return void
84
  */
85
- private function getTables() {
86
-
87
- if( !in_array( $this->db->prefix . 'users', $this->options->tables ) ) {
88
- array_push( $this->options->tables, $this->db->prefix . 'users' );
89
  $this->saveOptions();
90
  }
91
- if( !in_array( $this->db->prefix . 'usermeta', $this->options->tables ) ) {
92
- array_push( $this->options->tables, $this->db->prefix . 'usermeta' );
93
  $this->saveOptions();
94
  }
95
  }
@@ -99,9 +101,10 @@ class DatabaseExternal extends JobExecutable {
99
  * and mainJob is not updating the clone
100
  * @return boolean
101
  */
102
- private function isFatalError() {
103
- $path = trailingslashit( $this->options->cloneDir );
104
- if( isset( $this->options->mainJob ) && $this->options->mainJob !== 'updating' && (is_dir( $path ) && !wpstg_is_empty_dir( $path ) ) ) {
 
105
  $this->returnException(" Can not continue for security purposes. Directory {$path} is not empty! Use FTP or a file manager plugin and make sure it does not contain any files. ");
106
  }
107
  return false;
@@ -111,7 +114,8 @@ class DatabaseExternal extends JobExecutable {
111
  * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
112
  * @return void
113
  */
114
- protected function calculateTotalSteps() {
 
115
  $this->options->totalSteps = ($this->total === 0) ? 1 : $this->total;
116
  }
117
 
@@ -120,27 +124,26 @@ class DatabaseExternal extends JobExecutable {
120
  * Returns false when over threshold limits are hit or when the job is done, true otherwise
121
  * @return bool
122
  */
123
- protected function execute() {
124
-
125
-
126
  // Over limits threshold
127
- if( $this->isOverThreshold() ) {
128
  // Prepare response and save current progress
129
- $this->prepareResponse( false, false );
130
  $this->saveOptions();
131
  return false;
132
  }
133
 
134
  // No more steps, finished
135
  if (!$this->isRunning() || $this->options->currentStep > $this->total) {
136
- $this->prepareResponse( true, false );
137
  return false;
138
  }
139
 
140
  // Copy table
141
- if( isset( $this->options->tables[$this->options->currentStep] ) && !$this->copyTable( $this->options->tables[$this->options->currentStep] ) ) {
142
  // Prepare Response
143
- $this->prepareResponse( false, false );
144
 
145
  // Not finished
146
  return true;
@@ -157,10 +160,9 @@ class DatabaseExternal extends JobExecutable {
157
  * Get new prefix for the staging site
158
  * @return string
159
  */
160
- private function getStagingPrefix() {
161
-
162
- $this->options->prefix = !empty( $this->options->databasePrefix ) ? $this->options->databasePrefix : $this->db->prefix;
163
-
164
  return $this->options->prefix;
165
  }
166
 
@@ -169,33 +171,34 @@ class DatabaseExternal extends JobExecutable {
169
  * @param string $tableName
170
  * @return bool
171
  */
172
- private function copyTable( $tableName ) {
 
173
 
174
- $tableName = is_object( $tableName ) ? $tableName->name : $tableName;
175
- $newTableName = $this->getStagingPrefix() . $this->strings->str_replace_first( $this->db->prefix, null, $tableName );
176
 
177
  // Get wp_users from main site
178
- if( 'users' === $this->strings->str_replace_first( $this->db->prefix, null, $tableName ) ) {
179
  $tableName = $this->db->base_prefix . 'users';
180
  }
181
  // Get wp_usermeta from main site
182
- if( 'usermeta' === $this->strings->str_replace_first( $this->db->prefix, null, $tableName ) ) {
183
  $tableName = $this->db->base_prefix . 'usermeta';
184
  }
185
 
186
  // Drop table if necessary
187
- $this->dropTable( $newTableName );
188
 
189
  // Save current job
190
- $this->setJob( $newTableName );
191
 
192
  // Beginning of the job
193
- if( !$this->startJob( $newTableName, $tableName ) ) {
194
  return true;
195
  }
196
 
197
  // Copy data
198
- $this->copyData( $newTableName, $tableName );
199
 
200
  // Finish the step
201
  return $this->finishStep();
@@ -205,13 +208,14 @@ class DatabaseExternal extends JobExecutable {
205
  * Set the job
206
  * @param string $table
207
  */
208
- private function setJob( $table ) {
209
- if( isset( $this->options->job->current ) ) {
 
210
  return;
211
  }
212
 
213
  $this->options->job->current = $table;
214
- $this->options->job->start = 0;
215
  }
216
 
217
  /**
@@ -220,57 +224,58 @@ class DatabaseExternal extends JobExecutable {
220
  * @param string $old
221
  * @return bool
222
  */
223
- private function startJob( $new, $old ) {
224
-
225
- if( $this->isExcludedTable( $new ) ) {
226
  return false;
227
  }
228
 
229
- if( 0 != $this->options->job->start ) {
230
  return true;
231
  }
232
 
233
  // Table does not exist
234
- $result = $this->db->query( "SHOW TABLES LIKE '{$old}'" );
235
- if( false === $result || 0 === $result ) {
236
- $this->log( "DB External Copy: Table {$this->options->databaseDatabase}.{$old} does not exists. Skip!" );
237
  return true;
238
  }
239
 
240
- $this->log( "DB External Copy: CREATE table {$this->options->databaseDatabase}.{$new}" );
241
- $sql = $this->getCreateStatement( $old );
242
 
243
  $search = '';
244
  // Get table 'wp_users' from main site
245
- if( 'users' === $this->strings->str_replace_first( $this->db->base_prefix, null, $old ) ) {
246
  $search = $this->db->prefix . 'users';
247
  }
248
  // Get table 'wp_usermeta' from main site
249
- if( 'usermeta' === $this->strings->str_replace_first( $this->db->base_prefix, null, $old ) ) {
250
  $search = $this->db->prefix . 'usermeta';
251
  }
252
 
253
  // Replace table prefix to the new one
254
- $sql = str_replace( "CREATE TABLE `{$search}`", "CREATE TABLE `{$new}`", $sql );
 
 
 
255
 
256
  // Make constraint unique to prevent error:(errno: 121 "Duplicate key on write or update")
257
  $sql = wpstg_unique_constraint($sql);
258
 
259
- $this->stagingDb->query( 'SET FOREIGN_KEY_CHECKS=0;' );
260
 
261
- if( false === $this->stagingDb->query( $sql ) ) {
262
- $this->returnException( "DB External Copy - Fatal Error: {$this->stagingDb->last_error} Query: {$sql}" );
263
  }
264
 
265
-
266
  // Count amount of rows to insert with next step
267
  $this->options->job->total = 0;
268
- $this->options->job->total = ( int ) $this->db->get_var( "SELECT COUNT(1) FROM `{$this->db->dbname}`.`{$old}`" );
269
 
270
- $this->log( "DB External Copy: Table {$old} contains {$this->options->job->total} rows " );
271
 
272
-
273
- if( 0 == $this->options->job->total ) {
274
  $this->finishStep();
275
  return false;
276
  }
@@ -281,30 +286,32 @@ class DatabaseExternal extends JobExecutable {
281
  /**
282
  * Get MySQL query create table
283
  *
284
- * @param string $table_name Table name
285
  * @return array
286
  */
287
- private function getCreateStatement( $tableName ) {
288
-
289
- // Get the CREATE statement
290
- $row = $this->db->get_results( "SHOW CREATE TABLE `{$tableName}`", ARRAY_A );
291
 
292
  // Convert prefix and entire table name to lowercase to prevent capitalization issues:
293
  // https://dev.mysql.com/doc/refman/5.7/en/identifier-case-sensitivity.html
294
  // @todo Testing! Can lead to issues with CONSTRAINTS
295
- $row[0] = str_replace($tableName, strtolower( $tableName ), $row[0]);
 
 
296
 
297
  // Query: Get wp_users from main site
298
- if( 'users' === $this->strings->str_replace_first( strtolower($this->db->base_prefix), null, $tableName ) ) {
299
- $row[0] = str_replace( $tableName, $this->db->prefix . 'users', $row[0] );
300
  }
301
  // Query: Get wp_usermeta from main site
302
- if( 'usermeta' === $this->strings->str_replace_first( strtolower($this->db->base_prefix), null, $tableName ) ) {
303
- $row[0] = str_replace( $tableName, $this->db->prefix . 'usermeta', $row[0] );
304
  }
305
 
306
  // Get create table
307
- if( isset( $row[0]['Create Table'] ) ) {
308
  return $row[0]['Create Table'];
309
  }
310
  return array();
@@ -315,39 +322,38 @@ class DatabaseExternal extends JobExecutable {
315
  * @param string $new
316
  * @param string $old
317
  */
318
- private function copyData( $new, $old ) {
319
-
320
  $rows = $this->options->job->start + $this->settings->queryLimit;
321
  $this->log(
322
- "DB External Copy: INSERT {$this->db->dbname}.{$old} as {$this->options->databaseDatabase}.{$new} from {$this->options->job->start} to {$rows} records"
323
  );
324
 
325
  $limitation = '';
326
 
327
- if( 0 < ( int ) $this->settings->queryLimit ) {
328
  $limitation = " LIMIT {$this->settings->queryLimit} OFFSET {$this->options->job->start}";
329
  }
330
 
331
  // Get data from production site
332
- $rows = $this->db->get_results( "SELECT * FROM `{$old}` {$limitation}", ARRAY_A );
333
 
334
  // Start transaction
335
- $this->stagingDb->query( 'SET autocommit=0;' );
336
- $this->stagingDb->query( 'SET FOREIGN_KEY_CHECKS=0;' );
337
- $this->stagingDb->query( 'START TRANSACTION;' );
338
 
339
  // Copy into staging site
340
- foreach ( $rows as $row ) {
341
- $escaped_values = $this->mysqlEscapeMimic( array_values( $row ) );
342
- $values = implode( "', '", $escaped_values );
343
- if (false === $this->stagingDb->query( "INSERT INTO `{$new}` VALUES ('{$values}')" )){
344
  $this->log("Can not insert data into table {$new}", \WPStaging\Utils\Logger::TYPE_INFO);
345
  }
346
- //$this->stagingDb->query( "INSERT INTO `{$new}` VALUES ('{$values}')" );
347
  }
348
  // Commit transaction
349
- $this->stagingDb->query( 'COMMIT;' );
350
- $this->stagingDb->query( 'SET autocommit=1;' );
351
 
352
 
353
  // Set new offset
@@ -358,15 +364,16 @@ class DatabaseExternal extends JobExecutable {
358
  * Mimics the mysql_real_escape_string function. Adapted from a post by 'feedr' on php.net.
359
  * @link http://php.net/manual/en/function.mysql-real-escape-string.php#101248
360
  * @access public
361
- * @param string $input The string to escape.
362
  * @return string
363
  */
364
- private function mysqlEscapeMimic( $input ) {
365
- if( is_array( $input ) ) {
366
- return array_map( __METHOD__, $input );
 
367
  }
368
- if( !empty( $input ) && is_string( $input ) ) {
369
- return str_replace( array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $input );
370
  }
371
 
372
  return $input;
@@ -377,19 +384,20 @@ class DatabaseExternal extends JobExecutable {
377
  * @param string $table
378
  * @return boolean
379
  */
380
- private function isExcludedTable( $table ) {
 
381
 
382
- $customTables = apply_filters( 'wpstg_clone_database_tables_exclude', array() );
383
  $defaultTables = array('blogs', 'blog_versions');
384
 
385
- $tables = array_merge( $customTables, $defaultTables );
386
 
387
  $excludedTables = array();
388
- foreach ( $tables as $key => $value ) {
389
  $excludedTables[] = $this->options->prefix . $value;
390
  }
391
 
392
- if( in_array( $table, $excludedTables ) ) {
393
  return true;
394
  }
395
  return false;
@@ -398,14 +406,15 @@ class DatabaseExternal extends JobExecutable {
398
  /**
399
  * Finish the step
400
  */
401
- private function finishStep() {
 
402
  // This job is not finished yet
403
- if( $this->options->job->total > $this->options->job->start ) {
404
  return false;
405
  }
406
 
407
  // Add it to cloned tables listing
408
- $this->options->clonedTables[] = isset( $this->options->tables[$this->options->currentStep] ) ? $this->options->tables[$this->options->currentStep] : false;
409
 
410
  // Reset job
411
  $this->options->job = new \stdClass();
@@ -417,18 +426,19 @@ class DatabaseExternal extends JobExecutable {
417
  * Drop table if necessary
418
  * @param string $new
419
  */
420
- private function dropTable( $new ) {
 
421
 
422
- $old = $this->stagingDb->get_var( $this->stagingDb->prepare( "SHOW TABLES LIKE %s", $new ) );
423
 
424
- if( !$this->shouldDropTable( $new, $old ) ) {
425
  return;
426
  }
427
 
428
- $this->log( "DB External Copy: {$new} already exists, dropping it first" );
429
- $this->stagingDb->query( "SET FOREIGN_KEY_CHECKS=0" );
430
- $this->stagingDb->query( "DROP TABLE {$new}" );
431
- $this->stagingDb->query( "SET FOREIGN_KEY_CHECKS=1" );
432
  }
433
 
434
  /**
@@ -437,13 +447,14 @@ class DatabaseExternal extends JobExecutable {
437
  * @param string $old
438
  * @return bool
439
  */
440
- private function shouldDropTable( $new, $old ) {
441
- if( $old === $new &&
442
- (
443
- !isset( $this->options->job->current ) ||
444
- !isset( $this->options->job->start ) ||
 
445
  0 == $this->options->job->start
446
- ) ) {
447
  return true;
448
  }
449
  return false;
3
  namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
 
5
  // No Direct Access
6
+ if (!defined("WPINC")) {
7
  die;
8
  }
9
 
15
  * Class Database
16
  * @package WPStaging\Backend\Modules\Jobs
17
  */
18
+ class DatabaseExternal extends JobExecutable
19
+ {
20
 
21
  /**
22
  * @var int
43
  /**
44
  * Initialize
45
  */
46
+ public function initialize()
47
+ {
48
+ $this->db = WPStaging::getInstance()->get("wpdb");
49
  $this->stagingDb = $this->getExternalDBConnection();
50
  $this->getTables();
51
  // Add 2 more tables to total count because we need to copy two more tables from the main multisite installation wp_users and wp_usermeta
52
+ $this->total = count($this->options->tables) + 2;
53
  $this->isFatalError();
54
+ $this->strings = new Strings();
55
  }
56
 
57
  /**
58
  * Get external db object
59
  * @return mixed db | false
60
  */
61
+ private function getExternalDBConnection()
62
+ {
63
+ $db = new \wpdb( $this->options->databaseUser, $this->options->databasePassword, $this->options->databaseDatabase, $this->options->databaseServer );
64
 
65
  // Can not connect to mysql
66
+ if (!empty($db->error->errors['db_connect_fail']['0'])) {
67
+ $this->returnException("Can not connect to external database {$this->options->databaseDatabase}");
68
  return false;
69
  }
70
 
71
  // Can not connect to database
72
+ $db->select($this->options->databaseDatabase);
73
+ if (!$db->ready) {
74
+ $error = isset($db->error->errors['db_select_fail']) ? $db->error->errors['db_select_fail'] : "Error: Can't select {database} Either it does not exist or you don't have privileges to access it.";
75
+ $this->returnException($error);
76
  exit;
77
  }
78
  return $db;
84
  *
85
  * return void
86
  */
87
+ private function getTables()
88
+ {
89
+ if (!in_array($this->db->prefix . 'users', $this->options->tables)) {
90
+ array_push($this->options->tables, $this->db->prefix . 'users');
91
  $this->saveOptions();
92
  }
93
+ if (!in_array($this->db->prefix . 'usermeta', $this->options->tables)) {
94
+ array_push($this->options->tables, $this->db->prefix . 'usermeta');
95
  $this->saveOptions();
96
  }
97
  }
101
  * and mainJob is not updating the clone
102
  * @return boolean
103
  */
104
+ private function isFatalError()
105
+ {
106
+ $path = trailingslashit($this->options->cloneDir);
107
+ if (isset($this->options->mainJob) && $this->options->mainJob !== 'updating' && (is_dir($path) && !wpstg_is_empty_dir($path))) {
108
  $this->returnException(" Can not continue for security purposes. Directory {$path} is not empty! Use FTP or a file manager plugin and make sure it does not contain any files. ");
109
  }
110
  return false;
114
  * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
115
  * @return void
116
  */
117
+ protected function calculateTotalSteps()
118
+ {
119
  $this->options->totalSteps = ($this->total === 0) ? 1 : $this->total;
120
  }
121
 
124
  * Returns false when over threshold limits are hit or when the job is done, true otherwise
125
  * @return bool
126
  */
127
+ protected function execute()
128
+ {
 
129
  // Over limits threshold
130
+ if ($this->isOverThreshold()) {
131
  // Prepare response and save current progress
132
+ $this->prepareResponse(false, false);
133
  $this->saveOptions();
134
  return false;
135
  }
136
 
137
  // No more steps, finished
138
  if (!$this->isRunning() || $this->options->currentStep > $this->total) {
139
+ $this->prepareResponse(true, false);
140
  return false;
141
  }
142
 
143
  // Copy table
144
+ if (isset($this->options->tables[$this->options->currentStep]) && !$this->copyTable($this->options->tables[$this->options->currentStep])) {
145
  // Prepare Response
146
+ $this->prepareResponse(false, false);
147
 
148
  // Not finished
149
  return true;
160
  * Get new prefix for the staging site
161
  * @return string
162
  */
163
+ private function getStagingPrefix()
164
+ {
165
+ $this->options->prefix = !empty($this->options->databasePrefix) ? $this->options->databasePrefix : $this->db->prefix;
 
166
  return $this->options->prefix;
167
  }
168
 
171
  * @param string $tableName
172
  * @return bool
173
  */
174
+ private function copyTable($tableName)
175
+ {
176
 
177
+ $tableName = is_object($tableName) ? $tableName->name : $tableName;
178
+ $newTableName = $this->getStagingPrefix() . $this->strings->str_replace_first($this->db->prefix, null, $tableName);
179
 
180
  // Get wp_users from main site
181
+ if ('users' === $this->strings->str_replace_first($this->db->prefix, null, $tableName)) {
182
  $tableName = $this->db->base_prefix . 'users';
183
  }
184
  // Get wp_usermeta from main site
185
+ if ('usermeta' === $this->strings->str_replace_first($this->db->prefix, null, $tableName)) {
186
  $tableName = $this->db->base_prefix . 'usermeta';
187
  }
188
 
189
  // Drop table if necessary
190
+ $this->dropTable($newTableName);
191
 
192
  // Save current job
193
+ $this->setJob($newTableName);
194
 
195
  // Beginning of the job
196
+ if (!$this->startJob($newTableName, $tableName)) {
197
  return true;
198
  }
199
 
200
  // Copy data
201
+ $this->copyData($newTableName, $tableName);
202
 
203
  // Finish the step
204
  return $this->finishStep();
208
  * Set the job
209
  * @param string $table
210
  */
211
+ private function setJob($table)
212
+ {
213
+ if (isset($this->options->job->current)) {
214
  return;
215
  }
216
 
217
  $this->options->job->current = $table;
218
+ $this->options->job->start = 0;
219
  }
220
 
221
  /**
224
  * @param string $old
225
  * @return bool
226
  */
227
+ private function startJob($new, $old)
228
+ {
229
+ if ($this->isExcludedTable($new)) {
230
  return false;
231
  }
232
 
233
+ if (0 != $this->options->job->start) {
234
  return true;
235
  }
236
 
237
  // Table does not exist
238
+ $result = $this->db->query("SHOW TABLES LIKE '{$old}'");
239
+ if (false === $result || 0 === $result) {
240
+ $this->log("DB External Copy: Table {$this->options->databaseDatabase}.{$old} does not exists. Skip!");
241
  return true;
242
  }
243
 
244
+ $this->log("DB External Copy: CREATE table {$this->options->databaseDatabase}.{$new}");
245
+ $sql = $this->getTableCreateStatement($old);
246
 
247
  $search = '';
248
  // Get table 'wp_users' from main site
249
+ if ('users' === $this->strings->str_replace_first($this->db->base_prefix, null, $old)) {
250
  $search = $this->db->prefix . 'users';
251
  }
252
  // Get table 'wp_usermeta' from main site
253
+ if ('usermeta' === $this->strings->str_replace_first($this->db->base_prefix, null, $old)) {
254
  $search = $this->db->prefix . 'usermeta';
255
  }
256
 
257
  // Replace table prefix to the new one
258
+ $sql = str_replace("CREATE TABLE `{$search}`", "CREATE TABLE `{$new}`", $sql);
259
+
260
+ // Fix missing underscore issue #251. Replace whole table name. Prevents bug where $old table prefix contains no underscore
261
+ $sql = str_replace("CREATE TABLE `{$old}`", "CREATE TABLE `{$new}`", $sql);
262
 
263
  // Make constraint unique to prevent error:(errno: 121 "Duplicate key on write or update")
264
  $sql = wpstg_unique_constraint($sql);
265
 
266
+ $this->stagingDb->query('SET FOREIGN_KEY_CHECKS=0;');
267
 
268
+ if (false === $this->stagingDb->query($sql)) {
269
+ $this->returnException("DB External Copy - Fatal Error: {$this->stagingDb->last_error} Query: {$sql}");
270
  }
271
 
 
272
  // Count amount of rows to insert with next step
273
  $this->options->job->total = 0;
274
+ $this->options->job->total = ( int )$this->db->get_var("SELECT COUNT(1) FROM `{$this->db->dbname}`.`{$old}`");
275
 
276
+ $this->log("DB External Copy: Table {$old} contains {$this->options->job->total} rows ");
277
 
278
+ if (0 == $this->options->job->total) {
 
279
  $this->finishStep();
280
  return false;
281
  }
286
  /**
287
  * Get MySQL query create table
288
  *
289
+ * @param string $table_name Table name
290
  * @return array
291
  */
292
+ private function getTableCreateStatement($tableName)
293
+ {
294
+ // Get the CREATE statement from production table
295
+ $row = $this->db->get_results("SHOW CREATE TABLE `{$tableName}`", ARRAY_A);
296
 
297
  // Convert prefix and entire table name to lowercase to prevent capitalization issues:
298
  // https://dev.mysql.com/doc/refman/5.7/en/identifier-case-sensitivity.html
299
  // @todo Testing! Can lead to issues with CONSTRAINTS
300
+ // Deprecated, We do not change the capitalization any longer!
301
+ // This prevented sites from proper cloning where prefix contains capitalized letters
302
+ //$row[0] = str_replace($tableName, strtolower($tableName), $row[0]);
303
 
304
  // Query: Get wp_users from main site
305
+ if ('users' === $this->strings->str_replace_first($this->db->base_prefix, null, $tableName)) {
306
+ $row[0] = str_replace($tableName, $this->db->prefix . 'users', $row[0]);
307
  }
308
  // Query: Get wp_usermeta from main site
309
+ if ('usermeta' === $this->strings->str_replace_first($this->db->base_prefix, null, $tableName)) {
310
+ $row[0] = str_replace($tableName, $this->db->prefix . 'usermeta', $row[0]);
311
  }
312
 
313
  // Get create table
314
+ if (isset($row[0]['Create Table'])) {
315
  return $row[0]['Create Table'];
316
  }
317
  return array();
322
  * @param string $new
323
  * @param string $old
324
  */
325
+ private function copyData($new, $old)
326
+ {
327
  $rows = $this->options->job->start + $this->settings->queryLimit;
328
  $this->log(
329
+ "DB External Copy: INSERT {$this->db->dbname}.{$old} as {$this->options->databaseDatabase}.{$new} from {$this->options->job->start} to {$rows} records"
330
  );
331
 
332
  $limitation = '';
333
 
334
+ if (0 < ( int )$this->settings->queryLimit) {
335
  $limitation = " LIMIT {$this->settings->queryLimit} OFFSET {$this->options->job->start}";
336
  }
337
 
338
  // Get data from production site
339
+ $rows = $this->db->get_results("SELECT * FROM `{$old}` {$limitation}", ARRAY_A);
340
 
341
  // Start transaction
342
+ $this->stagingDb->query('SET autocommit=0;');
343
+ $this->stagingDb->query('SET FOREIGN_KEY_CHECKS=0;');
344
+ $this->stagingDb->query('START TRANSACTION;');
345
 
346
  // Copy into staging site
347
+ foreach ($rows as $row) {
348
+ $escaped_values = $this->mysqlEscapeMimic(array_values($row));
349
+ $values = implode("', '", $escaped_values);
350
+ if (false === $this->stagingDb->query("INSERT INTO `{$new}` VALUES ('{$values}')")) {
351
  $this->log("Can not insert data into table {$new}", \WPStaging\Utils\Logger::TYPE_INFO);
352
  }
 
353
  }
354
  // Commit transaction
355
+ $this->stagingDb->query('COMMIT;');
356
+ $this->stagingDb->query('SET autocommit=1;');
357
 
358
 
359
  // Set new offset
364
  * Mimics the mysql_real_escape_string function. Adapted from a post by 'feedr' on php.net.
365
  * @link http://php.net/manual/en/function.mysql-real-escape-string.php#101248
366
  * @access public
367
+ * @param string $input The string to escape.
368
  * @return string
369
  */
370
+ private function mysqlEscapeMimic($input)
371
+ {
372
+ if (is_array($input)) {
373
+ return array_map(__METHOD__, $input);
374
  }
375
+ if (!empty($input) && is_string($input)) {
376
+ return str_replace(array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $input);
377
  }
378
 
379
  return $input;
384
  * @param string $table
385
  * @return boolean
386
  */
387
+ private function isExcludedTable($table)
388
+ {
389
 
390
+ $customTables = apply_filters('wpstg_clone_database_tables_exclude', array());
391
  $defaultTables = array('blogs', 'blog_versions');
392
 
393
+ $tables = array_merge($customTables, $defaultTables);
394
 
395
  $excludedTables = array();
396
+ foreach ($tables as $key => $value) {
397
  $excludedTables[] = $this->options->prefix . $value;
398
  }
399
 
400
+ if (in_array($table, $excludedTables)) {
401
  return true;
402
  }
403
  return false;
406
  /**
407
  * Finish the step
408
  */
409
+ private function finishStep()
410
+ {
411
  // This job is not finished yet
412
+ if ($this->options->job->total > $this->options->job->start) {
413
  return false;
414
  }
415
 
416
  // Add it to cloned tables listing
417
+ $this->options->clonedTables[] = isset($this->options->tables[$this->options->currentStep]) ? $this->options->tables[$this->options->currentStep] : false;
418
 
419
  // Reset job
420
  $this->options->job = new \stdClass();
426
  * Drop table if necessary
427
  * @param string $new
428
  */
429
+ private function dropTable($new)
430
+ {
431
 
432
+ $old = $this->stagingDb->get_var($this->stagingDb->prepare("SHOW TABLES LIKE %s", $new));
433
 
434
+ if (!$this->shouldDropTable($new, $old)) {
435
  return;
436
  }
437
 
438
+ $this->log("DB External Copy: {$new} already exists, dropping it first");
439
+ $this->stagingDb->query("SET FOREIGN_KEY_CHECKS=0");
440
+ $this->stagingDb->query("DROP TABLE {$new}");
441
+ $this->stagingDb->query("SET FOREIGN_KEY_CHECKS=1");
442
  }
443
 
444
  /**
447
  * @param string $old
448
  * @return bool
449
  */
450
+ private function shouldDropTable($new, $old)
451
+ {
452
+ if ($old === $new &&
453
+ (
454
+ !isset($this->options->job->current) ||
455
+ !isset($this->options->job->start) ||
456
  0 == $this->options->job->start
457
+ )) {
458
  return true;
459
  }
460
  return false;
Backend/Modules/Jobs/Multisite/Files.php CHANGED
@@ -311,6 +311,10 @@ class Files extends JobExecutable
311
  $src = fopen($src, 'r');
312
  $dest = fopen($dst, 'w');
313
 
 
 
 
 
314
  // Try first method:
315
  while (!feof($src)) {
316
  if (false === fwrite($dest, fread($src, $buffersize))) {
311
  $src = fopen($src, 'r');
312
  $dest = fopen($dst, 'w');
313
 
314
+ if (!$src || !$dest) {
315
+ return false;
316
+ }
317
+
318
  // Try first method:
319
  while (!feof($src)) {
320
  if (false === fwrite($dest, fread($src, $buffersize))) {
Backend/Modules/Jobs/PreserveDataFirstStep.php ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs;
4
+
5
+ use WPStaging\WPStaging;
6
+ use WPStaging\Service\Adapter\SourceDatabase;
7
+
8
+ /**
9
+ * Preserve staging sites in wpstg_existing_clones_beta in staging database while updating a site
10
+ * While cloning, copy an existing entry from staging site to wpstg_tmp_data and after cloning restore that data.
11
+ * Mainly used while an existing staging site is updated, not initially cloned
12
+ * @package WPStaging\Backend\Modules\Jobs
13
+ */
14
+
15
+ class PreserveDataFirstStep extends JobExecutable
16
+ {
17
+ /** @var object */
18
+ private $stagingDb;
19
+
20
+ /** @var object */
21
+ private $productionDb;
22
+
23
+ /** @var string */
24
+ private $stagingPrefix;
25
+
26
+ protected function calculateTotalSteps()
27
+ {
28
+ $this->options->totalSteps = 1;
29
+ }
30
+
31
+ /**
32
+ * @return object
33
+ */
34
+ public function start()
35
+ {
36
+ $db = new SourceDatabase($this->options);
37
+
38
+ $this->stagingDb = $db->getDatabase();
39
+
40
+ $this->productionDb = WPStaging::getInstance()->get("wpdb");
41
+
42
+ $this->stagingPrefix = $this->options->prefix;
43
+
44
+ if($db->isExternalDatabase()){
45
+ $this->stagingPrefix = $this->options->databasePrefix;
46
+ }
47
+
48
+ $this->run();
49
+
50
+ $this->saveOptions();
51
+
52
+ return ( object )$this->response;
53
+ }
54
+
55
+ /**
56
+ * @return bool
57
+ */
58
+ protected function execute()
59
+ {
60
+
61
+ $this->copyToTmp();
62
+
63
+ $this->prepareResponse(true, true);
64
+ return false;
65
+ }
66
+
67
+ /**
68
+ * @return bool
69
+ */
70
+
71
+ public function copyToTmp()
72
+ {
73
+ // Delete wpstg_tmp_data and reset it
74
+ $delete = $this->productionDb->query(
75
+ $this->productionDb->prepare("DELETE FROM " . $this->productionDb->prefix . "options WHERE `option_name` = %s", "wpstg_tmp_data"
76
+ )
77
+ );
78
+
79
+ if(!$this->tableExists($this->stagingPrefix . "options")){
80
+ return true;
81
+ }
82
+
83
+ // Get wpstg_existing_clones_beta from staging database
84
+ $result = $this->stagingDb->get_var(
85
+ $this->stagingDb->prepare(
86
+ "SELECT `option_value` FROM " . $this->stagingPrefix . "options WHERE `option_name` = %s", "wpstg_existing_clones_beta"
87
+ )
88
+ );
89
+
90
+ // Nothing to do
91
+ if (!$result){
92
+ return true;
93
+ }
94
+
95
+ // Insert wpstg_existing_clones_beta into wpstg_tmp_data in production database
96
+ $insert = $this->productionDb->query(
97
+ $this->productionDb->prepare(
98
+ "INSERT INTO `" . $this->productionDb->prefix . "options` ( `option_id`, `option_name`, `option_value`, `autoload` ) VALUES ( NULL , %s, %s, %s )", "wpstg_tmp_data", $result, "no"
99
+ )
100
+ );
101
+
102
+ if (false === $delete) {
103
+ $this->log("Preserve Data: Failed to delete wpstg_tmp_data");
104
+ }
105
+ if (false === $result) {
106
+ $this->log("Preserve Data: Failed to get wpstg_existing_clones_beta");
107
+ }
108
+ if (false === $insert) {
109
+ $this->log("Preserve Data: Failed to insert wpstg_existing_clones_beta to wpstg_tmp_data");
110
+ }
111
+ return true;
112
+ }
113
+
114
+ /**
115
+ * Check if table exists
116
+ * @param string $table
117
+ * @return boolean
118
+ */
119
+ private function tableExists($table)
120
+ {
121
+ return !($this->stagingDb->get_var("SHOW TABLES LIKE '{$table}'") != $table);
122
+ }
123
+
124
+
125
+ }
Backend/Modules/Jobs/PreserveDataSecondStep.php ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs;
4
+
5
+ use WPStaging\WPStaging;
6
+ use WPStaging\Service\Adapter\SourceDatabase;
7
+
8
+ /**
9
+ * Copy wpstg_tmp_data back to wpstg_existing_clones_beta after cloning with class::PreserveDataSecondStep
10
+ * @package WPStaging\Backend\Modules\Jobs
11
+ */
12
+
13
+ class PreserveDataSecondStep extends JobExecutable
14
+ {
15
+ /** @var object */
16
+ private $stagingDb;
17
+
18
+ /** @var object */
19
+ private $productionDb;
20
+
21
+ /** @var string */
22
+ private $stagingPrefix;
23
+
24
+ protected function calculateTotalSteps()
25
+ {
26
+ $this->options->totalSteps = 1;
27
+ }
28
+
29
+ /**
30
+ * @return object
31
+ */
32
+ public function start()
33
+ {
34
+ $this->run();
35
+
36
+ $this->saveOptions();
37
+
38
+ return ( object )$this->response;
39
+ }
40
+
41
+ /**
42
+ * @return bool
43
+ */
44
+ protected function execute()
45
+ {
46
+ $db = new SourceDatabase($this->options);
47
+
48
+ $this->stagingDb = $db->getDatabase();
49
+
50
+ $this->productionDb = WPStaging::getInstance()->get("wpdb");
51
+
52
+ $this->stagingPrefix = $this->options->prefix;
53
+
54
+ if($db->isExternalDatabase()){
55
+ $this->stagingPrefix = $this->options->databasePrefix;
56
+ }
57
+
58
+ $this->copyToStaging();
59
+
60
+ $this->prepareResponse(true, true);
61
+ return false;
62
+ }
63
+
64
+ /**
65
+ * @return bool
66
+ */
67
+
68
+ public function copyToStaging()
69
+ {
70
+ // Get wpstg_tmp_data from production database
71
+ $result = $this->productionDb->get_var(
72
+ $this->productionDb->prepare(
73
+ "SELECT `option_value` FROM " . $this->productionDb->prefix . "options WHERE `option_name` = %s", "wpstg_tmp_data"
74
+ )
75
+ );
76
+
77
+ // Nothing to do
78
+ if (!$result){
79
+ return true;
80
+ }
81
+
82
+ // Delete wpstg_tmp_data
83
+ $delete = $this->stagingDb->query(
84
+ $this->stagingDb->prepare("DELETE FROM " . $this->stagingPrefix . "options WHERE `option_name` = %s", "wpstg_existing_clones_beta"
85
+ )
86
+ );
87
+
88
+ // Insert wpstg_existing_clones_beta in staging database
89
+ $insert = $this->stagingDb->query(
90
+ $this->stagingDb->prepare(
91
+ "INSERT INTO `" . $this->stagingPrefix . "options` ( `option_id`, `option_name`, `option_value`, `autoload` ) VALUES ( NULL , %s, %s, %s )", "wpstg_existing_clones_beta", $result, "no"
92
+ )
93
+ );
94
+
95
+ if (false === $delete) {
96
+ $this->log("Preserve Data Second Step: Failed to delete wpstg_tmp_data");
97
+ }
98
+ if (false === $result) {
99
+ $this->log("Preserve Data Second Step: Failed to get wpstg_existing_clones_beta");
100
+ }
101
+ if (false === $insert) {
102
+ $this->log("Preserve Data Second Step: Failed to insert wpstg_tmp_data to wpstg_existing_clones_beta");
103
+ }
104
+ return true;
105
+ }
106
+
107
+
108
+ }
Backend/Modules/Jobs/Scan.php CHANGED
@@ -7,6 +7,7 @@ if( !defined( "WPINC" ) ) {
7
  die;
8
  }
9
 
 
10
  use WPStaging\WPStaging;
11
  use WPStaging\Utils\Directories;
12
  use WPStaging\Backend\Optimizer\Optimizer;
@@ -50,11 +51,12 @@ class Scan extends Job {
50
 
51
  /**
52
  * Start Module
53
- * @return $this
 
54
  */
55
  public function start() {
56
  // Basic Options
57
- $this->options->root = str_replace( array("\\", '/'), DIRECTORY_SEPARATOR, \WPStaging\WPStaging::getWPpath() );
58
  $this->options->existingClones = get_option( "wpstg_existing_clones_beta", array() );
59
  $this->options->current = null;
60
 
@@ -81,8 +83,7 @@ class Scan extends Job {
81
  $this->options->scannedDirectories = array();
82
 
83
  // Job
84
- $this->options->currentJob = "database";
85
- //$this->options->currentJob = "directories";
86
  $this->options->currentStep = 0;
87
  $this->options->totalSteps = 0;
88
 
@@ -109,7 +110,7 @@ class Scan extends Job {
109
 
110
  /**
111
  * Format bytes into human readable form
112
- * @param int $bytes
113
  * @param int $precision
114
  * @return string
115
  */
@@ -128,7 +129,7 @@ class Scan extends Job {
128
  }
129
 
130
  /**
131
- * @param null|string $directories
132
  * @param bool $forceDisabled
133
  * @return string
134
  */
@@ -142,7 +143,7 @@ class Scan extends Job {
142
 
143
  $output = '';
144
  foreach ( $directories as $name => $directory ) {
145
- // Not a directory, possibly a symlink, therefore we will skip it
146
  if( !is_array( $directory ) ) {
147
  continue;
148
  }
@@ -161,7 +162,7 @@ class Scan extends Job {
161
  $dataSize = isset( $data["size"] ) ? $data["size"] : '';
162
 
163
 
164
- // Select all wp core folders and their sub dirs.
165
  // Unselect all other folders (default setting)
166
  $isDisabled = ($name !== 'wp-admin' &&
167
  $name !== 'wp-includes' &&
@@ -216,8 +217,7 @@ class Scan extends Job {
216
 
217
 
218
  $data = array(
219
- //'freespace' => $this->formatSize( $freeSpace ),
220
- 'usedspace' => $this->formatSize( $this->getDirectorySizeInclSubdirs( \WPStaging\WPStaging::getWPpath() ) )
221
  );
222
 
223
  echo json_encode( $data );
@@ -230,13 +230,6 @@ class Scan extends Job {
230
  protected function getTables() {
231
  $wpDB = WPStaging::getInstance()->get( "wpdb" );
232
 
233
- // if( strlen( $wpDB->prefix ) > 0 ) {
234
- // //$prefix = str_replace('_', '', $wpDB->prefix);
235
- // $sql = "SHOW TABLE STATUS LIKE '{$wpDB->prefix}%'";
236
- // } else {
237
- // $sql = "SHOW TABLE STATUS";
238
- // }
239
-
240
  $sql = "SHOW TABLE STATUS";
241
 
242
  $tables = $wpDB->get_results( $sql );
@@ -248,27 +241,33 @@ class Scan extends Job {
248
  foreach ( $tables as $table ) {
249
 
250
  // Create array of unchecked tables
251
- if( !empty( $wpDB->prefix ) && 0 !== strpos( $table->Name, $wpDB->prefix ) ) {
 
 
 
252
  $this->options->excludedTables[] = $table->Name;
253
  }
254
 
255
-
256
- $currentTables[] = array(
257
- "name" => $table->Name,
258
- "size" => ($table->Data_length + $table->Index_length)
259
- );
 
 
 
260
  }
261
 
262
  $this->options->tables = json_decode( json_encode( $currentTables ) );
263
  }
264
 
 
265
  /**
266
  * Get directories and main meta data about'em recursively
267
  */
268
  protected function getDirectories() {
269
 
270
- $directories = new Iterators\RecursiveDirectoryIterator( \WPStaging\WPStaging::getWPpath() );
271
-
272
 
273
  foreach ( $directories as $directory ) {
274
  // Not a valid directory
@@ -290,13 +289,11 @@ class Scan extends Job {
290
 
291
  // Gather Custom Uploads Folder if there is one
292
  $this->getSubDirectories( $this->getUploadDir() );
293
-
294
- // Gather /sites/ or /blogs.dir/ folder if there is one (for multisites)
295
- $this->getSubDirectories( $this->getMuUploadSitesDir() );
296
  }
297
 
298
  /**
299
  * @param string $path
 
300
  */
301
  protected function getSubDirectories( $path ) {
302
 
@@ -308,7 +305,7 @@ class Scan extends Job {
308
  return false;
309
  }
310
 
311
- // IMPORTANT: If this is not used and a folder belongs to another user
312
  // DirectoryIterator() will throw a fatal error which can not be catched with is_readable()
313
  if( !opendir( $path ) ) {
314
  return false;
@@ -324,12 +321,13 @@ class Scan extends Job {
324
 
325
  $this->handleDirectory( $path );
326
  }
 
327
  }
328
 
329
  /**
330
  * Get Path from $directory
331
- * @param \SplFileInfo $directory
332
- * @return string|false
333
  */
334
  protected function getPath( $directory ) {
335
 
@@ -339,10 +337,10 @@ class Scan extends Job {
339
  * Prevents open base dir restriction fatal errors
340
  */
341
 
342
- if( strpos( $directory->getRealPath(), \WPStaging\WPStaging::getWPpath() ) !== 0 ) {
343
  return false;
344
  }
345
- $path = str_replace( \WPStaging\WPStaging::getWPpath(), null, $directory->getRealPath() );
346
 
347
  // Using strpos() for symbolic links as they could create nasty stuff in nix stuff for directory structures
348
  if( !$directory->isDir() || strlen( $path ) < 1 ) {
@@ -358,7 +356,7 @@ class Scan extends Job {
358
  */
359
  protected function handleDirectory( $path ) {
360
  $directoryArray = explode( DIRECTORY_SEPARATOR, $path );
361
- $total = is_array( $directoryArray ) || $directoryArray instanceof Countable ? count( $directoryArray ) : 0;
362
 
363
  if( $total < 1 ) {
364
  return;
@@ -379,12 +377,12 @@ class Scan extends Job {
379
  continue;
380
  }
381
 
382
- $fullPath = \WPStaging\WPStaging::getWPpath() . $path;
383
  $size = $this->getDirectorySize( $fullPath );
384
 
385
  $currentArray["metaData"] = array(
386
  "size" => $size,
387
- "path" => \WPStaging\WPStaging::getWPpath() . $path,
388
  );
389
  }
390
  }
@@ -407,7 +405,7 @@ class Scan extends Job {
407
  * @param string $dir
408
  * @return int
409
  */
410
- function getDirectorySizeInclSubdirs( $dir ) {
411
  $size = 0;
412
  foreach ( glob( rtrim( $dir, '/' ) . '/*', GLOB_NOSORT ) as $each ) {
413
  $size += is_file( $each ) ? filesize( $each ) : $this->getDirectorySizeInclSubdirs( $each );
@@ -415,45 +413,15 @@ class Scan extends Job {
415
  return $size;
416
  }
417
 
418
- /**
419
- * Get absolute WP uploads path e.g.
420
- * Multisites: /var/www/htdocs/example.com/wp-content/uploads/sites/1 or /var/www/htdocs/example.com/wp-content/blogs.dir/1/files
421
- * Single sites: /var/www/htdocs/example.com/wp-content/uploads
422
- * @return string
423
- */
424
- protected function getUploadDir() {
425
- $uploads = wp_upload_dir( null, false );
426
-
427
- $baseDir = wpstg_replace_windows_directory_separator( $uploads['basedir'] );
428
-
429
- // If multisite (and if not the main site in a post-MU network)
430
- if( is_multisite() && !( is_main_network() && is_main_site() && defined( 'MULTISITE' ) ) ) {
431
- // blogs.dir is used on WP 3.5 and lower
432
- if( false !== strpos( $baseDir, 'blogs.dir' ) ) {
433
- // remove this piece from the basedir: /blogs.dir/2/files
434
- $uploadDir = wpstg_replace_first_match( '/blogs.dir/' . get_current_blog_id() . '/files', null, $baseDir );
435
- $dir = wpstg_replace_windows_directory_separator( $uploadDir . '/blogs.dir' );
436
- } else {
437
- // remove this piece from the basedir: /sites/2
438
- $uploadDir = wpstg_replace_first_match( '/sites/' . get_current_blog_id(), null, $baseDir );
439
- $dir = wpstg_replace_windows_directory_separator( $uploadDir . '/sites' );
440
- }
441
-
442
-
443
- return $dir;
444
- }
445
- return $baseDir;
446
- }
447
 
448
  /**
449
- * Get absolute WP uploads path e.g.
450
  * Multisites: /var/www/htdocs/example.com/wp-content/uploads/sites/1 or /var/www/htdocs/example.com/wp-content/blogs.dir/1/files
451
  * Single sites: /var/www/htdocs/example.com/wp-content/uploads
452
  * @return string
453
  */
454
- protected function getMuUploadSitesDir() {
455
  $uploads = wp_upload_dir( null, false );
456
-
457
  $baseDir = wpstg_replace_windows_directory_separator( $uploads['basedir'] );
458
 
459
  // If multisite (and if not the main site in a post-MU network)
7
  die;
8
  }
9
 
10
+ use WPStaging\Command\Database\Snapshot\SnapshotHandler;
11
  use WPStaging\WPStaging;
12
  use WPStaging\Utils\Directories;
13
  use WPStaging\Backend\Optimizer\Optimizer;
51
 
52
  /**
53
  * Start Module
54
+ * @return $this|object
55
+ * @throws \Exception
56
  */
57
  public function start() {
58
  // Basic Options
59
+ $this->options->root = str_replace( array("\\", '/'), DIRECTORY_SEPARATOR, WPStaging::getWPpath() );
60
  $this->options->existingClones = get_option( "wpstg_existing_clones_beta", array() );
61
  $this->options->current = null;
62
 
83
  $this->options->scannedDirectories = array();
84
 
85
  // Job
86
+ $this->options->currentJob = "PreserveDataFirstStep";
 
87
  $this->options->currentStep = 0;
88
  $this->options->totalSteps = 0;
89
 
110
 
111
  /**
112
  * Format bytes into human readable form
113
+ * @param float $bytes
114
  * @param int $precision
115
  * @return string
116
  */
129
  }
130
 
131
  /**
132
+ * @param null|array $directories
133
  * @param bool $forceDisabled
134
  * @return string
135
  */
143
 
144
  $output = '';
145
  foreach ( $directories as $name => $directory ) {
146
+ // Not a directory, possibly a symlink, therefore we will skip it
147
  if( !is_array( $directory ) ) {
148
  continue;
149
  }
162
  $dataSize = isset( $data["size"] ) ? $data["size"] : '';
163
 
164
 
165
+ // Select all wp core folders and their sub dirs.
166
  // Unselect all other folders (default setting)
167
  $isDisabled = ($name !== 'wp-admin' &&
168
  $name !== 'wp-includes' &&
217
 
218
 
219
  $data = array(
220
+ 'usedspace' => $this->formatSize( $this->getDirectorySizeInclSubdirs( WPStaging::getWPpath() ) )
 
221
  );
222
 
223
  echo json_encode( $data );
230
  protected function getTables() {
231
  $wpDB = WPStaging::getInstance()->get( "wpdb" );
232
 
 
 
 
 
 
 
 
233
  $sql = "SHOW TABLE STATUS";
234
 
235
  $tables = $wpDB->get_results( $sql );
241
  foreach ( $tables as $table ) {
242
 
243
  // Create array of unchecked tables
244
+ // On the main website of a multisite installation, do not select network site tables beginning with wp_1_, wp_2_ etc.
245
+ // (On network sites, the correct tables are selected anyway)
246
+ if (( ! empty($wpDB->prefix) && 0 !== strpos($table->Name, $wpDB->prefix))
247
+ || (is_multisite() && is_main_site() && preg_match('/^wp_\d+_/', $table->Name))) {
248
  $this->options->excludedTables[] = $table->Name;
249
  }
250
 
251
+ if ((0 !== strpos($table->Name, SnapshotHandler::PREFIX_MANUAL))
252
+ && (0 !== strpos($table->Name, SnapshotHandler::PREFIX_AUTOMATIC))
253
+ && ($table->Comment !== "VIEW")) {
254
+ $currentTables[] = array(
255
+ "name" => $table->Name,
256
+ "size" => ($table->Data_length + $table->Index_length)
257
+ );
258
+ }
259
  }
260
 
261
  $this->options->tables = json_decode( json_encode( $currentTables ) );
262
  }
263
 
264
+
265
  /**
266
  * Get directories and main meta data about'em recursively
267
  */
268
  protected function getDirectories() {
269
 
270
+ $directories = new Iterators\RecursiveDirectoryIterator( WPStaging::getWPpath() );
 
271
 
272
  foreach ( $directories as $directory ) {
273
  // Not a valid directory
289
 
290
  // Gather Custom Uploads Folder if there is one
291
  $this->getSubDirectories( $this->getUploadDir() );
 
 
 
292
  }
293
 
294
  /**
295
  * @param string $path
296
+ * @return bool
297
  */
298
  protected function getSubDirectories( $path ) {
299
 
305
  return false;
306
  }
307
 
308
+ // IMPORTANT: If this is not used and a folder belongs to another user
309
  // DirectoryIterator() will throw a fatal error which can not be catched with is_readable()
310
  if( !opendir( $path ) ) {
311
  return false;
321
 
322
  $this->handleDirectory( $path );
323
  }
324
+ return false;
325
  }
326
 
327
  /**
328
  * Get Path from $directory
329
+ * @param string
330
+ * @return bool|string
331
  */
332
  protected function getPath( $directory ) {
333
 
337
  * Prevents open base dir restriction fatal errors
338
  */
339
 
340
+ if( strpos( $directory->getRealPath(), WPStaging::getWPpath() ) !== 0 ) {
341
  return false;
342
  }
343
+ $path = str_replace( WPStaging::getWPpath(), null, $directory->getRealPath() );
344
 
345
  // Using strpos() for symbolic links as they could create nasty stuff in nix stuff for directory structures
346
  if( !$directory->isDir() || strlen( $path ) < 1 ) {
356
  */
357
  protected function handleDirectory( $path ) {
358
  $directoryArray = explode( DIRECTORY_SEPARATOR, $path );
359
+ $total = is_array( $directoryArray ) || $directoryArray instanceof \Countable ? count( $directoryArray ) : 0;
360
 
361
  if( $total < 1 ) {
362
  return;
377
  continue;
378
  }
379
 
380
+ $fullPath = WPStaging::getWPpath() . $path;
381
  $size = $this->getDirectorySize( $fullPath );
382
 
383
  $currentArray["metaData"] = array(
384
  "size" => $size,
385
+ "path" => WPStaging::getWPpath() . $path,
386
  );
387
  }
388
  }
405
  * @param string $dir
406
  * @return int
407
  */
408
+ protected function getDirectorySizeInclSubdirs( $dir ) {
409
  $size = 0;
410
  foreach ( glob( rtrim( $dir, '/' ) . '/*', GLOB_NOSORT ) as $each ) {
411
  $size += is_file( $each ) ? filesize( $each ) : $this->getDirectorySizeInclSubdirs( $each );
413
  return $size;
414
  }
415
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
 
417
  /**
418
+ * Get absolute WP uploads path e.g.
419
  * Multisites: /var/www/htdocs/example.com/wp-content/uploads/sites/1 or /var/www/htdocs/example.com/wp-content/blogs.dir/1/files
420
  * Single sites: /var/www/htdocs/example.com/wp-content/uploads
421
  * @return string
422
  */
423
+ protected function getUploadDir() {
424
  $uploads = wp_upload_dir( null, false );
 
425
  $baseDir = wpstg_replace_windows_directory_separator( $uploads['basedir'] );
426
 
427
  // If multisite (and if not the main site in a post-MU network)
Backend/Modules/Jobs/SearchReplace.php CHANGED
@@ -9,8 +9,6 @@ if (!defined("WPINC")) {
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
@@ -44,7 +42,7 @@ class SearchReplace extends JobExecutable
44
 
45
  /**
46
  *
47
- * @var Obj
48
  */
49
  private $strings;
50
 
@@ -367,6 +365,7 @@ class SearchReplace extends JobExecutable
367
  'wpstg_existing_clones',
368
  'wpstg_settings',
369
  'wpstg_license_status',
 
370
  'siteurl',
371
  'home'
372
  );
9
 
10
  use WPStaging\WPStaging;
11
  use WPStaging\Utils\Strings;
 
 
12
  /**
13
  * Class Database
14
  * @package WPStaging\Backend\Modules\Jobs
42
 
43
  /**
44
  *
45
+ * @var object
46
  */
47
  private $strings;
48
 
365
  'wpstg_existing_clones',
366
  'wpstg_settings',
367
  'wpstg_license_status',
368
+ 'wpstg_tmp_data',
369
  'siteurl',
370
  'home'
371
  );
Backend/Modules/Jobs/SearchReplaceExternal.php CHANGED
@@ -371,6 +371,7 @@ class SearchReplaceExternal extends JobExecutable
371
  'wpstg_existing_clones',
372
  'wpstg_settings',
373
  'wpstg_license_status',
 
374
  'siteurl',
375
  'home'
376
  );
371
  'wpstg_existing_clones',
372
  'wpstg_settings',
373
  'wpstg_license_status',
374
+ 'wpstg_tmp_data',
375
  'siteurl',
376
  'home'
377
  );
Backend/Modules/SystemInfo.php CHANGED
@@ -208,7 +208,8 @@ class SystemInfo extends InjectionAware
208
  //$output .= PHP_EOL . PHP_EOL;
209
 
210
  $output .= $this->info("Plugin Pro Version:", get_option('wpstgpro_version', 'undefined'));
211
- $output .= $this->info("Plugin Free Version:", get_option('wpstg_version', 'undefined'));
 
212
  $output .= $this->info("Install Date:", get_option('wpstg_installDate', 'undefined'));
213
  $output .= $this->info("Upgraded from Pro:", get_option('wpstgpro_version_upgraded_from', 'undefined'));
214
  $output .= $this->info("Upgraded from Free:", get_option('wpstg_version_upgraded_from', 'undefined'));
208
  //$output .= PHP_EOL . PHP_EOL;
209
 
210
  $output .= $this->info("Plugin Pro Version:", get_option('wpstgpro_version', 'undefined'));
211
+ $output .= $this->info("Plugin Pro License Key:", get_option('wpstg_license_key'));
212
+ $output .= $this->info("Plugin Free Version:", get_option('wpstg_version', 'undefined'));
213
  $output .= $this->info("Install Date:", get_option('wpstg_installDate', 'undefined'));
214
  $output .= $this->info("Upgraded from Pro:", get_option('wpstgpro_version_upgraded_from', 'undefined'));
215
  $output .= $this->info("Upgraded from Free:", get_option('wpstg_version_upgraded_from', 'undefined'));
Backend/public/css/wpstg-admin.css CHANGED
@@ -960,7 +960,7 @@
960
  margin-bottom:4px;
961
  }
962
 
963
- .wpstg-report-email {
964
  width: 100%;
965
  font-weight: 400;
966
  font-size: .8rem;
960
  margin-bottom:4px;
961
  }
962
 
963
+ .wpstg-report-email, .wpstg-report-hosting-provider {
964
  width: 100%;
965
  font-weight: 400;
966
  font-size: .8rem;
Backend/public/js/wpstg-admin.js CHANGED
@@ -274,11 +274,35 @@ var WPStaging = (function ($) {
274
  $('#wpstg_clone_dir').attr('placeholder', path);
275
  $('#wpstg_clone_hostname').attr('placeholder', uri);
276
  })
 
 
 
 
 
 
 
 
 
277
  ;
278
 
279
  cloneActions();
280
  };
281
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  /**
283
  * Clone actions
284
  */
@@ -383,6 +407,12 @@ var WPStaging = (function ($) {
383
  $existingClones.children("img").remove();
384
 
385
  cache.get(".wpstg-loader").hide();
 
 
 
 
 
 
386
  },
387
  "HTML"
388
  );
@@ -427,10 +457,7 @@ var WPStaging = (function ($) {
427
 
428
  $workFlow.removeClass("loading").html(response);
429
 
430
- cache.get(".wpstg-current-step")
431
- .removeClass("wpstg-current-step")
432
- .next("li")
433
- .addClass("wpstg-current-step");
434
  },
435
  "HTML"
436
  );
@@ -546,6 +573,11 @@ var WPStaging = (function ($) {
546
  var $this = $(this);
547
  var isScan = false;
548
 
 
 
 
 
 
549
  if ($this.data("action") === "wpstg_update") {
550
  // Update Clone - confirmed
551
  if (!confirm("STOP! This will overwrite your staging site with all selected data from the live site! This should be used only if you want to clone again your production site. Are you sure you want to do this? \n\nMake sure to exclude all tables and folders which you do not want to overwrite, first! \n\nDo not necessarily cancel the updating process! This can break your staging site. \n\n\Make sure you have a backop of your staging website before you proceed.")) {
@@ -602,10 +634,11 @@ var WPStaging = (function ($) {
602
  // Styling of elements
603
  $workFlow.removeClass("loading").html(response);
604
 
605
- cache.get(".wpstg-current-step")
606
- .removeClass("wpstg-current-step")
607
- .next("li")
608
- .addClass("wpstg-current-step");
 
609
 
610
  // Start cloning
611
  that.startCloning();
@@ -761,6 +794,8 @@ var WPStaging = (function ($) {
761
  },
762
  "HTML"
763
  );
 
 
764
  };
765
 
766
  /**
@@ -1241,6 +1276,10 @@ var WPStaging = (function ($) {
1241
  if (response.job === 'SearchReplace') {
1242
  cache.get("#wpstg-progress-db").css('background-color', '#3bc36b');
1243
  cache.get("#wpstg-progress-db").html('1. Database');
 
 
 
 
1244
  cache.get("#wpstg-progress-sr").width(response.percentage * 0.1 + '%').html(response.percentage + '%');
1245
  cache.get("#wpstg-processing-status").html(response.percentage.toFixed(0) + '%' + ' - Step 2 of 4 Preparing Database Data...');
1246
  }
@@ -1248,25 +1287,36 @@ var WPStaging = (function ($) {
1248
  if (response.job === 'directories') {
1249
  cache.get("#wpstg-progress-sr").css('background-color', '#3bc36b');
1250
  cache.get("#wpstg-progress-sr").html('2. Data');
 
 
1251
  cache.get("#wpstg-progress-dirs").width(response.percentage * 0.1 + '%').html(response.percentage + '%');
1252
  cache.get("#wpstg-processing-status").html(response.percentage.toFixed(0) + '%' + ' - Step 3 of 4 Getting files...');
1253
  }
1254
  if (response.job === 'files') {
1255
  cache.get("#wpstg-progress-dirs").css('background-color', '#3bc36b');
1256
  cache.get("#wpstg-progress-dirs").html('3. Files');
 
 
1257
  cache.get("#wpstg-progress-files").width(response.percentage * 0.6 + '%').html(response.percentage + '%');
1258
  cache.get("#wpstg-processing-status").html(response.percentage.toFixed(0) + '%' + ' - Step 4 of 4 Copy files...');
1259
  }
1260
  if (response.job === 'finish') {
1261
  cache.get("#wpstg-progress-files").css('background-color', '#3bc36b');
1262
  cache.get("#wpstg-progress-files").html('4. Copy Files');
 
 
1263
  cache.get("#wpstg-processing-status").html(response.percentage.toFixed(0) + '%' + ' - Cloning Process Finished');
1264
  }
1265
  }
1266
-
1267
-
1268
  });
1269
 
 
 
 
 
 
 
 
1270
  /**
1271
  * Initiation
1272
  * @type {Function}
@@ -1689,6 +1739,7 @@ jQuery(document).ready(function ($) {
1689
 
1690
  var spinner = self.next();
1691
  var email = $('.wpstg-report-email').val();
 
1692
  var message = $('.wpstg-report-description').val();
1693
  var syslog = $('.wpstg-report-syslog').is(':checked');
1694
  var terms = $('.wpstg-report-terms').is(':checked');
@@ -1705,6 +1756,7 @@ jQuery(document).ready(function ($) {
1705
  'action': 'wpstg_send_report',
1706
  'nonce': wpstg.nonce,
1707
  'wpstg_email': email,
 
1708
  'wpstg_message': message,
1709
  'wpstg_syslog': +syslog,
1710
  'wpstg_terms': +terms
274
  $('#wpstg_clone_dir').attr('placeholder', path);
275
  $('#wpstg_clone_hostname').attr('placeholder', uri);
276
  })
277
+ .on('input', '#wpstg_clone_hostname', function () {
278
+ if ($(this).val() === "" || validateTargetHost()) {
279
+ $('#wpstg_clone_hostname_error').remove();
280
+ return;
281
+ }
282
+ if (!validateTargetHost() && !$('#wpstg_clone_hostname_error').length) {
283
+ $('#wpstg-clone-directory tr:last-of-type').after('<tr><td>&nbsp;</td><td><p id="wpstg_clone_hostname_error" style="color: red;">&nbsp;Invalid host name. Please provide it in a format like http://example.com</p></td></tr>');
284
+ }
285
+ })
286
  ;
287
 
288
  cloneActions();
289
  };
290
 
291
+ /* @returns {boolean} */
292
+ var validateTargetHost = function () {
293
+ var the_domain = $('#wpstg_clone_hostname').val();
294
+
295
+ if (the_domain === "") {
296
+ return true;
297
+ }
298
+
299
+ var reg = /^http(s)?:\/\/.*$/;
300
+ if (reg.test(the_domain) === false) {
301
+ return false;
302
+ }
303
+ return true;
304
+ };
305
+
306
  /**
307
  * Clone actions
308
  */
407
  $existingClones.children("img").remove();
408
 
409
  cache.get(".wpstg-loader").hide();
410
+
411
+ $('html, body').animate({
412
+ //This logic is meant to be a "scrollBottom"
413
+ scrollTop: $("#wpstg-remove-clone").offset().top - $(window).height() +
414
+ $("#wpstg-remove-clone").height() + 10
415
+ }, 1000);
416
  },
417
  "HTML"
418
  );
457
 
458
  $workFlow.removeClass("loading").html(response);
459
 
460
+ that.switchStep(2);
 
 
 
461
  },
462
  "HTML"
463
  );
573
  var $this = $(this);
574
  var isScan = false;
575
 
576
+ if($('#wpstg_clone_hostname').length && !validateTargetHost()) {
577
+ $('#wpstg_clone_hostname').focus();
578
+ return false;
579
+ }
580
+
581
  if ($this.data("action") === "wpstg_update") {
582
  // Update Clone - confirmed
583
  if (!confirm("STOP! This will overwrite your staging site with all selected data from the live site! This should be used only if you want to clone again your production site. Are you sure you want to do this? \n\nMake sure to exclude all tables and folders which you do not want to overwrite, first! \n\nDo not necessarily cancel the updating process! This can break your staging site. \n\n\Make sure you have a backop of your staging website before you proceed.")) {
634
  // Styling of elements
635
  $workFlow.removeClass("loading").html(response);
636
 
637
+ if ($this.data("action") === "wpstg_scanning") {
638
+ that.switchStep(2);
639
+ } else if ($this.data("action") === "wpstg_cloning" || $this.data("action") === "wpstg_update") {
640
+ that.switchStep(3);
641
+ }
642
 
643
  // Start cloning
644
  that.startCloning();
794
  },
795
  "HTML"
796
  );
797
+
798
+ that.switchStep(1);
799
  };
800
 
801
  /**
1276
  if (response.job === 'SearchReplace') {
1277
  cache.get("#wpstg-progress-db").css('background-color', '#3bc36b');
1278
  cache.get("#wpstg-progress-db").html('1. Database');
1279
+ //Assumption: All previous steps are done.
1280
+ //This avoids bugs where some steps are skipped and the progress bar is incomplete as a result
1281
+ cache.get("#wpstg-progress-db").width('20%');
1282
+
1283
  cache.get("#wpstg-progress-sr").width(response.percentage * 0.1 + '%').html(response.percentage + '%');
1284
  cache.get("#wpstg-processing-status").html(response.percentage.toFixed(0) + '%' + ' - Step 2 of 4 Preparing Database Data...');
1285
  }
1287
  if (response.job === 'directories') {
1288
  cache.get("#wpstg-progress-sr").css('background-color', '#3bc36b');
1289
  cache.get("#wpstg-progress-sr").html('2. Data');
1290
+ cache.get("#wpstg-progress-sr").width('10%');
1291
+
1292
  cache.get("#wpstg-progress-dirs").width(response.percentage * 0.1 + '%').html(response.percentage + '%');
1293
  cache.get("#wpstg-processing-status").html(response.percentage.toFixed(0) + '%' + ' - Step 3 of 4 Getting files...');
1294
  }
1295
  if (response.job === 'files') {
1296
  cache.get("#wpstg-progress-dirs").css('background-color', '#3bc36b');
1297
  cache.get("#wpstg-progress-dirs").html('3. Files');
1298
+ cache.get("#wpstg-progress-dirs").width('10%');
1299
+
1300
  cache.get("#wpstg-progress-files").width(response.percentage * 0.6 + '%').html(response.percentage + '%');
1301
  cache.get("#wpstg-processing-status").html(response.percentage.toFixed(0) + '%' + ' - Step 4 of 4 Copy files...');
1302
  }
1303
  if (response.job === 'finish') {
1304
  cache.get("#wpstg-progress-files").css('background-color', '#3bc36b');
1305
  cache.get("#wpstg-progress-files").html('4. Copy Files');
1306
+ cache.get("#wpstg-progress-files").width('60%');
1307
+
1308
  cache.get("#wpstg-processing-status").html(response.percentage.toFixed(0) + '%' + ' - Cloning Process Finished');
1309
  }
1310
  }
 
 
1311
  });
1312
 
1313
+ that.switchStep = function (step) {
1314
+ cache.get(".wpstg-current-step")
1315
+ .removeClass("wpstg-current-step");
1316
+ cache.get(".wpstg-step" + step)
1317
+ .addClass("wpstg-current-step");
1318
+ }
1319
+
1320
  /**
1321
  * Initiation
1322
  * @type {Function}
1739
 
1740
  var spinner = self.next();
1741
  var email = $('.wpstg-report-email').val();
1742
+ var hosting_provider = $('.wpstg-report-hosting-provider').val();
1743
  var message = $('.wpstg-report-description').val();
1744
  var syslog = $('.wpstg-report-syslog').is(':checked');
1745
  var terms = $('.wpstg-report-terms').is(':checked');
1756
  'action': 'wpstg_send_report',
1757
  'nonce': wpstg.nonce,
1758
  'wpstg_email': email,
1759
+ 'wpstg_provider': hosting_provider,
1760
  'wpstg_message': message,
1761
  'wpstg_syslog': +syslog,
1762
  'wpstg_terms': +terms
Backend/views/_main/report-issue.php CHANGED
@@ -1,15 +1,18 @@
1
  <div class="wpstg-report-issue-form">
2
  <div class="wpstg-field">
3
- <input placeholder="Enter your email address..." type="email" id="wpstg-report-email" class="wpstg-report-email">
4
  </div>
5
  <div class="wpstg-field">
6
- <textarea rows="3" id="wpstg-report-description" class="wpstg-report-description" placeholder="Describe your issue here..."></textarea>
 
 
 
7
  </div>
8
  <div class="wpstg-field wpstg-report-privacy-policy">
9
  <label for="wpstg-report-syslog">
10
  <input type="checkbox" class="wpstg-report-syslog" id="wpstg-report-syslog">
11
  <?php echo sprintf(
12
- __('Optional: Submit the <a href="%s" target="_blank">System Log</a>. This helps us to resolve your technical issues.','wp-staging'),
13
  admin_url().'admin.php?page=wpstg-tools&tab=system_info'
14
  ); ?>
15
  </label>
@@ -21,7 +24,7 @@
21
  </label>
22
  </div>
23
  <div class="wpstg-field">
24
- <div class="wpstg-buttons">
25
  <button type="submit" id="wpstg-report-submit" class="wpstg-form-submit button-primary wpstg-button">
26
  <?php _e( 'Submit', 'wp-staging' ); ?>
27
  </button>
@@ -30,4 +33,4 @@
30
  <div class="wpstg-clear"></div>
31
  </div>
32
  </div>
33
- </div>
1
  <div class="wpstg-report-issue-form">
2
  <div class="wpstg-field">
3
+ <input placeholder="Your email address..." type="email" id="wpstg-report-email" class="wpstg-report-email">
4
  </div>
5
  <div class="wpstg-field">
6
+ <input placeholder="Your hosting provider...(optional)" type="text" id="wpstg-report-hosting-provider" class="wpstg-report-hosting-provider">
7
+ </div>
8
+ <div class="wpstg-field">
9
+ <textarea rows="3" id="wpstg-report-description" class="wpstg-report-description" placeholder="Describe your issue here! Optionally, include the login credentials to your WordPress admin so we can help you faster."></textarea>
10
  </div>
11
  <div class="wpstg-field wpstg-report-privacy-policy">
12
  <label for="wpstg-report-syslog">
13
  <input type="checkbox" class="wpstg-report-syslog" id="wpstg-report-syslog">
14
  <?php echo sprintf(
15
+ __('Optional: Submit the <a href="%s" target="_blank">System Log</a> and your WordPress debug log. This helps us to resolve your technical issues.','wp-staging'),
16
  admin_url().'admin.php?page=wpstg-tools&tab=system_info'
17
  ); ?>
18
  </label>
24
  </label>
25
  </div>
26
  <div class="wpstg-field">
27
+ <div class="wpstg-buttons">
28
  <button type="submit" id="wpstg-report-submit" class="wpstg-form-submit button-primary wpstg-button">
29
  <?php _e( 'Submit', 'wp-staging' ); ?>
30
  </button>
33
  <div class="wpstg-clear"></div>
34
  </div>
35
  </div>
36
+ </div>
Backend/views/clone/ajax/scan.php CHANGED
@@ -112,9 +112,8 @@
112
  </div>
113
  </div>
114
 
115
- <div style="line-height: 40px;">
116
- <strong>Important:</strong><a href="#" id="wpstg-check-space"><?php _e( 'Check required disk space', 'wp-staging' ); ?></a><span class="wpstg-loader" style="margin-left:6px;vertical-align:middle;"></span>
117
- </div>
118
 
119
  <button type="button" class="wpstg-prev-step-link wpstg-link-btn wpstg-blue-primary wpstg-button">
120
  <?php _e( "Back", "wp-staging" ) ?>
112
  </div>
113
  </div>
114
 
115
+ <strong>Important:</strong><a href="#" id="wpstg-check-space"><?php _e( 'Check required disk space', 'wp-staging' ); ?></a>
116
+ <p></p>
 
117
 
118
  <button type="button" class="wpstg-prev-step-link wpstg-link-btn wpstg-blue-primary wpstg-button">
119
  <?php _e( "Back", "wp-staging" ) ?>
Backend/views/clone/ajax/single-overview.php CHANGED
@@ -41,7 +41,13 @@
41
  $cloneDir = !empty( $data['path'] ) ? __( "Directory: <span class='wpstg-bold'>" . $data['path'], "wp-staging" ) . '</span> ' : 'Directory: ';
42
  $url = !empty( $data['url'] ) ? __(sprintf('URL: <a href="%s" target="_blank"><span class="wpstg-bold">%1$s</span></a>', $data['url']), "wp-staging") : 'URL: ';
43
  $datetime = !empty( $data['datetime'] ) ? __( "Updated: <span>" . date( "D, d M Y H:i:s T", $data['datetime'] ), "wp-staging" ) . '</span> ' : '&nbsp;&nbsp;&nbsp;';
44
- $status = !empty( $data['status'] ) && $data['status'] !== 'finished' ? "Status: <span class='wpstg-bold' style='color:#ffc2c2;'>" . $data['status'] . "</span>" : '&nbsp;&nbsp;&nbsp;';
 
 
 
 
 
 
45
 
46
  echo $dbname;
47
  echo '</br>';
41
  $cloneDir = !empty( $data['path'] ) ? __( "Directory: <span class='wpstg-bold'>" . $data['path'], "wp-staging" ) . '</span> ' : 'Directory: ';
42
  $url = !empty( $data['url'] ) ? __(sprintf('URL: <a href="%s" target="_blank"><span class="wpstg-bold">%1$s</span></a>', $data['url']), "wp-staging") : 'URL: ';
43
  $datetime = !empty( $data['datetime'] ) ? __( "Updated: <span>" . date( "D, d M Y H:i:s T", $data['datetime'] ), "wp-staging" ) . '</span> ' : '&nbsp;&nbsp;&nbsp;';
44
+ $statusTooltip = "This clone is incomplete and does not work. Clone or update it again! \n\n".
45
+ "Important: Keep the browser open until the cloning is finished. \n".
46
+ "It will not proceed if your browser is not open.\n\n".
47
+ "If you have an unstable internet connection and cloning breaks due to that, clone again only the folders wp-admin, wp-includes, and all database tables.\n\n".
48
+ "That will not take much time. Then, you can proceed with the wp-content folder that usually needs the most disk space. ".
49
+ "If it interrupts again, at least it will not break the existing staging site again, and you can repeat and resume the last operation.";
50
+ $status = !empty( $data['status'] ) && $data['status'] !== 'finished' ? "Status: <span class='wpstg-bold' style='color:#ffc2c2;' title='$statusTooltip'>" . $data['status'] . "</span>" : '&nbsp;&nbsp;&nbsp;';
51
 
52
  echo $dbname;
53
  echo '</br>';
Backend/views/clone/single-site/index.php CHANGED
@@ -1,13 +1,13 @@
1
  <ul id="wpstg-steps">
2
- <li class="wpstg-current-step">
3
  <span class="wpstg-step-num">1</span>
4
  <?php echo __( "Overview", "wp-staging" ) ?>
5
  </li>
6
- <li>
7
  <span class="wpstg-step-num">2</span>
8
  <?php echo __( "Scanning", "wp-staging" ) ?>
9
  </li>
10
- <li>
11
  <span class="wpstg-step-num">3</span>
12
  <?php echo __( "Cloning", "wp-staging" ) ?>
13
  </li>
@@ -31,4 +31,4 @@
31
  <img src="<?php echo WPSTG_PLUGIN_URL . 'Backend/public/img/wpstaging-banner200x400-tryout.gif'; ?>">
32
  </a>
33
  </div>
34
- <?php } ?>
1
  <ul id="wpstg-steps">
2
+ <li class="wpstg-current-step wpstg-step1">
3
  <span class="wpstg-step-num">1</span>
4
  <?php echo __( "Overview", "wp-staging" ) ?>
5
  </li>
6
+ <li class="wpstg-step2">
7
  <span class="wpstg-step-num">2</span>
8
  <?php echo __( "Scanning", "wp-staging" ) ?>
9
  </li>
10
+ <li class="wpstg-step3">
11
  <span class="wpstg-step-num">3</span>
12
  <?php echo __( "Cloning", "wp-staging" ) ?>
13
  </li>
31
  <img src="<?php echo WPSTG_PLUGIN_URL . 'Backend/public/img/wpstaging-banner200x400-tryout.gif'; ?>">
32
  </a>
33
  </div>
34
+ <?php } ?>
Command/Database/Export/ExportCommand.php ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // TODO PHP7.x; declare(strict_types=1);
4
+ // TODO PHP7.x; return types & type-hints
5
+
6
+ namespace WPStaging\Command\Database\Export;
7
+
8
+ use Exception;
9
+ use WPStaging\Manager\Database\Mysqldump\Mysqldump;
10
+ use Psr\Log\LoggerInterface;
11
+ use WPStaging\Manager\Database\TableManager;
12
+ use WPStaging\Service\Command\CommandInterface;
13
+
14
+ class ExportCommand implements CommandInterface
15
+ {
16
+ const FORMAT_GZIP = Mysqldump::GZIP;
17
+ const FORMAT_BZIP2 = Mysqldump::BZIP2;
18
+ const FORMAT_SQL = Mysqldump::NONE;
19
+
20
+ /** @var ExportDto */
21
+ private $dto;
22
+
23
+ /** @var TableManager */
24
+ private $tableManager;
25
+
26
+ /** @var LoggerInterface */
27
+ private $logger;
28
+
29
+ public function __construct(ExportDto $dto, TableManager $tableManager = null, LoggerInterface $logger = null)
30
+ {
31
+ $this->dto = $dto;
32
+ $this->logger = $logger;
33
+ $this->tableManager = $tableManager;
34
+ }
35
+
36
+ /**
37
+ * @inheritDoc
38
+ * @throws Exception
39
+ */
40
+ public function execute()
41
+ {
42
+ $this->generateSqlFile();
43
+ }
44
+
45
+ /**
46
+ * @throws Exception
47
+ */
48
+ protected function generateSqlFile()
49
+ {
50
+ $dumper = new Mysqldump(
51
+ sprintf('mysql:host=%s;port=%d;dbname=%s', $this->dto->getHost(), $this->dto->getPort(), $this->dto->getName()),
52
+ $this->dto->getUsername(),
53
+ $this->dto->getPassword(),
54
+ [
55
+ 'compress' => $this->dto->getFormat(),
56
+ 'include-tables' => $this->getIncludeTables(),
57
+ 'version' => $this->dto->getVersion(),
58
+ ]
59
+ );
60
+
61
+ try {
62
+ $dumper->start($this->dto->getFullPath());
63
+ return $this->dto->getFullPath();
64
+ }
65
+ catch (Exception $e) {
66
+ $this->logger->alert($e->getMessage());
67
+ throw new ExportException(sprintf(
68
+ 'Failed to export database. Database Name: %s | Prefix: %s | File Path: %s | %s',
69
+ $this->dto->getUsername() .':' . $this->dto->getPassword() . '@' . $this->dto->getHost() . ':' . $this->dto->getPort() . ' - ' . $this->dto->getName(),
70
+ $this->dto->getPrefix(),
71
+ $this->dto->getFullPath(),
72
+ $e->getMessage()
73
+ ));
74
+ }
75
+ }
76
+
77
+ protected function getIncludeTables()
78
+ {
79
+ $tables = $this->tableManager->findStartsWith($this->dto->getPrefix());
80
+ $data = [];
81
+ foreach ($tables as $table) {
82
+ $data[] = $table->getName();
83
+ }
84
+ return $data;
85
+ }
86
+ }
Command/Database/Export/ExportDto.php ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // TODO PHP7.x; declare(strict_types=1);
4
+ // TODO PHP7.x; return types & type-hints
5
+
6
+ namespace WPStaging\Command\Database\Export;
7
+
8
+ use DateTime;
9
+ use Exception;
10
+ use WPStaging\Service\Adapter\Database;
11
+ use WPStaging\Service\Traits\HydrateTrait;
12
+
13
+ class ExportDto
14
+ {
15
+ use HydrateTrait;
16
+
17
+ const DEFAULT_FORMAT = ExportCommand::FORMAT_GZIP;
18
+ const DEFAULT_PORT = 3306;
19
+
20
+ /** @var string */
21
+ private $host;
22
+
23
+ /** @var int */
24
+ private $port;
25
+
26
+ /** @var string */
27
+ private $name;
28
+
29
+ /** @var string */
30
+ private $username;
31
+
32
+ /** @var string */
33
+ private $password;
34
+
35
+ /** @var string */
36
+ private $prefix;
37
+
38
+ /** @var string */
39
+ private $format;
40
+
41
+ /** @var string|null */
42
+ private $directory;
43
+
44
+ /** @var string|null */
45
+ private $fullPath;
46
+
47
+ /** @var string */
48
+ private $version;
49
+
50
+ /**
51
+ * @return string
52
+ */
53
+ public function getHost()
54
+ {
55
+ if (!$this->host) {
56
+ return DB_HOST;
57
+ }
58
+ return $this->host;
59
+ }
60
+
61
+ /**
62
+ * @param string $host
63
+ */
64
+ public function setHost($host)
65
+ {
66
+ $this->host = $host;
67
+ }
68
+
69
+ /**
70
+ * @return int
71
+ */
72
+ public function getPort()
73
+ {
74
+ if ($this->port) {
75
+ return $this->port;
76
+ }
77
+
78
+ $parts = explode(':', DB_HOST);
79
+ if (isset($parts[1]) && 0 < (int) $parts[1]) {
80
+ return (int) $parts[1];
81
+ }
82
+
83
+ return self::DEFAULT_PORT;
84
+ }
85
+
86
+ /**
87
+ * @param int $port
88
+ */
89
+ public function setPort($port)
90
+ {
91
+ $this->port = (int) $port;
92
+ }
93
+
94
+ /**
95
+ * @return string
96
+ */
97
+ public function getName()
98
+ {
99
+ if (!$this->name) {
100
+ return DB_NAME;
101
+ }
102
+ return $this->name;
103
+ }
104
+
105
+ /**
106
+ * @param string $name
107
+ */
108
+ public function setName($name)
109
+ {
110
+ $this->name = $name;
111
+ }
112
+
113
+ /**
114
+ * @return string
115
+ */
116
+ public function getUsername()
117
+ {
118
+ if (!$this->username) {
119
+ return DB_USER;
120
+ }
121
+ return $this->username;
122
+ }
123
+
124
+ /**
125
+ * @param string $username
126
+ */
127
+ public function setUsername($username)
128
+ {
129
+ $this->username = $username;
130
+ }
131
+
132
+ /**
133
+ * @return string
134
+ */
135
+ public function getPassword()
136
+ {
137
+ if (!$this->password) {
138
+ return DB_PASSWORD;
139
+ }
140
+ return $this->password;
141
+ }
142
+
143
+ /**
144
+ * @param string $password
145
+ */
146
+ public function setPassword($password)
147
+ {
148
+ $this->password = $password;
149
+ }
150
+
151
+ /**
152
+ * @return string
153
+ */
154
+ public function getPrefix()
155
+ {
156
+ if (!$this->prefix) {
157
+ return (new Database)->getPrefix();
158
+ }
159
+ return $this->prefix;
160
+ }
161
+
162
+ /**
163
+ * @param string $prefix
164
+ */
165
+ public function setPrefix($prefix)
166
+ {
167
+ $this->prefix = $prefix;
168
+ }
169
+
170
+ /**
171
+ * @return string
172
+ */
173
+ public function getFormat()
174
+ {
175
+ return $this->format ?: self::DEFAULT_FORMAT;
176
+ }
177
+
178
+ /**
179
+ * @param string $format
180
+ */
181
+ public function setFormat($format)
182
+ {
183
+ $this->format = $format;
184
+ }
185
+
186
+ public function provideFileFormat()
187
+ {
188
+ switch($this->getFormat()) {
189
+ case ExportCommand::FORMAT_GZIP:
190
+ return 'gz';
191
+ case ExportCommand::FORMAT_SQL:
192
+ return 'sql';
193
+ case ExportCommand::FORMAT_BZIP2:
194
+ return 'bz2';
195
+ }
196
+
197
+ return $this->format;
198
+ }
199
+
200
+ /**
201
+ * @return string|null
202
+ */
203
+ public function getDirectory()
204
+ {
205
+ return $this->directory;
206
+ }
207
+
208
+ /**
209
+ * @param string|null $directory
210
+ */
211
+ public function setDirectory($directory)
212
+ {
213
+ $this->directory = $directory;
214
+ }
215
+
216
+ /**
217
+ * @return string
218
+ * @throws Exception
219
+ */
220
+ public function getFullPath()
221
+ {
222
+ if ($this->fullPath) {
223
+ return $this->fullPath;
224
+ }
225
+
226
+ $fileName = sprintf(
227
+ '%s_%s_%s.%s',
228
+ rtrim($this->prefix, '_-'),
229
+ (new DateTime)->format('Y-m-d'),
230
+ md5(mt_rand()),
231
+ $this->provideFileFormat()
232
+ );
233
+
234
+ $this->setFullPath($this->getDirectory() . $fileName);
235
+ return $this->fullPath;
236
+ }
237
+
238
+ /**
239
+ * @param string|null $fullPath
240
+ */
241
+ public function setFullPath($fullPath)
242
+ {
243
+ $this->fullPath = $fullPath;
244
+ }
245
+
246
+ /**
247
+ * @return string
248
+ */
249
+ public function getVersion()
250
+ {
251
+ return $this->version;
252
+ }
253
+
254
+ /**
255
+ * @param string $version
256
+ */
257
+ public function setVersion($version)
258
+ {
259
+ $this->version = $version;
260
+ }
261
+ }
Command/Database/Export/ExportException.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // TODO PHP7.x; declare(strict_types=1);
4
+ // TODO PHP7.x; return types & type-hints
5
+
6
+ namespace WPStaging\Command\Database\Export;
7
+
8
+ use RuntimeException;
9
+
10
+ class ExportException extends RuntimeException
11
+ {
12
+
13
+ }
Command/Database/Export/ExportHandler.php ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // TODO PHP7.x; declare(strict_types=1);
4
+ // TODO PHP7.x; return types & type-hints
5
+
6
+ namespace WPStaging\Command\Database\Export;
7
+
8
+ use Exception;
9
+ use Psr\Log\LoggerInterface;
10
+ use WPStaging\Manager\Database\TableManager;
11
+ use WPStaging\Manager\FileSystem\DirectoryManager;
12
+ use WPStaging\Plugin;
13
+
14
+ // TODO Check PDO extension installation: \WPStaging\Component\Snapshot\AjaxExport:38
15
+ class ExportHandler
16
+ {
17
+ const EXPORT_DIRNAME = 'snapshots/export';
18
+
19
+ /** @var Plugin */
20
+ private $plugin;
21
+
22
+ /** @var DirectoryManager */
23
+ private $directoryManager;
24
+
25
+ /** @var LoggerInterface */
26
+ private $logger;
27
+
28
+ /**
29
+ * @param Plugin $plugin
30
+ * @param DirectoryManager $directoryManager
31
+ * @param LoggerInterface $logger
32
+ */
33
+ public function __construct(Plugin $plugin, DirectoryManager $directoryManager, LoggerInterface $logger)
34
+ {
35
+ $this->plugin = $plugin;
36
+ $this->directoryManager = $directoryManager;
37
+ $this->logger = $logger;
38
+ }
39
+
40
+ /**
41
+ * @param string $prefix
42
+ *
43
+ * @return string
44
+ * @throws Exception
45
+ */
46
+ public function handle($prefix)
47
+ {
48
+ $dto = (new ExportDto)->hydrate([
49
+ 'prefix' => $prefix,
50
+ 'directory' => $this->generatePath(),
51
+ 'format' => $this->provideFormat(),
52
+ 'version' => $this->plugin->getVersion(),
53
+ ]);
54
+
55
+ $command = new ExportCommand($dto, new TableManager, $this->logger);
56
+ $command->execute();
57
+ return $dto->getFullPath();
58
+ }
59
+
60
+ /**
61
+ * @return string
62
+ */
63
+ public function generatePath()
64
+ {
65
+ $dirname = sprintf(
66
+ '%s/%s/',
67
+ $this->plugin->getDomain(),
68
+ self::EXPORT_DIRNAME
69
+ );
70
+ return $this->directoryManager->provideCustomUploadsDirectory($dirname);
71
+ }
72
+
73
+ private function provideFormat()
74
+ {
75
+ if (!function_exists('gzwrite')) {
76
+ return ExportCommand::FORMAT_SQL;
77
+ }
78
+ return ExportCommand::FORMAT_GZIP;
79
+ }
80
+ }
Command/Database/Snapshot/AbstractSnapshotCommand.php ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // TODO PHP7.x; declare(strict_types=1);
4
+ // TODO PHP7.x; type-hints & return types
5
+ // TODO PHP7.1; constant visibility
6
+
7
+ namespace WPStaging\Command\Database\Snapshot;
8
+
9
+ use WPStaging\Entity\Snapshot;
10
+ use WPStaging\Manager\Database\TableDto;
11
+ use WPStaging\Manager\Database\TableManager;
12
+ use WPStaging\Repository\SnapshotRepository;
13
+ use WPStaging\Service\Adapter\Database;
14
+ use WPStaging\Service\Collection\Collection;
15
+ use WPStaging\Service\Collection\OptionCollection;
16
+ use WPStaging\Service\Command\CommandInterface;
17
+
18
+ abstract class AbstractSnapshotCommand implements CommandInterface
19
+ {
20
+
21
+ /** @var Database */
22
+ protected $database;
23
+
24
+ /** @var SnapshotRepository */
25
+ protected $repository;
26
+
27
+ /** @var SnapshotDto */
28
+ protected $dto;
29
+
30
+ /** @var Snapshot[]|OptionCollection */
31
+ protected $snapshots;
32
+
33
+ abstract protected function saveSnapshots();
34
+
35
+ public function __construct(Database $database, SnapshotRepository $repository, SnapshotDto $dto = null)
36
+ {
37
+ $this->database = $database;
38
+ $this->repository = $repository;
39
+ $this->setDto($dto);
40
+
41
+ $this->snapshots = $this->repository->findAll()? : new OptionCollection(Snapshot::class);
42
+ }
43
+
44
+ /**
45
+ * @param SnapshotDto $dto
46
+ */
47
+ public function setDto(SnapshotDto $dto = null)
48
+ {
49
+ if (!$dto) {
50
+ return;
51
+ }
52
+
53
+ $this->dto = $dto;
54
+
55
+ if (!$this->dto->getSourcePrefix()) {
56
+ $this->dto->setSourcePrefix($this->database->getPrefix());
57
+ }
58
+ }
59
+
60
+ /**
61
+ * @throws SnapshotCommandException
62
+ */
63
+ protected function validateSnapshot()
64
+ {
65
+ if ($this->database->getPrefix() === $this->dto->getTargetPrefix()) {
66
+ throw new SnapshotCommandException('You are trying to process production tables!');
67
+ }
68
+
69
+ if ($this->dto->getSourcePrefix() === $this->dto->getTargetPrefix()) {
70
+ throw new SnapshotCommandException('You are trying to process same tables!');
71
+ }
72
+ }
73
+ }
Command/Database/Snapshot/CreateSnapshotCommand.php ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Command\Database\Snapshot;
4
+
5
+ use DateTime;
6
+ use WPStaging\Entity\Snapshot;
7
+ use WPStaging\Manager\Database\TableDto;
8
+ use WPStaging\Manager\Database\TableManager;
9
+
10
+ class CreateSnapshotCommand extends AbstractSnapshotCommand
11
+ {
12
+
13
+ /** @noinspection PhpUnhandledExceptionInspection */
14
+ public function execute()
15
+ {
16
+ $this->validateSnapshot();
17
+
18
+ if (null === $this->dto->getStep()) {
19
+ $this->executeAll();
20
+ return;
21
+ }
22
+
23
+ $this->executeStep();
24
+ }
25
+
26
+ protected function executeAll()
27
+ {
28
+ foreach($this->findTables() as $table) {
29
+ $this->backupTable($table);
30
+ }
31
+
32
+ $this->saveSnapshots();
33
+ }
34
+
35
+ /**
36
+ * @noinspection PhpUnhandledExceptionInspection
37
+ */
38
+ protected function executeStep()
39
+ {
40
+ /** @var array $tables */
41
+ $tables = $this->findTables();
42
+
43
+ if (!isset($tables[$this->dto->getStep()])) {
44
+ throw new SnapshotCommandException('failed to get tables with prefix: ' . $this->dto->getSourcePrefix());
45
+ }
46
+
47
+ $this->backupTable($tables[$this->dto->getStep()]);
48
+
49
+ // This was the last step, save the snapshot
50
+ if (count($tables) === $this->dto->getStep() + 1) {
51
+ $this->saveSnapshots();
52
+ }
53
+ }
54
+
55
+ /**
56
+ * @return TableDto[]|null
57
+ */
58
+ protected function findTables()
59
+ {
60
+ $tables = (new TableManager)->findStartsWith($this->dto->getSourcePrefix());
61
+ if (!$tables) {
62
+ return null;
63
+ }
64
+ return $tables->toArray();
65
+ }
66
+
67
+ protected function backupTable(TableDto $tableDto)
68
+ {
69
+ $newTableName = $this->dto->getTargetPrefix() . str_replace($this->dto->getSourcePrefix(), null, $tableDto->getName());
70
+ $this->database->exec('OPTIMIZE TABLE '. $tableDto->getName());
71
+ $this->database->exec('DROP TABLE IF EXISTS '. $newTableName);
72
+ $this->database->exec('CREATE TABLE ' . $newTableName . ' LIKE ' . $tableDto->getName());
73
+ $this->database->exec('INSERT INTO ' . $newTableName . ' SELECT * FROM ' . $tableDto->getName());
74
+ $this->database->exec('OPTIMIZE TABLE ' . $newTableName);
75
+ }
76
+
77
+ protected function saveSnapshots()
78
+ {
79
+ if (!$this->dto->isSaveRecords()) {
80
+ return;
81
+ }
82
+
83
+ /** @var Snapshot $snapshot */
84
+ $snapshot = $this->snapshots->findById($this->dto->getTargetPrefix());
85
+
86
+ if ($snapshot) {
87
+ $snapshot->setUpdatedAt(new DateTime);
88
+ $this->repository->save($this->snapshots);
89
+ return;
90
+ }
91
+
92
+ $snapshot = new Snapshot;
93
+ $snapshot->setId($this->dto->getTargetPrefix());
94
+ $snapshot->setName($this->dto->getName());
95
+ $snapshot->setNotes($this->dto->getNotes());
96
+ $snapshot->setCreatedAt(new DateTime);
97
+
98
+ $this->snapshots->attach($snapshot);
99
+ $this->repository->save($this->snapshots);
100
+ }
101
+ }
Command/Database/Snapshot/DeleteSnapshotCommand.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Command\Database\Snapshot;
4
+
5
+ use WPStaging\Manager\Database\TableManager;
6
+ use WPStaging\Manager\SnapshotManager;
7
+
8
+ class DeleteSnapshotCommand extends AbstractSnapshotCommand
9
+ {
10
+ /**
11
+ * @param bool force deletes snapshot from listing even if there are no tables in db
12
+ * @throws SnapshotCommandException
13
+ */
14
+ public function execute()
15
+ {
16
+ $this->validateSnapshot();
17
+
18
+ $tables = (new TableManager)->findStartsWith($this->dto->getTargetPrefix());
19
+
20
+ if (!$tables && $tables->count() < 1) {
21
+ throw new SnapshotCommandException('DeleteSnapshot tables do not exist: ', $this->dto->getTargetPrefix());
22
+ }
23
+
24
+ $this->database->exec('SET FOREIGN_KEY_CHECKS = 0');
25
+ foreach($tables as $table) {
26
+ $this->database->exec('DROP TABLE IF EXISTS ' . $table->getName());
27
+ }
28
+ $this->database->exec('SET FOREIGN_KEY_CHECKS = 1');
29
+
30
+ $this->saveSnapshots();
31
+ }
32
+
33
+ protected function saveSnapshots()
34
+ {
35
+ (new SnapshotManager)->deleteByPrefix($this->dto->getTargetPrefix());
36
+ }
37
+
38
+ /** @noinspection PhpUnhandledExceptionInspection */
39
+ protected function validateSnapshot()
40
+ {
41
+ parent::validateSnapshot();
42
+ if (!$this->snapshots->doesIncludeId($this->dto->getTargetPrefix())) {
43
+ throw new SnapshotCommandException(
44
+ 'DeleteSnapshot prefix does not exist: ' . $this->dto->getTargetPrefix()
45
+ );
46
+ }
47
+ }
48
+ }
Command/Database/Snapshot/SnapshotCommandException.php ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Command\Database\Snapshot;
4
+
5
+ use Exception;
6
+
7
+ class SnapshotCommandException extends Exception
8
+ {}
Command/Database/Snapshot/SnapshotDto.php ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // TODO PHP7.x; declare(strict_type=1);
4
+ // TODO PHP7.x; type hints & return types
5
+
6
+ namespace WPStaging\Command\Database\Snapshot;
7
+
8
+ class SnapshotDto
9
+ {
10
+ const SNAPSHOT_DEFAULT_NAME = 'snapshot';
11
+
12
+ /** @var string */
13
+ private $name;
14
+
15
+ /** @var string|null */
16
+ private $notes;
17
+
18
+ /** @var string */
19
+ private $targetPrefix;
20
+
21
+ /** @var string|null */
22
+ private $sourcePrefix;
23
+
24
+ /** @var int|null */
25
+ private $step;
26
+
27
+ /** @var bool */
28
+ private $isSaveRecords = true;
29
+
30
+ /**
31
+ * @return string
32
+ */
33
+ public function getName()
34
+ {
35
+ return $this->name ?: self::SNAPSHOT_DEFAULT_NAME;
36
+ }
37
+
38
+ /**
39
+ * @param string|null $name
40
+ */
41
+ public function setName($name)
42
+ {
43
+ $this->name = $name;
44
+ }
45
+
46
+ /**
47
+ * @return string|null
48
+ */
49
+ public function getNotes()
50
+ {
51
+ return $this->notes ?: null;
52
+ }
53
+
54
+ /**
55
+ * @param string|null $notes
56
+ */
57
+ public function setNotes($notes)
58
+ {
59
+ $this->notes = $notes;
60
+ }
61
+
62
+ /**
63
+ * @return string
64
+ */
65
+ public function getTargetPrefix()
66
+ {
67
+ return $this->targetPrefix;
68
+ }
69
+
70
+ /**
71
+ * @param string $targetPrefix
72
+ */
73
+ public function setTargetPrefix($targetPrefix)
74
+ {
75
+ $this->targetPrefix = $targetPrefix;
76
+ }
77
+
78
+ /**
79
+ * @return string|null
80
+ */
81
+ public function getSourcePrefix()
82
+ {
83
+ return $this->sourcePrefix;
84
+ }
85
+
86
+ /**
87
+ * @param string|null $sourcePrefix
88
+ */
89
+ public function setSourcePrefix($sourcePrefix)
90
+ {
91
+ $this->sourcePrefix = $sourcePrefix;
92
+ }
93
+
94
+ /**
95
+ * @return int|null
96
+ */
97
+ public function getStep()
98
+ {
99
+ return $this->step;
100
+ }
101
+
102
+ /**
103
+ * @param int|null $step
104
+ */
105
+ public function setStep($step)
106
+ {
107
+ $this->step = $step;
108
+ }
109
+
110
+ /**
111
+ * @return bool
112
+ */
113
+ public function isSaveRecords()
114
+ {
115
+ return $this->isSaveRecords;
116
+ }
117
+
118
+ /**
119
+ * @param bool $isSaveRecords
120
+ */
121
+ public function setIsSaveRecords($isSaveRecords)
122
+ {
123
+ $this->isSaveRecords = (bool) $isSaveRecords;
124
+ }
125
+ }
126
+
Command/Database/Snapshot/SnapshotHandler.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Command\Database\Snapshot;
4
+
5
+ use WPStaging\Service\Command\CommandInterface;
6
+ use WPStaging\Service\Command\HandlerInterface;
7
+ use SplObjectStorage;
8
+
9
+ class SnapshotHandler implements HandlerInterface
10
+ {
11
+ const PREFIX_AUTOMATIC = 'wpsa';
12
+ const PREFIX_MANUAL = 'wpsm';
13
+
14
+ /** @var SplObjectStorage */
15
+ private $commands;
16
+
17
+ /** @var SnapshotDto */
18
+ private $dto;
19
+
20
+ public function __construct(SnapshotDto $dto)
21
+ {
22
+ $this->dto = $dto;
23
+ $this->commands = new SplObjectStorage;
24
+ }
25
+
26
+ public function addCommand(CommandInterface $command)
27
+ {
28
+ /** @var AbstractSnapshotCommand $command */
29
+ if ($this->commands->contains($command)) {
30
+ return;
31
+ }
32
+
33
+ $command->setDto($this->dto);
34
+
35
+ $this->commands->attach($command);
36
+ }
37
+
38
+ /**
39
+ * @param string|null $action
40
+ */
41
+ public function handle($action = null)
42
+ {
43
+ /** @var CommandInterface $command */
44
+ foreach($this->commands as $command) {
45
+ $command->execute();
46
+ }
47
+ }
48
+ }
Command/Database/Snapshot/UpdateSnapshotCommand.php ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Command\Database\Snapshot;
4
+
5
+ use DateTime;
6
+ use WPStaging\Entity\Snapshot;
7
+ use WPStaging\Manager\Database\TableManager;
8
+
9
+ class UpdateSnapshotCommand extends AbstractSnapshotCommand
10
+ {
11
+ /**
12
+ * @noinspection PhpUnhandledExceptionInspection
13
+ * @noinspection DuplicatedCode
14
+ */
15
+ public function execute()
16
+ {
17
+ $this->validateSnapshot();
18
+
19
+ $tables = (new TableManager)->findStartsWith();
20
+ if (!$tables) {
21
+ throw new SnapshotCommandException('UpdateSnapshot failed to get tables');
22
+ }
23
+
24
+ $this->database->exec('SET FOREIGN_KEY_CHECKS = 0');
25
+ foreach($tables as $table) {
26
+ $newTableName = $this->dto->getTargetPrefix() . $table->getName();
27
+ // Why this way? Because of schema changes, do not go shortcut with UPDATE query!
28
+ $this->database->exec('DROP TABLE IF EXISTS ' . $newTableName);
29
+ $this->database->exec('OPTIMIZE TABLE '. $table->getName());
30
+ $this->database->exec('CREATE TABLE ' . $newTableName . ' LIKE ' . $table->getName());
31
+ $this->database->exec('INSERT INTO ' . $newTableName . ' SELECT * FROM ' . $table->getName());
32
+ }
33
+ $this->database->exec('SET FOREIGN_KEY_CHECKS = 1');
34
+
35
+ $this->saveSnapshots();
36
+ }
37
+
38
+ protected function saveSnapshots()
39
+ {
40
+ /** @var Snapshot $snapshot */
41
+ $snapshot = $this->snapshots->findById($this->dto->getTargetPrefix());
42
+ $snapshot->setUpdatedAt(new DateTime);
43
+ $this->repository->save($this->snapshots);
44
+ }
45
+
46
+ protected function validateSnapshot()
47
+ {
48
+ parent::validateSnapshot();
49
+ if (!$this->snapshots->doesIncludeId($this->dto->getTargetPrefix())) {
50
+ throw new SnapshotCommandException(
51
+ 'UpdateSnapshot prefix does not exist: ' . $this->dto->getTargetPrefix()
52
+ );
53
+ }
54
+ }
55
+ }
Command/Database/SnapshotFactory.php ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Command\Database;
4
+
5
+ use WPStaging\Command\Database\Snapshot\CreateSnapshotCommand;
6
+ use WPStaging\Command\Database\Snapshot\DeleteSnapshotCommand;
7
+ use WPStaging\Command\Database\Snapshot\SnapshotDto;
8
+ use WPStaging\Command\Database\Snapshot\SnapshotHandler;
9
+ use WPStaging\Command\Database\Snapshot\UpdateSnapshotCommand;
10
+ use WPStaging\Service\Command\CommandInterface;
11
+ use WPStaging\Service\Command\HandlerInterface;
12
+ use WPStaging\WPStaging;
13
+
14
+ class SnapshotFactory
15
+ {
16
+ // TODO PHP7.1; visibility
17
+ const CREATE_SNAPSHOT = 'create';
18
+ const UPDATE_SNAPSHOT = 'update';
19
+ const DELETE_SNAPSHOT = 'delete';
20
+
21
+ private static $container;
22
+
23
+ /**
24
+ * @param SnapshotDto $dto
25
+ * @param string|null $action
26
+ *
27
+ * @return SnapshotHandler
28
+ */
29
+ public static function make(SnapshotDto $dto, $action = null)
30
+ {
31
+ $handler = new SnapshotHandler($dto);
32
+
33
+ if ($action) {
34
+ self::makeAction($handler, $action);
35
+ return $handler;
36
+ }
37
+
38
+ foreach([self::CREATE_SNAPSHOT, self::UPDATE_SNAPSHOT, self::DELETE_SNAPSHOT] as $item) {
39
+ self::makeAction($handler, $item);
40
+ }
41
+
42
+ return $handler;
43
+ }
44
+
45
+ private static function makeAction(HandlerInterface $handler, $action)
46
+ {
47
+ switch($action) {
48
+ case self::CREATE_SNAPSHOT:
49
+ $handler->addCommand(self::getCommand(CreateSnapshotCommand::class));
50
+ return;
51
+ case self::UPDATE_SNAPSHOT:
52
+ $handler->addCommand(self::getCommand(UpdateSnapshotCommand::class));
53
+ return;
54
+ case self::DELETE_SNAPSHOT:
55
+ $handler->addCommand(self::getCommand(DeleteSnapshotCommand::class));
56
+ return;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * @param string $command
62
+ *
63
+ * @return CommandInterface|null
64
+ */
65
+ private static function getCommand($command)
66
+ {
67
+ // TODO RPoC v3.0.0
68
+ if (!self::$container) {
69
+ self::$container = WPStaging::getContainer();
70
+ }
71
+
72
+ /** @var CommandInterface|null $command */
73
+ /** @noinspection CallableParameterUseCaseInTypeContextInspection */
74
+ $command = self::$container->get($command);
75
+ return $command;
76
+ }
77
+ }
Core/Utils/Report.php CHANGED
@@ -5,81 +5,81 @@ namespace WPStaging\Utils;
5
  use WPStaging\Backend\Modules\SystemInfo;
6
  use WPStaging\DI\InjectionAware;
7
 
8
- class Report extends InjectionAware {
9
-
10
- /**
11
- * Send customer issue report
12
- *
13
- * @param string $email User e-mail
14
- * @param string $message User message
15
- * @param integer $terms User accept terms
16
- *
17
- * @return array
18
- */
19
- public function send( $email, $message, $terms, $syslog ) {
20
- $errors = array();
21
-
22
- if( !empty( $syslog ) ) {
 
 
 
 
 
23
  $message .= "\n\n'" . $this->getSyslog();
24
- }
25
-
26
- if( !filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
27
- $errors[] = __( 'Email address is not valid.', 'wp-staging' );
28
- } elseif( empty( $message ) ) {
29
- $errors[] = __( 'Please enter your issue.', 'wp-staging' );
30
- } elseif( empty( $terms ) ) {
31
- $errors[] = __( 'Please accept our privacy policy.', 'wp-staging' );
32
- } else {
33
-
34
- if( false === $this->sendMail( $email, $message ) ) {
35
- $errors[] = 'Can not send report. <br>Please send us a mail to<br>support@wp-staging.com';
36
- // $response = wp_remote_post(
37
- // 'https://wp-staging.com', array(
38
- // 'timeout' => 15,
39
- // 'body' => array(
40
- // 'email' => $email,
41
- // 'message' => $message,
42
- // ),
43
- // )
44
- // );
45
- //
46
- // if( is_wp_error( $response ) ) {
47
- // $errors[] = sprintf( __( 'Something went wrong: %s', 'wp-staging' ), $response->get_error_message() );
48
- // }
49
- }
50
- }
51
-
52
- return $errors;
53
- }
54
-
55
- private function getSyslog() {
56
-
57
- $syslog = new SystemInfo( $this->di );
58
- return $syslog->get();
59
- }
60
-
61
- /**
62
- * send feedback via email
63
- *
64
- * @return boolean
65
- */
66
- private function sendMail( $from, $text ) {
67
-
68
- $headers = array();
69
-
70
- $headers[] = "From: $from";
71
- $headers[] = "Reply-To: $from";
72
-
73
- $subject = 'Report Issue!';
74
-
75
- $success = wp_mail( 'support@wp-staging.com', $subject, $text, $headers );
76
-
77
- if( $success ) {
78
- return true;
79
- } else {
80
- return false;
81
- }
82
- die();
83
- }
84
 
85
  }
5
  use WPStaging\Backend\Modules\SystemInfo;
6
  use WPStaging\DI\InjectionAware;
7
 
8
+ class Report extends InjectionAware
9
+ {
10
+
11
+ /**
12
+ * Send customer issue report
13
+ *
14
+ * @param string $email User e-mail
15
+ * @param string $message User message
16
+ * @param integer $terms User accept terms
17
+ *
18
+ * @return array
19
+ */
20
+ public function send($email, $message, $terms, $syslog, $provider = null)
21
+ {
22
+ $errors = array();
23
+ $attachments = array();
24
+ $maxFileSize = 512 * 1024;
25
+ $message .= "\n\n'Hosting provider: " . $provider;
26
+
27
+ if ( ! empty($syslog)) {
28
  $message .= "\n\n'" . $this->getSyslog();
29
+
30
+ $debugLogFile = WP_CONTENT_DIR . '/debug.log';
31
+ if (filesize($debugLogFile) && filesize($debugLogFile) < $maxFileSize) {
32
+ $attachments[] = $debugLogFile;
33
+ }
34
+ }
35
+
36
+ if ( ! filter_var($email, FILTER_VALIDATE_EMAIL)) {
37
+ $errors[] = __('Email address is not valid.', 'wp-staging');
38
+ } elseif (empty($message)) {
39
+ $errors[] = __('Please enter your issue.', 'wp-staging');
40
+ } elseif (empty($terms)) {
41
+ $errors[] = __('Please accept our privacy policy.', 'wp-staging');
42
+ } else {
43
+
44
+ if (false === $this->sendMail($email, $message, $attachments)) {
45
+ $errors[] = 'Can not send report. <br>Please send us a mail to<br>support@wp-staging.com';
46
+ }
47
+ }
48
+
49
+ return $errors;
50
+ }
51
+
52
+ private function getSyslog()
53
+ {
54
+
55
+ $syslog = new SystemInfo($this->di);
56
+
57
+ return $syslog->get();
58
+ }
59
+
60
+ /**
61
+ * send feedback via email
62
+ *
63
+ * @return boolean
64
+ */
65
+ private function sendMail($from, $text, $attachments)
66
+ {
67
+
68
+ $headers = array();
69
+
70
+ $headers[] = "From: $from";
71
+ $headers[] = "Reply-To: $from";
72
+
73
+ $subject = 'Report Issue!';
74
+
75
+ $success = wp_mail('support@wp-staging.com', $subject, $text, $headers, $attachments);
76
+
77
+ if ($success) {
78
+ return true;
79
+ } else {
80
+ return false;
81
+ }
82
+ die();
83
+ }
 
 
 
 
 
84
 
85
  }
Core/Utils/functions.php CHANGED
@@ -110,9 +110,9 @@ function wpstg_mysql_escape_mimic($input)
110
 
111
  /**
112
  * Search & Replace first occurence of string in haystack
113
- * @param type $haystack
114
- * @param type $needle
115
- * @return type
116
  */
117
  function wpstg_replace_first_match($needle, $replace, $haystack)
118
  {
110
 
111
  /**
112
  * Search & Replace first occurence of string in haystack
113
+ * @param string $haystack
114
+ * @param string $needle
115
+ * @return string
116
  */
117
  function wpstg_replace_first_match($needle, $replace, $haystack)
118
  {
Core/WPStaging.php CHANGED
@@ -13,12 +13,14 @@ require_once __DIR__ . DIRECTORY_SEPARATOR . "Utils" . DIRECTORY_SEPARATOR . "Au
13
  use WPStaging\Backend\Administrator;
14
  use WPStaging\DTO\Settings;
15
  use WPStaging\Frontend\Frontend;
 
16
  use WPStaging\Service\Container\Container;
17
  use WPStaging\Utils\Autoloader;
18
  use WPStaging\Utils\Cache;
19
  use WPStaging\Utils\Loader;
20
  use WPStaging\Utils\Logger;
21
  use WPStaging\Service\PluginFactory;
 
22
 
23
  /**
24
  * Class WPStaging
@@ -41,7 +43,7 @@ final class WPStaging {
41
  * Absolute plugin path
42
  * @var string
43
  */
44
- public $pluginPath;
45
 
46
  /**
47
  * Services
@@ -55,6 +57,16 @@ final class WPStaging {
55
  */
56
  private static $instance;
57
 
 
 
 
 
 
 
 
 
 
 
58
  /**
59
  * WPStaging constructor.
60
  */
@@ -69,6 +81,7 @@ final class WPStaging {
69
  $this->initLicensing();
70
  $this->initVersion();
71
  $this->initActions();
 
72
  }
73
 
74
  /**
@@ -76,14 +89,14 @@ final class WPStaging {
76
  */
77
  private function initCron() {
78
  // Register cron job and add new interval 'weekly'
79
- $cron = new \WPStaging\Cron\Cron;
80
  }
81
 
82
  /**
83
  * Get root WP root path -
84
  * Changed ABSPATH trailingslash for windows compatibility
85
 
86
- * @return type
87
  */
88
  public static function getWPpath() {
89
  return str_replace( '/', DIRECTORY_SEPARATOR, ABSPATH );
@@ -94,19 +107,19 @@ final class WPStaging {
94
  */
95
  public function registerMain() {
96
  // Slug of the plugin
97
- $this->slug = plugin_basename( dirname( dirname( __FILE__ ) ) );
98
 
99
  // absolute path to the main plugin dir
100
- $this->pluginPath = plugin_dir_path( dirname( __FILE__ ) );
101
 
102
  // URL to main plugin folder
103
- $this->url = plugin_dir_url( dirname( __FILE__ ) );
104
 
105
  // URL to backend public folder folder
106
- $this->backend_url = plugin_dir_url( dirname( __FILE__ ) ) . "Backend/public/";
107
 
108
  // URL to frontend public folder folder
109
- $this->frontend_url = plugin_dir_url( dirname( __FILE__ ) ) . "Frontend/public/";
110
  }
111
 
112
  /**
@@ -121,8 +134,7 @@ final class WPStaging {
121
 
122
  /**
123
  * Remove heartbeat api and user login check
124
- * @param type $hook
125
- * @return type
126
  */
127
  public function removeWPCoreJs( $hook ) {
128
 
@@ -155,16 +167,16 @@ final class WPStaging {
155
 
156
  // Load this css file on frontend and backend on all pages if current site is a staging site
157
  if( wpstg_is_stagingsite() ) {
158
- wp_enqueue_style( "wpstg-admin-bar", $this->backend_url . "css/wpstg-admin-bar.css", array(), $this->getVersion() );
159
  }
160
 
161
  // Load js file on page plugins.php in free version only
162
  if( !defined('WPSTGPRO_VERSION') && $this->isPluginsPage() ) {
163
  wp_enqueue_script(
164
- "wpstg-admin-script", $this->backend_url . "js/wpstg-admin-plugins.js", array("jquery"), $this->getVersion(), false
165
  );
166
  wp_enqueue_style(
167
- "wpstg-admin-feedback", $this->backend_url . "css/wpstg-admin-feedback.css", array(), $this->getVersion()
168
  );
169
  }
170
 
@@ -175,13 +187,13 @@ final class WPStaging {
175
 
176
  // Load admin js files
177
  wp_enqueue_script(
178
- "wpstg-admin-script", $this->backend_url . "js/wpstg-admin.js", array("jquery"), $this->getVersion(), false
179
  );
180
 
181
  // Load admin js pro files
182
  if(defined('WPSTGPRO_VERSION')) {
183
  wp_enqueue_script(
184
- "wpstg-admin-pro-script", $this->url . "Backend/Pro/public/js/wpstg-admin-pro.js", array("jquery"), $this->getVersion(), false
185
  );
186
 
187
  // Sweet Alert
@@ -189,7 +201,7 @@ final class WPStaging {
189
  'wpstg-admin-pro-sweetalerts',
190
  $this->url . 'Backend/Pro/public/vendor/sweetalert2/sweetalert2.all.min.js',
191
  [],
192
- $this->getVersion(),
193
  true
194
  );
195
 
@@ -197,13 +209,13 @@ final class WPStaging {
197
  'wpstg-admin-pro-sweetalerts',
198
  $this->url . 'Backend/Pro/public/vendor/sweetalert2/wordpress-admin.min.css',
199
  [],
200
- $this->getVersion()
201
  );
202
  }
203
 
204
  // Load admin css files
205
  wp_enqueue_style(
206
- "wpstg-admin", $this->backend_url . "css/wpstg-admin.css", array(), $this->getVersion()
207
  );
208
 
209
  wp_localize_script( "wpstg-admin-script", "wpstg", array(
@@ -239,10 +251,7 @@ final class WPStaging {
239
  );
240
  }
241
 
242
- if( !in_array( $page, $availablePages ) || !is_admin() ) {
243
- return true;
244
- }
245
- return false;
246
  }
247
 
248
  /**
@@ -250,8 +259,7 @@ final class WPStaging {
250
  * @return string
251
  */
252
  public static function getTablePrefix() {
253
- $wpDB = WPStaging::getInstance()->get( "wpdb" );
254
- return $wpDB->prefix;
255
  }
256
 
257
  /**
@@ -302,28 +310,12 @@ final class WPStaging {
302
  return static::$instance;
303
  }
304
 
305
- /**
306
- * Prevent cloning
307
- * @return void
308
- */
309
- private function __clone() {
310
-
311
- }
312
-
313
- /**
314
- * Prevent unserialization
315
- * @return void
316
- */
317
- private function __wakeup() {
318
-
319
- }
320
-
321
  /**
322
  * Load Dependencies
323
  */
324
  private function loadDependencies() {
325
  // Load globally available functions
326
- require_once $this->pluginPath . 'Core/Utils/functions.php';
327
 
328
  $this->set( "loader", new Loader() );
329
 
@@ -350,6 +342,7 @@ final class WPStaging {
350
  * Execute Plugin
351
  */
352
  public function run() {
 
353
  $this->get( "loader" )->run();
354
  }
355
 
@@ -397,10 +390,9 @@ final class WPStaging {
397
  {
398
  return WPSTGPRO_VERSION;
399
  }
400
- if(defined('WPSTG_VERSION'))
401
- {
402
- return WPSTG_VERSION;
403
- }
404
  }
405
 
406
  /**
@@ -415,7 +407,7 @@ final class WPStaging {
415
  */
416
  public static function getSlug()
417
  {
418
- return plugin_basename(dirname(dirname(__FILE__)));
419
  }
420
 
421
  /**
@@ -423,15 +415,15 @@ final class WPStaging {
423
  * @return string
424
  */
425
  public function getPath() {
426
- return dirname( dirname( __FILE__ ) );
427
  }
428
 
429
  /**
430
  * Get main plugin url
431
- * @return type
432
  */
433
  public function getUrl() {
434
- return plugin_dir_url( dirname( __FILE__ ) );
435
  }
436
 
437
  /**
@@ -476,7 +468,7 @@ final class WPStaging {
476
  public function initLicensing() {
477
  // Add licensing stuff if class exists
478
  if( class_exists( 'WPStaging\Backend\Pro\Licensing\Licensing' ) ) {
479
- $licensing = new Backend\Pro\Licensing\Licensing();
480
  }
481
  return false;
482
  }
@@ -488,7 +480,7 @@ final class WPStaging {
488
  public function initVersion() {
489
  // Add licensing stuff if class exists
490
  if( class_exists( 'WPStaging\Backend\Pro\Licensing\Version' ) ) {
491
- $licensing = new Backend\Pro\Licensing\Version();
492
  }
493
  return false;
494
  }
@@ -507,4 +499,13 @@ final class WPStaging {
507
  }
508
  }
509
 
 
 
 
 
 
 
 
 
 
510
  }
13
  use WPStaging\Backend\Administrator;
14
  use WPStaging\DTO\Settings;
15
  use WPStaging\Frontend\Frontend;
16
+ use WPStaging\Service\Permalinks\PermalinksPurge;
17
  use WPStaging\Service\Container\Container;
18
  use WPStaging\Utils\Autoloader;
19
  use WPStaging\Utils\Cache;
20
  use WPStaging\Utils\Loader;
21
  use WPStaging\Utils\Logger;
22
  use WPStaging\Service\PluginFactory;
23
+ use WPStaging\Cron\Cron;
24
 
25
  /**
26
  * Class WPStaging
43
  * Absolute plugin path
44
  * @var string
45
  */
46
+ private $pluginPath;
47
 
48
  /**
49
  * Services
57
  */
58
  private static $instance;
59
 
60
+ /*
61
+ * @var string
62
+ */
63
+ private $backend_url;
64
+
65
+ /**
66
+ * @var string
67
+ */
68
+ private $url;
69
+
70
  /**
71
  * WPStaging constructor.
72
  */
81
  $this->initLicensing();
82
  $this->initVersion();
83
  $this->initActions();
84
+ $this->handleCacheIssues();
85
  }
86
 
87
  /**
89
  */
90
  private function initCron() {
91
  // Register cron job and add new interval 'weekly'
92
+ new Cron;
93
  }
94
 
95
  /**
96
  * Get root WP root path -
97
  * Changed ABSPATH trailingslash for windows compatibility
98
 
99
+ * @return string
100
  */
101
  public static function getWPpath() {
102
  return str_replace( '/', DIRECTORY_SEPARATOR, ABSPATH );
107
  */
108
  public function registerMain() {
109
  // Slug of the plugin
110
+ $this->slug = plugin_basename( dirname(__DIR__) );
111
 
112
  // absolute path to the main plugin dir
113
+ $this->pluginPath = plugin_dir_path(__DIR__);
114
 
115
  // URL to main plugin folder
116
+ $this->url = plugin_dir_url(__DIR__);
117
 
118
  // URL to backend public folder folder
119
+ $this->backend_url = plugin_dir_url(__DIR__) . "Backend/public/";
120
 
121
  // URL to frontend public folder folder
122
+ $this->frontend_url = plugin_dir_url(__DIR__) . "Frontend/public/";
123
  }
124
 
125
  /**
134
 
135
  /**
136
  * Remove heartbeat api and user login check
137
+ * @param bool $hook
 
138
  */
139
  public function removeWPCoreJs( $hook ) {
140
 
167
 
168
  // Load this css file on frontend and backend on all pages if current site is a staging site
169
  if( wpstg_is_stagingsite() ) {
170
+ wp_enqueue_style( "wpstg-admin-bar", $this->backend_url . "css/wpstg-admin-bar.css", array(), self::getVersion() );
171
  }
172
 
173
  // Load js file on page plugins.php in free version only
174
  if( !defined('WPSTGPRO_VERSION') && $this->isPluginsPage() ) {
175
  wp_enqueue_script(
176
+ "wpstg-admin-script", $this->backend_url . "js/wpstg-admin-plugins.js", array("jquery"), self::getVersion(), false
177
  );
178
  wp_enqueue_style(
179
+ "wpstg-admin-feedback", $this->backend_url . "css/wpstg-admin-feedback.css", array(), self::getVersion()
180
  );
181
  }
182
 
187
 
188
  // Load admin js files
189
  wp_enqueue_script(
190
+ "wpstg-admin-script", $this->backend_url . "js/wpstg-admin.js", array("jquery"), self::getVersion(), false
191
  );
192
 
193
  // Load admin js pro files
194
  if(defined('WPSTGPRO_VERSION')) {
195
  wp_enqueue_script(
196
+ "wpstg-admin-pro-script", $this->url . "Backend/Pro/public/js/wpstg-admin-pro.js", array("jquery"), self::getVersion(), false
197
  );
198
 
199
  // Sweet Alert
201
  'wpstg-admin-pro-sweetalerts',
202
  $this->url . 'Backend/Pro/public/vendor/sweetalert2/sweetalert2.all.min.js',
203
  [],
204
+ self::getVersion(),
205
  true
206
  );
207
 
209
  'wpstg-admin-pro-sweetalerts',
210
  $this->url . 'Backend/Pro/public/vendor/sweetalert2/wordpress-admin.min.css',
211
  [],
212
+ self::getVersion()
213
  );
214
  }
215
 
216
  // Load admin css files
217
  wp_enqueue_style(
218
+ "wpstg-admin", $this->backend_url . "css/wpstg-admin.css", array(), self::getVersion()
219
  );
220
 
221
  wp_localize_script( "wpstg-admin-script", "wpstg", array(
251
  );
252
  }
253
 
254
+ return !in_array($page, $availablePages) || !is_admin();
 
 
 
255
  }
256
 
257
  /**
259
  * @return string
260
  */
261
  public static function getTablePrefix() {
262
+ return WPStaging::getInstance()->get( "wpdb" )->prefix;
 
263
  }
264
 
265
  /**
310
  return static::$instance;
311
  }
312
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  /**
314
  * Load Dependencies
315
  */
316
  private function loadDependencies() {
317
  // Load globally available functions
318
+ require_once ($this->pluginPath . "Core/Utils/functions.php");
319
 
320
  $this->set( "loader", new Loader() );
321
 
342
  * Execute Plugin
343
  */
344
  public function run() {
345
+ /** @noinspection **/
346
  $this->get( "loader" )->run();
347
  }
348
 
390
  {
391
  return WPSTGPRO_VERSION;
392
  }
393
+
394
+ return WPSTG_VERSION;
395
+
 
396
  }
397
 
398
  /**
407
  */
408
  public static function getSlug()
409
  {
410
+ return plugin_basename(dirname(__DIR__));
411
  }
412
 
413
  /**
415
  * @return string
416
  */
417
  public function getPath() {
418
+ return dirname(__DIR__);
419
  }
420
 
421
  /**
422
  * Get main plugin url
423
+ * @return string
424
  */
425
  public function getUrl() {
426
+ return plugin_dir_url(__DIR__);
427
  }
428
 
429
  /**
468
  public function initLicensing() {
469
  // Add licensing stuff if class exists
470
  if( class_exists( 'WPStaging\Backend\Pro\Licensing\Licensing' ) ) {
471
+ new Backend\Pro\Licensing\Licensing();
472
  }
473
  return false;
474
  }
480
  public function initVersion() {
481
  // Add licensing stuff if class exists
482
  if( class_exists( 'WPStaging\Backend\Pro\Licensing\Version' ) ) {
483
+ new Backend\Pro\Licensing\Version();
484
  }
485
  return false;
486
  }
499
  }
500
  }
501
 
502
+ /**
503
+ * Takes care of cache issues in certain situations
504
+ */
505
+ private function handleCacheIssues() {
506
+ $permalinksPurge = new PermalinksPurge();
507
+ add_action( 'wpstg_pushing_complete', array( $permalinksPurge, 'executeAfterPushing' ));
508
+ add_action( 'wp_loaded', array( $permalinksPurge, 'purgePermalinks' ), $permalinksPurge::PLUGINS_LOADED_PRIORITY);
509
+ }
510
+
511
  }
Service/Adapter/SourceDatabase.php ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+
4
+ namespace WPStaging\Service\Adapter;
5
+
6
+ use stdClass;
7
+ use WPStaging\WPStaging;
8
+
9
+ class SourceDatabase
10
+ {
11
+ /** @var object */
12
+ private $wpdb;
13
+
14
+ /** @var object */
15
+ private $options;
16
+
17
+ public function __construct($options = stdClass::class)
18
+ {
19
+ $this->wpdb = WPStaging::getInstance()->get('wpdb');
20
+ $this->options = $options;
21
+ }
22
+
23
+ /**
24
+ * @return bool
25
+ */
26
+ public function isExternalDatabase()
27
+ {
28
+ return !(empty($this->options->databaseUser) ||
29
+ empty($this->options->databasePassword) ||
30
+ empty($this->options->databaseDatabase) ||
31
+ empty($this->options->databaseServer));
32
+ }
33
+
34
+ /**
35
+ * @return object
36
+ */
37
+ private function getExternalDb()
38
+ {
39
+ return new \wpdb($this->options->databaseUser, str_replace("\\\\", "\\", $this->options->databasePassword), $this->options->databaseDatabase, $this->options->databaseServer);
40
+ }
41
+
42
+ /**
43
+ * Check if source database is a local or external one and get the corresponding database object
44
+ *
45
+ * @return object
46
+ *
47
+ */
48
+ public function getDatabase()
49
+ {
50
+ if ($this->isExternalDatabase()) {
51
+ return $this->getExternalDb();
52
+ }
53
+ return $this->wpdb;
54
+ }
55
+ }
Service/Permalinks/PermalinksPurge.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Service\Permalinks;
4
+
5
+ class PermalinksPurge
6
+ {
7
+
8
+ const PLUGINS_LOADED_PRIORITY = 99999;
9
+ const TRANSIENT = "wpstg_permalinks_do_purge";
10
+
11
+ public function executeAfterPushing()
12
+ {
13
+ set_transient(self::TRANSIENT, "true");
14
+ }
15
+
16
+ public function purgePermalinks()
17
+ {
18
+ if (get_transient(self::TRANSIENT)) {
19
+ delete_transient(self::TRANSIENT);
20
+ flush_rewrite_rules(false);
21
+ }
22
+ }
23
+ }
Service/Utils/FileSystem.php CHANGED
@@ -4,6 +4,10 @@
4
 
5
  namespace WPStaging\Service\Utils;
6
 
 
 
 
 
7
  class FileSystem
8
  {
9
  /*
4
 
5
  namespace WPStaging\Service\Utils;
6
 
7
+ use FilesystemIterator;
8
+ use RecursiveDirectoryIterator;
9
+ use RecursiveIteratorIterator;
10
+
11
  class FileSystem
12
  {
13
  /*
Service/Utils/Strings.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Service\Utils;
4
+
5
+ /**
6
+ * Class Strings
7
+ * @package WPStaging\Service\Strings
8
+ */
9
+ class Strings
10
+ {
11
+ /**
12
+ * This function ensures backwards compatibility with Wordpress prior to the 4.7 release. sanitize_textarea_field
13
+ * was introduced with that version.
14
+ * @param $str
15
+ *
16
+ * @return string
17
+ */
18
+ public function sanitizeTextareaField($str)
19
+ {
20
+ if (function_exists('sanitize_textarea_field')) {
21
+ return sanitize_textarea_field($str);
22
+ } else {
23
+ return sanitize_text_field($str);
24
+ }
25
+ }
26
+ }
languages/wp-staging-de_DE.po CHANGED
@@ -188,7 +188,7 @@ msgstr "Anzahl der Datenbank Einträge, die zugleich kopiert werden. Je höher d
188
 
189
  #: Backend/views/clone/single-site/index.php:16
190
  msgid "Report Issue"
191
- msgstr "Fehlermeldung"
192
 
193
  #: Backend/views/clone/single-site/index.php:12
194
  msgid "Cloning"
@@ -348,8 +348,8 @@ msgid "By submitting, I accept the <a href=\"https://wp-staging.com/privacy-poli
348
  msgstr "Mit Übertragen akzeptiere ich die <a href=\"https://wp-staging.com/privacy-policy/\" target=\"_blank\">Datenschutz Vereinbarung</a> und erlaube, dass meine E-Mail Adresse gespeichert und verarbeitet wird um meine Support Anfrage zu beantworten."
349
 
350
  #: Backend/views/_main/report-issue.php:12
351
- msgid "Optional: Submit the <a href=\"%s\" target=\"_blank\">System Log</a>. This helps us to resolve your technical issues."
352
- msgstr "Optional: Übertrage das <a href=\"%s\" target=\"_blank\">System Log</a>. Das hilft uns Dein technisches Anliegen zu lösen."
353
 
354
  #: Backend/views/_main/messages/staging-directory-permission-problem.php:4
355
  msgid ""
188
 
189
  #: Backend/views/clone/single-site/index.php:16
190
  msgid "Report Issue"
191
+ msgstr "Fehler melden"
192
 
193
  #: Backend/views/clone/single-site/index.php:12
194
  msgid "Cloning"
348
  msgstr "Mit Übertragen akzeptiere ich die <a href=\"https://wp-staging.com/privacy-policy/\" target=\"_blank\">Datenschutz Vereinbarung</a> und erlaube, dass meine E-Mail Adresse gespeichert und verarbeitet wird um meine Support Anfrage zu beantworten."
349
 
350
  #: Backend/views/_main/report-issue.php:12
351
+ msgid "Optional: Submit the <a href=\"%s\" target=\"_blank\">System Log</a> and your WordPress debug log. This helps us to resolve your technical issues."
352
+ msgstr "Optional: Übertrage das <a href=\"%s\" target=\"_blank\">System Log</a> sowie dein WordPress-Debug-Log. Das hilft uns Dein technisches Anliegen zu lösen."
353
 
354
  #: Backend/views/_main/messages/staging-directory-permission-problem.php:4
355
  msgid ""
readme.txt CHANGED
@@ -153,6 +153,16 @@ https://wp-staging.com
153
 
154
  == Changelog ==
155
 
 
 
 
 
 
 
 
 
 
 
156
  = 2.7.3 =
157
  * New: Compatible up to WordPress 5.4.1
158
  * New: Allow filtering of staging site title
@@ -257,5 +267,11 @@ https://wp-staging.com
257
  Complete changelog: [https://wp-staging.com/wp-staging-changelog](https://wp-staging.com/wp-staging-changelog)
258
 
259
  == Upgrade Notice ==
260
- * Install this version for supporting latest WordPress version
261
- * Fix: Fatal error on WordPress 4.6 and older
 
 
 
 
 
 
153
 
154
  == Changelog ==
155
 
156
+ = 2.7.4 =
157
+ * New: Compatible up to WordPress 5.4.2
158
+ * Fix: Remove beta notice
159
+ * Fix: Error if views are cloned
160
+ * Fix: Fatal error if WordPress is older than 4.5
161
+ * Fix: Merge pro/free version
162
+ * Fix: Step switching logic does not work properly
163
+ * Fix: Fix progress bar when certains steps are skipped
164
+ * Fix: Change german translation for REPORT ISSUE
165
+
166
  = 2.7.3 =
167
  * New: Compatible up to WordPress 5.4.1
168
  * New: Allow filtering of staging site title
267
  Complete changelog: [https://wp-staging.com/wp-staging-changelog](https://wp-staging.com/wp-staging-changelog)
268
 
269
  == Upgrade Notice ==
270
+ * New: Compatible up to WordPress 5.4.2
271
+ * Fix: Remove beta notice
272
+ * Fix: Error if views are cloned
273
+ * Fix: Fatal error if WordPress is older than 4.5
274
+ * Fix: Merge pro/free version
275
+ * Fix: Step switching logic does not work properly
276
+ * Fix: Fix progress bar when certains steps are skipped
277
+ * Fix: Change german translation for REPORT ISSUE
vendor/autoload.php CHANGED
@@ -4,4 +4,4 @@
4
 
5
  require_once __DIR__ . '/composer/autoload_real.php';
6
 
7
- return ComposerAutoloaderInitd3ab211c510172b0edc8b09988ac2855::getLoader();
4
 
5
  require_once __DIR__ . '/composer/autoload_real.php';
6
 
7
+ return ComposerAutoloaderInitb9bb895dff0feeace986bca17666d0d0::getLoader();
vendor/composer/autoload_real.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  // autoload_real.php @generated by Composer
4
 
5
- class ComposerAutoloaderInitd3ab211c510172b0edc8b09988ac2855
6
  {
7
  private static $loader;
8
 
@@ -22,15 +22,15 @@ class ComposerAutoloaderInitd3ab211c510172b0edc8b09988ac2855
22
  return self::$loader;
23
  }
24
 
25
- spl_autoload_register(array('ComposerAutoloaderInitd3ab211c510172b0edc8b09988ac2855', 'loadClassLoader'), true, true);
26
  self::$loader = $loader = new \Composer\Autoload\ClassLoader();
27
- spl_autoload_unregister(array('ComposerAutoloaderInitd3ab211c510172b0edc8b09988ac2855', 'loadClassLoader'));
28
 
29
  $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
30
  if ($useStaticLoader) {
31
  require_once __DIR__ . '/autoload_static.php';
32
 
33
- call_user_func(\Composer\Autoload\ComposerStaticInitd3ab211c510172b0edc8b09988ac2855::getInitializer($loader));
34
  } else {
35
  $map = require __DIR__ . '/autoload_namespaces.php';
36
  foreach ($map as $namespace => $path) {
2
 
3
  // autoload_real.php @generated by Composer
4
 
5
+ class ComposerAutoloaderInitb9bb895dff0feeace986bca17666d0d0
6
  {
7
  private static $loader;
8
 
22
  return self::$loader;
23
  }
24
 
25
+ spl_autoload_register(array('ComposerAutoloaderInitb9bb895dff0feeace986bca17666d0d0', 'loadClassLoader'), true, true);
26
  self::$loader = $loader = new \Composer\Autoload\ClassLoader();
27
+ spl_autoload_unregister(array('ComposerAutoloaderInitb9bb895dff0feeace986bca17666d0d0', 'loadClassLoader'));
28
 
29
  $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
30
  if ($useStaticLoader) {
31
  require_once __DIR__ . '/autoload_static.php';
32
 
33
+ call_user_func(\Composer\Autoload\ComposerStaticInitb9bb895dff0feeace986bca17666d0d0::getInitializer($loader));
34
  } else {
35
  $map = require __DIR__ . '/autoload_namespaces.php';
36
  foreach ($map as $namespace => $path) {
vendor/composer/autoload_static.php CHANGED
@@ -4,7 +4,7 @@
4
 
5
  namespace Composer\Autoload;
6
 
7
- class ComposerStaticInitd3ab211c510172b0edc8b09988ac2855
8
  {
9
  public static $prefixLengthsPsr4 = array (
10
  'W' =>
@@ -36,8 +36,8 @@ class ComposerStaticInitd3ab211c510172b0edc8b09988ac2855
36
  public static function getInitializer(ClassLoader $loader)
37
  {
38
  return \Closure::bind(function () use ($loader) {
39
- $loader->prefixLengthsPsr4 = ComposerStaticInitd3ab211c510172b0edc8b09988ac2855::$prefixLengthsPsr4;
40
- $loader->prefixDirsPsr4 = ComposerStaticInitd3ab211c510172b0edc8b09988ac2855::$prefixDirsPsr4;
41
 
42
  }, null, ClassLoader::class);
43
  }
4
 
5
  namespace Composer\Autoload;
6
 
7
+ class ComposerStaticInitb9bb895dff0feeace986bca17666d0d0
8
  {
9
  public static $prefixLengthsPsr4 = array (
10
  'W' =>
36
  public static function getInitializer(ClassLoader $loader)
37
  {
38
  return \Closure::bind(function () use ($loader) {
39
+ $loader->prefixLengthsPsr4 = ComposerStaticInitb9bb895dff0feeace986bca17666d0d0::$prefixLengthsPsr4;
40
+ $loader->prefixDirsPsr4 = ComposerStaticInitb9bb895dff0feeace986bca17666d0d0::$prefixDirsPsr4;
41
 
42
  }, null, ClassLoader::class);
43
  }
wp-staging.php CHANGED
@@ -44,7 +44,7 @@ if (!defined('WPSTG_VERSION')) {
44
 
45
  // Compatible up to WordPress Version
46
  if (!defined('WPSTG_COMPATIBLE')) {
47
- define('WPSTG_COMPATIBLE', '5.4.1');
48
  }
49
 
50
  // Absolute path to plugin
44
 
45
  // Compatible up to WordPress Version
46
  if (!defined('WPSTG_COMPATIBLE')) {
47
+ define('WPSTG_COMPATIBLE', '5.4.2');
48
  }
49
 
50
  // Absolute path to plugin