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

Version Description

  • Tweak: Lowering default performance settings for more reliability during cloning
  • New: Set WP_CACHE to false in wp-config.php after cloning to prevent log in issues to staging site
  • New: Compatibility mode to skip certain tables from third party plugins from beeing searched & replaced
  • Fix: Do not clone db.php, object-cache.php and advanced-cache.php
  • Fix: Show error message if ajax requests fail for any reason
Download this release

Release Info

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

Code changes from version 2.3.7 to 2.3.8

apps/Backend/Modules/Jobs/Cloning.php CHANGED
@@ -47,6 +47,11 @@ class Cloning extends Job {
47
'.log',
48
'web.config'
49
);
50
$this->options->currentStep = 0;
51
52
47
'.log',
48
'web.config'
49
);
50
+ $this->options->excludedFilesFullPath = array(
51
+ 'wp-content' . DIRECTORY_SEPARATOR . 'db.php',
52
+ 'wp-content' . DIRECTORY_SEPARATOR . 'object-cache.php',
53
+ 'wp-content' . DIRECTORY_SEPARATOR . 'advanced-cache.php'
54
+ );
55
$this->options->currentStep = 0;
56
57
apps/Backend/Modules/Jobs/Data.php CHANGED
@@ -77,7 +77,7 @@ class Data extends JobExecutable {
77
* @return void
78
*/
79
protected function calculateTotalSteps() {
80
- $this->options->totalSteps = 13;
81
}
82
83
/**
@@ -759,6 +759,48 @@ class Data extends JobExecutable {
759
return true;
760
}
761
762
protected function getNewUploadPath() {
763
$uploadPath = get_option( 'upload_path' );
764
77
* @return void
78
*/
79
protected function calculateTotalSteps() {
80
+ $this->options->totalSteps = 14;
81
}
82
83
/**
759
return true;
760
}
761
762
+ /**
763
+ * Update WP_CACHE in wp-config.php
764
+ * @return bool
765
+ */
766
+ protected function step14() {
767
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
768
+
769
+ $this->log( "Preparing Data Step14: Set WP_CACHE in wp-config.php to false" );
770
+
771
+ if( false === ($content = file_get_contents( $path )) ) {
772
+ $this->log( "Preparing Data Step14: Failed to update WP_CACHE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
773
+ return false;
774
+ }
775
+
776
+
777
+ // Get WP_CACHE from wp-config.php
778
+ preg_match( "/define\s*\(\s*'WP_CACHE'\s*,\s*(.*)\s*\);/", $content, $matches );
779
+
780
+ if( !empty( $matches[1] ) ) {
781
+ $matches[1];
782
+
783
+ $pattern = "/define\s*\(\s*'WP_CACHE'\s*,\s*(.*)\s*\);/";
784
+
785
+ $replace = "define('WP_CACHE',false); // " . $matches[1];
786
+ $replace.= " // Changed by WP-Staging";
787
+
788
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
789
+ $this->log( "Preparing Data: Failed to change WP_CACHE", Logger::TYPE_ERROR );
790
+ return false;
791
+ }
792
+ } else {
793
+ $this->log( "Preparing Data Step14: WP_CACHE not defined in wp-config.php. Skipping this step." );
794
+ }
795
+
796
+ if( false === @file_put_contents( $path, $content ) ) {
797
+ $this->log( "Preparing Data Step14: Failed to update WP_CACHE. Can't save contents", Logger::TYPE_ERROR );
798
+ return false;
799
+ }
800
+ $this->Log( "Preparing Data: Finished Step 14 successfully" );
801
+ return true;
802
+ }
803
+
804
protected function getNewUploadPath() {
805
$uploadPath = get_option( 'upload_path' );
806
apps/Backend/Modules/Jobs/Files.php CHANGED
@@ -161,16 +161,22 @@ class Files extends JobExecutable {
161
162
// Directory is excluded
163
if( $this->isDirectoryExcluded( $directory ) ) {
164
- $this->debugLog( "Skipping directory by rule: {$file}", Logger::TYPE_INFO );
165
return false;
166
}
167
168
// File is excluded
169
if( $this->isFileExcluded( $file ) ) {
170
- $this->debugLog( "Skipping file by rule: {$file}", Logger::TYPE_INFO );
171
return false;
172
}
173
-
174
// File is over maximum allowed file size (8MB)
175
if( $fileSize >= $this->settings->maxFileSize * 1000000 ) {
176
$this->log( "Skipping big file: {$file}", Logger::TYPE_INFO );
@@ -224,7 +230,7 @@ class Files extends JobExecutable {
224
$destinationPath = $this->destination . $relativePath;
225
$destinationDirectory = dirname( $destinationPath );
226
227
- if( !is_dir( $destinationDirectory ) && !@mkdir( $destinationDirectory, 0775, true ) ) {
228
$this->log( "Files: Can not create directory {$destinationDirectory}", Logger::TYPE_ERROR );
229
return false;
230
}
@@ -273,22 +279,36 @@ class Files extends JobExecutable {
273
* @return boolean
274
*/
275
private function isFileExcluded( $file ) {
276
- $excluded = false;
277
- foreach ( $this->options->excludedFiles as $excludedFile ) {
278
- if( stripos( strrev( $file ), strrev( $excludedFile ) ) === 0 ) {
279
- $excluded = true;
280
- break;
281
- }
282
- }
283
284
// Do not copy wp-config.php if the clone gets updated. This is for security purposes,
285
// because if the updating process fails, the staging site would not be accessable any longer
286
if( isset( $this->options->mainJob ) && $this->options->mainJob == "updating" && stripos( strrev( $file ), strrev( "wp-config.php" ) ) === 0 ) {
287
- $excluded = true;
288
}
289
290
291
- return $excluded;
292
}
293
294
/**
@@ -337,7 +357,7 @@ class Files extends JobExecutable {
337
$directory = $this->sanitizeDirectorySeparator( $directory );
338
339
foreach ( $this->options->extraDirectories as $extraDirectory ) {
340
- if( strpos( $directory, $this->sanitizeDirectorySeparator($extraDirectory) ) === 0 ) {
341
return true;
342
}
343
}
161
162
// Directory is excluded
163
if( $this->isDirectoryExcluded( $directory ) ) {
164
+ $this->debugLog( "Directory Excluded: {$file}", Logger::TYPE_INFO );
165
return false;
166
}
167
168
// File is excluded
169
if( $this->isFileExcluded( $file ) ) {
170
+ $this->debugLog( "File excluded: {$file}", Logger::TYPE_INFO );
171
return false;
172
}
173
+
174
+ // Path + File is excluded
175
+ if( $this->isFileExcludedFullPath( $file ) ) {
176
+ $this->debugLog( "File Excluded Full Path: {$file}", Logger::TYPE_INFO );
177
+ return false;
178
+ }
179
+
180
// File is over maximum allowed file size (8MB)
181
if( $fileSize >= $this->settings->maxFileSize * 1000000 ) {
182
$this->log( "Skipping big file: {$file}", Logger::TYPE_INFO );
230
$destinationPath = $this->destination . $relativePath;
231
$destinationDirectory = dirname( $destinationPath );
232
233
+ if( !is_dir( $destinationDirectory ) && !@mkdir( $destinationDirectory, 0755, true ) ) {
234
$this->log( "Files: Can not create directory {$destinationDirectory}", Logger::TYPE_ERROR );
235
return false;
236
}
279
* @return boolean
280
*/
281
private function isFileExcluded( $file ) {
282
283
+ if( in_array( basename( $file ), $this->options->excludedFiles ) ) {
284
+ return true;
285
+ }
286
// Do not copy wp-config.php if the clone gets updated. This is for security purposes,
287
// because if the updating process fails, the staging site would not be accessable any longer
288
if( isset( $this->options->mainJob ) && $this->options->mainJob == "updating" && stripos( strrev( $file ), strrev( "wp-config.php" ) ) === 0 ) {
289
+ return true;
290
+
291
}
292
293
294
+ return false;
295
+ }
296
+
297
+ /**
298
+ * Check if certain file is excluded from copying process
299
+ *
300
+ * @param string $file filename including ending + (part) path e.g wp-content/db.php
301
+ * @return boolean
302
+ */
303
+ private function isFileExcludedFullPath( $file ) {
304
+ // If path + file exists
305
+ foreach ($this->options->excludedFilesFullPath as $excludedFile){
306
+ if( false !== strpos( $file, $excludedFile )){
307
+ return true;
308
+ }
309
+ }
310
+
311
+ return false;
312
}
313
314
/**
357
$directory = $this->sanitizeDirectorySeparator( $directory );
358
359
foreach ( $this->options->extraDirectories as $extraDirectory ) {
360
+ if( strpos( $directory, $this->sanitizeDirectorySeparator( $extraDirectory ) ) === 0 ) {
361
return true;
362
}
363
}
apps/Backend/Modules/Jobs/Job.php CHANGED
@@ -12,6 +12,8 @@ use WPStaging\Utils\Logger;
12
use WPStaging\WPStaging;
13
use WPStaging\Utils\Cache;
14
use WPStaging\Utils\Multisite;
15
16
/**
17
* Class Job
@@ -87,15 +89,21 @@ abstract class Job implements JobInterface {
87
88
/**
89
* Multisite home domain without scheme
90
- * @var type
91
*/
92
protected $multisiteDomainWithoutScheme;
93
94
/**
95
* Multisite home domain without scheme
96
- * @var type
97
*/
98
protected $multisiteHomeDomain;
99
100
/**
101
* @var int
@@ -109,12 +117,12 @@ abstract class Job implements JobInterface {
109
// Get max limits
110
$this->start = $this->time();
111
$this->maxMemoryLimit = $this->getMemoryInBytes( @ini_get( "memory_limit" ) );
112
113
$multisite = new Multisite;
114
$this->multisiteHomeUrlWithoutScheme = $multisite->getHomeUrlWithoutScheme();
115
$this->multisiteHomeDomain = $multisite->getHomeDomain();
116
$this->multisiteDomainWithoutScheme = $multisite->getHomeDomainWithoutScheme();
117
- //$this->multisiteUrl = $multisite->getHomeDomain();
118
119
//$this->maxExecutionTime = (int) ini_get("max_execution_time");
120
$this->maxExecutionTime = ( int ) 30;
12
use WPStaging\WPStaging;
13
use WPStaging\Utils\Cache;
14
use WPStaging\Utils\Multisite;
15
+ use WPStaging\thirdParty\thirdPartyCompatibility;
16
+
17
18
/**
19
* Class Job
89
90
/**
91
* Multisite home domain without scheme
92
+ * @var string
93
*/
94
protected $multisiteDomainWithoutScheme;
95
96
/**
97
* Multisite home domain without scheme
98
+ * @var string
99
*/
100
protected $multisiteHomeDomain;
101
+
102
+ /**
103
+ *
104
+ * @var object
105
+ */
106
+ protected $thirdParty;
107
108
/**
109
* @var int
117
// Get max limits
118
$this->start = $this->time();
119
$this->maxMemoryLimit = $this->getMemoryInBytes( @ini_get( "memory_limit" ) );
120
+ $this->thirdParty = new thirdPartyCompatibility($this);
121
122
$multisite = new Multisite;
123
$this->multisiteHomeUrlWithoutScheme = $multisite->getHomeUrlWithoutScheme();
124
$this->multisiteHomeDomain = $multisite->getHomeDomain();
125
$this->multisiteDomainWithoutScheme = $multisite->getHomeDomainWithoutScheme();
126
127
//$this->maxExecutionTime = (int) ini_get("max_execution_time");
128
$this->maxExecutionTime = ( int ) 30;
apps/Backend/Modules/Jobs/Multisite/Data.php CHANGED
@@ -1,938 +1,980 @@
1
- <?php
2
-
3
- namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
-
5
- // No Direct Access
6
- if( !defined( "WPINC" ) ) {
7
- die;
8
- }
9
-
10
- use WPStaging\Utils\Logger;
11
- use WPStaging\WPStaging;
12
- use WPStaging\Utils\Helper;
13
- use WPStaging\Utils\Multisite;
14
- use WPStaging\Utils\Strings;
15
- use WPStaging\Backend\Modules\Jobs\JobExecutable;
16
-
17
- /**
18
- * Class Data
19
- * @package WPStaging\Backend\Modules\Jobs
20
- */
21
- class Data extends JobExecutable {
22
-
23
- /**
24
- * @var \wpdb
25
- */
26
- private $db;
27
-
28
- /**
29
- * @var string
30
- */
31
- private $prefix;
32
-
33
- /**
34
- * Tables e.g wpstg3_options
35
- * @var array
36
- */
37
- private $tables;
38
-
39
- /**
40
- * Initialize
41
- */
42
- public function initialize() {
43
- $this->db = WPStaging::getInstance()->get( "wpdb" );
44
-
45
- $this->prefix = $this->options->prefix;
46
-
47
- $this->getTables();
48
-
49
- // Fix current step
50
- if( 0 == $this->options->currentStep ) {
51
- $this->options->currentStep = 1;
52
- }
53
- }
54
-
55
- /**
56
- * Get a list of tables to copy
57
- */
58
- private function getTables() {
59
- $strings = new Strings();
60
- $this->tables = array();
61
- foreach ( $this->options->tables as $table ) {
62
- $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->db->prefix, null, $table );
63
- }
64
- // Add extra global tables from main multisite (wpstgx_users and wpstgx_usermeta)
65
- $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->db->prefix, null, 'users' );
66
- $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->db->prefix, null, 'usermeta' );
67
- }
68
-
69
- /**
70
- * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
71
- * @return void
72
- */
73
- protected function calculateTotalSteps() {
74
- $this->options->totalSteps = 16;
75
- }
76
-
77
- /**
78
- * Start Module
79
- * @return object
80
- */
81
- public function start() {
82
- // Execute steps
83
- $this->run();
84
-
85
- // Save option, progress
86
- $this->saveOptions();
87
-
88
- return ( object ) $this->response;
89
- }
90
-
91
- /**
92
- * Execute the Current Step
93
- * Returns false when over threshold limits are hit or when the job is done, true otherwise
94
- * @return bool
95
- */
96
- protected function execute() {
97
- // Fatal error. Let this happen never and break here immediately
98
- if( $this->isRoot() ) {
99
- return false;
100
- }
101
-
102
- // Over limits threshold
103
- if( $this->isOverThreshold() ) {
104
- // Prepare response and save current progress
105
- $this->prepareResponse( false, false );
106
- $this->saveOptions();
107
- return false;
108
- }
109
-
110
- // No more steps, finished
111
- if( $this->isFinished() ) {
112
- $this->prepareResponse( true, false );
113
- return false;
114
- }
115
-
116
- // Execute step
117
- $stepMethodName = "step" . $this->options->currentStep;
118
- if( !$this->{$stepMethodName}() ) {
119
- $this->prepareResponse( false, false );
120
- return false;
121
- }
122
-
123
- // Prepare Response
124
- $this->prepareResponse();
125
-
126
- // Not finished
127
- return true;
128
- }
129
-
130
- /**
131
- * Checks Whether There is Any Job to Execute or Not
132
- * @return bool
133
- */
134
- protected function isFinished() {
135
- return (
136
- $this->options->currentStep > $this->options->totalSteps ||
137
- !method_exists( $this, "step" . $this->options->currentStep )
138
- );
139
- }
140
-
141
- /**
142
- * Check if current operation is done on the root folder or on the live DB
143
- * @return boolean
144
- */
145
- protected function isRoot() {
146
-
147
- // Prefix is the same as the one of live site
148
- $wpdb = WPStaging::getInstance()->get( "wpdb" );
149
- if( $wpdb->prefix === $this->prefix ) {
150
- return true;
151
- }
152
-
153
- // CloneName is empty
154
- $name = ( array ) $this->options->cloneDirectoryName;
155
- if( empty( $name ) ) {
156
- return true;
157
- }
158
-
159
- // Live Path === Staging path
160
- if( $this->multisiteHomeDomain . $this->options->cloneDirectoryName === $this->multisiteHomeDomain ) {
161
- return true;
162
- }
163
-
164
- return false;
165
- }
166
-
167
- /**
168
- * Check if table exists
169
- * @param string $table
170
- * @return boolean
171
- */
172
- protected function isTable( $table ) {
173
- if( $this->db->get_var( "SHOW TABLES LIKE '{$table}'" ) != $table ) {
174
- $this->log( "Table {$table} does not exists", Logger::TYPE_ERROR );
175
- return false;
176
- }
177
- return true;
178
- }
179
-
180
- /**
181
- * Get the install sub directory if WP is installed in sub directory
182
- * @return string
183
- */
184
- protected function getSubDir() {
185
- return '/';
186
-
187
- // $home = get_option( 'home' );
188
- // $siteurl = get_option( 'siteurl' );
189
- //
190
- // if( empty( $home ) || empty( $siteurl ) ) {
191
- // return '/';
192
- // }
193
- //
194
- // $dir = str_replace( $home, '', $siteurl );
195
- // return '/' . str_replace( '/', '', $dir ) . '/';
196
- }
197
-
198
- /**
199
- * Copy wp-config.php if it is located outside of root one level up
200
- * @todo Needs some more testing before it will be released
201
- * @return boolean
202
- */
203
- // protected function step0(){
204
- // $this->log( "Preparing Data Step0: Check if wp-config.php is located in root path", Logger::TYPE_INFO );
205
- //
206
- // $dir = trailingslashit( dirname( ABSPATH ) );
207
- //
208
- // $source = $dir . 'wp-config.php';
209
- //
210
- // $destination = trailingslashit(ABSPATH) . $this->clone->cloneDirectoryName . DIRECTORY_SEPARATOR . 'wp-config.php';
211
- //
212
- //
213
- // // Do not do anything
214
- // if( (!is_file( $source ) && !is_link($source)) || is_file($destination) ) {
215
- // $this->log( "Preparing Data Step0: Skip it", Logger::TYPE_INFO );
216
- // return true;
217
- // }
218
- //
219
- // // Copy target of a symbolic link
220
- // if (is_link($source)){
221
- // $this->log( "Preparing Data Step0: Symbolic link found...", Logger::TYPE_INFO );
222
- // if (!@copy(readlink($source), $destination)) {
223
- // $this->log("Preparing Data Step0: Failed to copy wp-config.php {$source} -> {$destination}", Logger::TYPE_ERROR);
224
- // return true;
225
- // }
226
- // }
227
- //
228
- // // regular copy of wp-config.php
229
- // if (!@copy($source, $destination)) {
230
- // $this->log("Preparing Data Step0: Failed to copy wp-config.php {$source} -> {$destination}", Logger::TYPE_ERROR);
231
- // return true;
232
- // }
233
- // $this->log( "Preparing Data Step0: Successfull", Logger::TYPE_INFO );
234
- // return true;
235
- //
236
- // }
237
-
238
- /**
239
- * Replace "siteurl" and "home"
240
- * @return bool
241
- */
242
- protected function step1() {
243
- $this->log( "Preparing Data Step1: Updating siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_INFO );
244
-
245
- // Skip - Table does not exist
246
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
247
- return true;
248
- }
249
- // Skip - Table is not selected or updated
250
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
251
- $this->log( "Preparing Data Step1: Skipping" );
252
- return true;
253
- }
254
-
255
- // Installed in sub-directory
256
- if( $this->isSubDir() ) {
257
- $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . rtrim( $this->multisiteHomeDomain, "/" ) . $this->getSubDir() . $this->options->cloneDirectoryName );
258
- // Replace URLs
259
- $result = $this->db->query(
260
- $this->db->prepare(
261
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", rtrim( $this->multisiteHomeDomain, "/" ) . $this->getSubDir() . $this->options->cloneDirectoryName
262
- )
263
- );
264
- } else {
265
- $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . rtrim( $this->multisiteHomeDomain, "/" ) . '/' . $this->options->cloneDirectoryName );
266
- // Replace URLs
267
- $result = $this->db->query(
268
- $this->db->prepare(
269
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", $this->multisiteHomeDomain . '/' . $this->options->cloneDirectoryName
270
- )
271
- );
272
- }
273
-
274
-
275
- // All good
276
- if( $result ) {
277
- return true;
278
- }
279
-
280
- $this->log( "Preparing Data Step1: Failed to update siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
281
- return false;
282
- }
283
-
284
- /**
285
- * Update "wpstg_is_staging_site"
286
- * @return bool
287
- */
288
- protected function step2() {
289
-
290
- $this->log( "Preparing Data Step2: Updating row wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}" );
291
-
292
- // Skip - Table does not exist
293
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
294
- return true;
295
- }
296
- // Skip - Table is not selected or updated
297
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
298
- $this->log( "Preparing Data Step2: Skipping" );
299
- return true;
300
- }
301
-
302
- $result = $this->db->query(
303
- $this->db->prepare(
304
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_is_staging_site'", "true"
305
- )
306
- );
307
-
308
- // No errors but no option name such as wpstg_is_staging_site
309
- if( '' === $this->db->last_error && 0 == $result ) {
310
- $result = $this->db->query(
311
- $this->db->prepare(
312
- "INSERT INTO {$this->prefix}options (option_name,option_value) VALUES ('wpstg_is_staging_site',%s)", "true"
313
- )
314
- );
315
- }
316
-
317
- // All good
318
- if( $result ) {
319
- return true;
320
- }
321
-
322
- $this->log( "Preparing Data Step2: Failed to update wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
323
- return false;
324
- }
325
-
326
- /**
327
- * Update rewrite_rules
328
- * @return bool
329
- */
330
- protected function step3() {
331
-
332
- $this->log( "Preparing Data Step3: Updating rewrite_rules in {$this->prefix}options {$this->db->last_error}" );
333
-
334
- // Skip - Table does not exist
335
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
336
- return true;
337
- }
338
-
339
- // Skip - Table is not selected or updated
340
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
341
- $this->log( "Preparing Data Step3: Skipping" );
342
- return true;
343
- }
344
-
345
- $result = $this->db->query(
346
- $this->db->prepare(
347
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'rewrite_rules'", ' '
348
- )
349
- );
350
-
351
- // All good
352
- if( $result ) {
353
- return true;
354
- }
355
-
356
- $this->log( "Preparing Data Step3: Failed to update rewrite_rules in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
357
- return true;
358
- }
359
-
360
- /**
361
- * Update Table Prefix in wp_usermeta and wp_options
362
- * @return bool
363
- */
364
- protected function step4() {
365
- $this->log( "Preparing Data Step4: Updating db prefix in {$this->prefix}usermeta. {$this->db->last_error}" );
366
-
367
- // Skip - Table does not exist
368
- if( false === $this->isTable( $this->prefix . 'usermeta' ) ) {
369
- return true;
370
- }
371
-
372
- // Skip - Table is not selected or updated
373
- if( !in_array( $this->prefix . 'usermeta', $this->tables ) ) {
374
- $this->log( "Preparing Data Step4: Skipping" );
375
- return true;
376
- }
377
-
378
- $update = $this->db->query(
379
- $this->db->prepare(
380
- "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 . "_%"
381
- )
382
- );
383
-
384
- if( !$update ) {
385
- $this->log( "Preparing Data Step4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}", Logger::TYPE_ERROR );
386
- $this->returnException( "Data Crunching Step 4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}" );
387
- return false;
388
- }
389
- return true;
390
- }
391
-
392
- /**
393
- * Update $table_prefix in wp-config.php
394
- * @return bool
395
- */
396
- protected function step5() {
397
- $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
398
-
399
- $this->log( "Preparing Data Step5: Updating table_prefix in {$path} to " . $this->prefix );
400
- if( false === ($content = file_get_contents( $path )) ) {
401
- $this->log( "Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR );
402
- return false;
403
- }
404
-
405
- // Replace table prefix
406
- $content = str_replace( '$table_prefix', '$table_prefix = \'' . $this->prefix . '\';//', $content );
407
-
408
- // Replace URLs
409
- $content = str_replace( $this->multisiteHomeDomain, $this->multisiteHomeDomain . '/' . $this->options->cloneDirectoryName, $content );
410
-
411
- if( false === @file_put_contents( $path, $content ) ) {
412
- $this->log( "Preparing Data Step5: Failed to update $table_prefix in {$path} to " . $this->prefix . ". Can't save contents", Logger::TYPE_ERROR );
413
- return false;
414
- }
415
-
416
- return true;
417
- }
418
-
419
- /**
420
- * Reset index.php to original file
421
- * This is needed if live site is located in subfolder
422
- * Check first if main wordpress is used in subfolder and index.php in parent directory
423
- * @see: https://codex.wordpress.org/Giving_WordPress_Its_Own_Directory
424
- * @return bool
425
- */
426
- protected function step6() {
427
-
428
- if( !$this->isSubDir() ) {
429
- $this->debugLog( "Preparing Data Step6: WP installation is not in a subdirectory! All good, skipping this step" );
430
- return true;
431
- }
432
-
433
- $path = ABSPATH . $this->options->cloneDirectoryName . "/index.php";
434
-
435
- if( false === ($content = file_get_contents( $path )) ) {
436
- $this->log( "Preparing Data Step6: Failed to reset {$path} for sub directory; can't read contents", Logger::TYPE_ERROR );
437
- return false;
438
- }
439
-
440
-
441
- if( !preg_match( "/(require(.*)wp-blog-header.php' \);)/", $content, $matches ) ) {
442
- $this->log(
443
- "Preparing Data Step6: Failed to reset index.php for sub directory; wp-blog-header.php is missing", Logger::TYPE_ERROR
444
- );
445
- return false;
446
- }
447
- $this->log( "Preparing Data: WP installation is in a subdirectory. Progressing..." );
448
-
449
- $pattern = "/require(.*) dirname(.*) __FILE__ (.*) \. '(.*)wp-blog-header.php'(.*);/";
450
-
451
- $replace = "require( dirname( __FILE__ ) . '/wp-blog-header.php' ); // " . $matches[0];
452
- $replace.= " // Changed by WP-Staging";
453
-
454
-
455
-
456
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
457
- $this->log( "Preparing Data: Failed to reset index.php for sub directory; replacement failed", Logger::TYPE_ERROR );
458
- return false;
459
- }
460
-
461
- if( false === @file_put_contents( $path, $content ) ) {
462
- $this->log( "Preparing Data: Failed to reset index.php for sub directory; can't save contents", Logger::TYPE_ERROR );
463
- return false;
464
- }
465
- $this->Log( "Preparing Data: Finished Step 6 successfully" );
466
- return true;
467
- }
468
-
469
- /**
470
- * Update wpstg_rmpermalinks_executed
471
- * @return bool
472
- */
473
- protected function step7() {
474
-
475
- $this->log( "Preparing Data Step7: Updating wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}" );
476
-
477
- // Skip - Table does not exist
478
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
479
- return true;
480
- }
481
-
482
- // Skip - Table is not selected or updated
483
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
484
- $this->log( "Preparing Data Step7: Skipping" );
485
- return true;
486
- }
487
-
488
- $result = $this->db->query(
489
- $this->db->prepare(
490
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_rmpermalinks_executed'", ' '
491
- )
492
- );
493
-
494
- // All good
495
- if( $result ) {
496
- $this->Log( "Preparing Data Step7: Finished Step 7 successfully" );
497
- return true;
498
- }
499
-
500
- $this->log( "Failed to update wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_WARNING );
501
- return true;
502
- }
503
-
504
- /**
505
- * Update permalink_structure
506
- * @return bool
507
- */
508
- protected function step8() {
509
-
510
- $this->log( "Preparing Data Step8: Updating permalink_structure in {$this->prefix}options {$this->db->last_error}" );
511
-
512
- // Skip - Table does not exist
513
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
514
- return true;
515
- }
516
-
517
- // Skip - Table is not selected or updated
518
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
519
- $this->log( "Preparing Data Step8: Skipping" );
520
- return true;
521
- }
522
-
523
- $result = $this->db->query(
524
- $this->db->prepare(
525
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'permalink_structure'", ' '
526
- )
527
- );
528
-
529
- // All good
530
- if( $result ) {
531
- $this->Log( "Preparing Data Step8: Finished Step 8 successfully" );
532
- return true;
533
- }
534
-
535
- $this->log( "Failed to update permalink_structure in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
536
- return true;
537
- }
538
-
539
- /**
540
- * Update blog_public option to not allow staging site to be indexed by search engines
541
- * @return bool
542
- */
543
- protected function step9() {
544
-
545
- $this->log( "Preparing Data Step9: Set staging site to noindex" );
546
-
547
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
548
- return true;
549
- }
550
-
551
- // Skip - Table is not selected or updated
552
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
553
- $this->log( "Preparing Data Step9: Skipping" );
554
- return true;
555
- }
556
-
557
- $result = $this->db->query(
558
- $this->db->prepare(
559
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'blog_public'", '0'
560
- )
561
- );
562
-
563
- // All good
564
- if( $result ) {
565
- $this->Log( "Preparing Data Step9: Finished Step 9 successfully" );
566
- return true;
567
- }
568
-
569
- $this->log( "Can not update staging site to noindex. Possible already done!", Logger::TYPE_WARNING );
570
- return true;
571
- }
572
-
573
- /**
574
- * Update WP_HOME in wp-config.php
575
- * @return bool
576
- */
577
- protected function step10() {
578
- $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
579
-
580
- $this->log( "Preparing Data Step10: Updating WP_HOME in wp-config.php to " . $this->getStagingSiteUrl() );
581
-
582
- if( false === ($content = file_get_contents( $path )) ) {
583
- $this->log( "Preparing Data Step10: Failed to update WP_HOME in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
584
- return false;
585
- }
586
-
587
-
588
- // Get WP_HOME from wp-config.php
589
- preg_match( "/define\s*\(\s*'WP_HOME'\s*,\s*(.*)\s*\);/", $content, $matches );
590
-
591
- if( !empty( $matches[1] ) ) {
592
- $matches[1];
593
-
594
- $pattern = "/define\s*\(\s*'WP_HOME'\s*,\s*(.*)\s*\);/";
595
-
596
- $replace = "define('WP_HOME','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
597
- $replace.= " // Changed by WP-Staging";
598
-
599
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
600
- $this->log( "Preparing Data: Failed to reset index.php for sub directory; replacement failed", Logger::TYPE_ERROR );
601
- return false;
602
- }
603
- } else {
604
- $this->log( "Preparing Data Step10: WP_HOME not defined in wp-config.php. Skipping this step." );
605
- }
606
-
607
- if( false === @file_put_contents( $path, $content ) ) {
608
- $this->log( "Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR );
609
- return false;
610
- }
611
- $this->Log( "Preparing Data: Finished Step 11 successfully" );
612
- return true;
613
- }
614
-
615
- /**
616
- * Update WP_SITEURL in wp-config.php
617
- * @return bool
618
- */
619
- protected function step11() {
620
- $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
621
-
622
- $this->log( "Preparing Data Step11: Updating WP_SITEURL in wp-config.php to " . $this->getStagingSiteUrl() );
623
-
624
- if( false === ($content = file_get_contents( $path )) ) {
625
- $this->log( "Preparing Data Step11: Failed to update WP_SITEURL in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
626
- return false;
627
- }
628
-
629
-
630
- // Get WP_SITEURL from wp-config.php
631
- preg_match( "/define\s*\(\s*'WP_SITEURL'\s*,\s*(.*)\s*\);/", $content, $matches );
632
-
633
- if( !empty( $matches[1] ) ) {
634
- $matches[1];
635
-
636
- $pattern = "/define\s*\(\s*'WP_SITEURL'\s*,\s*(.*)\s*\);/";
637
-
638
- $replace = "define('WP_SITEURL','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
639
- $replace.= " // Changed by WP-Staging";
640
-
641
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
642
- $this->log( "Preparing Data Step11: Failed to update WP_SITEURL", Logger::TYPE_ERROR );
643
- return false;
644
- }
645
- } else {
646
- $this->log( "Preparing Data Step11: WP_SITEURL not defined in wp-config.php. Skipping this step." );
647
- }
648
-
649
-
650
- if( false === @file_put_contents( $path, $content ) ) {
651
- $this->log( "Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR );
652
- return false;
653
- }
654
- $this->Log( "Preparing Data: Finished Step 11 successfully" );
655
- return true;
656
- }
657
-
658
- /**
659
- * Update WP_ALLOW_MULTISITE constant in wp-config.php
660
- * @return bool
661
- */
662
- protected function step12() {
663
- $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
664
-
665
- $this->log( "Preparing Data Step12: Updating WP_ALLOW_MULTISITE in wp-config.php to false" );
666
-
667
- if( false === ($content = file_get_contents( $path )) ) {
668
- $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
669
- return false;
670
- }
671
-
672
-
673
- // Get WP_SITEURL from wp-config.php
674
- preg_match( "/define\s*\(\s*'WP_ALLOW_MULTISITE'\s*,\s*(.*)\s*\);/", $content, $matches );
675
-
676
- if( !empty( $matches[1] ) ) {
677
- $matches[1];
678
-
679
- $pattern = "/define\s*\(\s*'WP_ALLOW_MULTISITE'\s*,\s*(.*)\s*\);/";
680
-
681
- $replace = "define('WP_ALLOW_MULTISITE',false); // " . $matches[1];
682
- $replace.= " // Changed by WP-Staging";
683
-
684
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
685
- $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE", Logger::TYPE_ERROR );
686
- return false;
687
- }
688
- } else {
689
- $this->log( "Preparing Data Step12: WP_ALLOW_MULTISITE not defined in wp-config.php. Skipping this step." );
690
- }
691
-
692
-
693
- if( false === @file_put_contents( $path, $content ) ) {
694
- $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE. Can't save contents", Logger::TYPE_ERROR );
695
- return false;
696
- }
697
- $this->Log( "Preparing Data: Finished Step 12 successfully" );
698
- return true;
699
- }
700
-
701
- /**
702
- * Update MULTISITE constant in wp-config.php
703
- * @return bool
704
- */
705
- protected function step13() {
706
- $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
707
-
708
- $this->log( "Preparing Data Step13: Updating MULTISITE in wp-config.php to false" );
709
-
710
- if( false === ($content = file_get_contents( $path )) ) {
711
- $this->log( "Preparing Data Step13: Failed to update MULTISITE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
712
- return false;
713
- }
714
-
715
-
716
- // Get WP_SITEURL from wp-config.php
717
- preg_match( "/define\s*\(\s*'MULTISITE'\s*,\s*(.*)\s*\);/", $content, $matches );
718
-
719
- if( !empty( $matches[1] ) ) {
720
- $matches[1];
721
-
722
- $pattern = "/define\s*\(\s*'MULTISITE'\s*,\s*(.*)\s*\);/";
723
-
724
- $replace = "define('MULTISITE',false); // " . $matches[1];
725
- $replace.= " // Changed by WP-Staging";
726
-
727
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
728
- $this->log( "Preparing Data Step13: Failed to update MULTISITE", Logger::TYPE_ERROR );
729
- return false;
730
- }
731
- } else {
732
- $this->log( "Preparing Data Step13: MULTISITE not defined in wp-config.php. Skipping this step." );
733
- }
734
-
735
-
736
- if( false === @file_put_contents( $path, $content ) ) {
737
- $this->log( "Preparing Data Step13: Failed to update MULTISITE. Can't save contents", Logger::TYPE_ERROR );
738
- return false;
739
- }
740
- $this->Log( "Preparing Data: Finished Step 13 successfully" );
741
- return true;
742
- }
743
-
744
- /**
745
- * Get active_sitewide_plugins from wp_sitemeta and active_plugins from subsite
746
- * Merge both arrays and copy them to the staging site into active_plugins
747
- */
748
- protected function step14() {
749
-
750
-
751
- $this->log( "Data Crunching Step 14: Updating active_plugins" );
752
-
753
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
754
- $this->log( 'Data Crunching Step 14: Fatal Error ' . $this->prefix . 'options does not exist' );
755
- $this->returnException( 'Data Crunching Step 8: Fatal Error ' . $this->prefix . 'options does not exist' );
756
- return false;
757
- }
758
-
759
- // Skip - Table is not selected or updated
760
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
761
- $this->log( "Preparing Data Step14: Skipping" );
762
- return true;
763
- }
764
-
765
- // Get active_plugins value from sub site options table
766
- $active_plugins = $this->db->get_var( "SELECT option_value FROM {$this->db->prefix}options WHERE option_name = 'active_plugins' " );
767
-
768
- if( !$active_plugins ) {
769
- $this->log( "Data Crunching Step 14: Option active_plugins are empty " );
770
- $active_plugins = array();
771
- }
772
- // Get active_sitewide_plugins value from main multisite wp_sitemeta table
773
- $active_sitewide_plugins = $this->db->get_var( "SELECT meta_value FROM {$this->db->base_prefix}sitemeta WHERE meta_key = 'active_sitewide_plugins' " );
774
-
775
- if( !$active_sitewide_plugins ) {
776
- $this->log( "Data Crunching Step 14: Options {$this->db->base_prefix}active_sitewide_plugins is empty " );
777
- $active_sitewide_plugins = array();
778
- }
779
-
780
- $active_sitewide_plugins = unserialize( $active_sitewide_plugins );
781
- $active_plugins = unserialize( $active_plugins );
782
-
783
- $all_plugins = array_merge( $active_plugins, array_keys( $active_sitewide_plugins ) );
784
-
785
- sort( $all_plugins );
786
-
787
-
788
- // Update active_plugins
789
- $update = $this->db->query(
790
- "UPDATE {$this->prefix}options SET option_value = '" . serialize( $all_plugins ) . "' WHERE option_name = 'active_plugins'"
791
- );
792
-
793
- if( false === $update ) {
794
- $this->log( "Data Crunching Step 14: Can not update option active_plugins in {$this->prefix}options", Logger::TYPE_WARNING );
795
- //$this->returnException( "Data Crunching Step 8: Can not update row wpstg_version in {$this->tmpPrefix}options - db error: " . $this->db->last_error );
796
- return false;
797
- }
798
-
799
- $this->log( "Data Crunching Step 14: Successfull!" );
800
- return true;
801
- }
802
-
803
- /**
804
- * Update Table Prefix in wp_options
805
- * @return bool
806
- */
807
- protected function step15() {
808
- $this->log( "Preparing Data Step15: Updating db prefix in {$this->prefix}options." );
809
-
810
- // Skip - Table does not exist
811
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
812
- return true;
813
- }
814
-
815
- // Skip - Table is not selected or updated
816
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
817
- $this->log( "Preparing Data Step4: Skipping" );
818
- return true;
819
- }
820
-
821
-
822
- $this->log( "Updating db option_names in {$this->prefix}options. " );
823
-
824
- // Filter the rows below. Do not update them!
825
- $filters = array(
826
- 'wp_mail_smtp',
827
- 'wp_mail_smtp_version',
828
- 'wp_mail_smtp_debug',
829
- );
830
-
831
- $filters = apply_filters( 'wpstg_data_excl_rows', $filters );
832
-
833
- $where = "";
834
- foreach ( $filters as $filter ) {
835
- $where .= " AND option_name <> '" . $filter . "'";
836
- }
837
-
838
- $updateOptions = $this->db->query(
839
- $this->db->prepare(
840
- "UPDATE IGNORE {$this->prefix}options SET option_name= replace(option_name, %s, %s) WHERE option_name LIKE %s" . $where, $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
841
- )
842
- );
843
-
844
- if( !$updateOptions ) {
845
- $this->log( "Preparing Data Step15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_ERROR );
846
- $this->returnException( "Data Crunching Step 15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
847
- return false;
848
- }
849
-
850
- return true;
851
- }
852
-
853
- /**
854
- * Change upload_path in wp_options (if it is defined)
855
- * @return bool
856
- */
857
- protected function step16() {
858
- $this->log( "Preparing Data Step16: Updating upload_path {$this->prefix}options." );
859
-
860
- // Skip - Table does not exist
861
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
862
- return true;
863
- }
864
-
865
- $newUploadPath = $this->getNewUploadPath();
866
-
867
- if( false === $newUploadPath ) {
868
- $this->log( "Preparing Data Step16: Skipping" );
869
- return true;
870
- }
871
-
872
- // Skip - Table is not selected or updated
873
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
874
- $this->log( "Preparing Data Step16: Skipping" );
875
- return true;
876
- }
877
-
878
- $error = isset( $this->db->last_error ) ? 'Last error: ' . $this->db->last_error : '';
879
-
880
- $this->log( "Updating upload_path in {$this->prefix}options. {$error}" );
881
-
882
- $updateOptions = $this->db->query(
883
- $this->db->prepare(
884
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'upload_path'", $newUploadPath
885
- )
886
- );
887
-
888
- if( !$updateOptions ) {
889
- $this->log( "Preparing Data Step16: Failed to update upload_path in {$this->prefix}options. {$error}", Logger::TYPE_ERROR );
890
- return true;
891
- }
892
- $this->Log( "Preparing Data: Finished Step 16 successfully" );
893
- return true;
894
- }
895
-
896
- protected function getNewUploadPath() {
897
- $uploadPath = get_option( 'upload_path' );
898
-
899
- if( !$uploadPath ) {
900
- return false;
901
- }
902
-
903
- $customSlug = str_replace( \WPStaging\WPStaging::getWPpath(), '', $uploadPath );
904
-
905
- $newUploadPath = \WPStaging\WPStaging::getWPpath() . $this->options->cloneDirectoryName . DIRECTORY_SEPARATOR . $customSlug;
906
-
907
- return $newUploadPath;
908
- }
909
-
910
- /**
911
- * Return URL to staging site
912
- * @return string
913
- */
914
- protected function getStagingSiteUrl() {
915
- if( $this->isSubDir() ) {
916
- return rtrim( $this->multisiteHomeDomain, "/" ) . $this->getSubDir() . $this->options->cloneDirectoryName;
917
- }
918
-
919
- return rtrim( $this->multisiteHomeDomain, "/" ) . '/' . $this->options->cloneDirectoryName;
920
- }
921
-
922
- /**
923
- * Check if WP is installed in subdir
924
- * @return boolean
925
- */
926
- protected function isSubDir() {
927
- // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
928
- // This is happening much more often than you would expect
929
- $siteurl = preg_replace( '#^https?://#', '', rtrim( get_option( 'siteurl' ), '/' ) );
930
- $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
931
-
932
- if( $home !== $siteurl ) {
933
- return true;
934
- }
935
- return false;
936
- }
937
-
938
- }
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
+
5
+ // No Direct Access
6
+ if( !defined( "WPINC" ) ) {
7
+ die;
8
+ }
9
+
10
+ use WPStaging\Utils\Logger;
11
+ use WPStaging\WPStaging;
12
+ use WPStaging\Utils\Helper;
13
+ use WPStaging\Utils\Multisite;
14
+ use WPStaging\Utils\Strings;
15
+ use WPStaging\Backend\Modules\Jobs\JobExecutable;
16
+
17
+ /**
18
+ * Class Data
19
+ * @package WPStaging\Backend\Modules\Jobs
20
+ */
21
+ class Data extends JobExecutable {
22
+
23
+ /**
24
+ * @var \wpdb
25
+ */
26
+ private $db;
27
+
28
+ /**
29
+ * @var string
30
+ */
31
+ private $prefix;
32
+
33
+ /**
34
+ * Tables e.g wpstg3_options
35
+ * @var array
36
+ */
37
+ private $tables;
38
+
39
+ /**
40
+ * Initialize
41
+ */
42
+ public function initialize() {
43
+ $this->db = WPStaging::getInstance()->get( "wpdb" );
44
+
45
+ $this->prefix = $this->options->prefix;
46
+
47
+ $this->getTables();
48
+
49
+ // Fix current step
50
+ if( 0 == $this->options->currentStep ) {
51
+ $this->options->currentStep = 1;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Get a list of tables to copy
57
+ */
58
+ private function getTables() {
59
+ $strings = new Strings();
60
+ $this->tables = array();
61
+ foreach ( $this->options->tables as $table ) {
62
+ $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->db->prefix, null, $table );
63
+ }
64
+ // Add extra global tables from main multisite (wpstgx_users and wpstgx_usermeta)
65
+ $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->db->prefix, null, 'users' );
66
+ $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->db->prefix, null, 'usermeta' );
67
+ }
68
+
69
+ /**
70
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
71
+ * @return void
72
+ */
73
+ protected function calculateTotalSteps() {
74
+ $this->options->totalSteps = 17;
75
+ }
76
+
77
+ /**
78
+ * Start Module
79
+ * @return object
80
+ */
81
+ public function start() {
82
+ // Execute steps
83
+ $this->run();
84
+
85
+ // Save option, progress
86
+ $this->saveOptions();
87
+
88
+ return ( object ) $this->response;
89
+ }
90
+
91
+ /**
92
+ * Execute the Current Step
93
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
94
+ * @return bool
95
+ */
96
+ protected function execute() {
97
+ // Fatal error. Let this happen never and break here immediately
98
+ if( $this->isRoot() ) {
99
+ return false;
100
+ }
101
+
102
+ // Over limits threshold
103
+ if( $this->isOverThreshold() ) {
104
+ // Prepare response and save current progress
105
+ $this->prepareResponse( false, false );
106
+ $this->saveOptions();
107
+ return false;
108
+ }
109
+
110
+ // No more steps, finished
111
+ if( $this->isFinished() ) {
112
+ $this->prepareResponse( true, false );
113
+ return false;
114
+ }
115
+
116
+ // Execute step
117
+ $stepMethodName = "step" . $this->options->currentStep;
118
+ if( !$this->{$stepMethodName}() ) {
119
+ $this->prepareResponse( false, false );
120
+ return false;
121
+ }
122
+
123
+ // Prepare Response
124
+ $this->prepareResponse();
125
+
126
+ // Not finished
127
+ return true;
128
+ }
129
+
130
+ /**
131
+ * Checks Whether There is Any Job to Execute or Not
132
+ * @return bool
133
+ */
134
+ protected function isFinished() {
135
+ return (
136
+ $this->options->currentStep > $this->options->totalSteps ||
137
+ !method_exists( $this, "step" . $this->options->currentStep )
138
+ );
139
+ }
140
+
141
+ /**
142
+ * Check if current operation is done on the root folder or on the live DB
143
+ * @return boolean
144
+ */
145
+ protected function isRoot() {
146
+
147
+ // Prefix is the same as the one of live site
148
+ $wpdb = WPStaging::getInstance()->get( "wpdb" );
149
+ if( $wpdb->prefix === $this->prefix ) {
150
+ return true;
151
+ }
152
+
153
+ // CloneName is empty
154
+ $name = ( array ) $this->options->cloneDirectoryName;
155
+ if( empty( $name ) ) {
156
+ return true;
157
+ }
158
+
159
+ // Live Path === Staging path
160
+ if( $this->multisiteHomeDomain . $this->options->cloneDirectoryName === $this->multisiteHomeDomain ) {
161
+ return true;
162
+ }
163
+
164
+ return false;
165
+ }
166
+
167
+ /**
168
+ * Check if table exists
169
+ * @param string $table
170
+ * @return boolean
171
+ */
172
+ protected function isTable( $table ) {
173
+ if( $this->db->get_var( "SHOW TABLES LIKE '{$table}'" ) != $table ) {
174
+ $this->log( "Table {$table} does not exists", Logger::TYPE_ERROR );
175
+ return false;
176
+ }
177
+ return true;
178
+ }
179
+
180
+ /**
181
+ * Get the install sub directory if WP is installed in sub directory
182
+ * @return string
183
+ */
184
+ protected function getSubDir() {
185
+ return '/';
186
+
187
+ // $home = get_option( 'home' );
188
+ // $siteurl = get_option( 'siteurl' );
189
+ //
190
+ // if( empty( $home ) || empty( $siteurl ) ) {
191
+ // return '/';
192
+ // }
193
+ //
194
+ // $dir = str_replace( $home, '', $siteurl );
195
+ // return '/' . str_replace( '/', '', $dir ) . '/';
196
+ }
197
+
198
+ /**
199
+ * Copy wp-config.php if it is located outside of root one level up
200
+ * @todo Needs some more testing before it will be released
201
+ * @return boolean
202
+ */
203
+ // protected function step0(){
204
+ // $this->log( "Preparing Data Step0: Check if wp-config.php is located in root path", Logger::TYPE_INFO );
205
+ //
206
+ // $dir = trailingslashit( dirname( ABSPATH ) );
207
+ //
208
+ // $source = $dir . 'wp-config.php';
209
+ //
210
+ // $destination = trailingslashit(ABSPATH) . $this->clone->cloneDirectoryName . DIRECTORY_SEPARATOR . 'wp-config.php';
211
+ //
212
+ //
213
+ // // Do not do anything
214
+ // if( (!is_file( $source ) && !is_link($source)) || is_file($destination) ) {
215
+ // $this->log( "Preparing Data Step0: Skip it", Logger::TYPE_INFO );
216
+ // return true;
217
+ // }
218
+ //
219
+ // // Copy target of a symbolic link
220
+ // if (is_link($source)){
221
+ // $this->log( "Preparing Data Step0: Symbolic link found...", Logger::TYPE_INFO );
222
+ // if (!@copy(readlink($source), $destination)) {
223
+ // $this->log("Preparing Data Step0: Failed to copy wp-config.php {$source} -> {$destination}", Logger::TYPE_ERROR);
224
+ // return true;
225
+ // }
226
+ // }
227
+ //
228
+ // // regular copy of wp-config.php
229
+ // if (!@copy($source, $destination)) {
230
+ // $this->log("Preparing Data Step0: Failed to copy wp-config.php {$source} -> {$destination}", Logger::TYPE_ERROR);
231
+ // return true;
232
+ // }
233
+ // $this->log( "Preparing Data Step0: Successfull", Logger::TYPE_INFO );
234
+ // return true;
235
+ //
236
+ // }
237
+
238
+ /**
239
+ * Replace "siteurl" and "home"
240
+ * @return bool
241
+ */
242
+ protected function step1() {
243
+ $this->log( "Preparing Data Step1: Updating siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_INFO );
244
+
245
+ // Skip - Table does not exist
246
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
247
+ return true;
248
+ }
249
+ // Skip - Table is not selected or updated
250
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
251
+ $this->log( "Preparing Data Step1: Skipping" );
252
+ return true;
253
+ }
254
+
255
+ // Installed in sub-directory
256
+ if( $this->isSubDir() ) {
257
+ $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . rtrim( $this->multisiteHomeDomain, "/" ) . $this->getSubDir() . $this->options->cloneDirectoryName );
258
+ // Replace URLs
259
+ $result = $this->db->query(
260
+ $this->db->prepare(
261
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", rtrim( $this->multisiteHomeDomain, "/" ) . $this->getSubDir() . $this->options->cloneDirectoryName
262
+ )
263
+ );
264
+ } else {
265
+ $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . rtrim( $this->multisiteHomeDomain, "/" ) . '/' . $this->options->cloneDirectoryName );
266
+ // Replace URLs
267
+ $result = $this->db->query(
268
+ $this->db->prepare(
269
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", $this->multisiteHomeDomain . '/' . $this->options->cloneDirectoryName
270
+ )
271
+ );
272
+ }
273
+
274
+
275
+ // All good
276
+ if( $result ) {
277
+ return true;
278
+ }
279
+
280
+ $this->log( "Preparing Data Step1: Failed to update siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
281
+ return false;
282
+ }
283
+
284
+ /**
285
+ * Update "wpstg_is_staging_site"
286
+ * @return bool
287
+ */
288
+ protected function step2() {
289
+
290
+ $this->log( "Preparing Data Step2: Updating row wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}" );
291
+
292
+ // Skip - Table does not exist
293
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
294
+ return true;
295
+ }
296
+ // Skip - Table is not selected or updated
297
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
298
+ $this->log( "Preparing Data Step2: Skipping" );
299
+ return true;
300
+ }
301
+
302
+ $result = $this->db->query(
303
+ $this->db->prepare(
304
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_is_staging_site'", "true"
305
+ )
306
+ );
307
+
308
+ // No errors but no option name such as wpstg_is_staging_site
309
+ if( '' === $this->db->last_error && 0 == $result ) {
310
+ $result = $this->db->query(
311
+ $this->db->prepare(
312
+ "INSERT INTO {$this->prefix}options (option_name,option_value) VALUES ('wpstg_is_staging_site',%s)", "true"
313
+ )
314
+ );
315
+ }
316
+
317
+ // All good
318
+ if( $result ) {
319
+ return true;
320
+ }
321
+
322
+ $this->log( "Preparing Data Step2: Failed to update wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
323
+ return false;
324
+ }
325
+
326
+ /**
327
+ * Update rewrite_rules
328
+ * @return bool
329
+ */
330
+ protected function step3() {
331
+
332
+ $this->log( "Preparing Data Step3: Updating rewrite_rules in {$this->prefix}options {$this->db->last_error}" );
333
+
334
+ // Skip - Table does not exist
335
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
336
+ return true;
337
+ }
338
+
339
+ // Skip - Table is not selected or updated
340
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
341
+ $this->log( "Preparing Data Step3: Skipping" );
342
+ return true;
343
+ }
344
+
345
+ $result = $this->db->query(
346
+ $this->db->prepare(
347
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'rewrite_rules'", ' '
348
+ )
349
+ );
350
+
351
+ // All good
352
+ if( $result ) {
353
+ return true;
354
+ }
355
+
356
+ $this->log( "Preparing Data Step3: Failed to update rewrite_rules in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
357
+ return true;
358
+ }
359
+
360
+ /**
361
+ * Update Table Prefix in wp_usermeta and wp_options
362
+ * @return bool
363
+ */
364
+ protected function step4() {
365
+ $this->log( "Preparing Data Step4: Updating db prefix in {$this->prefix}usermeta. {$this->db->last_error}" );
366
+
367
+ // Skip - Table does not exist
368
+ if( false === $this->isTable( $this->prefix . 'usermeta' ) ) {
369
+ return true;
370
+ }
371
+
372
+ // Skip - Table is not selected or updated
373
+ if( !in_array( $this->prefix . 'usermeta', $this->tables ) ) {
374
+ $this->log( "Preparing Data Step4: Skipping" );
375
+ return true;
376
+ }
377
+
378
+ $update = $this->db->query(
379
+ $this->db->prepare(
380
+ "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 . "_%"
381
+ )
382
+ );
383
+
384
+ if( !$update ) {
385
+ $this->log( "Preparing Data Step4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}", Logger::TYPE_ERROR );
386
+ $this->returnException( "Data Crunching Step 4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}" );
387
+ return false;
388
+ }
389
+ return true;
390
+ }
391
+
392
+ /**
393
+ * Update $table_prefix in wp-config.php
394
+ * @return bool
395
+ */
396
+ protected function step5() {
397
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
398
+
399
+ $this->log( "Preparing Data Step5: Updating table_prefix in {$path} to " . $this->prefix );
400
+ if( false === ($content = file_get_contents( $path )) ) {
401
+ $this->log( "Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR );
402
+ return false;
403
+ }
404
+
405
+ // Replace table prefix
406
+ $content = str_replace( '$table_prefix', '$table_prefix = \'' . $this->prefix . '\';//', $content );
407
+
408
+ // Replace URLs
409
+ $content = str_replace( $this->multisiteHomeDomain, $this->multisiteHomeDomain . '/' . $this->options->cloneDirectoryName, $content );
410
+
411
+ if( false === @file_put_contents( $path, $content ) ) {
412
+ $this->log( "Preparing Data Step5: Failed to update $table_prefix in {$path} to " . $this->prefix . ". Can't save contents", Logger::TYPE_ERROR );
413
+ return false;
414
+ }
415
+
416
+ return true;
417
+ }
418
+
419
+ /**
420
+ * Reset index.php to original file
421
+ * This is needed if live site is located in subfolder
422
+ * Check first if main wordpress is used in subfolder and index.php in parent directory
423
+ * @see: https://codex.wordpress.org/Giving_WordPress_Its_Own_Directory
424
+ * @return bool
425
+ */
426
+ protected function step6() {
427
+
428
+ if( !$this->isSubDir() ) {
429
+ $this->debugLog( "Preparing Data Step6: WP installation is not in a subdirectory! All good, skipping this step" );
430
+ return true;
431
+ }
432
+
433
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/index.php";
434
+
435
+ if( false === ($content = file_get_contents( $path )) ) {
436
+ $this->log( "Preparing Data Step6: Failed to reset {$path} for sub directory; can't read contents", Logger::TYPE_ERROR );
437
+ return false;
438
+ }
439
+
440
+
441
+ if( !preg_match( "/(require(.*)wp-blog-header.php' \);)/", $content, $matches ) ) {
442
+ $this->log(
443
+ "Preparing Data Step6: Failed to reset index.php for sub directory; wp-blog-header.php is missing", Logger::TYPE_ERROR
444
+ );
445
+ return false;
446
+ }
447
+ $this->log( "Preparing Data: WP installation is in a subdirectory. Progressing..." );
448
+
449
+ $pattern = "/require(.*) dirname(.*) __FILE__ (.*) \. '(.*)wp-blog-header.php'(.*);/";
450
+
451
+ $replace = "require( dirname( __FILE__ ) . '/wp-blog-header.php' ); // " . $matches[0];
452
+ $replace.= " // Changed by WP-Staging";
453
+
454
+
455
+
456
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
457
+ $this->log( "Preparing Data: Failed to reset index.php for sub directory; replacement failed", Logger::TYPE_ERROR );
458
+ return false;
459
+ }
460
+
461
+ if( false === @file_put_contents( $path, $content ) ) {
462
+ $this->log( "Preparing Data: Failed to reset index.php for sub directory; can't save contents", Logger::TYPE_ERROR );
463
+ return false;
464
+ }
465
+ $this->Log( "Preparing Data: Finished Step 6 successfully" );
466
+ return true;
467
+ }
468
+
469
+ /**
470
+ * Update wpstg_rmpermalinks_executed
471
+ * @return bool
472
+ */
473
+ protected function step7() {
474
+
475
+ $this->log( "Preparing Data Step7: Updating wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}" );
476
+
477
+ // Skip - Table does not exist
478
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
479
+ return true;
480
+ }
481
+
482
+ // Skip - Table is not selected or updated
483
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
484
+ $this->log( "Preparing Data Step7: Skipping" );
485
+ return true;
486
+ }
487
+
488
+ $result = $this->db->query(
489
+ $this->db->prepare(
490
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_rmpermalinks_executed'", ' '
491
+ )
492
+ );
493
+
494
+ // All good
495
+ if( $result ) {
496
+ $this->Log( "Preparing Data Step7: Finished Step 7 successfully" );
497
+ return true;
498
+ }
499
+
500
+ $this->log( "Failed to update wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_WARNING );
501
+ return true;
502
+ }
503
+
504
+ /**
505
+ * Update permalink_structure
506
+ * @return bool
507
+ */
508
+ protected function step8() {
509
+
510
+ $this->log( "Preparing Data Step8: Updating permalink_structure in {$this->prefix}options {$this->db->last_error}" );
511
+
512
+ // Skip - Table does not exist
513
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
514
+ return true;
515
+ }
516
+
517
+ // Skip - Table is not selected or updated
518
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
519
+ $this->log( "Preparing Data Step8: Skipping" );
520
+ return true;
521
+ }
522
+
523
+ $result = $this->db->query(
524
+ $this->db->prepare(
525
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'permalink_structure'", ' '
526
+ )
527
+ );
528
+
529
+ // All good
530
+ if( $result ) {
531
+ $this->Log( "Preparing Data Step8: Finished Step 8 successfully" );
532
+ return true;
533
+ }
534
+
535
+ $this->log( "Failed to update permalink_structure in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
536
+ return true;
537
+ }
538
+
539
+ /**
540
+ * Update blog_public option to not allow staging site to be indexed by search engines
541
+ * @return bool
542
+ */
543
+ protected function step9() {
544
+
545
+ $this->log( "Preparing Data Step9: Set staging site to noindex" );
546
+
547
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
548
+ return true;
549
+ }
550
+
551
+ // Skip - Table is not selected or updated
552
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
553
+ $this->log( "Preparing Data Step9: Skipping" );
554
+ return true;
555
+ }
556
+
557
+ $result = $this->db->query(
558
+ $this->db->prepare(
559
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'blog_public'", '0'
560
+ )
561
+ );
562
+
563
+ // All good
564
+ if( $result ) {
565
+ $this->Log( "Preparing Data Step9: Finished Step 9 successfully" );
566
+ return true;
567
+ }
568
+
569
+ $this->log( "Can not update staging site to noindex. Possible already done!", Logger::TYPE_WARNING );
570
+ return true;
571
+ }
572
+
573
+ /**
574
+ * Update WP_HOME in wp-config.php
575
+ * @return bool
576
+ */
577
+ protected function step10() {
578
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
579
+
580
+ $this->log( "Preparing Data Step10: Updating WP_HOME in wp-config.php to " . $this->getStagingSiteUrl() );
581
+
582
+ if( false === ($content = file_get_contents( $path )) ) {
583
+ $this->log( "Preparing Data Step10: Failed to update WP_HOME in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
584
+ return false;
585
+ }
586
+
587
+
588
+ // Get WP_HOME from wp-config.php
589
+ preg_match( "/define\s*\(\s*'WP_HOME'\s*,\s*(.*)\s*\);/", $content, $matches );
590
+
591
+ if( !empty( $matches[1] ) ) {
592
+ $matches[1];
593
+
594
+ $pattern = "/define\s*\(\s*'WP_HOME'\s*,\s*(.*)\s*\);/";
595
+
596
+ $replace = "define('WP_HOME','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
597
+ $replace.= " // Changed by WP-Staging";
598
+
599
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
600
+ $this->log( "Preparing Data: Failed to reset index.php for sub directory; replacement failed", Logger::TYPE_ERROR );
601
+ return false;
602
+ }
603
+ } else {
604
+ $this->log( "Preparing Data Step10: WP_HOME not defined in wp-config.php. Skipping this step." );
605
+ }
606
+
607
+ if( false === @file_put_contents( $path, $content ) ) {
608
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR );
609
+ return false;
610
+ }
611
+ $this->Log( "Preparing Data: Finished Step 11 successfully" );
612
+ return true;
613
+ }
614
+
615
+ /**
616
+ * Update WP_SITEURL in wp-config.php
617
+ * @return bool
618
+ */
619
+ protected function step11() {
620
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
621
+
622
+ $this->log( "Preparing Data Step11: Updating WP_SITEURL in wp-config.php to " . $this->getStagingSiteUrl() );
623
+
624
+ if( false === ($content = file_get_contents( $path )) ) {
625
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
626
+ return false;
627
+ }
628
+
629
+
630
+ // Get WP_SITEURL from wp-config.php
631
+ preg_match( "/define\s*\(\s*'WP_SITEURL'\s*,\s*(.*)\s*\);/", $content, $matches );
632
+
633
+ if( !empty( $matches[1] ) ) {
634
+ $matches[1];
635
+
636
+ $pattern = "/define\s*\(\s*'WP_SITEURL'\s*,\s*(.*)\s*\);/";
637
+
638
+ $replace = "define('WP_SITEURL','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
639
+ $replace.= " // Changed by WP-Staging";
640
+
641
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
642
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL", Logger::TYPE_ERROR );
643
+ return false;
644
+ }
645
+ } else {
646
+ $this->log( "Preparing Data Step11: WP_SITEURL not defined in wp-config.php. Skipping this step." );
647
+ }
648
+
649
+
650
+ if( false === @file_put_contents( $path, $content ) ) {
651
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR );
652
+ return false;
653
+ }
654
+ $this->Log( "Preparing Data: Finished Step 11 successfully" );
655
+ return true;
656
+ }
657
+
658
+ /**
659
+ * Update WP_ALLOW_MULTISITE constant in wp-config.php
660
+ * @return bool
661
+ */
662
+ protected function step12() {
663
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
664
+
665
+ $this->log( "Preparing Data Step12: Updating WP_ALLOW_MULTISITE in wp-config.php to false" );
666
+
667
+ if( false === ($content = file_get_contents( $path )) ) {
668
+ $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
669
+ return false;
670
+ }
671
+
672
+
673
+ // Get WP_SITEURL from wp-config.php
674
+ preg_match( "/define\s*\(\s*'WP_ALLOW_MULTISITE'\s*,\s*(.*)\s*\);/", $content, $matches );
675
+
676
+ if( !empty( $matches[1] ) ) {
677
+ $matches[1];
678
+
679
+ $pattern = "/define\s*\(\s*'WP_ALLOW_MULTISITE'\s*,\s*(.*)\s*\);/";
680
+
681
+ $replace = "define('WP_ALLOW_MULTISITE',false); // " . $matches[1];
682
+ $replace.= " // Changed by WP-Staging";
683
+
684
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
685
+ $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE", Logger::TYPE_ERROR );
686
+ return false;
687
+ }
688
+ } else {
689
+ $this->log( "Preparing Data Step12: WP_ALLOW_MULTISITE not defined in wp-config.php. Skipping this step." );
690
+ }
691
+
692
+
693
+ if( false === @file_put_contents( $path, $content ) ) {
694
+ $this->log( "Preparing Data Step12: Failed to update WP_ALLOW_MULTISITE. Can't save contents", Logger::TYPE_ERROR );
695
+ return false;
696
+ }
697
+ $this->Log( "Preparing Data: Finished Step 12 successfully" );
698
+ return true;
699
+ }
700
+
701
+ /**
702
+ * Update MULTISITE constant in wp-config.php
703
+ * @return bool
704
+ */
705
+ protected function step13() {
706
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
707
+
708
+ $this->log( "Preparing Data Step13: Updating MULTISITE in wp-config.php to false" );
709
+
710
+ if( false === ($content = file_get_contents( $path )) ) {
711
+ $this->log( "Preparing Data Step13: Failed to update MULTISITE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
712
+ return false;
713
+ }
714
+
715
+
716
+ // Get WP_SITEURL from wp-config.php
717
+ preg_match( "/define\s*\(\s*'MULTISITE'\s*,\s*(.*)\s*\);/", $content, $matches );
718
+
719
+ if( !empty( $matches[1] ) ) {
720
+ $matches[1];
721
+
722
+ $pattern = "/define\s*\(\s*'MULTISITE'\s*,\s*(.*)\s*\);/";
723
+
724
+ $replace = "define('MULTISITE',false); // " . $matches[1];
725
+ $replace.= " // Changed by WP-Staging";
726
+
727
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
728
+ $this->log( "Preparing Data Step13: Failed to update MULTISITE", Logger::TYPE_ERROR );
729
+ return false;
730
+ }
731
+ } else {
732
+ $this->log( "Preparing Data Step13: MULTISITE not defined in wp-config.php. Skipping this step." );
733
+ }
734
+
735
+
736
+ if( false === @file_put_contents( $path, $content ) ) {
737
+ $this->log( "Preparing Data Step13: Failed to update MULTISITE. Can't save contents", Logger::TYPE_ERROR );
738
+ return false;
739
+ }
740
+ $this->Log( "Preparing Data: Finished Step 13 successfully" );
741
+ return true;
742
+ }
743
+
744
+ /**
745
+ * Get active_sitewide_plugins from wp_sitemeta and active_plugins from subsite
746
+ * Merge both arrays and copy them to the staging site into active_plugins
747
+ */
748
+ protected function step14() {
749
+
750
+
751
+ $this->log( "Data Crunching Step 14: Updating active_plugins" );
752
+
753
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
754
+ $this->log( 'Data Crunching Step 14: Fatal Error ' . $this->prefix . 'options does not exist' );
755
+ $this->returnException( 'Data Crunching Step 8: Fatal Error ' . $this->prefix . 'options does not exist' );
756
+ return false;
757
+ }
758
+
759
+ // Skip - Table is not selected or updated
760
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
761
+ $this->log( "Preparing Data Step14: Skipping" );
762
+ return true;
763
+ }
764
+
765
+ // Get active_plugins value from sub site options table
766
+ $active_plugins = $this->db->get_var( "SELECT option_value FROM {$this->db->prefix}options WHERE option_name = 'active_plugins' " );
767
+
768
+ if( !$active_plugins ) {
769
+ $this->log( "Data Crunching Step 14: Option active_plugins are empty " );
770
+ $active_plugins = array();
771
+ }
772
+ // Get active_sitewide_plugins value from main multisite wp_sitemeta table
773
+ $active_sitewide_plugins = $this->db->get_var( "SELECT meta_value FROM {$this->db->base_prefix}sitemeta WHERE meta_key = 'active_sitewide_plugins' " );
774
+
775
+ if( !$active_sitewide_plugins ) {
776
+ $this->log( "Data Crunching Step 14: Options {$this->db->base_prefix}active_sitewide_plugins is empty " );
777
+ $active_sitewide_plugins = array();
778
+ }
779
+
780
+ $active_sitewide_plugins = unserialize( $active_sitewide_plugins );
781
+ $active_plugins = unserialize( $active_plugins );
782
+
783
+ $all_plugins = array_merge( $active_plugins, array_keys( $active_sitewide_plugins ) );
784
+
785
+ sort( $all_plugins );
786
+
787
+
788
+ // Update active_plugins
789
+ $update = $this->db->query(
790
+ "UPDATE {$this->prefix}options SET option_value = '" . serialize( $all_plugins ) . "' WHERE option_name = 'active_plugins'"
791
+ );
792
+
793
+ if( false === $update ) {
794
+ $this->log( "Data Crunching Step 14: Can not update option active_plugins in {$this->prefix}options", Logger::TYPE_WARNING );
795
+ //$this->returnException( "Data Crunching Step 8: Can not update row wpstg_version in {$this->tmpPrefix}options - db error: " . $this->db->last_error );
796
+ return false;
797
+ }
798
+
799
+ $this->log( "Data Crunching Step 14: Successfull!" );
800
+ return true;
801
+ }
802
+
803
+ /**
804
+ * Update Table Prefix in wp_options
805
+ * @return bool
806
+ */
807
+ protected function step15() {
808
+ $this->log( "Preparing Data Step15: Updating db prefix in {$this->prefix}options." );
809
+
810
+ // Skip - Table does not exist
811
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
812
+ return true;
813
+ }
814
+
815
+ // Skip - Table is not selected or updated
816
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
817
+ $this->log( "Preparing Data Step4: Skipping" );
818
+ return true;
819
+ }
820
+
821
+
822
+ $this->log( "Updating db option_names in {$this->prefix}options. " );
823
+
824
+ // Filter the rows below. Do not update them!
825
+ $filters = array(
826
+ 'wp_mail_smtp',
827
+ 'wp_mail_smtp_version',
828
+ 'wp_mail_smtp_debug',
829
+ );
830
+
831
+ $filters = apply_filters( 'wpstg_data_excl_rows', $filters );
832
+
833
+ $where = "";
834
+ foreach ( $filters as $filter ) {
835
+ $where .= " AND option_name <> '" . $filter . "'";
836
+ }
837
+
838
+ $updateOptions = $this->db->query(
839
+ $this->db->prepare(
840
+ "UPDATE IGNORE {$this->prefix}options SET option_name= replace(option_name, %s, %s) WHERE option_name LIKE %s" . $where, $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
841
+ )
842
+ );
843
+
844
+ if( !$updateOptions ) {
845
+ $this->log( "Preparing Data Step15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_ERROR );
846
+ $this->returnException( "Data Crunching Step 15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
847
+ return false;
848
+ }
849
+
850
+ return true;
851
+ }
852
+
853
+ /**
854
+ * Change upload_path in wp_options (if it is defined)
855
+ * @return bool
856
+ */
857
+ protected function step16() {
858
+ $this->log( "Preparing Data Step16: Updating upload_path {$this->prefix}options." );
859
+
860
+ // Skip - Table does not exist
861
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
862
+ return true;
863
+ }
864
+
865
+ $newUploadPath = $this->getNewUploadPath();
866
+
867
+ if( false === $newUploadPath ) {
868
+ $this->log( "Preparing Data Step16: Skipping" );
869
+ return true;
870
+ }
871
+
872
+ // Skip - Table is not selected or updated
873
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
874
+ $this->log( "Preparing Data Step16: Skipping" );
875
+ return true;
876
+ }
877
+
878
+ $error = isset( $this->db->last_error ) ? 'Last error: ' . $this->db->last_error : '';
879
+
880
+ $this->log( "Updating upload_path in {$this->prefix}options. {$error}" );
881
+
882
+ $updateOptions = $this->db->query(
883
+ $this->db->prepare(
884
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'upload_path'", $newUploadPath
885
+ )
886
+ );
887
+
888
+ if( !$updateOptions ) {
889
+ $this->log( "Preparing Data Step16: Failed to update upload_path in {$this->prefix}options. {$error}", Logger::TYPE_ERROR );
890
+ return true;
891
+ }
892
+ $this->Log( "Preparing Data: Finished Step 16 successfully" );
893
+ return true;
894
+ }
895
+
896
+ /**
897
+ * Update WP_CACHE in wp-config.php
898
+ * @return bool
899
+ */
900
+ protected function step17() {
901
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
902
+
903
+ $this->log( "Preparing Data Step17: Set WP_CACHE in wp-config.php to false");
904
+
905
+ if( false === ($content = file_get_contents( $path )) ) {
906
+ $this->log( "Preparing Data Step17: Failed to update WP_CACHE in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
907
+ return false;
908
+ }
909
+
910
+
911
+ // Get WP_CACHE from wp-config.php
912
+ preg_match( "/define\s*\(\s*'WP_CACHE'\s*,\s*(.*)\s*\);/", $content, $matches );
913
+
914
+ if( !empty( $matches[1] ) ) {
915
+ $matches[1];
916
+
917
+ $pattern = "/define\s*\(\s*'WP_CACHE'\s*,\s*(.*)\s*\);/";
918
+
919
+ $replace = "define('WP_CACHE',false); // " . $matches[1];
920
+ $replace.= " // Changed by WP-Staging";
921
+
922
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
923
+ $this->log( "Preparing Data: Failed to change WP_CACHE", Logger::TYPE_ERROR );
924
+ return false;
925
+ }
926
+ } else {
927
+ $this->log( "Preparing Data Step17: WP_CACHE not defined in wp-config.php. Skipping this step." );
928
+ }
929
+
930
+ if( false === @file_put_contents( $path, $content ) ) {
931
+ $this->log( "Preparing Data Step17: Failed to update WP_CACHE. Can't save contents", Logger::TYPE_ERROR );
932
+ return false;
933
+ }
934
+ $this->Log( "Preparing Data: Finished Step 17 successfully" );
935
+ return true;
936
+ }
937
+
938
+ protected function getNewUploadPath() {
939
+ $uploadPath = get_option( 'upload_path' );
940
+
941
+ if( !$uploadPath ) {
942
+ return false;
943
+ }
944
+
945
+ $customSlug = str_replace( \WPStaging\WPStaging::getWPpath(), '', $uploadPath );
946
+
947
+ $newUploadPath = \WPStaging\WPStaging::getWPpath() . $this->options->cloneDirectoryName . DIRECTORY_SEPARATOR . $customSlug;
948
+
949
+ return $newUploadPath;
950
+ }
951
+
952
+ /**
953
+ * Return URL to staging site
954
+ * @return string
955
+ */
956
+ protected function getStagingSiteUrl() {
957
+ if( $this->isSubDir() ) {
958
+ return rtrim( $this->multisiteHomeDomain, "/" ) . $this->getSubDir() . $this->options->cloneDirectoryName;
959
+ }
960
+
961
+ return rtrim( $this->multisiteHomeDomain, "/" ) . '/' . $this->options->cloneDirectoryName;
962
+ }
963
+
964
+ /**
965
+ * Check if WP is installed in subdir
966
+ * @return boolean
967
+ */
968
+ protected function isSubDir() {
969
+ // Compare names without scheme to bypass cases where siteurl and home have different schemes http / https
970
+ // This is happening much more often than you would expect
971
+ $siteurl = preg_replace( '#^https?://#', '', rtrim( get_option( 'siteurl' ), '/' ) );
972
+ $home = preg_replace( '#^https?://#', '', rtrim( get_option( 'home' ), '/' ) );
973
+
974
+ if( $home !== $siteurl ) {
975
+ return true;
976
+ }
977
+ return false;
978
+ }
979
+
980
+ }
apps/Backend/Modules/Jobs/Multisite/Database.php CHANGED
@@ -1,315 +1,315 @@
1
- <?php
2
-
3
- namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
-
5
- // No Direct Access
6
- if( !defined( "WPINC" ) ) {
7
- die;
8
- }
9
-
10
- use WPStaging\WPStaging;
11
- use WPStaging\Utils\Strings;
12
- use WPStaging\Backend\Modules\Jobs\JobExecutable;
13
-
14
- /**
15
- * Class Database
16
- * @package WPStaging\Backend\Modules\Jobs
17
- */
18
- class Database extends JobExecutable {
19
-
20
- /**
21
- * @var int
22
- */
23
- private $total = 0;
24
-
25
- /**
26
- * @var \WPDB
27
- */
28
- private $db;
29
-
30
- /**
31
- * Initialize
32
- */
33
- public function initialize() {
34
- // Add 2 to total table count because we need to copy two more tables from the main multisite installation wp_users and wp_usermeta
35
- $this->total = count( $this->options->tables ) + 2;
36
- $this->db = WPStaging::getInstance()->get( "wpdb" );
37
- $this->isFatalError();
38
-
39
- }
40
-
41
-
42
- /**
43
- * Return fatal error and stops here if subfolder already exists
44
- * and mainJob is not updating the clone
45
- * @return boolean
46
- */
47
- private function isFatalError(){
48
- $path = trailingslashit(get_home_path()) . $this->options->cloneDirectoryName;
49
- if (isset($this->options->mainJob) && $this->options->mainJob !== 'updating' && is_dir($path)){
50
- $this->returnException( " Can not continue! Change the name of the clone or delete existing folder. Then try again. Folder already exists: " . $path );
51
- }
52
- return false;
53
- }
54
-
55
- /**
56
- * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
57
- * @return void
58
- */
59
- protected function calculateTotalSteps() {
60
- $this->options->totalSteps = $this->total === 0 ? 1 : $this->total;
61
- }
62
-
63
- /**
64
- * Execute the Current Step
65
- * Returns false when over threshold limits are hit or when the job is done, true otherwise
66
- * @return bool
67
- */
68
- protected function execute() {
69
-
70
-
71
- // Over limits threshold
72
- if( $this->isOverThreshold() ) {
73
- // Prepare response and save current progress
74
- $this->prepareResponse( false, false );
75
- $this->saveOptions();
76
- return false;
77
- }
78
-
79
- // No more steps, finished
80
- //if( $this->options->currentStep > $this->total || !isset( $this->options->tables[$this->options->currentStep] ) ) {
81
- if( $this->options->currentStep > $this->total ) {
82
- $this->prepareResponse( true, false );
83
- return false;
84
- }
85
-
86
- // Copy table
87
- //if (!$this->copyTable($this->options->tables[$this->options->currentStep]->name))
88
- if( isset($this->options->tables[$this->options->currentStep]) && !$this->copyTable( $this->options->tables[$this->options->currentStep] ) ) {
89
- // Prepare Response
90
- $this->prepareResponse( false, false );
91
-
92
- // Not finished
93
- return true;
94
- }
95
-
96
- $this->copyWpUsers();
97
-
98
- $this->copyWpUsermeta();
99
-
100
- // Prepare Response
101
- $this->prepareResponse();
102
-
103
- // Not finished
104
- return true;
105
- }
106
-
107
- /**
108
- * Get new prefix for the staging site
109
- * @return string
110
- */
111
- private function getStagingPrefix() {
112
- $stagingPrefix = $this->options->prefix;
113
- // Make sure prefix of staging site is NEVER identical to prefix of live site!
114
- if( $stagingPrefix == $this->db->prefix ) {
115
- wp_die( 'Fatal error 7: The new database table prefix ' . $stagingPrefix . ' would be identical to the table prefix of the live site. Please open a support ticket at support@wp-staging.com' );
116
- }
117
- return $stagingPrefix;
118
- }
119
-
120
- /**
121
- * No worries, SQL queries don't eat from PHP execution time!
122
- * @param string $tableName
123
- * @return bool
124
- */
125
- private function copyTable( $tableName ) {
126
-
127
- $strings = new Strings();
128
- $tableName = is_object( $tableName ) ? $tableName->name : $tableName;
129
- $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->prefix, null, $tableName );
130
-
131
- // Drop table if necessary
132
- $this->dropTable( $newTableName );
133
-
134
- // Save current job
135
- $this->setJob( $newTableName );
136
-
137
- // Beginning of the job
138
- if( !$this->startJob( $newTableName, $tableName ) ) {
139
- return true;
140
- }
141
-
142
- // Copy data
143
- $this->copyData( $newTableName, $tableName );
144
-
145
- // Finish the step
146
- return $this->finishStep();
147
- }
148
-
149
- /**
150
- * Copy multisite global user table wp_users to wpstgX_users
151
- * @return bool
152
- */
153
- private function copyWpUsers() {
154
- $strings = new Strings();
155
- $tableName = $this->db->base_prefix . 'users';
156
- $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->base_prefix, null, $tableName );
157
-
158
- // Drop table if necessary
159
- $this->dropTable( $newTableName );
160
-
161
- // Save current job
162
- $this->setJob( $newTableName );
163
-
164
- // Beginning of the job
165
- if( !$this->startJob( $newTableName, $tableName ) ) {
166
- return true;
167
- }
168
-
169
- // Copy data
170
- $this->copyData( $newTableName, $tableName );
171
-
172
- // Finish the step
173
- return $this->finishStep();
174
- }
175
-
176
- /**
177
- * Copy multisite global user table wp_usermeta to wpstgX_users
178
- * @return bool
179
- */
180
- private function copyWpUsermeta() {
181
- $strings = new Strings();
182
- $tableName = $this->db->base_prefix . 'usermeta';
183
- $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->base_prefix, null, $tableName );
184
-
185
- // Drop table if necessary
186
- $this->dropTable( $newTableName );
187
-
188
- // Save current job
189
- $this->setJob( $newTableName );
190
-
191
- // Beginning of the job
192
- if( !$this->startJob( $newTableName, $tableName ) ) {
193
- return true;
194
- }
195
- // Copy data
196
- $this->copyData( $newTableName, $tableName );
197
-
198
- // Finish the step
199
- return $this->finishStep();
200
- }
201
-
202
- /**
203
- * Copy data from old table to new table
204
- * @param string $new
205
- * @param string $old
206
- */
207
- private function copyData( $new, $old ) {
208
- $rows = $this->options->job->start + $this->settings->queryLimit;
209
- $this->log(
210
- "DB Copy: {$old} as {$new} from {$this->options->job->start} to {$rows} records"
211
- );
212
-
213
- $limitation = '';
214
-
215
- if( 0 < ( int ) $this->settings->queryLimit ) {
216
- $limitation = " LIMIT {$this->settings->queryLimit} OFFSET {$this->options->job->start}";
217
- }
218
-
219
- $this->db->query(
220
- "INSERT INTO {$new} SELECT * FROM {$old} {$limitation}"
221
- );
222
-
223
- // Set new offset
224
- $this->options->job->start += $this->settings->queryLimit;
225
- }
226
-
227
- /**
228
- * Set the job
229
- * @param string $table
230
- */
231
- private function setJob( $table ) {
232
- if( isset( $this->options->job->current ) ) {
233
- return;
234
- }
235
-
236
- $this->options->job->current = $table;
237
- $this->options->job->start = 0;
238
- }
239
-
240
- /**
241
- * Start Job
242
- * @param string $new
243
- * @param string $old
244
- * @return bool
245
- */
246
- private function startJob( $new, $old ) {
247
- if( 0 != $this->options->job->start ) {
248
- return true;
249
- }
250
-
251
- $this->log( "DB Copy: Creating table {$new}" );
252
-
253
- $this->db->query( "CREATE TABLE {$new} LIKE {$old}" );
254
-
255
- $this->options->job->total = ( int ) $this->db->get_var( "SELECT COUNT(1) FROM {$old}" );
256
-
257
- if( 0 == $this->options->job->total ) {
258
- $this->finishStep();
259
- return false;
260
- }
261
-
262
- return true;
263
- }
264
-
265
- /**
266
- * Finish the step
267
- */
268
- private function finishStep() {
269
- // This job is not finished yet
270
- if( $this->options->job->total > $this->options->job->start ) {
271
- return false;
272
- }
273
-
274
- // Add it to cloned tables listing
275
- $this->options->clonedTables[] = isset($this->options->tables[$this->options->currentStep]) ? $this->options->tables[$this->options->currentStep] : false;
276
-
277
- // Reset job
278
- $this->options->job = new \stdClass();
279
-
280
- return true;
281
- }
282
-
283
- /**
284
- * Drop table if necessary
285
- * @param string $new
286
- */
287
- private function dropTable( $new ) {
288
- $old = $this->db->get_var( $this->db->prepare( "SHOW TABLES LIKE %s", $new ) );
289
-
290
- if( !$this->shouldDropTable( $new, $old ) ) {
291
- return;
292
- }
293
-
294
- $this->log( "DB Copy: {$new} already exists, dropping it first" );
295
- $this->db->query( "DROP TABLE {$new}" );
296
- }
297
-
298
- /**
299
- * Check if table needs to be dropped
300
- * @param string $new
301
- * @param string $old
302
- * @return bool
303
- */
304
- private function shouldDropTable( $new, $old ) {
305
- return (
306
- $old === $new &&
307
- (
308
- !isset( $this->options->job->current ) ||
309
- !isset( $this->options->job->start ) ||
310
- 0 == $this->options->job->start
311
- )
312
- );
313
- }
314
-
315
- }
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
+
5
+ // No Direct Access
6
+ if( !defined( "WPINC" ) ) {
7
+ die;
8
+ }
9
+
10
+ use WPStaging\WPStaging;
11
+ use WPStaging\Utils\Strings;
12
+ use WPStaging\Backend\Modules\Jobs\JobExecutable;
13
+
14
+ /**
15
+ * Class Database
16
+ * @package WPStaging\Backend\Modules\Jobs
17
+ */
18
+ class Database extends JobExecutable {
19
+
20
+ /**
21
+ * @var int
22
+ */
23
+ private $total = 0;
24
+
25
+ /**
26
+ * @var \WPDB
27
+ */
28
+ private $db;
29
+
30
+ /**
31
+ * Initialize
32
+ */
33
+ public function initialize() {
34
+ // Add 2 to total table count because we need to copy two more tables from the main multisite installation wp_users and wp_usermeta
35
+ $this->total = count( $this->options->tables ) + 2;
36
+ $this->db = WPStaging::getInstance()->get( "wpdb" );
37
+ $this->isFatalError();
38
+
39
+ }
40
+
41
+
42
+ /**
43
+ * Return fatal error and stops here if subfolder already exists
44
+ * and mainJob is not updating the clone
45
+ * @return boolean
46
+ */
47
+ private function isFatalError(){
48
+ $path = trailingslashit(get_home_path()) . $this->options->cloneDirectoryName;
49
+ if (isset($this->options->mainJob) && $this->options->mainJob !== 'updating' && is_dir($path)){
50
+ $this->returnException( " Can not continue! Change the name of the clone or delete existing folder. Then try again. Folder already exists: " . $path );
51
+ }
52
+ return false;
53
+ }
54
+
55
+ /**
56
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
57
+ * @return void
58
+ */
59
+ protected function calculateTotalSteps() {
60
+ $this->options->totalSteps = $this->total === 0 ? 1 : $this->total;
61
+ }
62
+
63
+ /**
64
+ * Execute the Current Step
65
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
66
+ * @return bool
67
+ */
68
+ protected function execute() {
69
+
70
+
71
+ // Over limits threshold
72
+ if( $this->isOverThreshold() ) {
73
+ // Prepare response and save current progress
74
+ $this->prepareResponse( false, false );
75
+ $this->saveOptions();
76
+ return false;
77
+ }
78
+
79
+ // No more steps, finished
80
+ //if( $this->options->currentStep > $this->total || !isset( $this->options->tables[$this->options->currentStep] ) ) {
81
+ if( $this->options->currentStep > $this->total ) {
82
+ $this->prepareResponse( true, false );
83
+ return false;
84
+ }
85
+
86
+ // Copy table
87
+ //if (!$this->copyTable($this->options->tables[$this->options->currentStep]->name))
88
+ if( isset($this->options->tables[$this->options->currentStep]) && !$this->copyTable( $this->options->tables[$this->options->currentStep] ) ) {
89
+ // Prepare Response
90
+ $this->prepareResponse( false, false );
91
+
92
+ // Not finished
93
+ return true;
94
+ }
95
+
96
+ $this->copyWpUsers();
97
+
98
+ $this->copyWpUsermeta();
99
+
100
+ // Prepare Response
101
+ $this->prepareResponse();
102
+
103
+ // Not finished
104
+ return true;
105
+ }
106
+
107
+ /**
108
+ * Get new prefix for the staging site
109
+ * @return string
110
+ */
111
+ private function getStagingPrefix() {
112
+ $stagingPrefix = $this->options->prefix;
113
+ // Make sure prefix of staging site is NEVER identical to prefix of live site!
114
+ if( $stagingPrefix == $this->db->prefix ) {
115
+ wp_die( 'Fatal error 7: The new database table prefix ' . $stagingPrefix . ' would be identical to the table prefix of the live site. Please open a support ticket at support@wp-staging.com' );
116
+ }
117
+ return $stagingPrefix;
118
+ }
119
+
120
+ /**
121
+ * No worries, SQL queries don't eat from PHP execution time!
122
+ * @param string $tableName
123
+ * @return bool
124
+ */
125
+ private function copyTable( $tableName ) {
126
+
127
+ $strings = new Strings();
128
+ $tableName = is_object( $tableName ) ? $tableName->name : $tableName;
129
+ $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->prefix, null, $tableName );
130
+
131
+ // Drop table if necessary
132
+ $this->dropTable( $newTableName );
133
+
134
+ // Save current job
135
+ $this->setJob( $newTableName );
136
+
137
+ // Beginning of the job
138
+ if( !$this->startJob( $newTableName, $tableName ) ) {
139
+ return true;
140
+ }
141
+
142
+ // Copy data
143
+ $this->copyData( $newTableName, $tableName );
144
+
145
+ // Finish the step
146
+ return $this->finishStep();
147
+ }
148
+
149
+ /**
150
+ * Copy multisite global user table wp_users to wpstgX_users
151
+ * @return bool
152
+ */
153
+ private function copyWpUsers() {
154
+ $strings = new Strings();
155
+ $tableName = $this->db->base_prefix . 'users';
156
+ $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->base_prefix, null, $tableName );
157
+
158
+ // Drop table if necessary
159
+ $this->dropTable( $newTableName );
160
+
161
+ // Save current job
162
+ $this->setJob( $newTableName );
163
+
164
+ // Beginning of the job
165
+ if( !$this->startJob( $newTableName, $tableName ) ) {
166
+ return true;
167
+ }
168
+
169
+ // Copy data
170
+ $this->copyData( $newTableName, $tableName );
171
+
172
+ // Finish the step
173
+ return $this->finishStep();
174
+ }
175
+
176
+ /**
177
+ * Copy multisite global user table wp_usermeta to wpstgX_users
178
+ * @return bool
179
+ */
180
+ private function copyWpUsermeta() {
181
+ $strings = new Strings();
182
+ $tableName = $this->db->base_prefix . 'usermeta';
183
+ $newTableName = $this->getStagingPrefix() . $strings->str_replace_first( $this->db->base_prefix, null, $tableName );
184
+
185
+ // Drop table if necessary
186
+ $this->dropTable( $newTableName );
187
+
188
+ // Save current job
189
+ $this->setJob( $newTableName );
190
+
191
+ // Beginning of the job
192
+ if( !$this->startJob( $newTableName, $tableName ) ) {
193
+ return true;
194
+ }
195
+ // Copy data
196
+ $this->copyData( $newTableName, $tableName );
197
+
198
+ // Finish the step
199
+ return $this->finishStep();
200
+ }
201
+
202
+ /**
203
+ * Copy data from old table to new table
204
+ * @param string $new
205
+ * @param string $old
206
+ */
207
+ private function copyData( $new, $old ) {
208
+ $rows = $this->options->job->start + $this->settings->queryLimit;
209
+ $this->log(
210
+ "DB Copy: {$old} as {$new} from {$this->options->job->start} to {$rows} records"
211
+ );
212
+
213
+ $limitation = '';
214
+
215
+ if( 0 < ( int ) $this->settings->queryLimit ) {
216
+ $limitation = " LIMIT {$this->settings->queryLimit} OFFSET {$this->options->job->start}";
217
+ }
218
+
219
+ $this->db->query(
220
+ "INSERT INTO {$new} SELECT * FROM {$old} {$limitation}"
221
+ );
222
+
223
+ // Set new offset
224
+ $this->options->job->start += $this->settings->queryLimit;
225
+ }
226
+
227
+ /**
228
+ * Set the job
229
+ * @param string $table
230
+ */
231
+ private function setJob( $table ) {
232
+ if( isset( $this->options->job->current ) ) {
233
+ return;
234
+ }
235
+
236
+ $this->options->job->current = $table;
237
+ $this->options->job->start = 0;
238
+ }
239
+
240
+ /**
241
+ * Start Job
242
+ * @param string $new
243
+ * @param string $old
244
+ * @return bool
245
+ */
246
+ private function startJob( $new, $old ) {
247
+ if( 0 != $this->options->job->start ) {
248
+ return true;
249
+ }
250
+
251
+ $this->log( "DB Copy: Creating table {$new}" );
252
+
253
+ $this->db->query( "CREATE TABLE {$new} LIKE {$old}" );
254
+
255
+ $this->options->job->total = ( int ) $this->db->get_var( "SELECT COUNT(1) FROM {$old}" );
256
+
257
+ if( 0 == $this->options->job->total ) {
258
+ $this->finishStep();
259
+ return false;
260
+ }
261
+
262
+ return true;
263
+ }
264
+
265
+ /**
266
+ * Finish the step
267
+ */
268
+ private function finishStep() {
269
+ // This job is not finished yet
270
+ if( $this->options->job->total > $this->options->job->start ) {
271
+ return false;
272
+ }
273
+
274
+ // Add it to cloned tables listing
275
+ $this->options->clonedTables[] = isset($this->options->tables[$this->options->currentStep]) ? $this->options->tables[$this->options->currentStep] : false;
276
+
277
+ // Reset job
278
+ $this->options->job = new \stdClass();
279
+
280
+ return true;
281
+ }
282
+
283
+ /**
284
+ * Drop table if necessary
285
+ * @param string $new
286
+ */
287
+ private function dropTable( $new ) {
288
+ $old = $this->db->get_var( $this->db->prepare( "SHOW TABLES LIKE %s", $new ) );
289
+
290
+ if( !$this->shouldDropTable( $new, $old ) ) {
291
+ return;
292
+ }
293
+
294
+ $this->log( "DB Copy: {$new} already exists, dropping it first" );
295
+ $this->db->query( "DROP TABLE {$new}" );
296
+ }
297
+
298
+ /**
299
+ * Check if table needs to be dropped
300
+ * @param string $new
301
+ * @param string $old
302
+ * @return bool
303
+ */
304
+ private function shouldDropTable( $new, $old ) {
305
+ return (
306
+ $old === $new &&
307
+ (
308
+ !isset( $this->options->job->current ) ||
309
+ !isset( $this->options->job->start ) ||
310
+ 0 == $this->options->job->start
311
+ )
312
+ );
313
+ }
314
+
315
+ }
apps/Backend/Modules/Jobs/Multisite/Directories.php CHANGED
@@ -1,655 +1,655 @@
1
- <?php
2
-
3
- namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
-
5
- // No Direct Access
6
- if( !defined( "WPINC" ) ) {
7
- die;
8
- }
9
-
10
- use WPStaging\WPStaging;
11
- use WPStaging\Utils\Logger;
12
- use WPStaging\Utils\Strings;
13
- use WPStaging\Iterators\RecursiveDirectoryIterator;
14
- use WPStaging\Iterators\RecursiveFilterNewLine;
15
- use WPStaging\Iterators\RecursiveFilterExclude;
16
- use WPStaging\Backend\Modules\Jobs\JobExecutable;
17
-
18
- /**
19
- * Class Files
20
- * @package WPStaging\Backend\Modules\Directories
21
- */
22
- class Directories extends JobExecutable {
23
-
24
- /**
25
- * @var array
26
- */
27
- private $files = array();
28
-
29
- /**
30
- * Total steps to do
31
- * @var int
32
- */
33
- private $total = 5;
34
-
35
- /**
36
- * path to the cache file
37
- * @var string
38
- */
39
- private $filename;
40
-
41
- /**
42
- * Initialize
43
- */
44
- public function initialize() {
45
- $this->filename = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
46
- }
47
-
48
- /**
49
- * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
50
- * @return void
51
- */
52
- protected function calculateTotalSteps() {
53
-
54
- $this->options->totalSteps = $this->total + count( $this->options->extraDirectories );
55
- }
56
-
57
- /**
58
- * Start Module
59
- * @return object
60
- */
61
- public function start() {
62
-
63
- // Execute steps
64
- $this->run();
65
-
66
- // Save option, progress
67
- $this->saveProgress();
68
-
69
- return ( object ) $this->response;
70
- }
71
-
72
- /**
73
- * Step 0
74
- * Get WP Root files
75
- * Does not collect any sub folders
76
- */
77
- private function getWpRootFiles() {
78
-
79
- // open file handle
80
- $files = $this->open( $this->filename, 'a' );
81
-
82
-
83
- try {
84
-
85
- // Iterate over wp root directory
86
- $iterator = new \DirectoryIterator( \WPStaging\WPStaging::getWPpath() );
87
-
88
- $this->log( "Scanning / for files" );
89
-
90
- // Write path line
91
- foreach ( $iterator as $item ) {
92
- if( !$item->isDot() && $item->isFile() ) {
93
- if( $this->write( $files, $iterator->getFilename() . PHP_EOL ) ) {
94
- $this->options->totalFiles++;
95
-
96
- // Add current file size
97
- $this->options->totalFileSize += $iterator->getSize();
98
- }
99
- }
100
- }
101
- } catch ( \Exception $e ) {
102
- $this->returnException( 'Error: ' . $e->getMessage() );
103
- } catch ( \Exception $e ) {
104
- // Skip bad file permissions
105
- }
106
-
107
- $this->close( $files );
108
- return true;
109
- }
110
-
111
- /**
112
- * Step 2
113
- * Get WP Content Files without multisite folder wp-content/uploads/sites
114
- */
115
- private function getWpContentFiles() {
116
-
117
- // Skip it
118
- if( $this->isDirectoryExcluded( WP_CONTENT_DIR ) ) {
119
- $this->log( "Skip " . \WPStaging\WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR );
120
- return true;
121
- }
122
- // open file handle
123
- $files = $this->open( $this->filename, 'a' );
124
-
125
- /**
126
- * Excluded folders relative to the folder to iterate
127
- */
128
- $excludePaths = array(
129
- 'cache',
130
- 'plugins' . DIRECTORY_SEPARATOR . 'wps-hide-login',
131
- 'uploads' . DIRECTORY_SEPARATOR . 'sites'
132
- );
133
-
134
- /**
135
- * Get user excluded folders
136
- */
137
- $directory = array();
138
- foreach ( $this->options->excludedDirectories as $dir ) {
139
- if( strpos( $dir, WP_CONTENT_DIR ) !== false ) {
140
- $directory[] = ltrim( str_replace( WP_CONTENT_DIR, '', $dir ), '/' );
141
- }
142
- }
143
-
144
- $excludePaths = array_merge( $excludePaths, $directory );
145
-
146
- // $excludeFolders = array(
147
- // 'cache',
148
- // 'node_modules',
149
- // 'nbproject',
150
- // 'wps-hide-login'
151
- // );
152
-
153
- try {
154
-
155
- // Iterate over content directory
156
- $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( WP_CONTENT_DIR );
157
-
158
- // Exclude new line file names
159
- $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
160
-
161
- // Exclude sites, uploads, plugins or themes
162
- $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, apply_filters( 'wpstg_clone_mu_excl_folders', $excludePaths ) );
163
- // Recursively iterate over content directory
164
- $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
165
-
166
- $this->log( "Scanning /wp-content for its sub-directories and files" );
167
-
168
- // Write path line
169
- foreach ( $iterator as $item ) {
170
- if( $item->isFile() ) {
171
- $file = 'wp-content' . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL;
172
- if( $this->write( $files, $file ) ) {
173
- $this->options->totalFiles++;
174
-
175
- // Add current file size
176
- $this->options->totalFileSize += $iterator->getSize();
177
- }
178
- }
179
- }
180
- } catch ( \Exception $e ) {
181
- $this->returnException( 'Error: ' . $e->getMessage() );
182
- //throw new \Exception( 'Error: ' . $e->getMessage() );
183
- } catch ( \Exception $e ) {
184
- // Skip bad file permissions
185
- }
186
-
187
- // close the file handler
188
- $this->close( $files );
189
- return true;
190
- }
191
-
192
- /**
193
- * Step 2
194
- * @return boolean
195
- * @throws \Exception
196
- */
197
- private function getWpIncludesFiles() {
198
-
199
- // Skip it
200
- if( $this->isDirectoryExcluded( \WPStaging\WPStaging::getWPpath() . 'wp-includes' . DIRECTORY_SEPARATOR ) ) {
201
- $this->log( "Skip " . \WPStaging\WPStaging::getWPpath() . 'wp-includes' . DIRECTORY_SEPARATOR );
202
- return true;
203
- }
204
-
205
- // open file handle and attach data to end of file
206
- $files = $this->open( $this->filename, 'a' );
207
-
208
- try {
209
-
210
- // Iterate over wp-admin directory
211
- $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( \WPStaging\WPStaging::getWPpath() . 'wp-includes' . DIRECTORY_SEPARATOR );
212
-
213
- // Exclude new line file names
214
- $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
215
-
216
- // Recursively iterate over wp-includes directory
217
- $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
218
-
219
- $this->log( "Scanning /wp-includes for its sub-directories and files" );
220
-
221
- // Write files
222
- foreach ( $iterator as $item ) {
223
- if( $item->isFile() ) {
224
-