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

Version Description

  • New: Compatible up to WordPress 4.9.8
  • New: Support for Windows Azure cloud servers
  • Fix: Missing http(s) scheme after cloning multisites results in not working clones
Download this release

Release Info

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

Code changes from version 2.3.3 to 2.3.4

apps/Backend/Modules/Jobs/Cloning.php CHANGED
@@ -1,4 +1,5 @@
1
  <?php
 
2
  namespace WPStaging\Backend\Modules\Jobs;
3
 
4
  use WPStaging\Backend\Modules\Jobs\Exceptions\JobNotFoundException;
@@ -8,268 +9,250 @@ use WPStaging\WPStaging;
8
  * Class Cloning
9
  * @package WPStaging\Backend\Modules\Jobs
10
  */
11
- class Cloning extends Job
12
- {
13
- /**
14
- * Initialize is called in \Job
15
- */
16
- public function initialize(){
17
- $this->db = WPStaging::getInstance()->get("wpdb");
18
- }
19
-
20
- /**
21
- * Save Chosen Cloning Settings
22
- * @return bool
23
- */
24
- public function save()
25
- {
26
- if (!isset($_POST) || !isset($_POST["cloneID"]))
27
- {
28
- return false;
29
- }
30
-
31
- // Generate Options
32
- // Clone
33
- $this->options->clone = $_POST["cloneID"];
34
- $this->options->cloneDirectoryName = preg_replace("#\W+#", '-', strtolower($this->options->clone));
35
- $this->options->cloneNumber = 1;
36
- $this->options->prefix = $this->setStagingPrefix();
37
- $this->options->includedDirectories = array();
38
- $this->options->excludedDirectories = array();
39
- $this->options->extraDirectories = array();
40
- $this->options->excludedFiles = array('.htaccess', '.DS_Store', '.git', '.svn', '.tmp', 'desktop.ini', '.gitignore', '.log');
41
- $this->options->currentStep = 0;
42
-
43
- // Job
44
- $this->options->job = new \stdClass();
45
-
46
- // Check if clone data already exists and use that one
47
- if (isset($this->options->existingClones[$this->options->clone]) )
48
- {
49
-
50
- $this->options->cloneNumber = $this->options->existingClones[$this->options->clone]->number;
51
-
52
- $this->options->prefix = isset($this->options->existingClones[$this->options->clone]->prefix) ?
53
- $this->options->existingClones[$this->options->clone]->prefix :
54
- $this->setStagingPrefix();
55
-
56
- } // Clone does not exist but there are other clones in db
57
- // Get data and increment it
58
- elseif (!empty($this->options->existingClones))
59
- {
60
- $this->options->cloneNumber = count($this->options->existingClones)+1;
61
- $this->options->prefix = $this->setStagingPrefix();
62
- }
63
-
64
- // Included Tables
65
- if (isset($_POST["includedTables"]) && is_array($_POST["includedTables"]))
66
- {
67
- $this->options->tables = $_POST["includedTables"];
68
- } else {
69
- $this->options->tables = array();
70
- }
71
- // Excluded Tables
 
 
 
 
 
72
  // if (isset($_POST["excludedTables"]) && is_array($_POST["excludedTables"]))
73
  // {
74
  // $this->options->excludedTables = $_POST["excludedTables"];
75
  // }
 
 
 
 
76
 
77
- // Excluded Directories
78
- if (isset($_POST["excludedDirectories"]) && is_array($_POST["excludedDirectories"]))
79
- {
80
- $this->options->excludedDirectories = $_POST["excludedDirectories"];
81
- }
82
-
83
-
84
- // Excluded Directories TOTAL
85
- // Do not copy these folders and plugins
86
- $excludedDirectories = array(
87
- ABSPATH . 'wp-content' . DIRECTORY_SEPARATOR . 'cache',
88
- ABSPATH . 'wp-content' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'wps-hide-login',
89
- ABSPATH . 'wp-content' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'wp-super-cache',
90
- ABSPATH . 'wp-content' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'peters-login-redirect',
91
- );
92
-
93
- $this->options->excludedDirectories = array_merge($excludedDirectories, $this->options->excludedDirectories);
94
-
95
- // Included Directories
96
- if (isset($_POST["includedDirectories"]) && is_array($_POST["includedDirectories"]))
97
- {
98
- $this->options->includedDirectories = $_POST["includedDirectories"];
99
- }
100
-
101
- // Extra Directories
102
- if (isset($_POST["extraDirectories"]) && !empty($_POST["extraDirectories"]) )
103
- {
104
- $this->options->extraDirectories = $_POST["extraDirectories"];
105
- }
106
-
107
- // Directories to Copy
108
- $this->options->directoriesToCopy = array_merge(
109
- $this->options->includedDirectories,
110
- $this->options->extraDirectories
111
- );
112
-
113
- array_unshift($this->options->directoriesToCopy, ABSPATH);
114
-
115
- // Delete files to copy listing
116
- $this->cache->delete("files_to_copy");
117
-
118
- return $this->saveOptions();
119
- }
120
-
121
- /**
122
- * Create a new staging prefix which does not already exists in database
123
- */
124
  private function setStagingPrefix() {
125
-
126
- // Get & find a new prefix that does not already exist in database.
127
- // Loop through up to 1000 different possible prefixes should be enough here;)
128
- for($i=0; $i <= 10000; $i++){
129
- $this->options->prefix = isset($this->options->existingClones) ?
130
- 'wpstg' . (count($this->options->existingClones)+$i) . '_' :
131
- 'wpstg' . $i . '_';
132
-
133
- $sql = "SHOW TABLE STATUS LIKE '{$this->options->prefix}%'";
134
- $tables = $this->db->get_results($sql);
135
-
136
- // Prefix does not exists. We can use it
137
- if (!$tables){
138
- //$this->returnException('new ' . $this->options->prefix);
139
- return $this->options->prefix;
140
- }
141
- }
142
- $this->returnException("Fatal Error: Can not create staging prefix. '{$this->options->prefix}' already exists! Stopping for security reasons. Contact support@wp-staging.com");
143
- wp_die("Fatal Error: Can not create staging prefix. Prefix '{$this->options->prefix}' already exists! Stopping for security reasons. Contact support@wp-staging.com");
144
- }
145
-
146
- /**
147
- * Check if potential new prefix of staging site would be identical with live site.
148
- * @return boolean
149
- */
150
- private function isPrefixIdentical(){
151
- $db = WPStaging::getInstance()->get("wpdb");
152
-
153
- $livePrefix = $db->prefix;
154
- $stagingPrefix = $this->options->prefix;
155
-
156
- if ($livePrefix == $stagingPrefix){
157
- return true;
158
- }
159
- return false;
160
- }
161
-
162
- /**
163
- * Start the cloning job
164
- */
165
- public function start()
166
- {
167
- if (null === $this->options->currentJob)
168
- {
169
- $this->log("Cloning job for {$this->options->clone} finished");
170
- return true;
171
- }
172
-
173
- $methodName = "job" . ucwords($this->options->currentJob);
174
-
175
- if (!method_exists($this, $methodName))
176
- {
177
- $this->log("Can't execute job; Job's method {$methodName} is not found");
178
- throw new JobNotFoundException($methodName);
179
- }
180
-
181
- // Call the job
182
- //$this->log("execute job: Job's method {$methodName}");
183
- return $this->{$methodName}();
184
- }
185
-
186
- /**
187
- * @param object $response
188
- * @param string $nextJob
189
- * @return object
190
- */
191
- private function handleJobResponse($response, $nextJob)
192
- {
193
- // Job is not done
194
- if (true !== $response->status)
195
- {
196
- return $response;
197
- }
198
-
199
- $this->options->currentJob = $nextJob;
200
- $this->options->currentStep = 0;
201
- $this->options->totalSteps = 0;
202
-
203
- // Save options
204
- $this->saveOptions();
205
-
206
- return $response;
207
- }
208
-
209
-
210
-
211
-
212
-
213
- /**
214
- * Clone Database
215
- * @return object
216
- */
217
- public function jobDatabase()
218
- {
219
- $database = new Database();
220
- return $this->handleJobResponse($database->start(), "SearchReplace");
221
- }
222
-
223
- /**
224
- * Search & Replace
225
- * @return object
226
- */
227
- public function jobSearchReplace()
228
- {
229
- $searchReplace = new SearchReplace();
230
- return $this->handleJobResponse($searchReplace->start(), "directories");
231
- }
232
-
233
- /**
234
- * Get All Files From Selected Directories Recursively Into a File
235
- * @return object
236
- */
237
- public function jobDirectories()
238
- {
239
- $directories = new Directories();
240
- return $this->handleJobResponse($directories->start(), "files");
241
- }
242
-
243
- /**
244
- * Copy Files
245
- * @return object
246
- */
247
- public function jobFiles()
248
- {
249
- $files = new Files();
250
- return $this->handleJobResponse($files->start(), "data");
251
- }
252
-
253
- /**
254
- * Replace Data
255
- * @return object
256
- */
257
- public function jobData()
258
- {
259
- $data = new Data();
260
- return $this->handleJobResponse($data->start(), "finish");
261
- }
262
-
263
-
264
- /**
265
- * Save Clone Data
266
- * @return object
267
- */
268
- public function jobFinish()
269
- {
270
- $finish = new Finish();
271
- return $this->handleJobResponse($finish->start(), '');
272
- }
273
-
274
-
275
- }
1
  <?php
2
+
3
  namespace WPStaging\Backend\Modules\Jobs;
4
 
5
  use WPStaging\Backend\Modules\Jobs\Exceptions\JobNotFoundException;
9
  * Class Cloning
10
  * @package WPStaging\Backend\Modules\Jobs
11
  */
12
+ class Cloning extends Job {
13
+
14
+ /**
15
+ * Initialize is called in \Job
16
+ */
17
+ public function initialize() {
18
+ $this->db = WPStaging::getInstance()->get( "wpdb" );
19
+ }
20
+
21
+ /**
22
+ * Save Chosen Cloning Settings
23
+ * @return bool
24
+ */
25
+ public function save() {
26
+ if( !isset( $_POST ) || !isset( $_POST["cloneID"] ) ) {
27
+ return false;
28
+ }
29
+
30
+ // Generate Options
31
+ // Clone
32
+ $this->options->clone = $_POST["cloneID"];
33
+ $this->options->cloneDirectoryName = preg_replace( "#\W+#", '-', strtolower( $this->options->clone ) );
34
+ $this->options->cloneNumber = 1;
35
+ $this->options->prefix = $this->setStagingPrefix();
36
+ $this->options->includedDirectories = array();
37
+ $this->options->excludedDirectories = array();
38
+ $this->options->extraDirectories = array();
39
+ $this->options->excludedFiles = array(
40
+ '.htaccess',
41
+ '.DS_Store',
42
+ '.git',
43
+ '.svn',
44
+ '.tmp',
45
+ 'desktop.ini',
46
+ '.gitignore',
47
+ '.log',
48
+ 'web.config'
49
+ );
50
+ $this->options->currentStep = 0;
51
+
52
+
53
+ // Job
54
+ $this->options->job = new \stdClass();
55
+
56
+ // Check if clone data already exists and use that one
57
+ if( isset( $this->options->existingClones[$this->options->clone] ) ) {
58
+
59
+ $this->options->cloneNumber = $this->options->existingClones[$this->options->clone]->number;
60
+
61
+ $this->options->prefix = isset( $this->options->existingClones[$this->options->clone]->prefix ) ?
62
+ $this->options->existingClones[$this->options->clone]->prefix :
63
+ $this->setStagingPrefix();
64
+ } // Clone does not exist but there are other clones in db
65
+ // Get data and increment it
66
+ elseif( !empty( $this->options->existingClones ) ) {
67
+ $this->options->cloneNumber = count( $this->options->existingClones ) + 1;
68
+ $this->options->prefix = $this->setStagingPrefix();
69
+ }
70
+
71
+ // Included Tables
72
+ if( isset( $_POST["includedTables"] ) && is_array( $_POST["includedTables"] ) ) {
73
+ $this->options->tables = $_POST["includedTables"];
74
+ } else {
75
+ $this->options->tables = array();
76
+ }
77
+ // Excluded Tables
78
  // if (isset($_POST["excludedTables"]) && is_array($_POST["excludedTables"]))
79
  // {
80
  // $this->options->excludedTables = $_POST["excludedTables"];
81
  // }
82
+ // Excluded Directories
83
+ if( isset( $_POST["excludedDirectories"] ) && is_array( $_POST["excludedDirectories"] ) ) {
84
+ $this->options->excludedDirectories = $_POST["excludedDirectories"];
85
+ }
86
 
87
+
88
+ // Excluded Directories TOTAL
89
+ // Do not copy these folders and plugins
90
+ $excludedDirectories = array(
91
+ \WPStaging\WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR . 'cache',
92
+ \WPStaging\WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'wps-hide-login',
93
+ \WPStaging\WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'wp-super-cache',
94
+ \WPStaging\WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'peters-login-redirect',
95
+ );
96
+
97
+ $this->options->excludedDirectories = array_merge( $excludedDirectories, $this->options->excludedDirectories );
98
+
99
+ // Included Directories
100
+ if( isset( $_POST["includedDirectories"] ) && is_array( $_POST["includedDirectories"] ) ) {
101
+ $this->options->includedDirectories = $_POST["includedDirectories"];
102
+ }
103
+
104
+ // Extra Directories
105
+ if( isset( $_POST["extraDirectories"] ) && !empty( $_POST["extraDirectories"] ) ) {
106
+ $this->options->extraDirectories = $_POST["extraDirectories"];
107
+ }
108
+
109
+ // Directories to Copy
110
+ $this->options->directoriesToCopy = array_merge(
111
+ $this->options->includedDirectories, $this->options->extraDirectories
112
+ );
113
+
114
+ array_unshift( $this->options->directoriesToCopy, \WPStaging\WPStaging::getWPpath() );
115
+
116
+ // Delete files to copy listing
117
+ $this->cache->delete( "files_to_copy" );
118
+
119
+ return $this->saveOptions();
120
+ }
121
+
122
+ /**
123
+ * Create a new staging prefix which does not already exists in database
124
+ */
 
 
 
 
 
 
 
 
 
125
  private function setStagingPrefix() {
126
+
127
+ // Get & find a new prefix that does not already exist in database.
128
+ // Loop through up to 1000 different possible prefixes should be enough here;)
129
+ for ( $i = 0; $i <= 10000; $i++ ) {
130
+ $this->options->prefix = isset( $this->options->existingClones ) ?
131
+ 'wpstg' . (count( $this->options->existingClones ) + $i) . '_' :
132
+ 'wpstg' . $i . '_';
133
+
134
+ $sql = "SHOW TABLE STATUS LIKE '{$this->options->prefix}%'";
135
+ $tables = $this->db->get_results( $sql );
136
+
137
+ // Prefix does not exists. We can use it
138
+ if( !$tables ) {
139
+ return $this->options->prefix;
140
+ }
141
+ }
142
+ $this->returnException( "Fatal Error: Can not create staging prefix. '{$this->options->prefix}' already exists! Stopping for security reasons. Contact support@wp-staging.com" );
143
+ wp_die( "Fatal Error: Can not create staging prefix. Prefix '{$this->options->prefix}' already exists! Stopping for security reasons. Contact support@wp-staging.com" );
144
+ }
145
+
146
+ /**
147
+ * Check if potential new prefix of staging site would be identical with live site.
148
+ * @return boolean
149
+ */
150
+ private function isPrefixIdentical() {
151
+ $db = WPStaging::getInstance()->get( "wpdb" );
152
+
153
+ $livePrefix = $db->prefix;
154
+ $stagingPrefix = $this->options->prefix;
155
+
156
+ if( $livePrefix == $stagingPrefix ) {
157
+ return true;
158
+ }
159
+ return false;
160
+ }
161
+
162
+ /**
163
+ * Start the cloning job
164
+ */
165
+ public function start() {
166
+ if( null === $this->options->currentJob ) {
167
+ $this->log( "Cloning job for {$this->options->clone} finished" );
168
+ return true;
169
+ }
170
+
171
+ $methodName = "job" . ucwords( $this->options->currentJob );
172
+
173
+ if( !method_exists( $this, $methodName ) ) {
174
+ $this->log( "Can't execute job; Job's method {$methodName} is not found" );
175
+ throw new JobNotFoundException( $methodName );
176
+ }
177
+
178
+ // Call the job
179
+ //$this->log("execute job: Job's method {$methodName}");
180
+ return $this->{$methodName}();
181
+ }
182
+
183
+ /**
184
+ * @param object $response
185
+ * @param string $nextJob
186
+ * @return object
187
+ */
188
+ private function handleJobResponse( $response, $nextJob ) {
189
+ // Job is not done
190
+ if( true !== $response->status ) {
191
+ return $response;
192
+ }
193
+
194
+ $this->options->currentJob = $nextJob;
195
+ $this->options->currentStep = 0;
196
+ $this->options->totalSteps = 0;
197
+
198
+ // Save options
199
+ $this->saveOptions();
200
+
201
+ return $response;
202
+ }
203
+
204
+ /**
205
+ * Clone Database
206
+ * @return object
207
+ */
208
+ public function jobDatabase() {
209
+ $database = new Database();
210
+ return $this->handleJobResponse( $database->start(), "SearchReplace" );
211
+ }
212
+
213
+ /**
214
+ * Search & Replace
215
+ * @return object
216
+ */
217
+ public function jobSearchReplace() {
218
+ $searchReplace = new SearchReplace();
219
+ return $this->handleJobResponse( $searchReplace->start(), "directories" );
220
+ }
221
+
222
+ /**
223
+ * Get All Files From Selected Directories Recursively Into a File
224
+ * @return object
225
+ */
226
+ public function jobDirectories() {
227
+ $directories = new Directories();
228
+ return $this->handleJobResponse( $directories->start(), "files" );
229
+ }
230
+
231
+ /**
232
+ * Copy Files
233
+ * @return object
234
+ */
235
+ public function jobFiles() {
236
+ $files = new Files();
237
+ return $this->handleJobResponse( $files->start(), "data" );
238
+ }
239
+
240
+ /**
241
+ * Replace Data
242
+ * @return object
243
+ */
244
+ public function jobData() {
245
+ $data = new Data();
246
+ return $this->handleJobResponse( $data->start(), "finish" );
247
+ }
248
+
249
+ /**
250
+ * Save Clone Data
251
+ * @return object
252
+ */
253
+ public function jobFinish() {
254
+ $finish = new Finish();
255
+ return $this->handleJobResponse( $finish->start(), '' );
256
+ }
257
+
258
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
apps/Backend/Modules/Jobs/Directories.php CHANGED
@@ -84,7 +84,7 @@ class Directories extends JobExecutable {
84
  try {
85
 
86
  // Iterate over wp root directory
87
- $iterator = new \DirectoryIterator( ABSPATH );
88
 
89
  $this->log( "Scanning / for files" );
90
 
@@ -118,6 +118,7 @@ class Directories extends JobExecutable {
118
 
119
  // Skip it
120
  if( $this->isDirectoryExcluded( WP_CONTENT_DIR ) ) {
 
121
  return true;
122
  }
123
  // open file handle
@@ -177,7 +178,8 @@ class Directories extends JobExecutable {
177
  private function getWpIncludesFiles() {
178
 
179
  // Skip it
180
- if( $this->isDirectoryExcluded( ABSPATH . 'wp-includes' . DIRECTORY_SEPARATOR ) ) {
 
181
  return true;
182
  }
183
 
@@ -187,13 +189,11 @@ class Directories extends JobExecutable {
187
  try {
188
 
189
  // Iterate over wp-admin directory
190
- $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( ABSPATH . 'wp-includes' . DIRECTORY_SEPARATOR );
191
 
192
  // Exclude new line file names
193
  $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
194
 
195
- // Exclude uploads, plugins or themes
196
- //$iterator = new \WPStaging\Iterators\RecursiveFilterExclude($iterator, apply_filters('wpstg_exclude_content', $exclude_filters));
197
  // Recursively iterate over wp-includes directory
198
  $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
199
 
@@ -230,7 +230,8 @@ class Directories extends JobExecutable {
230
  private function getWpAdminFiles() {
231
 
232
  // Skip it
233
- if( $this->isDirectoryExcluded( ABSPATH . 'wp-admin' . DIRECTORY_SEPARATOR ) ) {
 
234
  return true;
235
  }
236
 
@@ -240,13 +241,11 @@ class Directories extends JobExecutable {
240
  try {
241
 
242
  // Iterate over wp-admin directory
243
- $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( ABSPATH . 'wp-admin' . DIRECTORY_SEPARATOR );
244
 
245
  // Exclude new line file names
246
  $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
247
 
248
- // Exclude uploads, plugins or themes
249
- //$iterator = new \WPStaging\Iterators\RecursiveFilterExclude($iterator, apply_filters('wpstg_exclude_content', $exclude_filters));
250
  // Recursively iterate over content directory
251
  $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
252
 
@@ -317,7 +316,7 @@ class Directories extends JobExecutable {
317
  foreach ( $iterator as $item ) {
318
  if( $item->isFile() ) {
319
  //if( $this->write( $files, $strings->getLastElemAfterString( '/', $folder ) . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
320
- if( $this->write( $files, str_replace( ABSPATH, '', $folder ) . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
321
  $this->options->totalFiles++;
322
  // Add current file size
323
  $this->options->totalFileSize += $iterator->getSize();
@@ -514,14 +513,32 @@ class Directories extends JobExecutable {
514
  $this->files = explode( PHP_EOL, $this->files );
515
  }
516
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
  /**
518
  * Check if directory is excluded
519
  * @param string $directory
520
  * @return bool
521
  */
522
  protected function isDirectoryExcluded( $directory ) {
 
 
 
523
  foreach ( $this->options->excludedDirectories as $excludedDirectory ) {
524
- if( strpos( $directory, $excludedDirectory ) === 0 ) {
 
 
525
  return true;
526
  }
527
  }
84
  try {
85
 
86
  // Iterate over wp root directory
87
+ $iterator = new \DirectoryIterator( \WPStaging\WPStaging::getWPpath() );
88
 
89
  $this->log( "Scanning / for files" );
90
 
118
 
119
  // Skip it
120
  if( $this->isDirectoryExcluded( WP_CONTENT_DIR ) ) {
121
+ $this->log( "Skip " . \WPStaging\WPStaging::getWPpath() . 'wp-content' . DIRECTORY_SEPARATOR);
122
  return true;
123
  }
124
  // open file handle
178
  private function getWpIncludesFiles() {
179
 
180
  // Skip it
181
+ if( $this->isDirectoryExcluded( \WPStaging\WPStaging::getWPpath() . 'wp-includes' . DIRECTORY_SEPARATOR ) ) {
182
+ $this->log( "Skip " . \WPStaging\WPStaging::getWPpath() . 'wp-includes' . DIRECTORY_SEPARATOR);
183
  return true;
184
  }
185
 
189
  try {
190
 
191
  // Iterate over wp-admin directory
192
+ $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( \WPStaging\WPStaging::getWPpath() . 'wp-includes' . DIRECTORY_SEPARATOR );
193
 
194
  // Exclude new line file names
195
  $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
196
 
 
 
197
  // Recursively iterate over wp-includes directory
198
  $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
199
 
230
  private function getWpAdminFiles() {
231
 
232
  // Skip it
233
+ if( $this->isDirectoryExcluded( \WPStaging\WPStaging::getWPpath() . 'wp-admin' . DIRECTORY_SEPARATOR ) ) {
234
+ $this->log( "Skip " . \WPStaging\WPStaging::getWPpath() . 'wp-admin' . DIRECTORY_SEPARATOR);
235
  return true;
236
  }
237
 
241
  try {
242
 
243
  // Iterate over wp-admin directory
244
+ $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( \WPStaging\WPStaging::getWPpath() . 'wp-admin' . DIRECTORY_SEPARATOR );
245
 
246
  // Exclude new line file names
247
  $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
248
 
 
 
249
  // Recursively iterate over content directory
250
  $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
251
 
316
  foreach ( $iterator as $item ) {
317
  if( $item->isFile() ) {
318
  //if( $this->write( $files, $strings->getLastElemAfterString( '/', $folder ) . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
319
+ if( $this->write( $files, str_replace( \WPStaging\WPStaging::getWPpath(), '', $folder ) . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
320
  $this->options->totalFiles++;
321
  // Add current file size
322
  $this->options->totalFileSize += $iterator->getSize();
513
  $this->files = explode( PHP_EOL, $this->files );
514
  }
515
 
516
+
517
+ /**
518
+ * Replace forward slash with current directory separator
519
+ *
520
+ * @param string $path Path
521
+ *
522
+ * @return string
523
+ */
524
+ private function sanitizeDirectorySeparator( $path ) {
525
+ $string = str_replace( "/", "\\", $path );
526
+ return str_replace( '\\\\', '\\', $string );
527
+ }
528
+
529
  /**
530
  * Check if directory is excluded
531
  * @param string $directory
532
  * @return bool
533
  */
534
  protected function isDirectoryExcluded( $directory ) {
535
+
536
+ $directory = $this->sanitizeDirectorySeparator($directory);
537
+
538
  foreach ( $this->options->excludedDirectories as $excludedDirectory ) {
539
+ //echo $this->sanitizeDirectorySeparator($excludedDirectory). '</br>';
540
+ //echo $this->sanitizeDirectorySeparator($directory). '</br>';
541
+ if( strpos( $directory, $this->sanitizeDirectorySeparator($excludedDirectory) ) === 0 ) {
542
  return true;
543
  }
544
  }
apps/Backend/Modules/Jobs/Files.php CHANGED
@@ -3,10 +3,11 @@
3
  namespace WPStaging\Backend\Modules\Jobs;
4
 
5
  // No Direct Access
 
6
  use WPStaging\Utils\Logger;
7
 
8
- if (!defined("WPINC")) {
9
- die;
10
  }
11
 
12
  /**
@@ -15,315 +16,333 @@ if (!defined("WPINC")) {
15
  */
16
  class Files extends JobExecutable {
17
 
18
- /**
19
- * @var \SplFileObject
20
- */
21
- private $file;
22
-
23
- /**
24
- * @var int
25
- */
26
- private $maxFilesPerRun;
27
-
28
- /**
29
- * @var string
30
- */
31
- private $destination;
32
-
33
- /**
34
- * Initialization
35
- */
36
- public function initialize() {
37
-
38
- $this->destination = ABSPATH . $this->options->cloneDirectoryName . DIRECTORY_SEPARATOR;
39
-
40
- $filePath = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
41
-
42
- if (is_file($filePath)) {
43
- $this->file = new \SplFileObject($filePath, 'r');
44
- }
45
-
46
- // Informational logs
47
- if (0 == $this->options->currentStep) {
48
- $this->log("Copying files...");
49
- }
50
-
51
- $this->settings->batchSize = $this->settings->batchSize * 1000000;
52
- $this->maxFilesPerRun = $this->settings->fileLimit;
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 = ceil($this->options->totalFiles / $this->maxFilesPerRun);
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
- // Finished
70
- if ($this->isFinished()) {
71
- $this->log("Copying files finished");
72
- $this->prepareResponse(true, false);
73
- return false;
74
- }
75
-
76
- // Get files and copy'em
77
- if (!$this->getFilesAndCopy()) {
78
- $this->prepareResponse(false, false);
79
- return false;
80
- }
81
-
82
- // Prepare and return response
83
- $this->prepareResponse();
84
-
85
- // Not finished
86
- return true;
87
- }
88
-
89
- /**
90
- * Get files and copy
91
- * @return bool
92
- */
93
- private function getFilesAndCopy() {
94
- // Over limits threshold
95
- if ($this->isOverThreshold()) {
96
- // Prepare response and save current progress
97
- $this->prepareResponse(false, false);
98
- $this->saveOptions();
99
- return false;
100
- }
101
-
102
- // Go to last copied line and than to next one
103
- //if ($this->options->copiedFiles != 0) {
104
- if (isset($this->options->copiedFiles) && $this->options->copiedFiles != 0) {
105
- $this->file->seek($this->options->copiedFiles - 1);
106
- }
107
-
108
- $this->file->setFlags(\SplFileObject::SKIP_EMPTY | \SplFileObject::READ_AHEAD);
109
-
110
-
111
- // Loop x files at a time
112
- for ($i = 0; $i < $this->maxFilesPerRun; $i++) {
113
-
114
- // Increment copied files
115
- // Do this anytime to make sure to not stuck in the same step / files
116
- $this->options->copiedFiles++;
117
-
118
- // End of file
119
- if ($this->file->eof()) {
120
- break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  }
122
-
123
-
124
- $file = $this->file->fgets();
125
- //$this->debugLog('copy file ' . $file, Logger::TYPE_DEBUG);
126
- $this->copyFile($file);
127
- }
128
-
129
- $totalFiles = $this->options->copiedFiles;
130
- // Log this only every 50 entries to keep the log small and to not block the rendering browser
131
- if ($this->options->copiedFiles %50 == 0){
132
- $this->log("Total {$totalFiles} files processed");
133
- }
134
-
135
- return true;
136
- }
137
-
138
- /**
139
- * Checks Whether There is Any Job to Execute or Not
140
- * @return bool
141
- */
142
- private function isFinished() {
143
- return (
144
- $this->options->currentStep > $this->options->totalSteps ||
145
- $this->options->copiedFiles >= $this->options->totalFiles
146
- );
147
- }
148
-
149
- /**
150
- * @param string $file
151
- * @return bool
152
- */
153
- private function copyFile($file) {
154
- $file = trim(ABSPATH . $file);
155
-
156
- $directory = dirname($file);
157
-
158
- // Get file size
159
- $fileSize = filesize($file);
160
-
161
- // Directory is excluded
162
- if ($this->isDirectoryExcluded($directory)) {
163
- $this->debugLog("Skipping directory by rule: {$file}", Logger::TYPE_INFO);
164
- return false;
165
- }
166
-
167
- // File is excluded
168
- if ($this->isFileExcluded($file)) {
169
- $this->debugLog("Skipping file by rule: {$file}", Logger::TYPE_INFO);
170
- return false;
171
- }
172
-
173
- // File is over maximum allowed file size (8MB)
174
- if ($fileSize >= $this->settings->maxFileSize * 1000000) {
175
- $this->log("Skipping big file: {$file}", Logger::TYPE_INFO);
176
- return false;
177
- }
178
-
179
- // Invalid file, skipping it as if succeeded
180
- if (!is_file($file)) {
181
- $this->debugLog("Not a file {$file}");
 
 
 
182
  return true;
183
- }
184
- // Invalid file, skipping it as if succeeded
185
- if (!is_readable($file)) {
186
- $this->log("Can't read file {$file}");
 
 
 
 
 
 
 
 
 
 
 
 
187
  return true;
188
- }
189
-
190
- // Failed to get destination
191
- if (false === ($destination = $this->getDestination($file))) {
192
- $this->log("Can't get the destination of {$file}");
193
- return false;
194
- }
195
-
196
- // File is over batch size
197
- if ($fileSize >= $this->settings->batchSize) {
198
- $this->log("Trying to copy big file: {$file} -> {$destination}", Logger::TYPE_INFO);
199
- return $this->copyBig($file, $destination, $this->settings->batchSize);
200
- }
201
-
202
- // Attempt to copy
203
- if (!@copy($file, $destination)) {
204
- $this->log("Failed to copy file to destination: {$file} -> {$destination}", Logger::TYPE_ERROR);
205
- return false;
206
- }
207
-
208
- return true;
209
-
210
- // Good old PHP
211
- //return $this->copy($file, $destination);
212
- }
213
-
214
- /**
215
- * Gets destination file and checks if the directory exists, if it does not attempts to create it.
216
- * If creating destination directory fails, it returns false, gives destination full path otherwise
217
- * @param string $file
218
- * @return bool|string
219
- */
220
- private function getDestination($file) {
221
- $relativePath = str_replace(ABSPATH, null, $file);
222
- $destinationPath = $this->destination . $relativePath;
223
- $destinationDirectory = dirname($destinationPath);
224
-
225
- if (!is_dir($destinationDirectory) && !@mkdir($destinationDirectory, 0775, true)) {
226
- $this->log("Files: Can not create directory {$destinationDirectory}", Logger::TYPE_ERROR);
227
- return false;
228
- }
229
-
230
- return $destinationPath;
231
- }
232
-
233
-
234
-
235
-
236
- /**
237
- * Copy bigger files than $this->settings->batchSize
238
- * @param string $src
239
- * @param string $dst
240
- * @param int $buffersize
241
- * @return boolean
242
- */
243
- private function copyBig($src, $dst, $buffersize) {
244
- $src = fopen($src, 'r');
245
- $dest = fopen($dst, 'w');
246
-
247
- // Try first method:
248
- while (!feof($src)) {
249
- if (false === fwrite($dest, fread($src, $buffersize))) {
250
- $error = true;
251
- }
252
- }
253
- // Try second method if first one failed
254
- if (isset($error) && ($error === true)) {
255
- while (!feof($src)) {
256
- if (false === stream_copy_to_stream($src, $dest, 1024)) {
257
- $this->log("Can not copy big file; {$src} -> {$dest}");
258
- fclose($src);
259
- fclose($dest);
260
- return false;
261
- }
262
- }
263
- }
264
- // Close any open handler
265
- fclose($src);
266
- fclose($dest);
267
- return true;
268
- }
269
-
270
- /**
271
- * Check if file is excluded from copying process
272
- *
273
- * @param string $file filename including ending
274
- * @return boolean
275
- */
276
- private function isFileExcluded($file) {
277
- $excluded = false;
278
- foreach ($this->options->excludedFiles as $excludedFile) {
279
- if (stripos(strrev($file), strrev($excludedFile)) === 0) {
280
- $excluded = true;
281
- break;
282
- }
283
- }
284
-
285
- // Do not copy wp-config.php if the clone gets updated. This is for security purposes,
286
- // because if the updating process fails, the staging site would not be accessable any longer
287
- if (isset($this->options->mainJob ) && $this->options->mainJob == "updating" && stripos(strrev($file), strrev("wp-config.php")) === 0){
288
- $excluded = true;
289
- }
290
-
291
-
292
- return $excluded;
293
- }
294
-
295
- /**
296
- * Check if directory is excluded from copying
297
- * @param string $directory
298
- * @return bool
299
- */
300
- private function isDirectoryExcluded($directory) {
301
- // Make sure that wp-staging-pro directory / plugin is never excluded
302
- if (false !== strpos( $directory, 'wp-staging' ) || false !== strpos( $directory, 'wp-staging-pro' ) ){
303
- return false;
304
- }
305
-
306
- foreach ($this->options->excludedDirectories as $excludedDirectory) {
307
- if (strpos($directory, $excludedDirectory) === 0 && !$this->isExtraDirectory($directory)) {
308
- return true;
309
- }
310
- }
311
-
312
- return false;
313
- }
314
-
315
- /**
316
- * Check if directory is an extra directory and should be copied
317
- * @param string $directory
318
- * @return boolean
319
- */
320
- private function isExtraDirectory($directory) {
321
- foreach ($this->options->extraDirectories as $extraDirectory) {
322
- if (strpos($directory, $extraDirectory) === 0) {
323
- return true;
324
- }
325
- }
326
 
327
- return false;
328
- }
329
  }
3
  namespace WPStaging\Backend\Modules\Jobs;
4
 
5
  // No Direct Access
6
+ use WPStaging\WPStaging;
7
  use WPStaging\Utils\Logger;
8
 
9
+ if( !defined( "WPINC" ) ) {
10
+ die;
11
  }
12
 
13
  /**
16
  */
17
  class Files extends JobExecutable {
18
 
19
+ /**
20
+ * @var \SplFileObject
21
+ */
22
+ private $file;
23
+
24
+ /**
25
+ * @var int
26
+ */
27
+ private $maxFilesPerRun;
28
+
29
+ /**
30
+ * @var string
31
+ */
32
+ private $destination;
33
+
34
+ /**
35
+ * Initialization
36
+ */
37
+ public function initialize() {
38
+
39
+ $this->destination = \WPStaging\WPStaging::getWPpath() . $this->options->cloneDirectoryName . DIRECTORY_SEPARATOR;
40
+
41
+ $filePath = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
42
+
43
+ if( is_file( $filePath ) ) {
44
+ $this->file = new \SplFileObject( $filePath, 'r' );
45
+ }
46
+
47
+ // Informational logs
48
+ if( 0 == $this->options->currentStep ) {
49
+ $this->log( "Copying files..." );
50
+ }
51
+
52
+ $this->settings->batchSize = $this->settings->batchSize * 1000000;
53
+ $this->maxFilesPerRun = $this->settings->fileLimit;
54
+ }
55
+
56
+ /**
57
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
58
+ * @return void
59
+ */
60
+ protected function calculateTotalSteps() {
61
+ $this->options->totalSteps = ceil( $this->options->totalFiles / $this->maxFilesPerRun );
62
+ }
63
+
64
+ /**
65
+ * Execute the Current Step
66
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
67
+ * @return bool
68
+ */
69
+ protected function execute() {
70
+ // Finished
71
+ if( $this->isFinished() ) {
72
+ $this->log( "Copying files finished" );
73
+ $this->prepareResponse( true, false );
74
+ return false;
75
+ }
76
+
77
+ // Get files and copy'em
78
+ if( !$this->getFilesAndCopy() ) {
79
+ $this->prepareResponse( false, false );
80
+ return false;
81
+ }
82
+
83
+ // Prepare and return response
84
+ $this->prepareResponse();
85
+
86
+ // Not finished
87
+ return true;
88
+ }
89
+
90
+ /**
91
+ * Get files and copy
92
+ * @return bool
93
+ */
94
+ private function getFilesAndCopy() {
95
+ // Over limits threshold
96
+ if( $this->isOverThreshold() ) {
97
+ // Prepare response and save current progress
98
+ $this->prepareResponse( false, false );
99
+ $this->saveOptions();
100
+ return false;
101
+ }
102
+
103
+ // Go to last copied line and than to next one
104
+ //if ($this->options->copiedFiles != 0) {
105
+ if( isset( $this->options->copiedFiles ) && $this->options->copiedFiles != 0 ) {
106
+ $this->file->seek( $this->options->copiedFiles - 1 );
107
+ }
108
+
109
+ $this->file->setFlags( \SplFileObject::SKIP_EMPTY | \SplFileObject::READ_AHEAD );
110
+
111
+
112
+ // Loop x files at a time
113
+ for ( $i = 0; $i < $this->maxFilesPerRun; $i++ ) {
114
+
115
+ // Increment copied files
116
+ // Do this anytime to make sure to not stuck in the same step / files
117
+ $this->options->copiedFiles++;
118
+
119
+ // End of file
120
+ if( $this->file->eof() ) {
121
+ break;
122
+ }
123
+
124
+
125
+ $file = $this->file->fgets();
126
+ //$this->debugLog('copy file ' . $file, Logger::TYPE_DEBUG);
127
+ $this->copyFile( $file );
128
+ }
129
+
130
+ $totalFiles = $this->options->copiedFiles;
131
+ // Log this only every 50 entries to keep the log small and to not block the rendering browser
132
+ if( $this->options->copiedFiles % 50 == 0 ) {
133
+ $this->log( "Total {$totalFiles} files processed" );
134
+ }
135
+
136
+ return true;
137
+ }
138
+
139
+ /**
140
+ * Checks Whether There is Any Job to Execute or Not
141
+ * @return bool
142
+ */
143
+ private function isFinished() {
144
+ return (
145
+ $this->options->currentStep > $this->options->totalSteps ||
146
+ $this->options->copiedFiles >= $this->options->totalFiles
147
+ );
148
+ }
149
+
150
+ /**
151
+ * @param string $file
152
+ * @return bool
153
+ */
154
+ private function copyFile( $file ) {
155
+ $file = trim( \WPStaging\WPStaging::getWPpath() . $file );
156
+
157
+ $directory = dirname( $file );
158
+
159
+ // Get file size
160
+ $fileSize = filesize( $file );
161
+
162
+ // Directory is excluded
163
+ if( $this->isDirectoryExcluded( $directory ) ) {
164
+ $this->debugLog( "Skipping directory by rule: {$file}", Logger::TYPE_INFO );
165
+ return false;
166
+ }
167
+
168
+ // File is excluded
169
+ if( $this->isFileExcluded( $file ) ) {
170
+ $this->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 );
177
+ return false;
178
+ }
179
+
180
+ // Invalid file, skipping it as if succeeded
181
+ if( !is_file( $file ) ) {
182
+ $this->debugLog( "Not a file {$file}" );
183
+ return true;
184
+ }
185
+ // Invalid file, skipping it as if succeeded
186
+ if( !is_readable( $file ) ) {
187
+ $this->log( "Can't read file {$file}" );
188
+ return true;
189
+ }
190
+
191
+ // Failed to get destination
192
+ if( false === ($destination = $this->getDestination( $file )) ) {
193
+ $this->log( "Can't get the destination of {$file}" );
194
+ return false;
195
+ }
196
+
197
+ // File is over batch size
198
+ if( $fileSize >= $this->settings->batchSize ) {
199
+ $this->log( "Trying to copy big file: {$file} -> {$destination}", Logger::TYPE_INFO );
200
+ return $this->copyBig( $file, $destination, $this->settings->batchSize );
201
+ }
202
+
203
+ // Attempt to copy
204
+ if( !@copy( $file, $destination ) ) {
205
+ $errors = error_get_last();
206
+ $this->log( "Files: Failed to copy file to destination. Error: {$errors['message']} {$file} -> {$destination}", Logger::TYPE_ERROR );
207
+ return false;
208
+ }
209
+
210
+ return true;
211
+
212
+ // Good old PHP
213
+ //return $this->copy($file, $destination);
214
+ }
215
+
216
+ /**
217
+ * Gets destination file and checks if the directory exists, if it does not attempts to create it.
218
+ * If creating destination directory fails, it returns false, gives destination full path otherwise
219
+ * @param string $file
220
+ * @return bool|string
221
+ */
222
+ private function getDestination( $file ) {
223
+ $relativePath = str_replace( \WPStaging\WPStaging::getWPpath(), null, $file );
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
+ }
231
+
232
+ return $destinationPath;
233
+ }
234
+
235
+ /**
236
+ * Copy bigger files than $this->settings->batchSize
237
+ * @param string $src
238
+ * @param string $dst
239
+ * @param int $buffersize
240
+ * @return boolean
241
+ */
242
+ private function copyBig( $src, $dst, $buffersize ) {
243
+ $src = fopen( $src, 'r' );
244
+ $dest = fopen( $dst, 'w' );
245
+
246
+ // Try first method:
247
+ while ( !feof( $src ) ) {
248
+ if( false === fwrite( $dest, fread( $src, $buffersize ) ) ) {
249
+ $error = true;
250
+ }
251
+ }
252
+ // Try second method if first one failed
253
+ if( isset( $error ) && ($error === true) ) {
254
+ while ( !feof( $src ) ) {
255
+ if( false === stream_copy_to_stream( $src, $dest, 1024 ) ) {
256
+ $this->log( "Can not copy big file; {$src} -> {$dest}" );
257
+ fclose( $src );
258
+ fclose( $dest );
259
+ return false;
260
  }
261
+ }
262
+ }
263
+ // Close any open handler
264
+ fclose( $src );
265
+ fclose( $dest );
266
+ return true;
267
+ }
268
+
269
+ /**
270
+ * Check if file is excluded from copying process
271
+ *
272
+ * @param string $file filename including ending
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
+ /**
295
+ * Replace forward slash with current directory separator
296
+ * Windows Compatibility Fix
297
+ * @param string $path Path
298
+ *
299
+ * @return string
300
+ */
301
+ private function sanitizeDirectorySeparator( $path ) {
302
+ $string = str_replace( "/", "\\", $path );
303
+ return str_replace( '\\\\', '\\', $string );
304
+ }
305
+
306
+ /**
307
+ * Check if directory is excluded from copying
308
+ * @param string $directory
309
+ * @return bool
310
+ */
311
+ private function isDirectoryExcluded( $directory ) {
312
+
313
+ // Make sure that wp-staging-pro directory / plugin is never excluded
314
+ if( false !== strpos( $directory, 'wp-staging' ) || false !== strpos( $directory, 'wp-staging-pro' ) ) {
315
+ return false;
316
+ }
317
+
318
+ $directory = $this->sanitizeDirectorySeparator( $directory );
319
+
320
+ foreach ( $this->options->excludedDirectories as $excludedDirectory ) {
321
+ //echo 'excl:' . $excludedDirectory . '<br>';
322
+ //echo 'dir' . $directory . '<br>';
323
+ if( strpos( $directory, $this->sanitizeDirectorySeparator( $excludedDirectory ) ) === 0 && !$this->isExtraDirectory( $directory ) ) {
324
  return true;
325
+ }
326
+ }
327
+
328
+ return false;
329
+ }
330
+
331
+ /**
332
+ * Check if directory is an extra directory and should be copied
333
+ * @param string $directory
334
+ * @return boolean
335
+ */
336
+ private function isExtraDirectory( $directory ) {
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
+ }
344
+
345
+ return false;
346
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
 
 
 
348
  }
apps/Backend/Modules/Jobs/Finish.php CHANGED
@@ -35,7 +35,7 @@ class Finish extends Job
35
 
36
  $return = array(
37
  "directoryName" => $this->options->cloneDirectoryName,
38
- "path" => ABSPATH . $this->options->cloneDirectoryName,
39
  "url" => get_site_url() . '/' . $this->options->cloneDirectoryName,
40
  "number" => $this->options->cloneNumber,
41
  "version" => \WPStaging\WPStaging::VERSION,
@@ -92,7 +92,7 @@ class Finish extends Job
92
 
93
  $this->options->existingClones[$this->clone] = array(
94
  "directoryName" => $this->options->cloneDirectoryName,
95
- "path" => ABSPATH . $this->options->cloneDirectoryName,
96
  "url" => get_site_url() . '/' . $this->options->cloneDirectoryName,
97
  "number" => $this->options->cloneNumber,
98
  "version" => \WPStaging\WPStaging::VERSION,
35
 
36
  $return = array(
37
  "directoryName" => $this->options->cloneDirectoryName,
38
+ "path" => \WPStaging\WPStaging::getWPpath() . $this->options->cloneDirectoryName,
39
  "url" => get_site_url() . '/' . $this->options->cloneDirectoryName,
40
  "number" => $this->options->cloneNumber,
41
  "version" => \WPStaging\WPStaging::VERSION,
92
 
93
  $this->options->existingClones[$this->clone] = array(
94
  "directoryName" => $this->options->cloneDirectoryName,
95
+ "path" => \WPStaging\WPStaging::getWPpath() . $this->options->cloneDirectoryName,
96
  "url" => get_site_url() . '/' . $this->options->cloneDirectoryName,
97
  "number" => $this->options->cloneNumber,
98
  "version" => \WPStaging\WPStaging::VERSION,
apps/Backend/Modules/Jobs/Multisite/Directories.php CHANGED
@@ -137,9 +137,9 @@ class Directories extends JobExecutable {
137
  * Get user excluded folders
138
  */
139
  $directory = array();
140
- foreach ($this->options->excludedDirectories as $dir){
141
- if( strpos($dir, WP_CONTENT_DIR) !== false){
142
- $directory[] = ltrim(str_replace(WP_CONTENT_DIR, '', $dir), '/');
143
  }
144
  }
145
 
@@ -161,7 +161,7 @@ class Directories extends JobExecutable {
161
  $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
162
 
163
  // Exclude sites, uploads, plugins or themes
164
- $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, $excludePaths);
165
  // Recursively iterate over content directory
166
  $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
167
 
@@ -299,7 +299,7 @@ class Directories extends JobExecutable {
299
  */
300
  private function getWpContentUploadsSites() {
301
 
302
- if(is_main_site()){
303
  return true;
304
  }
305
 
@@ -329,9 +329,9 @@ class Directories extends JobExecutable {
329
  * Get user excluded folders
330
  */
331
  $directory = array();
332
- foreach ($this->options->excludedDirectories as $dir){
333
- if( strpos($dir, $path) !== false){
334
- $directory[] = ltrim(str_replace($path, '', $dir), '/');
335
  }
336
  }
337
 
@@ -346,7 +346,7 @@ class Directories extends JobExecutable {
346
  $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
347
 
348
  // Exclude sites, uploads, plugins or themes
349
- $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, $excludePaths);
350
  // Recursively iterate over content directory
351
  $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
352
  $this->log( "Scanning /wp-content/uploads/sites/{$blogId} for its sub-directories and files" );
@@ -433,7 +433,6 @@ class Directories extends JobExecutable {
433
  // close the file handler
434
  $this->close( $files );
435
  return true;
436
-
437
  }
438
 
439
  /**
@@ -584,13 +583,27 @@ class Directories extends JobExecutable {
584
  $this->files = explode( PHP_EOL, $this->files );
585
  }
586
 
 
 
 
 
 
 
 
 
 
 
 
 
587
  /**
588
  * Check if directory is excluded
589
  * @param string $directory
590
  * @return bool
591
  */
592
  protected function isDirectoryExcluded( $directory ) {
 
593
  foreach ( $this->options->excludedDirectories as $excludedDirectory ) {
 
594
  if( strpos( $directory, $excludedDirectory ) === 0 ) {
595
  return true;
596
  }
137
  * Get user excluded folders
138
  */
139
  $directory = array();
140
+ foreach ( $this->options->excludedDirectories as $dir ) {
141
+ if( strpos( $dir, WP_CONTENT_DIR ) !== false ) {
142
+ $directory[] = ltrim( str_replace( WP_CONTENT_DIR, '', $dir ), '/' );
143
  }
144
  }
145
 
161
  $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
162
 
163
  // Exclude sites, uploads, plugins or themes
164
+ $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, $excludePaths );
165
  // Recursively iterate over content directory
166
  $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
167
 
299
  */
300
  private function getWpContentUploadsSites() {
301
 
302
+ if( is_main_site() ) {
303
  return true;
304
  }
305
 
329
  * Get user excluded folders
330
  */
331
  $directory = array();
332
+ foreach ( $this->options->excludedDirectories as $dir ) {
333
+ if( strpos( $dir, $path ) !== false ) {
334
+ $directory[] = ltrim( str_replace( $path, '', $dir ), '/' );
335
  }
336
  }
337
 
346
  $iterator = new \WPStaging\Iterators\RecursiveFilterNewLine( $iterator );
347
 
348
  // Exclude sites, uploads, plugins or themes
349
+ $iterator = new \WPStaging\Iterators\RecursiveFilterExclude( $iterator, $excludePaths );
350
  // Recursively iterate over content directory
351
  $iterator = new \RecursiveIteratorIterator( $iterator, \RecursiveIteratorIterator::LEAVES_ONLY, \RecursiveIteratorIterator::CATCH_GET_CHILD );
352
  $this->log( "Scanning /wp-content/uploads/sites/{$blogId} for its sub-directories and files" );
433
  // close the file handler
434
  $this->close( $files );
435
  return true;
 
436
  }
437
 
438
  /**
583
  $this->files = explode( PHP_EOL, $this->files );
584
  }
585
 
586
+ /**
587
+ * Replace forward slash with current directory separator
588
+ * Windows Compatibility Fix
589
+ * @param string $path Path
590
+ *
591
+ * @return string
592
+ */
593
+ private function sanitizeDirectorySeparator( $path ) {
594
+ $string = str_replace( "/", "\\", $path );
595
+ return str_replace( '\\\\', '\\', $string );
596
+ }
597
+
598
  /**
599
  * Check if directory is excluded
600
  * @param string $directory
601
  * @return bool
602
  */
603
  protected function isDirectoryExcluded( $directory ) {
604
+ $directory = $this->sanitizeDirectorySeparator( $directory );
605
  foreach ( $this->options->excludedDirectories as $excludedDirectory ) {
606
+ $excludedDirectory = $this->sanitizeDirectorySeparator( $excludedDirectory );
607
  if( strpos( $directory, $excludedDirectory ) === 0 ) {
608
  return true;
609
  }
apps/Backend/Modules/Jobs/Multisite/Files.php CHANGED
@@ -1,13 +1,13 @@
1
  <?php
2
 
3
  namespace WPStaging\Backend\Modules\Jobs\Multisite;
4
- use WPStaging\Backend\Modules\Jobs\JobExecutable;
5
 
 
6
  // No Direct Access
7
  use WPStaging\Utils\Logger;
8
 
9
- if (!defined("WPINC")) {
10
- die;
11
  }
12
 
13
  /**
@@ -16,321 +16,336 @@ if (!defined("WPINC")) {
16
  */
17
  class Files extends JobExecutable {
18
 
19
- /**
20
- * @var \SplFileObject
21
- */
22
- private $file;
23
-
24
- /**
25
- * @var int
26
- */
27
- private $maxFilesPerRun;
28
-
29
- /**
30
- * @var string
31
- */
32
- private $destination;
33
-
34
- /**
35
- * Initialization
36
- */
37
- public function initialize() {
38
-
39
- $this->destination = ABSPATH . $this->options->cloneDirectoryName . DIRECTORY_SEPARATOR;
40
-
41
- $filePath = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
42
-
43
- if (is_file($filePath)) {
44
- $this->file = new \SplFileObject($filePath, 'r');
45
- }
46
-
47
- // Informational logs
48
- if (0 == $this->options->currentStep) {
49
- $this->log("Copying files...");
50
- }
51
-
52
- $this->settings->batchSize = $this->settings->batchSize * 1000000;
53
- $this->maxFilesPerRun = $this->settings->fileLimit;
54
- }
55
-
56
- /**
57
- * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
58
- * @return void
59
- */
60
- protected function calculateTotalSteps() {
61
- $this->options->totalSteps = ceil($this->options->totalFiles / $this->maxFilesPerRun);
62
- }
63
-
64
- /**
65
- * Execute the Current Step
66
- * Returns false when over threshold limits are hit or when the job is done, true otherwise
67
- * @return bool
68
- */
69
- protected function execute() {
70
- // Finished
71
- if ($this->isFinished()) {
72
- $this->log("Copying files finished");
73
- $this->prepareResponse(true, false);
74
- return false;
75
- }
76
-
77
- // Get files and copy'em
78
- if (!$this->getFilesAndCopy()) {
79
- $this->prepareResponse(false, false);
80
- return false;
81
- }
82
-
83
- // Prepare and return response
84
- $this->prepareResponse();
85
-
86
- // Not finished
87
- return true;
88
- }
89
-
90
- /**
91
- * Get files and copy
92
- * @return bool
93
- */
94
- private function getFilesAndCopy() {
95
- // Over limits threshold
96
- if ($this->isOverThreshold()) {
97
- // Prepare response and save current progress
98
- $this->prepareResponse(false, false);
99
- $this->saveOptions();
100
- return false;
101
- }
102
-
103
- // Go to last copied line and than to next one
104
- //if ($this->options->copiedFiles != 0) {
105
- if (isset($this->options->copiedFiles) && $this->options->copiedFiles != 0) {
106
- $this->file->seek($this->options->copiedFiles - 1);
107
- }
108
-
109
- $this->file->setFlags(\SplFileObject::SKIP_EMPTY | \SplFileObject::READ_AHEAD);
110
-
111
-
112
- // Loop x files at a time
113
- for ($i = 0; $i < $this->maxFilesPerRun; $i++) {
114
-
115
- // Increment copied files
116
- // Do this anytime to make sure to not stuck in the same step / files
117
- $this->options->copiedFiles++;
118
-
119
- // End of file
120
- if ($this->file->eof()) {
121
- break;
122
- }
123
 
 
 
 
 
124
 
125
- $file = $this->file->fgets();
126
-
127
- $this->copyFile($file);
128
- }
129
-
130
- $totalFiles = $this->options->copiedFiles;
131
- // Log this only every 50 entries to keep the log small and to not block the rendering browser
132
- if ($this->options->copiedFiles %50 == 0){
133
- $this->log("Total {$totalFiles} files processed");
134
- }
135
-
136
- return true;
137
- }
138
-
139
- /**
140
- * Checks Whether There is Any Job to Execute or Not
141
- * @return bool
142
- */
143
- private function isFinished() {
144
- return (
145
- $this->options->currentStep > $this->options->totalSteps ||
146
- $this->options->copiedFiles >= $this->options->totalFiles
147
- );
148
- }
149
-
150
- /**
151
- * @param string $file
152
- * @return bool
153
- */
154
- private function copyFile($file) {
155
- $file = trim(ABSPATH . $file);
156
-
157
- $directory = dirname($file);
158
-
159
- // Get file size
160
- $fileSize = filesize($file);
161
-
162
- // Directory is excluded
163
- if ($this->isDirectoryExcluded($directory)) {
164
- $this->debugLog("Skipping directory by rule: {$file}", Logger::TYPE_INFO);
165
- return false;
166
- }
167
-
168
- // File is excluded
169
- if ($this->isFileExcluded($file)) {
170
- $this->log("Skipping file by rule: {$file}", Logger::TYPE_INFO);
171
- return false;
172
- }
173
-
174
- // File is over maximum allowed file size (8MB)
175
- if ($fileSize >= $this->settings->maxFileSize * 1000000) {
176
- $this->log("Skipping big file: {$file}", Logger::TYPE_INFO);
177
- return false;
178
- }
179
-
180
- // Invalid file, skipping it as if succeeded
181
- if (!is_file($file) || !is_readable($file)) {
182
- $this->log("Can't read file or file doesn't exist {$file}");
183
- return true;
184
- }
185
-
186
- // Failed to get destination
187
- if (false === ($destination = $this->getDestination($file))) {
188
- $this->log("Can't get the destination of {$file}");
189
- return false;
190
- }
191
-
192
- // File is over batch size
193
- if ($fileSize >= $this->settings->batchSize) {
194
- $this->log("Trying to copy big file: {$file} -> {$destination}", Logger::TYPE_INFO);
195
- return $this->copyBig($file, $destination, $this->settings->batchSize);
196
- }
197
-
198
- // Attempt to copy
199
- if (!@copy($file, $destination)) {
200
- $this->log("Failed to copy file to destination: {$file} -> {$destination}", Logger::TYPE_ERROR);
201
- return false;
202
- }
203
-
204
- return true;
205
-
206
- // Good old PHP
207
- //return $this->copy($file, $destination);
208
- }
209
-
210
- /**
211
- * Gets destination file and checks if the directory exists, if it does not attempts to create it.
212
- * If creating destination directory fails, it returns false, gives destination full path otherwise
213
- * @param string $file
214
- * @return bool|string
215
- */
216
- private function getDestination($file) {
217
- $file = $this->replaceMultisiteUploadFolder($file);
218
- $relativePath = str_replace(ABSPATH, null, $file);
219
- $destinationPath = $this->destination . $relativePath;
220
- $destinationDirectory = dirname($destinationPath);
221
-
222
- if (!is_dir($destinationDirectory) && !@mkdir($destinationDirectory, 0775, true)) {
223
- $this->log("Files: Can not create directory {$destinationDirectory}", Logger::TYPE_ERROR);
224
- return false;
225
- }
226
-
227
- return $destinationPath;
228
- }
229
-
230
- /**
231
- * Copy bigger files than $this->settings->batchSize
232
- * @param string $src
233
- * @param string $dst
234
- * @param int $buffersize
235
- * @return boolean
236
- */
237
- private function copyBig($src, $dst, $buffersize) {
238
- $src = fopen($src, 'r');
239
- $dest = fopen($dst, 'w');
240
-
241
- // Try first method:
242
- while (!feof($src)) {
243
- if (false === fwrite($dest, fread($src, $buffersize))) {
244
- $error = true;
245
- }
246
- }
247
- // Try second method if first one failed
248
- if (isset($error) && ($error === true)) {
249
- while (!feof($src)) {
250
- if (false === stream_copy_to_stream($src, $dest, 1024)) {
251
- $this->log("Can not copy big file; {$src} -> {$dest}");
252
- fclose($src);
253
- fclose($dest);
254
- return false;
255
- }
256
- }
257
- }
258
- // Close any open handler
259
- fclose($src);
260
- fclose($dest);
261
- return true;
262
- }
263
-
264
- /**
265
- * Check if file is excluded from copying process
266
- *
267
- * @param string $file filename including ending
268
- * @return boolean
269
- */
270
- private function isFileExcluded($file) {
271
- $excluded = false;
272
- foreach ($this->options->excludedFiles as $excludedFile) {
273
- if (stripos(strrev($file), strrev($excludedFile)) === 0) {
274
- $excluded = true;
275
- break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  }
277
- }
278
-
279
- // Do not copy wp-config.php if the clone gets updated. This is for security purposes,
280
- // because if the updating process fails, the staging site is not accessable any longer
281
- if (isset($this->options->mainJob ) && $this->options->mainJob == "updating" && stripos(strrev($file), strrev("wp-config.php")) === 0){
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  $excluded = true;
283
- }
284
-
285
-
286
- return $excluded;
287
- }
288
-
289
- /**
290
- * Check if directory is excluded from copying
291
- * @param string $directory
292
- * @return bool
293
- */
294
- private function isDirectoryExcluded($directory) {
295
- // Make sure that wp-staging-pro directory / plugin is never excluded
296
- if (false !== strpos( $directory, 'wp-staging' ) || false !== strpos( $directory, 'wp-staging-pro' ) ){
297
- return false;
298
- }
299
-
300
- foreach ($this->options->excludedDirectories as $excludedDirectory) {
301
- if (strpos($directory, $excludedDirectory) === 0 && !$this->isExtraDirectory($directory)) {
302
- return true;
303
- }
304
- }
305
-
306
- return false;
307
- }
308
-
309
- /**
310
- * Check if directory is an extra directory and should be copied
311
- * @param string $directory
312
- * @return boolean
313
- */
314
- private function isExtraDirectory($directory) {
315
- foreach ($this->options->extraDirectories as $extraDirectory) {
316
- if (strpos($directory, $extraDirectory) === 0) {
317
- return true;
318
- }
319
- }
320
-
321
- return false;
322
- }
323
-
324
- /**
325
- * Replace relative path of file if its located in multisite upload folder wp-content/uploads/sites/x/
326
- * @return boolean
327
- */
328
- private function replaceMultisiteUploadFolder( $file ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  $search = 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . get_current_blog_id();
330
  $replace = 'wp-content' . DIRECTORY_SEPARATOR . 'uploads';
331
 
332
- return str_replace($search, $replace, $file);
333
-
334
  }
335
 
336
  }
1
  <?php
2
 
3
  namespace WPStaging\Backend\Modules\Jobs\Multisite;
 
4
 
5
+ use WPStaging\Backend\Modules\Jobs\JobExecutable;
6
  // No Direct Access
7
  use WPStaging\Utils\Logger;
8
 
9
+ if( !defined( "WPINC" ) ) {
10
+ die;
11
  }
12
 
13
  /**
16
  */
17
  class Files extends JobExecutable {
18
 
19
+ /**
20
+ * @var \SplFileObject
21
+ */
22
+ private $file;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
+ /**
25
+ * @var int
26
+ */
27
+ private $maxFilesPerRun;
28
 
29
+ /**
30
+ * @var string
31
+ */
32
+ private $destination;
33
+
34
+ /**
35
+ * Initialization
36
+ */
37
+ public function initialize() {
38
+
39
+ $this->destination = ABSPATH . $this->options->cloneDirectoryName . DIRECTORY_SEPARATOR;
40
+
41
+ $filePath = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
42
+
43
+ if( is_file( $filePath ) ) {
44
+ $this->file = new \SplFileObject( $filePath, 'r' );
45
+ }
46
+
47
+ // Informational logs
48
+ if( 0 == $this->options->currentStep ) {
49
+ $this->log( "Copying files..." );
50
+ }
51
+
52
+ $this->settings->batchSize = $this->settings->batchSize * 1000000;
53
+ $this->maxFilesPerRun = $this->settings->fileLimit;
54
+ }
55
+
56
+ /**
57
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
58
+ * @return void
59
+ */
60
+ protected function calculateTotalSteps() {
61
+ $this->options->totalSteps = ceil( $this->options->totalFiles / $this->maxFilesPerRun );
62
+ }
63
+
64
+ /**
65
+ * Execute the Current Step
66
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
67
+ * @return bool
68
+ */
69
+ protected function execute() {
70
+ // Finished
71
+ if( $this->isFinished() ) {
72
+ $this->log( "Copying files finished" );
73
+ $this->prepareResponse( true, false );
74
+ return false;
75
+ }
76
+
77
+ // Get files and copy'em
78
+ if( !$this->getFilesAndCopy() ) {
79
+ $this->prepareResponse( false, false );
80
+ return false;
81
+ }
82
+
83
+ // Prepare and return response
84
+ $this->prepareResponse();
85
+
86
+ // Not finished
87
+ return true;
88
+ }
89
+
90
+ /**
91
+ * Get files and copy
92
+ * @return bool
93
+ */
94
+ private function getFilesAndCopy() {
95
+ // Over limits threshold
96
+ if( $this->isOverThreshold() ) {
97
+ // Prepare response and save current progress
98
+ $this->prepareResponse( false, false );
99
+ $this->saveOptions();
100
+ return false;
101
+ }
102
+
103
+ // Go to last copied line and than to next one
104
+ //if ($this->options->copiedFiles != 0) {
105
+ if( isset( $this->options->copiedFiles ) && $this->options->copiedFiles != 0 ) {
106
+ $this->file->seek( $this->options->copiedFiles - 1 );
107
+ }
108
+
109
+ $this->file->setFlags( \SplFileObject::SKIP_EMPTY | \SplFileObject::READ_AHEAD );
110
+
111
+
112
+ // Loop x files at a time
113
+ for ( $i = 0; $i < $this->maxFilesPerRun; $i++ ) {
114
+
115
+ // Increment copied files
116
+ // Do this anytime to make sure to not stuck in the same step / files
117
+ $this->options->copiedFiles++;
118
+
119
+ // End of file
120
+ if( $this->file->eof() ) {
121
+ break;
122
+ }
123
+
124
+
125
+ $file = $this->file->fgets();
126
+
127
+ $this->copyFile( $file );
128
+ }
129
+
130
+ $totalFiles = $this->options->copiedFiles;
131
+ // Log this only every 50 entries to keep the log small and to not block the rendering browser
132
+ if( $this->options->copiedFiles % 50 == 0 ) {
133
+ $this->log( "Total {$totalFiles} files processed" );
134
+ }
135
+
136
+ return true;
137
+ }
138
+
139
+ /**
140
+ * Checks Whether There is Any Job to Execute or Not
141
+ * @return bool
142
+ */
143
+ private function isFinished() {
144
+ return (
145
+ $this->options->currentStep > $this->options->totalSteps ||
146
+ $this->options->copiedFiles >= $this->options->totalFiles
147
+ );
148
+ }
149
+
150
+ /**
151
+ * @param string $file
152
+ * @return bool
153
+ */
154
+ private function copyFile( $file ) {
155
+ $file = trim( ABSPATH . $file );
156
+
157
+ $directory = dirname( $file );
158
+
159
+ // Get file size
160
+ $fileSize = filesize( $file );
161
+
162
+ // Directory is excluded
163
+ if( $this->isDirectoryExcluded( $directory ) ) {
164
+ $this->debugLog( "Skipping directory by rule: {$file}", Logger::TYPE_INFO );
165
+ return false;
166
+ }
167
+
168
+ // File is excluded
169
+ if( $this->isFileExcluded( $file ) ) {
170
+ $this->log( "Skipping file by rule: {$file}", Logger::TYPE_INFO );
171
+ return false;
172
+ }
173
+
174
+ // File is over maximum allowed file size (8MB)
175
+ if( $fileSize >= $this->settings->maxFileSize * 1000000 ) {
176
+ $this->log( "Skipping big file: {$file}", Logger::TYPE_INFO );
177
+ return false;
178
+ }
179
+
180
+ // Invalid file, skipping it as if succeeded
181
+ if( !is_file( $file ) || !is_readable( $file ) ) {
182
+ $this->log( "Can't read file or file doesn't exist {$file}" );
183
+ return true;
184
+ }
185
+
186
+ // Failed to get destination
187
+ if( false === ($destination = $this->getDestination( $file )) ) {
188
+ $this->log( "Can't get the destination of {$file}" );
189
+ return false;
190
+ }
191
+
192
+ // File is over batch size
193
+ if( $fileSize >= $this->settings->batchSize ) {
194
+ $this->log( "Trying to copy big file: {$file} -> {$destination}", Logger::TYPE_INFO );
195
+ return $this->copyBig( $file, $destination, $this->settings->batchSize );
196
+ }
197
+
198
+ // Attempt to copy
199
+ if( !@copy( $file, $destination ) ) {
200
+ $errors = error_get_last();
201
+ $this->log( "Files: Failed to copy file to destination. Error: {$errors['message']} {$file} -> {$destination}", Logger::TYPE_ERROR );
202
+ return false;
203
+ }
204
+
205
+ return true;
206
+
207
+ // Good old PHP
208
+ //return $this->copy($file, $destination);
209
+ }
210
+
211
+ /**
212
+ * Gets destination file and checks if the directory exists, if it does not attempts to create it.
213
+ * If creating destination directory fails, it returns false, gives destination full path otherwise
214
+ * @param string $file
215
+ * @return bool|string
216
+ */
217
+ private function getDestination( $file ) {
218
+ $file = $this->replaceMultisiteUploadFolder( $file );
219
+ $relativePath = str_replace( ABSPATH, null, $file );
220
+ $destinationPath = $this->destination . $relativePath;
221
+ $destinationDirectory = dirname( $destinationPath );
222
+
223
+ if( !is_dir( $destinationDirectory ) && !@mkdir( $destinationDirectory, 0775, true ) ) {
224
+ $this->log( "Files: Can not create directory {$destinationDirectory}", Logger::TYPE_ERROR );
225
+ return false;
226
+ }
227
+
228
+ return $destinationPath;
229
+ }
230
+
231
+ /**
232
+ * Copy bigger files than $this->settings->batchSize
233
+ * @param string $src
234
+ * @param string $dst
235
+ * @param int $buffersize
236
+ * @return boolean
237
+ */
238
+ private function copyBig( $src, $dst, $buffersize ) {
239
+ $src = fopen( $src, 'r' );
240
+ $dest = fopen( $dst, 'w' );
241
+
242
+ // Try first method:
243
+ while ( !feof( $src ) ) {
244
+ if( false === fwrite( $dest, fread( $src, $buffersize ) ) ) {
245
+ $error = true;
246
+ }
247
+ }
248
+ // Try second method if first one failed
249
+ if( isset( $error ) && ($error === true) ) {
250
+ while ( !feof( $src ) ) {
251
+ if( false === stream_copy_to_stream( $src, $dest, 1024 ) ) {
252
+ $this->log( "Can not copy big file; {$src} -> {$dest}" );
253
+ fclose( $src );
254
+ fclose( $dest );
255
+ return false;
256
  }
257
+ }
258
+ }
259
+ // Close any open handler
260
+ fclose( $src );
261
+ fclose( $dest );
262
+ return true;
263
+ }
264
+
265
+ /**
266
+ * Check if file is excluded from copying process
267
+ *
268
+ * @param string $file filename including ending
269
+ * @return boolean
270
+ */
271
+ private function isFileExcluded( $file ) {
272
+ $excluded = false;
273
+ foreach ( $this->options->excludedFiles as $excludedFile ) {
274
+ if( stripos( strrev( $file ), strrev( $excludedFile ) ) === 0 ) {
275
  $excluded = true;
276
+ break;
277
+ }
278
+ }
279
+
280
+ // Do not copy wp-config.php if the clone gets updated. This is for security purposes,
281
+ // because if the updating process fails, the staging site is not accessable any longer
282
+ if( isset( $this->options->mainJob ) && $this->options->mainJob == "updating" && stripos( strrev( $file ), strrev( "wp-config.php" ) ) === 0 ) {
283
+ $excluded = true;
284
+ }
285
+
286
+
287
+ return $excluded;
288
+ }
289
+
290
+ /**
291
+ * Replace forward slash with current directory separator
292
+ * Windows Compatibility Fix
293
+ * @param string $path Path
294
+ *
295
+ * @return string
296
+ */
297
+ private function sanitizeDirectorySeparator( $path ) {
298
+ $string = str_replace( "/", "\\", $path );
299
+ return str_replace( '\\\\', '\\', $string );
300
+ }
301
+
302
+ /**
303
+ * Check if directory is excluded from copying
304
+ * @param string $directory
305
+ * @return bool
306
+ */
307
+ private function isDirectoryExcluded( $directory ) {
308
+ // Make sure that wp-staging-pro directory / plugin is never excluded
309
+ if( false !== strpos( $directory, 'wp-staging' ) || false !== strpos( $directory, 'wp-staging-pro' ) ) {
310
+ return false;
311
+ }
312
+
313
+ $directory = $this->sanitizeDirectorySeparator( $directory );
314
+
315
+ foreach ( $this->options->excludedDirectories as $excludedDirectory ) {
316
+ $excludedDirectory = $this->sanitizeDirectorySeparator( $excludedDirectory );
317
+ if( strpos( $directory, $excludedDirectory ) === 0 && !$this->isExtraDirectory( $directory ) ) {
318
+ return true;
319
+ }
320
+ }
321
+
322
+ return false;
323
+ }
324
+
325
+ /**
326
+ * Check if directory is an extra directory and should be copied
327
+ * @param string $directory
328
+ * @return boolean
329
+ */
330
+ private function isExtraDirectory( $directory ) {
331
+ foreach ( $this->options->extraDirectories as $extraDirectory ) {
332
+ if( strpos( $directory, $extraDirectory ) === 0 ) {
333
+ return true;
334
+ }
335
+ }
336
+
337
+ return false;
338
+ }
339
+
340
+ /**
341
+ * Replace relative path of file if its located in multisite upload folder wp-content/uploads/sites/x/
342
+ * @return boolean
343
+ */
344
+ private function replaceMultisiteUploadFolder( $file ) {
345
  $search = 'wp-content' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . get_current_blog_id();
346
  $replace = 'wp-content' . DIRECTORY_SEPARATOR . 'uploads';
347
 
348
+ return str_replace( $search, $replace, $file );
 
349
  }
350
 
351
  }
apps/Backend/Modules/Jobs/Scan.php CHANGED
@@ -48,7 +48,7 @@ class Scan extends Job {
48
  */
49
  public function start() {
50
  // Basic Options
51
- $this->options->root = str_replace( array("\\", '/'), DIRECTORY_SEPARATOR, ABSPATH );
52
  $this->options->existingClones = get_option( "wpstg_existing_clones_beta", array() );
53
  $this->options->current = null;
54
 
@@ -76,6 +76,7 @@ class Scan extends Job {
76
 
77
  // Job
78
  $this->options->currentJob = "database";
 
79
  $this->options->currentStep = 0;
80
  $this->options->totalSteps = 0;
81
 
@@ -194,12 +195,12 @@ class Scan extends Job {
194
  return null;
195
  }
196
 
197
- $freeSpace = @disk_free_space( ABSPATH );
198
 
199
  if( false === $freeSpace ) {
200
  $data = array(
201
  'freespace' => false,
202
- 'usedspace' => $this->formatSize( $this->getDirectorySizeInclSubdirs( ABSPATH ) )
203
  );
204
  echo json_encode( $data );
205
  die();
@@ -208,7 +209,7 @@ class Scan extends Job {
208
 
209
  $data = array(
210
  'freespace' => $this->formatSize( $freeSpace ),
211
- 'usedspace' => $this->formatSize( $this->getDirectorySizeInclSubdirs( ABSPATH ) )
212
  );
213
 
214
  echo json_encode( $data );
@@ -228,6 +229,7 @@ class Scan extends Job {
228
  $sql = "SHOW TABLE STATUS";
229
  }
230
 
 
231
  $tables = $wpDB->get_results( $sql );
232
 
233
  $currentTables = array();
@@ -260,13 +262,14 @@ class Scan extends Job {
260
  * Get directories and main meta data about'em recursively
261
  */
262
  protected function directories() {
263
- $directories = new \DirectoryIterator( ABSPATH );
264
 
265
  foreach ( $directories as $directory ) {
266
  // Not a valid directory
267
  if( false === ($path = $this->getPath( $directory )) ) {
268
  continue;
269
  }
 
270
 
271
  $this->handleDirectory( $path );
272
 
@@ -314,10 +317,16 @@ class Scan extends Job {
314
  * This must be done before \SplFileInfo->isDir() is used!
315
  * Prevents open base dir restriction fatal errors
316
  */
317
- if( strpos( $directory->getRealPath(), ABSPATH ) !== 0 ) {
 
 
 
 
 
318
  return false;
319
  }
320
- $path = str_replace( ABSPATH, null, $directory->getRealPath() );
 
321
 
322
  // Using strpos() for symbolic links as they could create nasty stuff in nix stuff for directory structures
323
  if( !$directory->isDir() || strlen( $path ) < 1 ) {
@@ -332,9 +341,8 @@ class Scan extends Job {
332
  * @param string $path
333
  */
334
  protected function handleDirectory( $path ) {
335
-
336
  $directoryArray = explode( DIRECTORY_SEPARATOR, $path );
337
- $total = (is_array($directoryArray) || $directoryArray instanceof Countable ) ? count( $directoryArray ) : 0;
338
 
339
  if( $total < 1 ) {
340
  return;
@@ -355,12 +363,12 @@ class Scan extends Job {
355
  continue;
356
  }
357
 
358
- $fullPath = ABSPATH . $path;
359
  $size = $this->getDirectorySize( $fullPath );
360
 
361
  $currentArray["metaData"] = array(
362
  "size" => $size,
363
- "path" => ABSPATH . $path,
364
  );
365
  }
366
  }
48
  */
49
  public function start() {
50
  // Basic Options
51
+ $this->options->root = str_replace( array("\\", '/'), DIRECTORY_SEPARATOR, \WPStaging\WPStaging::getWPpath() );
52
  $this->options->existingClones = get_option( "wpstg_existing_clones_beta", array() );
53
  $this->options->current = null;
54
 
76
 
77
  // Job
78
  $this->options->currentJob = "database";
79
+ //$this->options->currentJob = "directories";
80
  $this->options->currentStep = 0;
81
  $this->options->totalSteps = 0;
82
 
195
  return null;
196
  }
197
 
198
+ $freeSpace = @disk_free_space( \WPStaging\WPStaging::getWPpath() );
199
 
200
  if( false === $freeSpace ) {
201
  $data = array(
202
  'freespace' => false,
203
+ 'usedspace' => $this->formatSize( $this->getDirectorySizeInclSubdirs( \WPStaging\WPStaging::getWPpath() ) )
204
  );
205
  echo json_encode( $data );
206
  die();
209
 
210
  $data = array(
211
  'freespace' => $this->formatSize( $freeSpace ),
212
+ 'usedspace' => $this->formatSize( $this->getDirectorySizeInclSubdirs( \WPStaging\WPStaging::getWPpath() ) )
213
  );
214
 
215
  echo json_encode( $data );
229
  $sql = "SHOW TABLE STATUS";
230
  }
231
 
232
+
233
  $tables = $wpDB->get_results( $sql );
234
 
235
  $currentTables = array();
262
  * Get directories and main meta data about'em recursively
263
  */
264
  protected function directories() {
265
+ $directories = new \DirectoryIterator( \WPStaging\WPStaging::getWPpath() );
266
 
267
  foreach ( $directories as $directory ) {
268
  // Not a valid directory
269
  if( false === ($path = $this->getPath( $directory )) ) {
270
  continue;
271
  }
272
+ //echo $directory . '<br>';
273
 
274
  $this->handleDirectory( $path );
275
 
317
  * This must be done before \SplFileInfo->isDir() is used!
318
  * Prevents open base dir restriction fatal errors
319
  */
320
+
321
+ //echo $directory->getRealPath() . '<br>';
322
+ //echo 'abspath: ' . \WPStaging\WPStaging::getWPpath() . '<br>';
323
+
324
+ //if( strpos( $directory->getRealPath(), ABSPATH ) !== 0 ) {
325
+ if( strpos( $directory->getRealPath(), \WPStaging\WPStaging::getWPpath() ) !== 0 ) {
326
  return false;
327
  }
328
+ //$path = str_replace( ABSPATH, null, $directory->getRealPath() );
329
+ $path = str_replace( \WPStaging\WPStaging::getWPpath(), null, $directory->getRealPath() );
330
 
331
  // Using strpos() for symbolic links as they could create nasty stuff in nix stuff for directory structures
332
  if( !$directory->isDir() || strlen( $path ) < 1 ) {
341
  * @param string $path
342
  */
343
  protected function handleDirectory( $path ) {
 
344
  $directoryArray = explode( DIRECTORY_SEPARATOR, $path );
345
+ $total = is_array( $directoryArray ) || $directoryArray instanceof Countable ? count( $directoryArray ) : 0;
346
 
347
  if( $total < 1 ) {
348
  return;
363
  continue;
364
  }
365
 
366
+ $fullPath = \WPStaging\WPStaging::getWPpath() . $path;
367
  $size = $this->getDirectorySize( $fullPath );
368
 
369
  $currentArray["metaData"] = array(
370
  "size" => $size,
371
+ "path" => \WPStaging\WPStaging::getWPpath() . $path,
372
  );
373
  }
374
  }
apps/Backend/Modules/SystemInfo.php CHANGED
@@ -6,6 +6,7 @@ use WPStaging\DI\InjectionAware;
6
  use WPStaging\Library\Browser;
7
  use WPStaging\WPStaging;
8
  use WPStaging\Utils;
 
9
 
10
  // No Direct Access
11
  if( !defined( "WPINC" ) ) {
@@ -56,6 +57,8 @@ class SystemInfo extends InjectionAware {
56
 
57
  $output .= $this->site();
58
 
 
 
59
  $output .= $this->browser();
60
 
61
  $output .= $this->wp();
@@ -119,12 +122,32 @@ class SystemInfo extends InjectionAware {
119
  $output .= $this->info( "Home Path:", get_home_path() );
120
  $output .= $this->info( "ABSPATH:", ABSPATH );
121
  $output .= $this->info( "Installed in subdir:", ( $this->isSubDir() ? 'Yes' : 'No' ) );
122
- $output .= $this->info( "Multisite:", ($this->isMultiSite ? "Yes" : "No" ) );
123
- $output .= $this->info( "Multisite Blog ID:", get_current_blog_id() );
124
 
125
  return apply_filters( "wpstg_sysinfo_after_site_info", $output );
126
  }
127
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  /**
129
  * Wp Staging plugin Information
130
  * @return string
@@ -145,7 +168,7 @@ class SystemInfo extends InjectionAware {
145
  $output .= $this->info( "Batch Size:", isset( $settings->batchSize ) ? $settings->batchSize : 'undefined' );
146
  $output .= $this->info( "CPU Load:", isset( $settings->cpuLoad ) ? $settings->cpuLoad : 'undefined' );
147
  $output .= $this->info( "WP in Subdir:", isset( $settings->wpSubDirectory ) ? $settings->wpSubDirectory : 'false' );
148
- $output .= $this->info( "Login Custom Link:", isset( $settings->loginSlug ) ? $settings->loginSlug : 'false' );
149
 
150
  $output .= PHP_EOL . PHP_EOL . "-- Available Sites Version < 1.1.6.x" . PHP_EOL . PHP_EOL;
151
 
@@ -249,11 +272,11 @@ class SystemInfo extends InjectionAware {
249
  public function wp() {
250
  $output = $this->header( "WordPress Configuration" );
251
  $output .= $this->info( "Version:", get_bloginfo( "version" ) );
252
- $output .= $this->info( "Language:", (defined( "WPLANG" ) && WPLANG) ? WPLANG : "en_US" );
253
 
254
  $permalinkStructure = get_option( "permalink_structure" );
255
  ;
256
- $output .= $this->info( "Permalink Structure:", ($permalinkStructure) ? $permalinkStructure : "Default" );
257
 
258
  $output .= $this->info( "Active Theme:", $this->theme() );
259
  $output .= $this->info( "Show On Front:", get_option( "show_on_front" ) );
@@ -275,15 +298,15 @@ class SystemInfo extends InjectionAware {
275
  // Constants
276
  $output .= $this->info( "WP Content Path:", WP_CONTENT_DIR );
277
  $output .= $this->info( "WP Plugin Dir:", WP_PLUGIN_DIR );
278
- if (defined('UPLOADS'))
279
  $output .= $this->info( "WP UPLOADS CONST:", UPLOADS );
280
  $uploads = wp_upload_dir();
281
- $output .= $this->info( "WP Uploads Dir:", $uploads['basedir'] );
282
- if (defined('WP_TEMP_DIR'))
283
  $output .= $this->info( "WP Temp Dir:", WP_TEMP_DIR );
284
 
285
  // WP Debug
286
- $output .= $this->info( "WP_DEBUG:", (defined( "WP_DEBUG" )) ? WP_DEBUG ? "Enabled" : "Disabled" : "Not set" );
287
  $output .= $this->info( "Memory Limit:", WP_MEMORY_LIMIT );
288
  $output .= $this->info( "Registered Post Stati:", implode( ", ", \get_post_stati() ) );
289
 
@@ -406,7 +429,7 @@ class SystemInfo extends InjectionAware {
406
  $output .= $this->info( "Max Input Vars:", ini_get( "max_input_vars" ) );
407
 
408
  $displayErrors = ini_get( "display_errors" );
409
- $output .= $this->info( "Display Errors:", ($displayErrors) ? "On ({$displayErrors})" : "N/A" );
410
 
411
  return apply_filters( "wpstg_sysinfo_after_php_config", $output );
412
  }
@@ -451,8 +474,28 @@ class SystemInfo extends InjectionAware {
451
  */
452
  public function phpExtensions() {
453
  // Important PHP Extensions
 
 
 
 
 
 
 
 
 
454
  $output = $this->header( "PHP Extensions" );
455
  $output .= $this->info( "cURL:", $this->isSupported( "curl_init" ) );
 
 
 
 
 
 
 
 
 
 
 
456
  $output .= $this->info( "fsockopen:", $this->isSupported( "fsockopen" ) );
457
  $output .= $this->info( "SOAP Client:", $this->isInstalled( "SoapClient" ) );
458
  $output .= $this->info( "Suhosin:", $this->isInstalled( "suhosin", false ) );
@@ -474,6 +517,7 @@ class SystemInfo extends InjectionAware {
474
  /**
475
  * Check and return prefix of the staging site
476
  */
 
477
  /**
478
  * Try to get the staging prefix from wp-config.php of staging site
479
  * @param array $clone
@@ -481,7 +525,7 @@ class SystemInfo extends InjectionAware {
481
  */
482
  private function getStagingPrefix( $clone = array() ) {
483
  // Throw error
484
- $path = ABSPATH . $clone['directoryName'] . "/wp-config.php";
485
  if( false === ($content = @file_get_contents( $path )) ) {
486
  return 'Can\'t find staging wp-config.php';
487
  } else {
6
  use WPStaging\Library\Browser;
7
  use WPStaging\WPStaging;
8
  use WPStaging\Utils;
9
+ use WPStaging\Utils\Multisite;
10
 
11
  // No Direct Access
12
  if( !defined( "WPINC" ) ) {
57
 
58
  $output .= $this->site();
59
 
60
+ $output .= $this->getMultisiteInfo();
61
+
62
  $output .= $this->browser();
63
 
64
  $output .= $this->wp();
122
  $output .= $this->info( "Home Path:", get_home_path() );
123
  $output .= $this->info( "ABSPATH:", ABSPATH );
124
  $output .= $this->info( "Installed in subdir:", ( $this->isSubDir() ? 'Yes' : 'No' ) );
 
 
125
 
126
  return apply_filters( "wpstg_sysinfo_after_site_info", $output );
127
  }
128
 
129
+ /**
130
+ * Multisite information
131
+ * @return string
132
+ */
133
+ private function getMultisiteInfo(){
134
+ if (!$this->isMultiSite){
135
+ return '';
136
+ }
137
+
138
+ $multisite = new Multisite();
139
+
140
+ $output = $this->info( "Multisite:", ($this->isMultiSite ? "Yes" : "No" ) );
141
+ $output .= $this->info( "Multisite Blog ID:", get_current_blog_id() );
142
+ $output .= $this->info( "MultiSite URL scheme:", $multisite->getHomeURL() );
143
+ $output .= $this->info( "MultiSite URL without scheme:", $multisite->getHomeUrlWithoutScheme() );
144
+
145
+ return apply_filters( "wpstg_sysinfo_after_multisite_info", $output );
146
+
147
+
148
+ }
149
+
150
+
151
  /**
152
  * Wp Staging plugin Information
153
  * @return string
168
  $output .= $this->info( "Batch Size:", isset( $settings->batchSize ) ? $settings->batchSize : 'undefined' );
169
  $output .= $this->info( "CPU Load:", isset( $settings->cpuLoad ) ? $settings->cpuLoad : 'undefined' );
170
  $output .= $this->info( "WP in Subdir:", isset( $settings->wpSubDirectory ) ? $settings->wpSubDirectory : 'false' );
171
+ //$output .= $this->info( "Login Custom Link:", isset( $settings->loginSlug ) ? $settings->loginSlug : 'false' );
172
 
173
  $output .= PHP_EOL . PHP_EOL . "-- Available Sites Version < 1.1.6.x" . PHP_EOL . PHP_EOL;
174
 
272
  public function wp() {
273
  $output = $this->header( "WordPress Configuration" );
274
  $output .= $this->info( "Version:", get_bloginfo( "version" ) );
275
+ $output .= $this->info( "Language:", (defined( "WPLANG" ) && WPLANG) ? WPLANG : "en_US" );
276
 
277
  $permalinkStructure = get_option( "permalink_structure" );
278
  ;
279
+ $output .= $this->info( "Permalink Structure:", ($permalinkStructure) ? $permalinkStructure : "Default" );
280
 
281
  $output .= $this->info( "Active Theme:", $this->theme() );
282
  $output .= $this->info( "Show On Front:", get_option( "show_on_front" ) );
298
  // Constants
299
  $output .= $this->info( "WP Content Path:", WP_CONTENT_DIR );
300
  $output .= $this->info( "WP Plugin Dir:", WP_PLUGIN_DIR );
301
+ if( defined( 'UPLOADS' ) )
302
  $output .= $this->info( "WP UPLOADS CONST:", UPLOADS );
303
  $uploads = wp_upload_dir();
304
+ $output .= $this->info( "WP Uploads Dir:", $uploads['basedir'] );
305
+ if( defined( 'WP_TEMP_DIR' ) )
306
  $output .= $this->info( "WP Temp Dir:", WP_TEMP_DIR );
307
 
308
  // WP Debug
309
+ $output .= $this->info( "WP_DEBUG:", (defined( "WP_DEBUG" )) ? WP_DEBUG ? "Enabled" : "Disabled" : "Not set" );
310
  $output .= $this->info( "Memory Limit:", WP_MEMORY_LIMIT );
311
  $output .= $this->info( "Registered Post Stati:", implode( ", ", \get_post_stati() ) );
312
 
429
  $output .= $this->info( "Max Input Vars:", ini_get( "max_input_vars" ) );
430
 
431
  $displayErrors = ini_get( "display_errors" );
432
+ $output .= $this->info( "Display Errors:", ($displayErrors) ? "On ({$displayErrors})" : "N/A" );
433
 
434
  return apply_filters( "wpstg_sysinfo_after_php_config", $output );
435
  }
474
  */
475
  public function phpExtensions() {
476
  // Important PHP Extensions
477
+ $version = curl_version();
478
+
479
+ $bitfields = Array(
480
+ 'CURL_VERSION_IPV6',
481
+ 'CURL_VERSION_KERBEROS4',
482
+ 'CURL_VERSION_SSL',
483
+ 'CURL_VERSION_LIBZ'
484
+ );
485
+
486
  $output = $this->header( "PHP Extensions" );
487
  $output .= $this->info( "cURL:", $this->isSupported( "curl_init" ) );
488
+ $output .= $this->info( "cURL version:", $version['version'] );
489
+ $output .= $this->info( "cURL ssl version number:", $version['ssl_version'] );
490
+ $output .= $this->info( "cURL host:", $version['host'] );
491
+ foreach ( $version['protocols'] as $protocols ) {
492
+ $output .= $this->info( "cURL protocols:", $protocols );
493
+ }
494
+ foreach ( $bitfields as $feature ) {
495
+ $output .= $feature . ($version['features'] & constant( $feature ) ? ' yes' : ' no') . PHP_EOL;
496
+ }
497
+
498
+
499
  $output .= $this->info( "fsockopen:", $this->isSupported( "fsockopen" ) );
500
  $output .= $this->info( "SOAP Client:", $this->isInstalled( "SoapClient" ) );
501
  $output .= $this->info( "Suhosin:", $this->isInstalled( "suhosin", false ) );
517
  /**
518
  * Check and return prefix of the staging site
519
  */
520
+
521
  /**
522
  * Try to get the staging prefix from wp-config.php of staging site
523
  * @param array $clone
525
  */
526
  private function getStagingPrefix( $clone = array() ) {
527
  // Throw error
528
+ $path = ABSPATH . $clone['directoryName'] . DIRECTORY_SEPARATOR . "wp-config.php";
529
  if( false === ($content = @file_get_contents( $path )) ) {
530
  return 'Can\'t find staging wp-config.php';
531
  } else {
apps/Core/WPStaging.php CHANGED
@@ -29,7 +29,7 @@ final class WPStaging {
29
  /**
30
  * Plugin version
31
  */
32
- const VERSION = "2.3.3";
33
 
34
  /**
35
  * Plugin name
@@ -46,6 +46,8 @@ final class WPStaging {
46
  */
47
  const WP_COMPATIBLE = "4.9.8";
48
 
 
 
49
  /**
50
  * Slug: Either wp-staging or wp-staging-pro
51
  * @var string
@@ -86,6 +88,18 @@ final class WPStaging {
86
  $this->initLicensing();
87
  }
88
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  /**
90
  * Method to be executed upon activation of the plugin
91
  */
@@ -164,23 +178,11 @@ final class WPStaging {
164
 
165
  wp_localize_script( "wpstg-admin-script", "wpstg", array(
166
  "nonce" => wp_create_nonce( "wpstg_ajax_nonce" ),
167
- "mu_plugin_confirmation" => __(
168
- "If confirmed we will install an additional WordPress 'Must Use' plugin. "
169
- . "This plugin will allow us to control which plugins are loaded during "
170
- . "WP Staging specific operations. Do you wish to continue?", "wpstg"
171
- ),
172
- "plugin_compatibility_settings_problem" => __(
173
- "A problem occurred when trying to change the plugin compatibility setting.", "wpstg"
174
- ),
175
- "saved" => __( "Saved", "The settings were saved successfully", "wpstg" ),
176
- "status" => __( "Status", "Current request status", "wpstg" ),
177
- "response" => __( "Response", "The message the server responded with", "wpstg" ),
178
- "blacklist_problem" => __(
179
- "A problem occurred when trying to add plugins to backlist.", "wpstg"
180
- ),
181
  "cpuLoad" => $this->getCPULoadSetting(),
182
  "settings" => ( object ) array(), // TODO add settings?
183
- "tblprefix" => self::getTablePrefix()
 
184
  ) );
185
  }
186
 
@@ -217,6 +219,10 @@ final class WPStaging {
217
  "WPStaging" => array(
218
  $this->pluginPath . 'apps' . DIRECTORY_SEPARATOR,
219
  $this->pluginPath . 'apps' . DIRECTORY_SEPARATOR . 'Core' . DIRECTORY_SEPARATOR,
 
 
 
 
220
  )
221
  ) );
222
 
29
  /**
30
  * Plugin version
31
  */
32
+ const VERSION = "2.3.4";
33
 
34
  /**
35
  * Plugin name
46
  */
47
  const WP_COMPATIBLE = "4.9.8";
48
 
49
+ public $wpPath;
50
+
51
  /**
52
  * Slug: Either wp-staging or wp-staging-pro
53
  * @var string
88
  $this->initLicensing();
89
  }
90
 
91
+ /**
92
+ * Get root WP root path -
93
+ * Changed ABSPATH trailingslash for windows compatibility
94
+
95
+ * @return type
96
+ */
97
+ public static function getWPpath(){
98
+ return str_replace('/', DIRECTORY_SEPARATOR, ABSPATH);
99
+ }
100
+
101
+
102
+
103
  /**
104
  * Method to be executed upon activation of the plugin
105
  */
178
 
179
  wp_localize_script( "wpstg-admin-script", "wpstg", array(
180
  "nonce" => wp_create_nonce( "wpstg_ajax_nonce" ),
181
+ "noncetick" => apply_filters( 'nonce_life', DAY_IN_SECONDS ),
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  "cpuLoad" => $this->getCPULoadSetting(),
183
  "settings" => ( object ) array(), // TODO add settings?
184
+ "tblprefix" => self::getTablePrefix(),
185
+ "isMultisite" => is_multisite() ? true : false
186
  ) );
187
  }
188
 
219
  "WPStaging" => array(
220
  $this->pluginPath . 'apps' . DIRECTORY_SEPARATOR,
221
  $this->pluginPath . 'apps' . DIRECTORY_SEPARATOR . 'Core' . DIRECTORY_SEPARATOR,
222
+ $this->pluginPath . 'apps' . DIRECTORY_SEPARATOR . 'Core' . DIRECTORY_SEPARATOR . 'Iterators' . DIRECTORY_SEPARATOR,
223
+ ),
224
+ "splitbrain" => array(
225
+ $this->pluginPath . 'vendor' . DIRECTORY_SEPARATOR . 'splitbrain' . DIRECTORY_SEPARATOR
226
  )
227
  ) );
228
 
readme.txt CHANGED
@@ -9,7 +9,7 @@ License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
  Tags: staging, duplication, cloning, clone, migration, sandbox, test site, testing, backup, post, admin, administration, duplicate posts
10
  Requires at least: 3.6+
11
  Tested up to: 4.9
12
- Stable tag: 2.3.3
13
  Requires PHP: 5.3
14
 
15
  A duplicator plugin! Clone, duplicate and migrate live sites to independent staging and development sites that are available only to administrators.
@@ -146,6 +146,11 @@ https://wp-staging.com
146
 
147
  == Changelog ==
148
 
 
 
 
 
 
149
  = 2.3.3 =
150
  * New: Compatible up to WordPress 4.9.8
151
  * New: Better error reporting
@@ -202,6 +207,6 @@ Complete changelog: [https://wp-staging.com/changelog.txt](https://wp-staging.co
202
 
203
  == Upgrade Notice ==
204
 
205
- = 2.3.3 =
206
  * New: Compatible to WordPress 4.9.7
207
 
9
  Tags: staging, duplication, cloning, clone, migration, sandbox, test site, testing, backup, post, admin, administration, duplicate posts
10
  Requires at least: 3.6+
11
  Tested up to: 4.9
12
+ Stable tag: 2.3.4
13
  Requires PHP: 5.3
14
 
15
  A duplicator plugin! Clone, duplicate and migrate live sites to independent staging and development sites that are available only to administrators.
146
 
147
  == Changelog ==
148
 
149
+ = 2.3.4 =
150
+ * New: Compatible up to WordPress 4.9.8
151
+ * New: Support for Windows Azure cloud servers
152
+ * Fix: Missing http(s) scheme after cloning multisites results in not working clones
153
+
154
  = 2.3.3 =
155
  * New: Compatible up to WordPress 4.9.8
156
  * New: Better error reporting
207
 
208
  == Upgrade Notice ==
209
 
210
+ = 2.3.4 =
211
  * New: Compatible to WordPress 4.9.7
212
 
wp-staging.php CHANGED
@@ -7,7 +7,7 @@
7
  * Author: WP-Staging
8
  * Author URI: https://wp-staging.com
9
  * Contributors: ReneHermi, ilgityildirim
10
- * Version: 2.3.3
11
  * Text Domain: wpstg
12
  * Domain Path: /languages/
13
 
7
  * Author: WP-Staging
8
  * Author URI: https://wp-staging.com
9
  * Contributors: ReneHermi, ilgityildirim
10
+ * Version: 2.3.4
11
  * Text Domain: wpstg
12
  * Domain Path: /languages/
13