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 | 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 +242 -259
- apps/Backend/Modules/Jobs/Directories.php +28 -11
- apps/Backend/Modules/Jobs/Files.php +328 -309
- apps/Backend/Modules/Jobs/Finish.php +2 -2
- apps/Backend/Modules/Jobs/Multisite/Directories.php +23 -10
- apps/Backend/Modules/Jobs/Multisite/Files.php +326 -311
- apps/Backend/Modules/Jobs/Scan.php +19 -11
- apps/Backend/Modules/SystemInfo.php +55 -11
- apps/Core/WPStaging.php +22 -16
- readme.txt +7 -2
- wp-staging.php +1 -1
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 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
|
|
|
|
|
|
|
|
|
|
72 |
// if (isset($_POST["excludedTables"]) && is_array($_POST["excludedTables"]))
|
73 |
// {
|
74 |
// $this->options->excludedTables = $_POST["excludedTables"];
|
75 |
// }
|
|
|
|
|
|
|
|
|
76 |
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
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 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
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(
|
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(
|
|
|
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(
|
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(
|
|
|
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(
|
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(
|
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 |
-
|
|
|
|
|
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
|
9 |
-
|
10 |
}
|
11 |
|
12 |
/**
|
@@ -15,315 +16,333 @@ if (!defined("WPINC")) {
|
|
15 |
*/
|
16 |
class Files extends JobExecutable {
|
17 |
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
121 |
}
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
|
|
|
|
|
|
182 |
return true;
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
187 |
return true;
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
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" =>
|
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" =>
|
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
|
10 |
-
|
11 |
}
|
12 |
|
13 |
/**
|
@@ -16,321 +16,336 @@ if (!defined("WPINC")) {
|
|
16 |
*/
|
17 |
class Files extends JobExecutable {
|
18 |
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
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 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
276 |
}
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
282 |
$excluded = true;
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
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(
|
198 |
|
199 |
if( false === $freeSpace ) {
|
200 |
$data = array(
|
201 |
'freespace' => false,
|
202 |
-
'usedspace' => $this->formatSize( $this->getDirectorySizeInclSubdirs(
|
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(
|
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(
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
318 |
return false;
|
319 |
}
|
320 |
-
|
|
|
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 =
|
338 |
|
339 |
if( $total < 1 ) {
|
340 |
return;
|
@@ -355,12 +363,12 @@ class Scan extends Job {
|
|
355 |
continue;
|
356 |
}
|
357 |
|
358 |
-
$fullPath =
|
359 |
$size = $this->getDirectorySize( $fullPath );
|
360 |
|
361 |
$currentArray["metaData"] = array(
|
362 |
"size" => $size,
|
363 |
-
"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 |
-
|
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
|
279 |
$output .= $this->info( "WP UPLOADS CONST:", UPLOADS );
|
280 |
$uploads = wp_upload_dir();
|
281 |
-
$output .= $this->info( "WP Uploads Dir:",
|
282 |
-
if
|
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'] . "
|
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.
|
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 |
-
"
|
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.
|
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.
|
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.
|
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 |
|