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

Version Description

  • Fix: Creating a new clone resets the custom wp staging user settings. E.g. ignores authentication setting
  • Fix: Search & replace path not correct if scheme http/https is not identical in wp-config and db wp_options
  • New: Setting to specify the maximum allowed file size to copy
Download this release

Release Info

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

Code changes from version 2.3.0 to 2.3.1

apps/Backend/Modules/Jobs/Cloning.php CHANGED
@@ -1,275 +1,275 @@
1
- <?php
2
- namespace WPStaging\Backend\Modules\Jobs;
3
-
4
- use WPStaging\Backend\Modules\Jobs\Exceptions\JobNotFoundException;
5
- use WPStaging\WPStaging;
6
-
7
- /**
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
+ namespace WPStaging\Backend\Modules\Jobs;
3
+
4
+ use WPStaging\Backend\Modules\Jobs\Exceptions\JobNotFoundException;
5
+ use WPStaging\WPStaging;
6
+
7
+ /**
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
  }
apps/Backend/Modules/Jobs/Data.php CHANGED
@@ -1,727 +1,727 @@
1
- <?php
2
-
3
- namespace WPStaging\Backend\Modules\Jobs;
4
-
5
- // No Direct Access
6
- if( !defined( "WPINC" ) ) {
7
- die;
8
- }
9
-
10
- use WPStaging\Utils\Logger;
11
- use WPStaging\WPStaging;
12
- use WPStaging\Utils\Helper;
13
- use WPStaging\Utils\Strings;
14
-
15
- /**
16
- * Class Data
17
- * @package WPStaging\Backend\Modules\Jobs
18
- */
19
- class Data extends JobExecutable {
20
-
21
- /**
22
- * @var \wpdb
23
- */
24
- private $db;
25
-
26
- /**
27
- * @var string
28
- */
29
- private $prefix;
30
-
31
- /**
32
- *
33
- * @var string
34
- */
35
- private $homeUrl;
36
-
37
- /**
38
- * Tables e.g wpstg3_options
39
- * @var array
40
- */
41
- private $tables;
42
-
43
- /**
44
- * Initialize
45
- */
46
- public function initialize() {
47
- $this->db = WPStaging::getInstance()->get( "wpdb" );
48
-
49
- $this->prefix = $this->options->prefix;
50
-
51
- $this->getTables();
52
-
53
- $helper = new Helper();
54
-
55
- $this->homeUrl = $helper->get_home_url();
56
-
57
-
58
- // Fix current step
59
- if( 0 == $this->options->currentStep ) {
60
- $this->options->currentStep = 1;
61
- }
62
- }
63
-
64
- /**
65
- * Get a list of tables to copy
66
- */
67
- private function getTables() {
68
- $strings = new Strings();
69
- $this->tables = array();
70
- foreach ( $this->options->tables as $table ) {
71
- $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->db->prefix, null, $table );
72
- }
73
- }
74
-
75
- /**
76
- * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
77
- * @return void
78
- */
79
- protected function calculateTotalSteps() {
80
- $this->options->totalSteps = 12;
81
- }
82
-
83
- /**
84
- * Start Module
85
- * @return object
86
- */
87
- public function start() {
88
- // Execute steps
89
- $this->run();
90
-
91
- // Save option, progress
92
- $this->saveOptions();
93
-
94
- return ( object ) $this->response;
95
- }
96
-
97
- /**
98
- * Execute the Current Step
99
- * Returns false when over threshold limits are hit or when the job is done, true otherwise
100
- * @return bool
101
- */
102
- protected function execute() {
103
- // Fatal error. Let this happen never and break here immediately
104
- if( $this->isRoot() ) {
105
- return false;
106
- }
107
-
108
- // Over limits threshold
109
- if( $this->isOverThreshold() ) {
110
- // Prepare response and save current progress
111
- $this->prepareResponse( false, false );
112
- $this->saveOptions();
113
- return false;
114
- }
115
-
116
- // No more steps, finished
117
- if( $this->isFinished() ) {
118
- $this->prepareResponse( true, false );
119
- return false;
120
- }
121
-
122
- // Execute step
123
- $stepMethodName = "step" . $this->options->currentStep;
124
- if( !$this->{$stepMethodName}() ) {
125
- $this->prepareResponse( false, false );
126
- return false;
127
- }
128
-
129
- // Prepare Response
130
- $this->prepareResponse();
131
-
132
- // Not finished
133
- return true;
134
- }
135
-
136
- /**
137
- * Checks Whether There is Any Job to Execute or Not
138
- * @return bool
139
- */
140
- protected function isFinished() {
141
- return (
142
- $this->options->currentStep > $this->options->totalSteps ||
143
- !method_exists( $this, "step" . $this->options->currentStep )
144
- );
145
- }
146
-
147
- /**
148
- * Check if current operation is done on the root folder or on the live DB
149
- * @return boolean
150
- */
151
- protected function isRoot() {
152
-
153
- // Prefix is the same as the one of live site
154
- $wpdb = WPStaging::getInstance()->get( "wpdb" );
155
- if( $wpdb->prefix === $this->prefix ) {
156
- return true;
157
- }
158
-
159
- // CloneName is empty
160
- $name = ( array ) $this->options->cloneDirectoryName;
161
- if( empty( $name ) ) {
162
- return true;
163
- }
164
-
165
- // Live Path === Staging path
166
- if( $this->homeUrl . $this->options->cloneDirectoryName === $this->homeUrl ) {
167
- return true;
168
- }
169
-
170
- return false;
171
- }
172
-
173
- /**
174
- * Check if table exists
175
- * @param string $table
176
- * @return boolean
177
- */
178
- protected function isTable( $table ) {
179
- if( $this->db->get_var( "SHOW TABLES LIKE '{$table}'" ) != $table ) {
180
- $this->log( "Table {$table} does not exists", Logger::TYPE_ERROR );
181
- return false;
182
- }
183
- return true;
184
- }
185
-
186
- /**
187
- * Get the install sub directory if WP is installed in sub directory
188
- * @return string
189
- */
190
- protected function getSubDir() {
191
- $home = get_option( 'home' );
192
- $siteurl = get_option( 'siteurl' );
193
-
194
- if( empty( $home ) || empty( $siteurl ) ) {
195
- return '/';
196
- }
197
-
198
- $dir = str_replace( $home, '', $siteurl );
199
- return '/' . str_replace( '/', '', $dir ) . '/';
200
- }
201
-
202
- /**
203
- * Replace "siteurl" and "home"
204
- * @return bool
205
- */
206
- protected function step1() {
207
- $this->log( "Preparing Data Step1: Updating siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_INFO );
208
-
209
- // Skip - Table does not exist
210
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
211
- return true;
212
- }
213
- // Skip - Table is not selected or updated
214
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
215
- $this->log( "Preparing Data Step1: Skipping" );
216
- return true;
217
- }
218
-
219
- // Installed in sub-directory
220
- if( $this->isSubDir() ) {
221
- $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . rtrim( $this->homeUrl, "/" ) . $this->getSubDir() . $this->options->cloneDirectoryName );
222
- // Replace URLs
223
- $result = $this->db->query(
224
- $this->db->prepare(
225
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", rtrim( $this->homeUrl, "/" ) . $this->getSubDir() . $this->options->cloneDirectoryName
226
- )
227
- );
228
- } else {
229
- $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . rtrim( $this->homeUrl, "/" ) . '/' . $this->options->cloneDirectoryName );
230
- // Replace URLs
231
- $result = $this->db->query(
232
- $this->db->prepare(
233
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", $this->homeUrl . '/' . $this->options->cloneDirectoryName
234
- )
235
- );
236
- }
237
-
238
-
239
- // All good
240
- if( $result ) {
241
- return true;
242
- }
243
-
244
- $this->log( "Preparing Data Step1: Failed to update siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
245
- return false;
246
- }
247
-
248
- /**
249
- * Update "wpstg_is_staging_site"
250
- * @return bool
251
- */
252
- protected function step2() {
253
-
254
- $this->log( "Preparing Data Step2: Updating row wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}" );
255
-
256
- // Skip - Table does not exist
257
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
258
- return true;
259
- }
260
- // Skip - Table is not selected or updated
261
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
262
- $this->log( "Preparing Data Step2: Skipping" );
263
- return true;
264
- }
265
-
266
- $result = $this->db->query(
267
- $this->db->prepare(
268
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_is_staging_site'", "true"
269
- )
270
- );
271
-
272
- // No errors but no option name such as wpstg_is_staging_site
273
- if( '' === $this->db->last_error && 0 == $result ) {
274
- $result = $this->db->query(
275
- $this->db->prepare(
276
- "INSERT INTO {$this->prefix}options (option_name,option_value) VALUES ('wpstg_is_staging_site',%s)", "true"
277
- )
278
- );
279
- }
280
-
281
- // All good
282
- if( $result ) {
283
- return true;
284
- }
285
-
286
- $this->log( "Preparing Data Step2: Failed to update wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
287
- return false;
288
- }
289
-
290
- /**
291
- * Update rewrite_rules
292
- * @return bool
293
- */
294
- protected function step3() {
295
-
296
- $this->log( "Preparing Data Step3: Updating rewrite_rules in {$this->prefix}options {$this->db->last_error}" );
297
-
298
- // Skip - Table does not exist
299
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
300
- return true;
301
- }
302
-
303
- // Skip - Table is not selected or updated
304
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
305
- $this->log( "Preparing Data Step3: Skipping" );
306
- return true;
307
- }
308
-
309
- $result = $this->db->query(
310
- $this->db->prepare(
311
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'rewrite_rules'", ' '
312
- )
313
- );
314
-
315
- // All good
316
- if( $result ) {
317
- return true;
318
- }
319
-
320
- $this->log( "Preparing Data Step3: Failed to update rewrite_rules in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
321
- return true;
322
- }
323
-
324
- /**
325
- * Update Table Prefix in wp_usermeta and wp_options
326
- * @return bool
327
- */
328
- protected function step4() {
329
- $this->log( "Preparing Data Step4: Updating db prefix in {$this->prefix}usermeta. " );
330
-
331
- // Skip - Table does not exist
332
- if( false === $this->isTable( $this->prefix . 'usermeta' ) ) {
333
- return true;
334
- }
335
-
336
- // Skip - Table is not selected or updated
337
- if( !in_array( $this->prefix . 'usermeta', $this->tables ) ) {
338
- $this->log( "Preparing Data Step4: Skipping" );
339
- return true;
340
- }
341
-
342
- $update = $this->db->query(
343
- $this->db->prepare(
344
- "UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, %s, %s) WHERE meta_key LIKE %s", $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
345
- )
346
- );
347
-
348
- if( !$update ) {
349
- $this->log( "Preparing Data Step4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}", Logger::TYPE_ERROR );
350
- $this->returnException( "Data Crunching Step 4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}" );
351
- return false;
352
- }
353
-
354
- // if( false === $this->isTable( $this->prefix . 'options' ) ) {
355
- // return true;
356
- // }
357
- // $this->log( "Updating db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
358
- //
359
- // // Filter the rows below. Do not update them!
360
- // $filters = array(
361
- // 'wp_mail_smtp',
362
- // 'wp_mail_smtp_version',
363
- // 'wp_mail_smtp_debug',
364
- // );
365
- //
366
- // $filters = apply_filters('wpstg_filter_options_replace', $filters);
367
- //
368
- // $where = "";
369
- // foreach($filters as $filter){
370
- // $where .= " AND option_name <> '" . $filter . "'";
371
- // }
372
- //
373
- // $updateOptions = $this->db->query(
374
- // $this->db->prepare(
375
- // "UPDATE IGNORE {$this->prefix}options SET option_name= replace(option_name, %s, %s) WHERE option_name LIKE %s" . $where, $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
376
- // )
377
- // );
378
- //
379
- // if( !$updateOptions ) {
380
- // $this->log( "Preparing Data Step4: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_ERROR );
381
- // $this->returnException( "Data Crunching Step 4: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
382
- // return false;
383
- // }
384
-
385
- return true;
386
- }
387
-
388
- /**
389
- * Update $table_prefix in wp-config.php
390
- * @return bool
391
- */
392
- protected function step5() {
393
- $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
394
-
395
- $this->log( "Preparing Data Step5: Updating table_prefix in {$path} to " . $this->prefix );
396
- if( false === ($content = file_get_contents( $path )) ) {
397
- $this->log( "Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR );
398
- return false;
399
- }
400
-
401
- // Replace table prefix
402
- $content = str_replace( '$table_prefix', '$table_prefix = \'' . $this->prefix . '\';//', $content );
403
-
404
- // Replace URLs
405
- $content = str_replace( $this->homeUrl, $this->homeUrl . '/' . $this->options->cloneDirectoryName, $content );
406
-
407
- if( false === @file_put_contents( $path, $content ) ) {
408
- $this->log( "Preparing Data Step5: Failed to update $table_prefix in {$path} to " . $this->prefix . ". Can't save contents", Logger::TYPE_ERROR );
409
- return false;
410
- }
411
-
412
- return true;
413
- }
414
-
415
- /**
416
- * Reset index.php to original file
417
- * This is needed if live site is located in subfolder
418
- * Check first if main wordpress is used in subfolder and index.php in parent directory
419
- * @see: https://codex.wordpress.org/Giving_WordPress_Its_Own_Directory
420
- * @return bool
421
- */
422
- protected function step6() {
423
-
424
- if( !$this->isSubDir() ) {
425
- $this->debugLog( "Preparing Data Step6: WP installation is not in a subdirectory! All good, skipping this step" );
426
- return true;
427
- }
428
-
429
- $path = ABSPATH . $this->options->cloneDirectoryName . "/index.php";
430
-
431
- if( false === ($content = file_get_contents( $path )) ) {
432
- $this->log( "Preparing Data Step6: Failed to reset {$path} for sub directory; can't read contents", Logger::TYPE_ERROR );
433
- return false;
434
- }
435
-
436
-
437
- if( !preg_match( "/(require(.*)wp-blog-header.php' \);)/", $content, $matches ) ) {
438
- $this->log(
439
- "Preparing Data Step6: Failed to reset index.php for sub directory; wp-blog-header.php is missing", Logger::TYPE_ERROR
440
- );
441
- return false;
442
- }
443
- $this->log( "Preparing Data: WP installation is in a subdirectory. Progressing..." );
444
-
445
- $pattern = "/require(.*) dirname(.*) __FILE__ (.*) \. '(.*)wp-blog-header.php'(.*);/";
446
-
447
- $replace = "require( dirname( __FILE__ ) . '/wp-blog-header.php' ); // " . $matches[0];
448
- $replace.= " // Changed by WP-Staging";
449
-
450
-
451
-
452
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
453
- $this->log( "Preparing Data: Failed to reset index.php for sub directory; replacement failed", Logger::TYPE_ERROR );
454
- return false;
455
- }
456
-
457
- if( false === @file_put_contents( $path, $content ) ) {
458
- $this->log( "Preparing Data: Failed to reset index.php for sub directory; can't save contents", Logger::TYPE_ERROR );
459
- return false;
460
- }
461
- $this->Log( "Preparing Data: Finished Step 6 successfully" );
462
- return true;
463
- }
464
-
465
- /**
466
- * Update wpstg_rmpermalinks_executed
467
- * @return bool
468
- */
469
- protected function step7() {
470
-
471
- $this->log( "Preparing Data Step7: Updating wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}" );
472
-
473
- // Skip - Table does not exist
474
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
475
- return true;
476
- }
477
-
478
- // Skip - Table is not selected or updated
479
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
480
- $this->log( "Preparing Data Step7: Skipping" );
481
- return true;
482
- }
483
-
484
- $result = $this->db->query(
485
- $this->db->prepare(
486
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_rmpermalinks_executed'", ' '
487
- )
488
- );
489
-
490
- // All good
491
- if( $result ) {
492
- $this->Log( "Preparing Data Step7: Finished Step 7 successfully" );
493
- return true;
494
- }
495
-
496
- $this->log( "Failed to update wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_WARNING );
497
- return true;
498
- }
499
-
500
- /**
501
- * Update permalink_structure
502
- * @return bool
503
- */
504
- protected function step8() {
505
-
506
- $this->log( "Preparing Data Step8: Updating permalink_structure in {$this->prefix}options {$this->db->last_error}" );
507
-
508
- // Skip - Table does not exist
509
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
510
- return true;
511
- }
512
-
513
- // Skip - Table is not selected or updated
514
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
515
- $this->log( "Preparing Data Step8: Skipping" );
516
- return true;
517
- }
518
-
519
- $result = $this->db->query(
520
- $this->db->prepare(
521
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'permalink_structure'", ' '
522
- )
523
- );
524
-
525
- // All good
526
- if( $result ) {
527
- $this->Log( "Preparing Data Step8: Finished Step 8 successfully" );
528
- return true;
529
- }
530
-
531
- $this->log( "Failed to update permalink_structure in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
532
- return true;
533
- }
534
-
535
- /**
536
- * Update blog_public option to not allow staging site to be indexed by search engines
537
- * @return bool
538
- */
539
- protected function step9() {
540
-
541
- $this->log( "Preparing Data Step9: Set staging site to noindex" );
542
-
543
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
544
- return true;
545
- }
546
-
547
- // Skip - Table is not selected or updated
548
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
549
- $this->log( "Preparing Data Step9: Skipping" );
550
- return true;
551
- }
552
-
553
- $result = $this->db->query(
554
- $this->db->prepare(
555
- "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'blog_public'", '0'
556
- )
557
- );
558
-
559
- // All good
560
- if( $result ) {
561
- $this->Log( "Preparing Data Step9: Finished Step 9 successfully" );
562
- return true;
563
- }
564
-
565
- $this->log( "Can not update staging site to noindex. Possible already done!", Logger::TYPE_WARNING );
566
- return true;
567
- }
568
-
569
- /**
570
- * Update WP_HOME in wp-config.php
571
- * @return bool
572
- */
573
- protected function step10() {
574
- $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
575
-
576
- $this->log( "Preparing Data Step10: Updating WP_HOME in wp-config.php to " . $this->getStagingSiteUrl() );
577
-
578
- if( false === ($content = file_get_contents( $path )) ) {
579
- $this->log( "Preparing Data Step10: Failed to update WP_HOME in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
580
- return false;
581
- }
582
-
583
-
584
- // Get WP_HOME from wp-config.php
585
- preg_match( "/define\s*\(\s*'WP_HOME'\s*,\s*(.*)\s*\);/", $content, $matches );
586
-
587
- if( !empty( $matches[1] ) ) {
588
- $matches[1];
589
-
590
- $pattern = "/define\s*\(\s*'WP_HOME'\s*,\s*(.*)\s*\);/";
591
-
592
- $replace = "define('WP_HOME','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
593
- $replace.= " // Changed by WP-Staging";
594
-
595
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
596
- $this->log( "Preparing Data: Failed to reset index.php for sub directory; replacement failed", Logger::TYPE_ERROR );
597
- return false;
598
- }
599
- } else {
600
- $this->log( "Preparing Data Step10: WP_HOME not defined in wp-config.php. Skipping this step." );
601
- }
602
-
603
- if( false === @file_put_contents( $path, $content ) ) {
604
- $this->log( "Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR );
605
- return false;
606
- }
607
- $this->Log( "Preparing Data: Finished Step 11 successfully" );
608
- return true;
609
- }
610
-
611
- /**
612
- * Update WP_SITEURL in wp-config.php
613
- * @return bool
614
- */
615
- protected function step11() {
616
- $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
617
-
618
- $this->log( "Preparing Data Step11: Updating WP_SITEURL in wp-config.php to " . $this->getStagingSiteUrl() );
619
-
620
- if( false === ($content = file_get_contents( $path )) ) {
621
- $this->log( "Preparing Data Step11: Failed to update WP_SITEURL in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
622
- return false;
623
- }
624
-
625
-
626
- // Get WP_SITEURL from wp-config.php
627
- preg_match( "/define\s*\(\s*'WP_SITEURL'\s*,\s*(.*)\s*\);/", $content, $matches );
628
-
629
- if( !empty( $matches[1] ) ) {
630
- $matches[1];
631
-
632
- $pattern = "/define\s*\(\s*'WP_SITEURL'\s*,\s*(.*)\s*\);/";
633
-
634
- $replace = "define('WP_SITEURL','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
635
- $replace.= " // Changed by WP-Staging";
636
-
637
- if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
638
- $this->log( "Preparing Data Step11: Failed to update WP_SITEURL", Logger::TYPE_ERROR );
639
- return false;
640
- }
641
- } else {
642
- $this->log( "Preparing Data Step11: WP_SITEURL not defined in wp-config.php. Skipping this step." );
643
- }
644
-
645
-
646
- if( false === @file_put_contents( $path, $content ) ) {
647
- $this->log( "Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR );
648
- return false;
649
- }
650
- $this->Log( "Preparing Data: Finished Step 11 successfully" );
651
- return true;
652
- }
653
-
654
- /**
655
- * Update Table Prefix in wp_options
656
- * @return bool
657
- */
658
- protected function step12() {
659
- $this->log( "Preparing Data Step12: Updating db prefix in {$this->prefix}options. Error: {$this->db->last_error}" );
660
-
661
- // Skip - Table does not exist
662
- if( false === $this->isTable( $this->prefix . 'options' ) ) {
663
- return true;
664
- }
665
-
666
- // Skip - Table is not selected or updated
667
- if( !in_array( $this->prefix . 'options', $this->tables ) ) {
668
- $this->log( "Preparing Data Step12: Skipping" );
669
- return true;
670
- }
671
-
672
-
673
- $this->log( "Updating db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
674
-
675
- // Filter the rows below. Do not update them!
676
- $filters = array(
677
- 'wp_mail_smtp',
678
- 'wp_mail_smtp_version',
679
- 'wp_mail_smtp_debug',
680
- );
681
-
682
- $filters = apply_filters( 'wpstg_filter_options_replace', $filters );
683
-
684
- $where = "";
685
- foreach ( $filters as $filter ) {
686
- $where .= " AND option_name <> '" . $filter . "'";
687
- }
688
-
689
- $updateOptions = $this->db->query(
690
- $this->db->prepare(
691
- "UPDATE IGNORE {$this->prefix}options SET option_name= replace(option_name, %s, %s) WHERE option_name LIKE %s" . $where, $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
692
- )
693
- );
694
-
695
- if( !$updateOptions ) {
696
- $this->log( "Preparing Data Step12: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_ERROR );
697
- $this->returnException( "Data Crunching Step 15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
698
- return false;
699
- }
700
-
701
- return true;
702
- }
703
-
704
- /**
705
- * Return URL to staging site
706
- * @return string
707
- */
708
- protected function getStagingSiteUrl() {
709
- if( $this->isSubDir() ) {
710
- return rtrim( $this->homeUrl, "/" ) . $this->getSubDir() . $this->options->cloneDirectoryName;
711
- }
712
-
713
- return rtrim( $this->homeUrl, "/" ) . '/' . $this->options->cloneDirectoryName;
714
- }
715
-
716
- /**
717
- * Check if WP is installed in subdir
718
- * @return boolean
719
- */
720
- protected function isSubDir() {
721
- if( get_option( 'siteurl' ) !== get_option( 'home' ) ) {
722
- return true;
723
- }
724
- return false;
725
- }
726
-
727
- }
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs;
4
+
5
+ // No Direct Access
6
+ if( !defined( "WPINC" ) ) {
7
+ die;
8
+ }
9
+
10
+ use WPStaging\Utils\Logger;
11
+ use WPStaging\WPStaging;
12
+ use WPStaging\Utils\Helper;
13
+ use WPStaging\Utils\Strings;
14
+
15
+ /**
16
+ * Class Data
17
+ * @package WPStaging\Backend\Modules\Jobs
18
+ */
19
+ class Data extends JobExecutable {
20
+
21
+ /**
22
+ * @var \wpdb
23
+ */
24
+ private $db;
25
+
26
+ /**
27
+ * @var string
28
+ */
29
+ private $prefix;
30
+
31
+ /**
32
+ *
33
+ * @var string
34
+ */
35
+ private $homeUrl;
36
+
37
+ /**
38
+ * Tables e.g wpstg3_options
39
+ * @var array
40
+ */
41
+ private $tables;
42
+
43
+ /**
44
+ * Initialize
45
+ */
46
+ public function initialize() {
47
+ $this->db = WPStaging::getInstance()->get( "wpdb" );
48
+
49
+ $this->prefix = $this->options->prefix;
50
+
51
+ $this->getTables();
52
+
53
+ $helper = new Helper();
54
+
55
+ $this->homeUrl = $helper->get_home_url();
56
+
57
+
58
+ // Fix current step
59
+ if( 0 == $this->options->currentStep ) {
60
+ $this->options->currentStep = 1;
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Get a list of tables to copy
66
+ */
67
+ private function getTables() {
68
+ $strings = new Strings();
69
+ $this->tables = array();
70
+ foreach ( $this->options->tables as $table ) {
71
+ $this->tables[] = $this->options->prefix . $strings->str_replace_first( $this->db->prefix, null, $table );
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
77
+ * @return void
78
+ */
79
+ protected function calculateTotalSteps() {
80
+ $this->options->totalSteps = 12;
81
+ }
82
+
83
+ /**
84
+ * Start Module
85
+ * @return object
86
+ */
87
+ public function start() {
88
+ // Execute steps
89
+ $this->run();
90
+
91
+ // Save option, progress
92
+ $this->saveOptions();
93
+
94
+ return ( object ) $this->response;
95
+ }
96
+
97
+ /**
98
+ * Execute the Current Step
99
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
100
+ * @return bool
101
+ */
102
+ protected function execute() {
103
+ // Fatal error. Let this happen never and break here immediately
104
+ if( $this->isRoot() ) {
105
+ return false;
106
+ }
107
+
108
+ // Over limits threshold
109
+ if( $this->isOverThreshold() ) {
110
+ // Prepare response and save current progress
111
+ $this->prepareResponse( false, false );
112
+ $this->saveOptions();
113
+ return false;
114
+ }
115
+
116
+ // No more steps, finished
117
+ if( $this->isFinished() ) {
118
+ $this->prepareResponse( true, false );
119
+ return false;
120
+ }
121
+
122
+ // Execute step
123
+ $stepMethodName = "step" . $this->options->currentStep;
124
+ if( !$this->{$stepMethodName}() ) {
125
+ $this->prepareResponse( false, false );
126
+ return false;
127
+ }
128
+
129
+ // Prepare Response
130
+ $this->prepareResponse();
131
+
132
+ // Not finished
133
+ return true;
134
+ }
135
+
136
+ /**
137
+ * Checks Whether There is Any Job to Execute or Not
138
+ * @return bool
139
+ */
140
+ protected function isFinished() {
141
+ return (
142
+ $this->options->currentStep > $this->options->totalSteps ||
143
+ !method_exists( $this, "step" . $this->options->currentStep )
144
+ );
145
+ }
146
+
147
+ /**
148
+ * Check if current operation is done on the root folder or on the live DB
149
+ * @return boolean
150
+ */
151
+ protected function isRoot() {
152
+
153
+ // Prefix is the same as the one of live site
154
+ $wpdb = WPStaging::getInstance()->get( "wpdb" );
155
+ if( $wpdb->prefix === $this->prefix ) {
156
+ return true;
157
+ }
158
+
159
+ // CloneName is empty
160
+ $name = ( array ) $this->options->cloneDirectoryName;
161
+ if( empty( $name ) ) {
162
+ return true;
163
+ }
164
+
165
+ // Live Path === Staging path
166
+ if( $this->homeUrl . $this->options->cloneDirectoryName === $this->homeUrl ) {
167
+ return true;
168
+ }
169
+
170
+ return false;
171
+ }
172
+
173
+ /**
174
+ * Check if table exists
175
+ * @param string $table
176
+ * @return boolean
177
+ */
178
+ protected function isTable( $table ) {
179
+ if( $this->db->get_var( "SHOW TABLES LIKE '{$table}'" ) != $table ) {
180
+ $this->log( "Table {$table} does not exists", Logger::TYPE_ERROR );
181
+ return false;
182
+ }
183
+ return true;
184
+ }
185
+
186
+ /**
187
+ * Get the install sub directory if WP is installed in sub directory
188
+ * @return string
189
+ */
190
+ protected function getSubDir() {
191
+ $home = get_option( 'home' );
192
+ $siteurl = get_option( 'siteurl' );
193
+
194
+ if( empty( $home ) || empty( $siteurl ) ) {
195
+ return '/';
196
+ }
197
+
198
+ $dir = str_replace( $home, '', $siteurl );
199
+ return '/' . str_replace( '/', '', $dir ) . '/';
200
+ }
201
+
202
+ /**
203
+ * Replace "siteurl" and "home"
204
+ * @return bool
205
+ */
206
+ protected function step1() {
207
+ $this->log( "Preparing Data Step1: Updating siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_INFO );
208
+
209
+ // Skip - Table does not exist
210
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
211
+ return true;
212
+ }
213
+ // Skip - Table is not selected or updated
214
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
215
+ $this->log( "Preparing Data Step1: Skipping" );
216
+ return true;
217
+ }
218
+
219
+ // Installed in sub-directory
220
+ if( $this->isSubDir() ) {
221
+ $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . rtrim( $this->homeUrl, "/" ) . $this->getSubDir() . $this->options->cloneDirectoryName );
222
+ // Replace URLs
223
+ $result = $this->db->query(
224
+ $this->db->prepare(
225
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", rtrim( $this->homeUrl, "/" ) . $this->getSubDir() . $this->options->cloneDirectoryName
226
+ )
227
+ );
228
+ } else {
229
+ $this->log( "Preparing Data Step1: Updating siteurl and homeurl to " . rtrim( $this->homeUrl, "/" ) . '/' . $this->options->cloneDirectoryName );
230
+ // Replace URLs
231
+ $result = $this->db->query(
232
+ $this->db->prepare(
233
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'siteurl' or option_name='home'", $this->homeUrl . '/' . $this->options->cloneDirectoryName
234
+ )
235
+ );
236
+ }
237
+
238
+
239
+ // All good
240
+ if( $result ) {
241
+ return true;
242
+ }
243
+
244
+ $this->log( "Preparing Data Step1: Failed to update siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
245
+ return false;
246
+ }
247
+
248
+ /**
249
+ * Update "wpstg_is_staging_site"
250
+ * @return bool
251
+ */
252
+ protected function step2() {
253
+
254
+ $this->log( "Preparing Data Step2: Updating row wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}" );
255
+
256
+ // Skip - Table does not exist
257
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
258
+ return true;
259
+ }
260
+ // Skip - Table is not selected or updated
261
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
262
+ $this->log( "Preparing Data Step2: Skipping" );
263
+ return true;
264
+ }
265
+
266
+ $result = $this->db->query(
267
+ $this->db->prepare(
268
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_is_staging_site'", "true"
269
+ )
270
+ );
271
+
272
+ // No errors but no option name such as wpstg_is_staging_site
273
+ if( '' === $this->db->last_error && 0 == $result ) {
274
+ $result = $this->db->query(
275
+ $this->db->prepare(
276
+ "INSERT INTO {$this->prefix}options (option_name,option_value) VALUES ('wpstg_is_staging_site',%s)", "true"
277
+ )
278
+ );
279
+ }
280
+
281
+ // All good
282
+ if( $result ) {
283
+ return true;
284
+ }
285
+
286
+ $this->log( "Preparing Data Step2: Failed to update wpstg_is_staging_site in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
287
+ return false;
288
+ }
289
+
290
+ /**
291
+ * Update rewrite_rules
292
+ * @return bool
293
+ */
294
+ protected function step3() {
295
+
296
+ $this->log( "Preparing Data Step3: Updating rewrite_rules in {$this->prefix}options {$this->db->last_error}" );
297
+
298
+ // Skip - Table does not exist
299
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
300
+ return true;
301
+ }
302
+
303
+ // Skip - Table is not selected or updated
304
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
305
+ $this->log( "Preparing Data Step3: Skipping" );
306
+ return true;
307
+ }
308
+
309
+ $result = $this->db->query(
310
+ $this->db->prepare(
311
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'rewrite_rules'", ' '
312
+ )
313
+ );
314
+
315
+ // All good
316
+ if( $result ) {
317
+ return true;
318
+ }
319
+
320
+ $this->log( "Preparing Data Step3: Failed to update rewrite_rules in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
321
+ return true;
322
+ }
323
+
324
+ /**
325
+ * Update Table Prefix in wp_usermeta and wp_options
326
+ * @return bool
327
+ */
328
+ protected function step4() {
329
+ $this->log( "Preparing Data Step4: Updating db prefix in {$this->prefix}usermeta. " );
330
+
331
+ // Skip - Table does not exist
332
+ if( false === $this->isTable( $this->prefix . 'usermeta' ) ) {
333
+ return true;
334
+ }
335
+
336
+ // Skip - Table is not selected or updated
337
+ if( !in_array( $this->prefix . 'usermeta', $this->tables ) ) {
338
+ $this->log( "Preparing Data Step4: Skipping" );
339
+ return true;
340
+ }
341
+
342
+ $update = $this->db->query(
343
+ $this->db->prepare(
344
+ "UPDATE {$this->prefix}usermeta SET meta_key = replace(meta_key, %s, %s) WHERE meta_key LIKE %s", $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
345
+ )
346
+ );
347
+
348
+ if( !$update ) {
349
+ $this->log( "Preparing Data Step4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}", Logger::TYPE_ERROR );
350
+ $this->returnException( "Data Crunching Step 4: Failed to update {$this->prefix}usermeta meta_key database table prefixes; {$this->db->last_error}" );
351
+ return false;
352
+ }
353
+
354
+ // if( false === $this->isTable( $this->prefix . 'options' ) ) {
355
+ // return true;
356
+ // }
357
+ // $this->log( "Updating db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
358
+ //
359
+ // // Filter the rows below. Do not update them!
360
+ // $filters = array(
361
+ // 'wp_mail_smtp',
362
+ // 'wp_mail_smtp_version',
363
+ // 'wp_mail_smtp_debug',
364
+ // );
365
+ //
366
+ // $filters = apply_filters('wpstg_filter_options_replace', $filters);
367
+ //
368
+ // $where = "";
369
+ // foreach($filters as $filter){
370
+ // $where .= " AND option_name <> '" . $filter . "'";
371
+ // }
372
+ //
373
+ // $updateOptions = $this->db->query(
374
+ // $this->db->prepare(
375
+ // "UPDATE IGNORE {$this->prefix}options SET option_name= replace(option_name, %s, %s) WHERE option_name LIKE %s" . $where, $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
376
+ // )
377
+ // );
378
+ //
379
+ // if( !$updateOptions ) {
380
+ // $this->log( "Preparing Data Step4: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_ERROR );
381
+ // $this->returnException( "Data Crunching Step 4: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
382
+ // return false;
383
+ // }
384
+
385
+ return true;
386
+ }
387
+
388
+ /**
389
+ * Update $table_prefix in wp-config.php
390
+ * @return bool
391
+ */
392
+ protected function step5() {
393
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
394
+
395
+ $this->log( "Preparing Data Step5: Updating table_prefix in {$path} to " . $this->prefix );
396
+ if( false === ($content = file_get_contents( $path )) ) {
397
+ $this->log( "Preparing Data Step5: Failed to update table_prefix in {$path}. Can't read contents", Logger::TYPE_ERROR );
398
+ return false;
399
+ }
400
+
401
+ // Replace table prefix
402
+ $content = str_replace( '$table_prefix', '$table_prefix = \'' . $this->prefix . '\';//', $content );
403
+
404
+ // Replace URLs
405
+ $content = str_replace( $this->homeUrl, $this->homeUrl . '/' . $this->options->cloneDirectoryName, $content );
406
+
407
+ if( false === @file_put_contents( $path, $content ) ) {
408
+ $this->log( "Preparing Data Step5: Failed to update $table_prefix in {$path} to " . $this->prefix . ". Can't save contents", Logger::TYPE_ERROR );
409
+ return false;
410
+ }
411
+
412
+ return true;
413
+ }
414
+
415
+ /**
416
+ * Reset index.php to original file
417
+ * This is needed if live site is located in subfolder
418
+ * Check first if main wordpress is used in subfolder and index.php in parent directory
419
+ * @see: https://codex.wordpress.org/Giving_WordPress_Its_Own_Directory
420
+ * @return bool
421
+ */
422
+ protected function step6() {
423
+
424
+ if( !$this->isSubDir() ) {
425
+ $this->debugLog( "Preparing Data Step6: WP installation is not in a subdirectory! All good, skipping this step" );
426
+ return true;
427
+ }
428
+
429
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/index.php";
430
+
431
+ if( false === ($content = file_get_contents( $path )) ) {
432
+ $this->log( "Preparing Data Step6: Failed to reset {$path} for sub directory; can't read contents", Logger::TYPE_ERROR );
433
+ return false;
434
+ }
435
+
436
+
437
+ if( !preg_match( "/(require(.*)wp-blog-header.php' \);)/", $content, $matches ) ) {
438
+ $this->log(
439
+ "Preparing Data Step6: Failed to reset index.php for sub directory; wp-blog-header.php is missing", Logger::TYPE_ERROR
440
+ );
441
+ return false;
442
+ }
443
+ $this->log( "Preparing Data: WP installation is in a subdirectory. Progressing..." );
444
+
445
+ $pattern = "/require(.*) dirname(.*) __FILE__ (.*) \. '(.*)wp-blog-header.php'(.*);/";
446
+
447
+ $replace = "require( dirname( __FILE__ ) . '/wp-blog-header.php' ); // " . $matches[0];
448
+ $replace.= " // Changed by WP-Staging";
449
+
450
+
451
+
452
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
453
+ $this->log( "Preparing Data: Failed to reset index.php for sub directory; replacement failed", Logger::TYPE_ERROR );
454
+ return false;
455
+ }
456
+
457
+ if( false === @file_put_contents( $path, $content ) ) {
458
+ $this->log( "Preparing Data: Failed to reset index.php for sub directory; can't save contents", Logger::TYPE_ERROR );
459
+ return false;
460
+ }
461
+ $this->Log( "Preparing Data: Finished Step 6 successfully" );
462
+ return true;
463
+ }
464
+
465
+ /**
466
+ * Update wpstg_rmpermalinks_executed
467
+ * @return bool
468
+ */
469
+ protected function step7() {
470
+
471
+ $this->log( "Preparing Data Step7: Updating wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}" );
472
+
473
+ // Skip - Table does not exist
474
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
475
+ return true;
476
+ }
477
+
478
+ // Skip - Table is not selected or updated
479
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
480
+ $this->log( "Preparing Data Step7: Skipping" );
481
+ return true;
482
+ }
483
+
484
+ $result = $this->db->query(
485
+ $this->db->prepare(
486
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'wpstg_rmpermalinks_executed'", ' '
487
+ )
488
+ );
489
+
490
+ // All good
491
+ if( $result ) {
492
+ $this->Log( "Preparing Data Step7: Finished Step 7 successfully" );
493
+ return true;
494
+ }
495
+
496
+ $this->log( "Failed to update wpstg_rmpermalinks_executed in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_WARNING );
497
+ return true;
498
+ }
499
+
500
+ /**
501
+ * Update permalink_structure
502
+ * @return bool
503
+ */
504
+ protected function step8() {
505
+
506
+ $this->log( "Preparing Data Step8: Updating permalink_structure in {$this->prefix}options {$this->db->last_error}" );
507
+
508
+ // Skip - Table does not exist
509
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
510
+ return true;
511
+ }
512
+
513
+ // Skip - Table is not selected or updated
514
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
515
+ $this->log( "Preparing Data Step8: Skipping" );
516
+ return true;
517
+ }
518
+
519
+ $result = $this->db->query(
520
+ $this->db->prepare(
521
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'permalink_structure'", ' '
522
+ )
523
+ );
524
+
525
+ // All good
526
+ if( $result ) {
527
+ $this->Log( "Preparing Data Step8: Finished Step 8 successfully" );
528
+ return true;
529
+ }
530
+
531
+ $this->log( "Failed to update permalink_structure in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
532
+ return true;
533
+ }
534
+
535
+ /**
536
+ * Update blog_public option to not allow staging site to be indexed by search engines
537
+ * @return bool
538
+ */
539
+ protected function step9() {
540
+
541
+ $this->log( "Preparing Data Step9: Set staging site to noindex" );
542
+
543
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
544
+ return true;
545
+ }
546
+
547
+ // Skip - Table is not selected or updated
548
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
549
+ $this->log( "Preparing Data Step9: Skipping" );
550
+ return true;
551
+ }
552
+
553
+ $result = $this->db->query(
554
+ $this->db->prepare(
555
+ "UPDATE {$this->prefix}options SET option_value = %s WHERE option_name = 'blog_public'", '0'
556
+ )
557
+ );
558
+
559
+ // All good
560
+ if( $result ) {
561
+ $this->Log( "Preparing Data Step9: Finished Step 9 successfully" );
562
+ return true;
563
+ }
564
+
565
+ $this->log( "Can not update staging site to noindex. Possible already done!", Logger::TYPE_WARNING );
566
+ return true;
567
+ }
568
+
569
+ /**
570
+ * Update WP_HOME in wp-config.php
571
+ * @return bool
572
+ */
573
+ protected function step10() {
574
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
575
+
576
+ $this->log( "Preparing Data Step10: Updating WP_HOME in wp-config.php to " . $this->getStagingSiteUrl() );
577
+
578
+ if( false === ($content = file_get_contents( $path )) ) {
579
+ $this->log( "Preparing Data Step10: Failed to update WP_HOME in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
580
+ return false;
581
+ }
582
+
583
+
584
+ // Get WP_HOME from wp-config.php
585
+ preg_match( "/define\s*\(\s*'WP_HOME'\s*,\s*(.*)\s*\);/", $content, $matches );
586
+
587
+ if( !empty( $matches[1] ) ) {
588
+ $matches[1];
589
+
590
+ $pattern = "/define\s*\(\s*'WP_HOME'\s*,\s*(.*)\s*\);/";
591
+
592
+ $replace = "define('WP_HOME','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
593
+ $replace.= " // Changed by WP-Staging";
594
+
595
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
596
+ $this->log( "Preparing Data: Failed to reset index.php for sub directory; replacement failed", Logger::TYPE_ERROR );
597
+ return false;
598
+ }
599
+ } else {
600
+ $this->log( "Preparing Data Step10: WP_HOME not defined in wp-config.php. Skipping this step." );
601
+ }
602
+
603
+ if( false === @file_put_contents( $path, $content ) ) {
604
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR );
605
+ return false;
606
+ }
607
+ $this->Log( "Preparing Data: Finished Step 11 successfully" );
608
+ return true;
609
+ }
610
+
611
+ /**
612
+ * Update WP_SITEURL in wp-config.php
613
+ * @return bool
614
+ */
615
+ protected function step11() {
616
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
617
+
618
+ $this->log( "Preparing Data Step11: Updating WP_SITEURL in wp-config.php to " . $this->getStagingSiteUrl() );
619
+
620
+ if( false === ($content = file_get_contents( $path )) ) {
621
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL in wp-config.php. Can't read wp-config.php", Logger::TYPE_ERROR );
622
+ return false;
623
+ }
624
+
625
+
626
+ // Get WP_SITEURL from wp-config.php
627
+ preg_match( "/define\s*\(\s*'WP_SITEURL'\s*,\s*(.*)\s*\);/", $content, $matches );
628
+
629
+ if( !empty( $matches[1] ) ) {
630
+ $matches[1];
631
+
632
+ $pattern = "/define\s*\(\s*'WP_SITEURL'\s*,\s*(.*)\s*\);/";
633
+
634
+ $replace = "define('WP_SITEURL','" . $this->getStagingSiteUrl() . "'); // " . $matches[1];
635
+ $replace.= " // Changed by WP-Staging";
636
+
637
+ if( null === ($content = preg_replace( array($pattern), $replace, $content )) ) {
638
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL", Logger::TYPE_ERROR );
639
+ return false;
640
+ }
641
+ } else {
642
+ $this->log( "Preparing Data Step11: WP_SITEURL not defined in wp-config.php. Skipping this step." );
643
+ }
644
+
645
+
646
+ if( false === @file_put_contents( $path, $content ) ) {
647
+ $this->log( "Preparing Data Step11: Failed to update WP_SITEURL. Can't save contents", Logger::TYPE_ERROR );
648
+ return false;
649
+ }
650
+ $this->Log( "Preparing Data: Finished Step 11 successfully" );
651
+ return true;
652
+ }
653
+
654
+ /**
655
+ * Update Table Prefix in wp_options
656
+ * @return bool
657
+ */
658
+ protected function step12() {
659
+ $this->log( "Preparing Data Step12: Updating db prefix in {$this->prefix}options. Error: {$this->db->last_error}" );
660
+
661
+ // Skip - Table does not exist
662
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
663
+ return true;
664
+ }
665
+
666
+ // Skip - Table is not selected or updated
667
+ if( !in_array( $this->prefix . 'options', $this->tables ) ) {
668
+ $this->log( "Preparing Data Step12: Skipping" );
669
+ return true;
670
+ }
671
+
672
+
673
+ $this->log( "Updating db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
674
+
675
+ // Filter the rows below. Do not update them!
676
+ $filters = array(
677
+ 'wp_mail_smtp',
678
+ 'wp_mail_smtp_version',
679
+ 'wp_mail_smtp_debug',
680
+ );
681
+
682
+ $filters = apply_filters( 'wpstg_filter_options_replace', $filters );
683
+
684
+ $where = "";
685
+ foreach ( $filters as $filter ) {
686
+ $where .= " AND option_name <> '" . $filter . "'";
687
+ }
688
+
689
+ $updateOptions = $this->db->query(
690
+ $this->db->prepare(
691
+ "UPDATE IGNORE {$this->prefix}options SET option_name= replace(option_name, %s, %s) WHERE option_name LIKE %s" . $where, $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
692
+ )
693
+ );
694
+
695
+ if( !$updateOptions ) {
696
+ $this->log( "Preparing Data Step12: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_ERROR );
697
+ $this->returnException( "Data Crunching Step 15: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
698
+ return false;
699
+ }
700
+
701
+ return true;
702
+ }
703
+
704
+ /**
705
+ * Return URL to staging site
706
+ * @return string
707
+ */
708
+ protected function getStagingSiteUrl() {
709
+ if( $this->isSubDir() ) {
710
+ return rtrim( $this->homeUrl, "/" ) . $this->getSubDir() . $this->options->cloneDirectoryName;
711
+ }
712
+
713
+ return rtrim( $this->homeUrl, "/" ) . '/' . $this->options->cloneDirectoryName;
714
+ }
715
+
716
+ /**
717
+ * Check if WP is installed in subdir
718
+ * @return boolean
719
+ */
720
+ protected function isSubDir() {
721
+ if( get_option( 'siteurl' ) !== get_option( 'home' ) ) {
722
+ return true;
723
+ }
724
+ return false;
725
+ }
726
+
727
+ }
apps/Backend/Modules/Jobs/Database.php CHANGED
@@ -1,282 +1,282 @@
1
- <?php
2
- namespace WPStaging\Backend\Modules\Jobs;
3
-
4
- // No Direct Access
5
- if (!defined("WPINC"))
6
- {
7
- die;
8
- }
9
-
10
- use WPStaging\WPStaging;
11
- use WPStaging\Utils\Strings;
12
-
13
- /**
14
- * Class Database
15
- * @package WPStaging\Backend\Modules\Jobs
16
- */
17
- class Database extends JobExecutable
18
- {
19
-
20
- /**
21
- * @var int
22
- */
23
- private $total = 0;
24
-
25
- /**
26
- * @var \WPDB
27
- */
28
- private $db;
29
-
30
- /**
31
- * Initialize
32
- */
33
- public function initialize()
34
- {
35
- // Variables
36
- $this->total = count($this->options->tables);
37
- $this->db = WPStaging::getInstance()->get("wpdb");
38
- $this->isFatalError();
39
-
40
- }
41
-
42
-
43
- /**
44
- * Return fatal error and stops here if subfolder already exists
45
- * and mainJob is not updating the clone
46
- * @return boolean
47
- */
48
- private function isFatalError(){
49
- $path = trailingslashit(get_home_path()) . $this->options->cloneDirectoryName;
50
- if (isset($this->options->mainJob) && $this->options->mainJob !== 'updating' && is_dir($path)){
51
- $this->returnException( " Can not continue! Change the name of the clone or delete existing folder. Then try again. Folder already exists: " . $path );
52
- }
53
- return false;
54
- }
55
-
56
- /**
57
- * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
58
- * @return void
59
- */
60
- protected function calculateTotalSteps()
61
- {
62
- $this->options->totalSteps = $this->total === 0 ? 1 : $this->total;
63
- }
64
-
65
- /**
66
- * Execute the Current Step
67
- * Returns false when over threshold limits are hit or when the job is done, true otherwise
68
- * @return bool
69
- */
70
- protected function execute()
71
- {
72
- // Over limits threshold
73
- if ($this->isOverThreshold())
74
- {
75
- // Prepare response and save current progress
76
- $this->prepareResponse(false, false);
77
- $this->saveOptions();
78
- return false;
79
- }
80
-
81
- // No more steps, finished
82
- if ($this->options->currentStep > $this->total || !isset($this->options->tables[$this->options->currentStep]))
83
- {
84
- $this->prepareResponse(true, false);
85
- return false;
86
- }
87
-
88
- // Table is excluded
89
- // if (in_array($this->options->tables[$this->options->currentStep]->name, $this->options->excludedTables))
90
- // {
91
- // $this->prepareResponse();
92
- // return true;
93
- // }
94
-
95
- // Copy table
96
- //if (!$this->copyTable($this->options->tables[$this->options->currentStep]->name))
97
- if (!$this->copyTable($this->options->tables[$this->options->currentStep]))
98
- {
99
- // Prepare Response
100
- $this->prepareResponse(false, false);
101
-
102
- // Not finished
103
- return true;
104
- }
105
-
106
- // Prepare Response
107
- $this->prepareResponse();
108
-
109
- // Not finished
110
- return true;
111
- }
112
-
113
- /**
114
- * Get new prefix for the staging site
115
- * @return string
116
- */
117
- private function getStagingPrefix(){
118
- $stagingPrefix = $this->options->prefix;
119
- // Make sure prefix of staging site is NEVER identical to prefix of live site!
120
- if ( $stagingPrefix == $this->db->prefix ){
121
- wp_die('Fatal error 7: The new database table prefix '. $stagingPrefix .' would be identical to the table prefix of the live site. Please open a support ticket to support@wp-staging.com');
122
- }
123
- return $stagingPrefix;
124
- }
125
-
126
- /**
127
- * No worries, SQL queries don't eat from PHP execution time!
128
- * @param string $tableName
129
- * @return bool
130
- */
131
- private function copyTable($tableName)
132
- {
133
-
134
- $strings = new Strings();
135
- $tableName = is_object($tableName) ? $tableName->name : $tableName;
136
- $newTableName = $this->getStagingPrefix() . $strings->str_replace_first($this->db->prefix, null, $tableName);
137
-
138
- // Drop table if necessary
139
- $this->dropTable($newTableName);
140
-
141
- // Save current job
142
- $this->setJob($newTableName);
143
-
144
- // Beginning of the job
145
- if (!$this->startJob($newTableName, $tableName))
146
- {
147
- return true;
148
- }
149
-
150
- // Copy data
151
- $this->copyData($newTableName, $tableName);
152
-
153
- // Finis the step
154
- return $this->finishStep();
155
- }
156
-
157
- /**
158
- * Copy data from old table to new table
159
- * @param string $new
160
- * @param string $old
161
- */
162
- private function copyData($new, $old)
163
- {
164
- $rows = $this->options->job->start+$this->settings->queryLimit;
165
- $this->log(
166
- "DB Copy: {$old} as {$new} from {$this->options->job->start} to {$rows} records"
167
- );
168
-
169
- $limitation = '';
170
-
171
- if (0 < (int) $this->settings->queryLimit)
172
- {
173
- $limitation = " LIMIT {$this->settings->queryLimit} OFFSET {$this->options->job->start}";
174
- }
175
-
176
- $this->db->query(
177
- "INSERT INTO {$new} SELECT * FROM {$old} {$limitation}"
178
- );
179
-
180
- // Set new offset
181
- $this->options->job->start += $this->settings->queryLimit;
182
- }
183
-
184
- /**
185
- * Set the job
186
- * @param string $table
187
- */
188
- private function setJob($table)
189
- {
190
- if (isset($this->options->job->current))
191
- {
192
- return;
193
- }
194
-
195
- $this->options->job->current = $table;
196
- $this->options->job->start = 0;
197
- }
198
-
199
- /**
200
- * Start Job
201
- * @param string $new
202
- * @param string $old
203
- * @return bool
204
- */
205
- private function startJob($new, $old)
206
- {
207
- if (0 != $this->options->job->start)
208
- {
209
- return true;
210
- }
211
-
212
- $this->log("DB Copy: Creating table {$new}");
213
-
214
- $this->db->query("CREATE TABLE {$new} LIKE {$old}");
215
-
216
- $this->options->job->total = (int) $this->db->get_var("SELECT COUNT(1) FROM {$old}");
217
-
218
- if (0 == $this->options->job->total)
219
- {
220
- $this->finishStep();
221
- return false;
222
- }
223
-
224
- return true;
225
- }
226
-
227
- /**
228
- * Finish the step
229
- */
230
- private function finishStep()
231
- {
232
- // This job is not finished yet
233
- if ($this->options->job->total > $this->options->job->start)
234
- {
235
- return false;
236
- }
237
-
238
- // Add it to cloned tables listing
239
- $this->options->clonedTables[] = $this->options->tables[$this->options->currentStep];
240
-
241
- // Reset job
242
- $this->options->job = new \stdClass();
243
-
244
- return true;
245
- }
246
-
247
- /**
248
- * Drop table if necessary
249
- * @param string $new
250
- */
251
- private function dropTable($new)
252
- {
253
- $old = $this->db->get_var($this->db->prepare("SHOW TABLES LIKE %s", $new));
254
-
255
- if (!$this->shouldDropTable($new, $old))
256
- {
257
- return;
258
- }
259
-
260
- $this->log("DB Copy: {$new} already exists, dropping it first");
261
- $this->db->query("DROP TABLE {$new}");
262
- }
263
-
264
- /**
265
- * Check if table needs to be dropped
266
- * @param string $new
267
- * @param string $old
268
- * @return bool
269
- */
270
- private function shouldDropTable($new, $old)
271
- {
272
- return (
273
- $old === $new &&
274
- (
275
- !isset($this->options->job->current) ||
276
- !isset($this->options->job->start) ||
277
- 0 == $this->options->job->start
278
- )
279
- );
280
- }
281
-
282
  }
1
+ <?php
2
+ namespace WPStaging\Backend\Modules\Jobs;
3
+
4
+ // No Direct Access
5
+ if (!defined("WPINC"))
6
+ {
7
+ die;
8
+ }
9
+
10
+ use WPStaging\WPStaging;
11
+ use WPStaging\Utils\Strings;
12
+
13
+ /**
14
+ * Class Database
15
+ * @package WPStaging\Backend\Modules\Jobs
16
+ */
17
+ class Database extends JobExecutable
18
+ {
19
+
20
+ /**
21
+ * @var int
22
+ */
23
+ private $total = 0;
24
+
25
+ /**
26
+ * @var \WPDB
27
+ */
28
+ private $db;
29
+
30
+ /**
31
+ * Initialize
32
+ */
33
+ public function initialize()
34
+ {
35
+ // Variables
36
+ $this->total = count($this->options->tables);
37
+ $this->db = WPStaging::getInstance()->get("wpdb");
38
+ $this->isFatalError();
39
+
40
+ }
41
+
42
+
43
+ /**
44
+ * Return fatal error and stops here if subfolder already exists
45
+ * and mainJob is not updating the clone
46
+ * @return boolean
47
+ */
48
+ private function isFatalError(){
49
+ $path = trailingslashit(get_home_path()) . $this->options->cloneDirectoryName;
50
+ if (isset($this->options->mainJob) && $this->options->mainJob !== 'updating' && is_dir($path)){
51
+ $this->returnException( " Can not continue! Change the name of the clone or delete existing folder. Then try again. Folder already exists: " . $path );
52
+ }
53
+ return false;
54
+ }
55
+
56
+ /**
57
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
58
+ * @return void
59
+ */
60
+ protected function calculateTotalSteps()
61
+ {
62
+ $this->options->totalSteps = $this->total === 0 ? 1 : $this->total;
63
+ }
64
+
65
+ /**
66
+ * Execute the Current Step
67
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
68
+ * @return bool
69
+ */
70
+ protected function execute()
71
+ {
72
+ // Over limits threshold
73
+ if ($this->isOverThreshold())
74
+ {
75
+ // Prepare response and save current progress
76
+ $this->prepareResponse(false, false);
77
+ $this->saveOptions();
78
+ return false;
79
+ }
80
+
81
+ // No more steps, finished
82
+ if ($this->options->currentStep > $this->total || !isset($this->options->tables[$this->options->currentStep]))
83
+ {
84
+ $this->prepareResponse(true, false);
85
+ return false;
86
+ }
87
+
88
+ // Table is excluded
89
+ // if (in_array($this->options->tables[$this->options->currentStep]->name, $this->options->excludedTables))
90
+ // {
91
+ // $this->prepareResponse();
92
+ // return true;
93
+ // }
94
+
95
+ // Copy table
96
+ //if (!$this->copyTable($this->options->tables[$this->options->currentStep]->name))
97
+ if (!$this->copyTable($this->options->tables[$this->options->currentStep]))
98
+ {
99
+ // Prepare Response
100
+ $this->prepareResponse(false, false);
101
+
102
+ // Not finished
103
+ return true;
104
+ }
105
+
106
+ // Prepare Response
107
+ $this->prepareResponse();
108
+
109
+ // Not finished
110
+ return true;
111
+ }
112
+
113
+ /**
114
+ * Get new prefix for the staging site
115
+ * @return string
116
+ */
117
+ private function getStagingPrefix(){
118
+ $stagingPrefix = $this->options->prefix;
119
+ // Make sure prefix of staging site is NEVER identical to prefix of live site!
120
+ if ( $stagingPrefix == $this->db->prefix ){
121
+ wp_die('Fatal error 7: The new database table prefix '. $stagingPrefix .' would be identical to the table prefix of the live site. Please open a support ticket to support@wp-staging.com');
122
+ }
123
+ return $stagingPrefix;
124
+ }
125
+
126
+ /**
127
+ * No worries, SQL queries don't eat from PHP execution time!
128
+ * @param string $tableName
129
+ * @return bool
130
+ */
131
+ private function copyTable($tableName)
132
+ {
133
+
134
+ $strings = new Strings();
135
+ $tableName = is_object($tableName) ? $tableName->name : $tableName;
136
+ $newTableName = $this->getStagingPrefix() . $strings->str_replace_first($this->db->prefix, null, $tableName);
137
+
138
+ // Drop table if necessary
139
+ $this->dropTable($newTableName);
140
+
141
+ // Save current job
142
+ $this->setJob($newTableName);
143
+
144
+ // Beginning of the job
145
+ if (!$this->startJob($newTableName, $tableName))
146
+ {
147
+ return true;
148
+ }
149
+
150
+ // Copy data
151
+ $this->copyData($newTableName, $tableName);
152
+
153
+ // Finis the step
154
+ return $this->finishStep();
155
+ }
156
+
157
+ /**
158
+ * Copy data from old table to new table
159
+ * @param string $new
160
+ * @param string $old
161
+ */
162
+ private function copyData($new, $old)
163
+ {
164
+ $rows = $this->options->job->start+$this->settings->queryLimit;
165
+ $this->log(
166
+ "DB Copy: {$old} as {$new} from {$this->options->job->start} to {$rows} records"
167
+ );
168
+
169
+ $limitation = '';
170
+
171
+ if (0 < (int) $this->settings->queryLimit)
172
+ {
173
+ $limitation = " LIMIT {$this->settings->queryLimit} OFFSET {$this->options->job->start}";
174
+ }
175
+
176
+ $this->db->query(
177
+ "INSERT INTO {$new} SELECT * FROM {$old} {$limitation}"
178
+ );
179
+
180
+ // Set new offset
181
+ $this->options->job->start += $this->settings->queryLimit;
182
+ }
183
+
184
+ /**
185
+ * Set the job
186
+ * @param string $table
187
+ */
188
+ private function setJob($table)
189
+ {
190
+ if (isset($this->options->job->current))
191
+ {
192
+ return;
193
+ }
194
+
195
+ $this->options->job->current = $table;
196
+ $this->options->job->start = 0;
197
+ }
198
+
199
+ /**
200
+ * Start Job
201
+ * @param string $new
202
+ * @param string $old
203
+ * @return bool
204
+ */
205
+ private function startJob($new, $old)
206
+ {
207
+ if (0 != $this->options->job->start)
208
+ {
209
+ return true;
210
+ }
211
+
212
+ $this->log("DB Copy: Creating table {$new}");
213
+
214
+ $this->db->query("CREATE TABLE {$new} LIKE {$old}");
215
+
216
+ $this->options->job->total = (int) $this->db->get_var("SELECT COUNT(1) FROM {$old}");
217
+
218
+ if (0 == $this->options->job->total)
219
+ {
220
+ $this->finishStep();
221
+ return false;
222
+ }
223
+
224
+ return true;
225
+ }
226
+
227
+ /**
228
+ * Finish the step
229
+ */
230
+ private function finishStep()
231
+ {
232
+ // This job is not finished yet
233
+ if ($this->options->job->total > $this->options->job->start)
234
+ {
235
+ return false;
236
+ }
237
+
238
+ // Add it to cloned tables listing
239
+ $this->options->clonedTables[] = $this->options->tables[$this->options->currentStep];
240
+
241
+ // Reset job
242
+ $this->options->job = new \stdClass();
243
+
244
+ return true;
245
+ }
246
+
247
+ /**
248
+ * Drop table if necessary
249
+ * @param string $new
250
+ */
251
+ private function dropTable($new)
252
+ {
253
+ $old = $this->db->get_var($this->db->prepare("SHOW TABLES LIKE %s", $new));
254
+
255
+ if (!$this->shouldDropTable($new, $old))
256
+ {
257
+ return;
258
+ }
259
+
260
+ $this->log("DB Copy: {$new} already exists, dropping it first");
261
+ $this->db->query("DROP TABLE {$new}");
262
+ }
263
+
264
+ /**
265
+ * Check if table needs to be dropped
266
+ * @param string $new
267
+ * @param string $old
268
+ * @return bool
269
+ */
270
+ private function shouldDropTable($new, $old)
271
+ {
272
+ return (
273
+ $old === $new &&
274
+ (
275
+ !isset($this->options->job->current) ||
276
+ !isset($this->options->job->start) ||
277
+ 0 == $this->options->job->start
278
+ )
279
+ );
280
+ }
281
+
282
  }
apps/Backend/Modules/Jobs/Delete.php CHANGED
@@ -1,429 +1,429 @@
1
- <?php
2
-
3
- namespace WPStaging\Backend\Modules\Jobs;
4
-
5
- use WPStaging\Backend\Modules\Jobs\Exceptions\CloneNotFoundException;
6
- use WPStaging\Utils\Directories;
7
- use WPStaging\Utils\Logger;
8
- use WPStaging\WPStaging;
9
-
10
- /**
11
- * Class Delete
12
- * @package WPStaging\Backend\Modules\Jobs
13
- */
14
- class Delete extends Job {
15
-
16
- /**
17
- * @var false
18
- */
19
- private $clone = false;
20
-
21
- /**
22
- * @var null|object
23
- */
24
- private $tables = null;
25
-
26
- /**
27
- * @var object|null
28
- */
29
- private $job = null;
30
-
31
- /**
32
- * @var bool
33
- */
34
- private $forceDeleteDirectories = false;
35
-
36
- /**
37
- *
38
- * @var object
39
- */
40
- public $wpdb;
41
-
42
- public function __construct() {
43
- parent::__construct();
44
- $this->wpdb = WPStaging::getInstance()->get("wpdb");
45
- }
46
-
47
- /**
48
- * Sets Clone and Table Records
49
- * @param null|array $clone
50
- */
51
- public function setData($clone = null) {
52
- if (!is_array($clone)) {
53
- $this->getCloneRecords();
54
- } else {
55
- $this->clone = (object) $clone;
56
- $this->forceDeleteDirectories = true;
57
- }
58
-
59
- $this->getTableRecords();
60
- }
61
-
62
- /**
63
- * Get clone
64
- * @param null|string $name
65
- * @throws CloneNotFoundException
66
- */
67
- private function getCloneRecords($name = null) {
68
- if (null === $name && !isset($_POST["clone"])) {
69
- $this->log("Clone name is not set", Logger::TYPE_FATAL);
70
- throw new CloneNotFoundException();
71
- }
72
-
73
- if (null === $name) {
74
- $name = $_POST["clone"];
75
- }
76
-
77
- $clones = get_option("wpstg_existing_clones_beta", array());
78
-
79
- if (empty($clones) || !isset($clones[$name])) {
80
- $this->log("Couldn't find clone name {$name} or no existing clone", Logger::TYPE_FATAL);
81
- throw new CloneNotFoundException();
82
- }
83
-
84
- $this->clone = $clones[$name];
85
- $this->clone["name"] = $name;
86
-
87
- $this->clone = (object) $this->clone;
88
-
89
- unset($clones);
90
- }
91
-
92
- /**
93
- * Get Tables
94
- */
95
- private function getTableRecords() {
96
- //$wpdb = WPStaging::getInstance()->get("wpdb");
97
- $this->wpdb = WPStaging::getInstance()->get("wpdb");
98
-
99
- $stagingPrefix = $this->getStagingPrefix();
100
-
101
- $tables = $this->wpdb->get_results("SHOW TABLE STATUS LIKE '{$stagingPrefix}%'");
102
-
103
- $this->tables = array();
104
-
105
- foreach ($tables as $table) {
106
- $this->tables[] = array(
107
- "name" => $table->Name,
108
- "size" => $this->formatSize(($table->Data_length + $table->Index_length))
109
- );
110
- }
111
-
112
- $this->tables = json_decode(json_encode($this->tables));
113
- }
114
-
115
- /**
116
- * Check and return prefix of the staging site
117
- */
118
- public function getStagingPrefix() {
119
- // Prefix not defined! Happens if staging site has ben generated with older version of wpstg
120
- // Try to get staging prefix from wp-config.php of staging site
121
- if (empty($this->clone->prefix)) {
122
- // Throw error
123
- $path = ABSPATH . $this->clone->directoryName . "/wp-config.php";
124
- if (false === ($content = @file_get_contents($path))) {
125
- $this->log("Can not open {$path}. Can't read contents", Logger::TYPE_ERROR);
126
- // Create a random prefix which hopefully never exists.
127
- $this->clone->prefix = rand(7, 15) . '_';
128
- } else {
129
-
130
- // Get prefix from wp-config.php
131
- //preg_match_all("/table_prefix\s*=\s*'(\w*)';/", $content, $matches);
132
- preg_match("/table_prefix\s*=\s*'(\w*)';/", $content, $matches);
133
- //wp_die(var_dump($matches));
134
-
135
- if (!empty($matches[1])) {
136
- $this->clone->prefix = $matches[1];
137
- } else {
138
- $this->log("Fatal Error: Can not delete staging site. Can not find Prefix. '{$matches[1]}'. Stopping for security reasons. Creating a new staging site will likely resolve this the next time. Contact support@wp-staging.com");
139
- // Create a random prefix which hopefully never exists.
140
- return $this->clone->prefix = rand(7, 15) . '_';
141
- }
142
- }
143
- }
144
-
145
- // Check if staging prefix is the same as the live prefix
146
- if ($this->wpdb->prefix == $this->clone->prefix) {
147
- $this->log("Fatal Error: Can not delete staging site. Prefix. '{$this->clone->prefix}' is used for the live site. Creating a new staging site will likely resolve this the next time. Stopping for security reasons. Contact support@wp-staging.com");
148
- wp_die("Fatal Error: Can not delete staging site. Prefix. '{$this->clone->prefix}' is used for the live site. Creating a new staging site will likely resolve this the next time. Stopping for security reasons. Contact support@wp-staging.com");
149
- }
150
-
151
- // Else
152
- return $this->clone->prefix;
153
- }
154
-
155
- /**
156
- * Format bytes into human readable form
157
- * @param int $bytes
158
- * @param int $precision
159
- * @return string
160
- */
161
- public function formatSize($bytes, $precision = 2) {
162
- if ((int) $bytes < 1) {
163
- return '';
164
- }
165
-
166
- $units = array('B', "KB", "MB", "GB", "TB");
167
-
168
- $bytes = (int) $bytes;
169
- $base = log($bytes) / log(1000); // 1024 would be for MiB KiB etc
170
- $pow = pow(1000, $base - floor($base)); // Same rule for 1000
171
-
172
- return round($pow, $precision) . ' ' . $units[(int) floor($base)];
173
- }
174
-
175
- /**
176
- * @return false
177
- */
178
- public function getClone() {
179
- return $this->clone;
180
- }
181
-
182
- /**
183
- * @return null|object
184
- */
185
- public function getTables() {
186
- return $this->tables;
187
- }
188
-
189
- /**
190
- * Start Module
191
- * @param null|array $clone
192
- * @return bool
193
- */
194
- public function start($clone = null) {
195
- // Set data
196
- $this->setData($clone);
197
-
198
- // Get the job first
199
- $this->getJob();
200
-
201
- $method = "delete" . ucwords($this->job->current);
202
- return $this->{$method}();
203
- }
204
-
205
- /**
206
- * Get job data
207
- */
208
- private function getJob() {
209
- $this->job = $this->cache->get("delete_job_{$this->clone->name}");
210
-
211
-
212
- if (null !== $this->job) {
213
- return;
214
- }
215
-
216
- // Generate JOB
217
- $this->job = (object) array(
218
- "current" => "tables",
219
- "nextDirectoryToDelete" => $this->clone->path,
220
- "name" => $this->clone->name
221
- );
222
-
223
- $this->cache->save("delete_job_{$this->clone->name}", $this->job);
224
- }
225
-
226
- /**
227
- * @return bool
228
- */
229
- private function updateJob() {
230
- $this->job->nextDirectoryToDelete = trim($this->job->nextDirectoryToDelete);
231
- return $this->cache->save("delete_job_{$this->clone->name}", $this->job);
232
- }
233
-
234
- /**
235
- * @return array
236
- */
237
- private function getTablesToRemove() {
238
- $tables = $this->getTableNames();
239
-
240
- if (!isset($_POST["excludedTables"]) || !is_array($_POST["excludedTables"]) || empty($_POST["excludedTables"])) {
241
- return $tables;
242
- }
243
-
244
- return array_diff($tables, $_POST["excludedTables"]);
245
- }
246
-
247
- /**
248
- * @return array
249
- */
250
- private function getTableNames() {
251
- return (!is_array($this->tables)) ? array() : array_map(function($value) {
252
- return ($value->name);
253
- }, $this->tables);
254
- }
255
-
256
- /**
257
- * Delete Tables
258
- */
259
- public function deleteTables() {
260
- if ($this->isOverThreshold()) {
261
- return;
262
- }
263
-
264
- //$wpdb = WPStaging::getInstance()->get("wpdb");
265
-
266
- foreach ($this->getTablesToRemove() as $table) {
267
- // PROTECTION: Never delete any table that beginns with wp prefix of live site
268
- if ($this->startsWith($table, $this->wpdb->prefix)) {
269
- $this->log("Fatal Error: Trying to delete table {$table} of main WP installation!", Logger::TYPE_CRITICAL);
270
- return false;
271
- } else {
272
- $this->wpdb->query("DROP TABLE {$table}");
273
- }
274
- }
275
-
276
- // Move on to the next
277
- $this->job->current = "directory";
278
- $this->updateJob();
279
- }
280
-
281
- /**
282
- * Check if a strings start with a specific string
283
- * @param string $haystack
284
- * @param string $needle
285
- * @return bool
286
- */
287
- protected function startsWith($haystack, $needle) {
288
- $length = strlen($needle);
289
- return (substr($haystack, 0, $length) === $needle);
290
- }
291
-
292
- /**
293
- * Delete complete directory including all files and subfolders
294
- *
295
- * @throws InvalidArgumentException
296
- */
297
- public function deleteDirectory() {
298
- if ($this->isFatalError()) {
299
- $this->returnException('Can not delete directory: ' . $this->clone->path . '. This seems to be the root directory. Please contact support@wp-staging.com');
300
- throw new \Exception('Can not delete directory: ' . $this->clone->path . ' This seems to be the root directory. Please contact support@wp-staging.com');
301
- }
302
- // Finished or path does not exist
303
- if (
304
- empty($this->clone->path) ||
305
- $this->clone->path == get_home_path() ||
306
- !is_dir($this->clone->path)) {
307
-
308
- $this->job->current = "finish";
309
- $this->updateJob();
310
- return $this->returnFinish();
311
- }
312
-
313
- $this->log("Delete staging site: " . $this->clone->path, Logger::TYPE_INFO);
314
-
315
- // Just to make sure the root dir is never deleted!
316
- if ($this->clone->path == get_home_path()) {
317
- $this->log("Fatal Error 8: Trying to delete root of WP installation!", Logger::TYPE_CRITICAL);
318
- $this->returnException('Fatal Error 8: Trying to delete root of WP installation!');
319
- }
320
-
321
- // Check if threshold is reached
322
- if ($this->isOverThreshold()) {
323
- //$this->returnException('Maximum PHP execution time exceeded. Run again and repeat the deletion process until it is sucessfully finished.');
324
- return;
325
- }
326
-
327
- $di = new \RecursiveDirectoryIterator($this->clone->path, \FilesystemIterator::SKIP_DOTS);
328
- $ri = new \RecursiveIteratorIterator($di, \RecursiveIteratorIterator::CHILD_FIRST);
329
- foreach ($ri as $file) {
330
- //$file->isDir() ? @rmdir($file) : unlink($file);
331
- $this->deleteFile($file);
332
- if ($this->isOverThreshold()) {
333
- //$this->returnException('Maximum PHP execution time exceeded. Run again and repeat the deletion process until it is sucessfully finished.');
334
- return;
335
- }
336
- }
337
-
338
- if (@rmdir($this->clone->path)) {
339
- return $this->returnFinish();
340
- }
341
- return;
342
- }
343
-
344
- /**
345
- * Delete file
346
- * @param object iterator $file
347
- */
348
- private function deleteFile($file) {
349
- if ($file->isDir()) {
350
- if (!@rmdir($file)) {
351
- $this->returnException('Permission Error: Can not delete folder ' . $file);
352
- }
353
- } else {
354
- if (!unlink($file)) {
355
- $this->returnException('Permission Error: Can not delete file ' . $file);
356
- }
357
- }
358
- }
359
-
360
- /**
361
- * @return bool
362
- */
363
- public function isDirectoryDeletingFinished() {
364
- return (
365
- (false === $this->forceDeleteDirectories && (!isset($_POST["deleteDir"]) || '1' !== $_POST["deleteDir"])) ||
366
- !is_dir($this->clone->path) || ABSPATH === $this->job->nextDirectoryToDelete
367
- );
368
- }
369
-
370
-
371
-
372
- /**
373
- *
374
- * @return boolean
375
- */
376
- public function isFatalError(){
377
- $homePath = rtrim(get_home_path(), "/");
378
- if (rtrim($this->clone->path,"/") == $homePath){
379
- return true;
380
- }
381
- return false;
382
- }
383
-
384
- /**
385
- * Finish / Update Existing Clones
386
- */
387
- public function deleteFinish() {
388
- $existingClones = get_option("wpstg_existing_clones_beta", array());
389
-
390
- // Check if clones still exist
391
- $this->log("Verifying existing clones...");
392
- foreach ($existingClones as $name => $clone) {
393
- if (!is_dir($clone["path"])) {
394
- unset($existingClones[$name]);
395
- }
396
- }
397
- $this->log("Existing clones verified!");
398
-
399
- if (false === update_option("wpstg_existing_clones_beta", $existingClones)) {
400
- $this->log("Failed to save {$this->options->clone}'s clone job data to database'");
401
- }
402
-
403
- // Delete cached file
404
- $this->cache->delete("delete_job_{$this->clone->name}");
405
- $this->cache->delete("delete_directories_{$this->clone->name}");
406
-
407
- //return true;
408
- $response = array('delete' => 'finished');
409
- wp_die(json_encode($response));
410
- }
411
-
412
- /**
413
- * Get json response
414
- * return json
415
- */
416
- private function returnFinish($message = '') {
417
-
418
- $this->deleteFinish();
419
-
420
- wp_die(json_encode(array(
421
- 'job' => 'delete',
422
- 'status' => true,
423
- 'message' => $message,
424
- 'error' => false,
425
- 'delete' => 'finished'
426
- )));
427
- }
428
-
429
- }
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs;
4
+
5
+ use WPStaging\Backend\Modules\Jobs\Exceptions\CloneNotFoundException;
6
+ use WPStaging\Utils\Directories;
7
+ use WPStaging\Utils\Logger;
8
+ use WPStaging\WPStaging;
9
+
10
+ /**
11
+ * Class Delete
12
+ * @package WPStaging\Backend\Modules\Jobs
13
+ */
14
+ class Delete extends Job {
15
+
16
+ /**
17
+ * @var false
18
+ */
19
+ private $clone = false;
20
+
21
+ /**
22
+ * @var null|object
23
+ */
24
+ private $tables = null;
25
+
26
+ /**
27
+ * @var object|null
28
+ */
29
+ private $job = null;
30
+
31
+ /**
32
+ * @var bool
33
+ */
34
+ private $forceDeleteDirectories = false;
35
+
36
+ /**
37
+ *
38
+ * @var object
39
+ */
40
+ public $wpdb;
41
+
42
+ public function __construct() {
43
+ parent::__construct();
44
+ $this->wpdb = WPStaging::getInstance()->get("wpdb");
45
+ }
46
+
47
+ /**
48
+ * Sets Clone and Table Records
49
+ * @param null|array $clone
50
+ */
51
+ public function setData($clone = null) {
52
+ if (!is_array($clone)) {
53
+ $this->getCloneRecords();
54
+ } else {
55
+ $this->clone = (object) $clone;
56
+ $this->forceDeleteDirectories = true;
57
+ }
58
+
59
+ $this->getTableRecords();
60
+ }
61
+
62
+ /**
63
+ * Get clone
64
+ * @param null|string $name
65
+ * @throws CloneNotFoundException
66
+ */
67
+ private function getCloneRecords($name = null) {
68
+ if (null === $name && !isset($_POST["clone"])) {
69
+ $this->log("Clone name is not set", Logger::TYPE_FATAL);
70
+ throw new CloneNotFoundException();
71
+ }
72
+
73
+ if (null === $name) {
74
+ $name = $_POST["clone"];
75
+ }
76
+
77
+ $clones = get_option("wpstg_existing_clones_beta", array());
78
+
79
+ if (empty($clones) || !isset($clones[$name])) {
80
+ $this->log("Couldn't find clone name {$name} or no existing clone", Logger::TYPE_FATAL);
81
+ throw new CloneNotFoundException();
82
+ }
83
+
84
+ $this->clone = $clones[$name];
85
+ $this->clone["name"] = $name;
86
+
87
+ $this->clone = (object) $this->clone;
88
+
89
+ unset($clones);
90
+ }
91
+
92
+ /**
93
+ * Get Tables
94
+ */
95
+ private function getTableRecords() {
96
+ //$wpdb = WPStaging::getInstance()->get("wpdb");
97
+ $this->wpdb = WPStaging::getInstance()->get("wpdb");
98
+
99
+ $stagingPrefix = $this->getStagingPrefix();
100
+
101
+ $tables = $this->wpdb->get_results("SHOW TABLE STATUS LIKE '{$stagingPrefix}%'");
102
+
103
+ $this->tables = array();
104
+
105
+ foreach ($tables as $table) {
106
+ $this->tables[] = array(
107
+ "name" => $table->Name,
108
+ "size" => $this->formatSize(($table->Data_length + $table->Index_length))
109
+ );
110
+ }
111
+
112
+ $this->tables = json_decode(json_encode($this->tables));
113
+ }
114
+
115
+ /**
116
+ * Check and return prefix of the staging site
117
+ */
118
+ public function getStagingPrefix() {
119
+ // Prefix not defined! Happens if staging site has ben generated with older version of wpstg
120
+ // Try to get staging prefix from wp-config.php of staging site
121
+ if (empty($this->clone->prefix)) {
122
+ // Throw error
123
+ $path = ABSPATH . $this->clone->directoryName . "/wp-config.php";
124
+ if (false === ($content = @file_get_contents($path))) {
125
+ $this->log("Can not open {$path}. Can't read contents", Logger::TYPE_ERROR);
126
+ // Create a random prefix which hopefully never exists.
127
+ $this->clone->prefix = rand(7, 15) . '_';
128
+ } else {
129
+
130
+ // Get prefix from wp-config.php
131
+ //preg_match_all("/table_prefix\s*=\s*'(\w*)';/", $content, $matches);
132
+ preg_match("/table_prefix\s*=\s*'(\w*)';/", $content, $matches);
133
+ //wp_die(var_dump($matches));
134
+
135
+ if (!empty($matches[1])) {
136
+ $this->clone->prefix = $matches[1];
137
+ } else {
138
+ $this->log("Fatal Error: Can not delete staging site. Can not find Prefix. '{$matches[1]}'. Stopping for security reasons. Creating a new staging site will likely resolve this the next time. Contact support@wp-staging.com");
139
+ // Create a random prefix which hopefully never exists.
140
+ return $this->clone->prefix = rand(7, 15) . '_';
141
+ }
142
+ }
143
+ }
144
+
145
+ // Check if staging prefix is the same as the live prefix
146
+ if ($this->wpdb->prefix == $this->clone->prefix) {
147
+ $this->log("Fatal Error: Can not delete staging site. Prefix. '{$this->clone->prefix}' is used for the live site. Creating a new staging site will likely resolve this the next time. Stopping for security reasons. Contact support@wp-staging.com");
148
+ wp_die("Fatal Error: Can not delete staging site. Prefix. '{$this->clone->prefix}' is used for the live site. Creating a new staging site will likely resolve this the next time. Stopping for security reasons. Contact support@wp-staging.com");
149
+ }
150
+
151
+ // Else
152
+ return $this->clone->prefix;
153
+ }
154
+
155
+ /**
156
+ * Format bytes into human readable form
157
+ * @param int $bytes
158
+ * @param int $precision
159
+ * @return string
160
+ */
161
+ public function formatSize($bytes, $precision = 2) {
162
+ if ((int) $bytes < 1) {
163
+ return '';
164
+ }
165
+
166
+ $units = array('B', "KB", "MB", "GB", "TB");
167
+
168
+ $bytes = (int) $bytes;
169
+ $base = log($bytes) / log(1000); // 1024 would be for MiB KiB etc
170
+ $pow = pow(1000, $base - floor($base)); // Same rule for 1000
171
+
172
+ return round($pow, $precision) . ' ' . $units[(int) floor($base)];
173
+ }
174
+
175
+ /**
176
+ * @return false
177
+ */
178
+ public function getClone() {
179
+ return $this->clone;
180
+ }
181
+
182
+ /**
183
+ * @return null|object
184
+ */
185
+ public function getTables() {
186
+ return $this->tables;
187
+ }
188
+
189
+ /**
190
+ * Start Module
191
+ * @param null|array $clone
192
+ * @return bool
193
+ */
194
+ public function start($clone = null) {
195
+ // Set data
196
+ $this->setData($clone);
197
+
198
+ // Get the job first
199
+ $this->getJob();
200
+
201
+ $method = "delete" . ucwords($this->job->current);
202
+ return $this->{$method}();
203
+ }
204
+
205
+ /**
206
+ * Get job data
207
+ */
208
+ private function getJob() {
209
+ $this->job = $this->cache->get("delete_job_{$this->clone->name}");
210
+
211
+
212
+ if (null !== $this->job) {
213
+ return;
214
+ }
215
+
216
+ // Generate JOB
217
+ $this->job = (object) array(
218
+ "current" => "tables",
219
+ "nextDirectoryToDelete" => $this->clone->path,
220
+ "name" => $this->clone->name
221
+ );
222
+
223
+ $this->cache->save("delete_job_{$this->clone->name}", $this->job);
224
+ }
225
+
226
+ /**
227
+ * @return bool
228
+ */
229
+ private function updateJob() {
230
+ $this->job->nextDirectoryToDelete = trim($this->job->nextDirectoryToDelete);
231
+ return $this->cache->save("delete_job_{$this->clone->name}", $this->job);
232
+ }
233
+
234
+ /**
235
+ * @return array
236
+ */
237
+ private function getTablesToRemove() {
238
+ $tables = $this->getTableNames();
239
+
240
+ if (!isset($_POST["excludedTables"]) || !is_array($_POST["excludedTables"]) || empty($_POST["excludedTables"])) {
241
+ return $tables;
242
+ }
243
+
244
+ return array_diff($tables, $_POST["excludedTables"]);
245
+ }
246
+
247
+ /**
248
+ * @return array
249
+ */
250
+ private function getTableNames() {
251
+ return (!is_array($this->tables)) ? array() : array_map(function($value) {
252
+ return ($value->name);
253
+ }, $this->tables);
254
+ }
255
+
256
+ /**
257
+ * Delete Tables
258
+ */
259
+ public function deleteTables() {
260
+ if ($this->isOverThreshold()) {
261
+ return;
262
+ }
263
+
264
+ //$wpdb = WPStaging::getInstance()->get("wpdb");
265
+
266
+ foreach ($this->getTablesToRemove() as $table) {
267
+ // PROTECTION: Never delete any table that beginns with wp prefix of live site
268
+ if ($this->startsWith($table, $this->wpdb->prefix)) {
269
+ $this->log("Fatal Error: Trying to delete table {$table} of main WP installation!", Logger::TYPE_CRITICAL);
270
+ return false;
271
+ } else {
272
+ $this->wpdb->query("DROP TABLE {$table}");
273
+ }
274
+ }
275
+
276
+ // Move on to the next
277
+ $this->job->current = "directory";
278
+ $this->updateJob();
279
+ }
280
+
281
+ /**
282
+ * Check if a strings start with a specific string
283
+ * @param string $haystack
284
+ * @param string $needle
285
+ * @return bool
286
+ */
287
+ protected function startsWith($haystack, $needle) {
288
+ $length = strlen($needle);
289
+ return (substr($haystack, 0, $length) === $needle);
290
+ }
291
+
292
+ /**
293
+ * Delete complete directory including all files and subfolders
294
+ *
295
+ * @throws InvalidArgumentException
296
+ */
297
+ public function deleteDirectory() {
298
+ if ($this->isFatalError()) {
299
+ $this->returnException('Can not delete directory: ' . $this->clone->path . '. This seems to be the root directory. Please contact support@wp-staging.com');
300
+ throw new \Exception('Can not delete directory: ' . $this->clone->path . ' This seems to be the root directory. Please contact support@wp-staging.com');
301
+ }
302
+ // Finished or path does not exist
303
+ if (
304
+ empty($this->clone->path) ||
305
+ $this->clone->path == get_home_path() ||
306
+ !is_dir($this->clone->path)) {
307
+
308
+ $this->job->current = "finish";
309
+ $this->updateJob();
310
+ return $this->returnFinish();
311
+ }
312
+
313
+ $this->log("Delete staging site: " . $this->clone->path, Logger::TYPE_INFO);
314
+
315
+ // Just to make sure the root dir is never deleted!
316
+ if ($this->clone->path == get_home_path()) {
317
+ $this->log("Fatal Error 8: Trying to delete root of WP installation!", Logger::TYPE_CRITICAL);
318
+ $this->returnException('Fatal Error 8: Trying to delete root of WP installation!');
319
+ }
320
+
321
+ // Check if threshold is reached
322
+ if ($this->isOverThreshold()) {
323
+ //$this->returnException('Maximum PHP execution time exceeded. Run again and repeat the deletion process until it is sucessfully finished.');
324
+ return;
325
+ }
326
+
327
+ $di = new \RecursiveDirectoryIterator($this->clone->path, \FilesystemIterator::SKIP_DOTS);
328
+ $ri = new \RecursiveIteratorIterator($di, \RecursiveIteratorIterator::CHILD_FIRST);
329
+ foreach ($ri as $file) {
330
+ //$file->isDir() ? @rmdir($file) : unlink($file);
331
+ $this->deleteFile($file);
332
+ if ($this->isOverThreshold()) {
333
+ //$this->returnException('Maximum PHP execution time exceeded. Run again and repeat the deletion process until it is sucessfully finished.');
334
+ return;
335
+ }
336
+ }
337
+
338
+ if (@rmdir($this->clone->path)) {
339
+ return $this->returnFinish();
340
+ }
341
+ return;
342
+ }
343
+
344
+ /**
345
+ * Delete file
346
+ * @param object iterator $file
347
+ */
348
+ private function deleteFile($file) {
349
+ if ($file->isDir()) {
350
+ if (!@rmdir($file)) {
351
+ $this->returnException('Permission Error: Can not delete folder ' . $file);
352
+ }
353
+ } else {
354
+ if (!unlink($file)) {
355
+ $this->returnException('Permission Error: Can not delete file ' . $file);
356
+ }
357
+ }
358
+ }
359
+
360
+ /**
361
+ * @return bool
362
+ */
363
+ public function isDirectoryDeletingFinished() {
364
+ return (
365
+ (false === $this->forceDeleteDirectories && (!isset($_POST["deleteDir"]) || '1' !== $_POST["deleteDir"])) ||
366
+ !is_dir($this->clone->path) || ABSPATH === $this->job->nextDirectoryToDelete
367
+ );
368
+ }
369
+
370
+
371
+
372
+ /**
373
+ *
374
+ * @return boolean
375
+ */
376
+ public function isFatalError(){
377
+ $homePath = rtrim(get_home_path(), "/");
378
+ if (rtrim($this->clone->path,"/") == $homePath){
379
+ return true;
380
+ }
381
+ return false;
382
+ }
383
+
384
+ /**
385
+ * Finish / Update Existing Clones
386
+ */
387
+ public function deleteFinish() {
388
+ $existingClones = get_option("wpstg_existing_clones_beta", array());
389
+
390
+ // Check if clones still exist
391
+ $this->log("Verifying existing clones...");
392
+ foreach ($existingClones as $name => $clone) {
393
+ if (!is_dir($clone["path"])) {
394
+ unset($existingClones[$name]);
395
+ }
396
+ }
397
+ $this->log("Existing clones verified!");
398
+
399
+ if (false === update_option("wpstg_existing_clones_beta", $existingClones)) {
400
+ $this->log("Failed to save {$this->options->clone}'s clone job data to database'");
401
+ }
402
+
403
+ // Delete cached file
404
+ $this->cache->delete("delete_job_{$this->clone->name}");
405
+ $this->cache->delete("delete_directories_{$this->clone->name}");
406
+
407
+ //return true;
408
+ $response = array('delete' => 'finished');
409
+ wp_die(json_encode($response));
410
+ }
411
+
412
+ /**
413
+ * Get json response
414
+ * return json
415
+ */
416
+ private function returnFinish($message = '') {
417
+
418
+ $this->deleteFinish();
419
+
420
+ wp_die(json_encode(array(
421
+ 'job' => 'delete',
422
+ 'status' => true,
423
+ 'message' => $message,
424
+ 'error' => false,
425
+ 'delete' => 'finished'
426
+ )));
427
+ }
428
+
429
+ }
apps/Backend/Modules/Jobs/Files.php CHANGED
@@ -1,329 +1,329 @@
1
- <?php
2
-
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
- /**
13
- * Class Files
14
- * @package WPStaging\Backend\Modules\Jobs
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
- }
1
+ <?php
2
+
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
+ /**
13
+ * Class Files
14
+ * @package WPStaging\Backend\Modules\Jobs
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
+ }
apps/Backend/Modules/Jobs/Job.php CHANGED
@@ -1,466 +1,466 @@
1
- <?php
2
-
3
- namespace WPStaging\Backend\Modules\Jobs;
4
-
5
- // No Direct Access
6
- if( !defined( "WPINC" ) ) {
7
- die;
8
- }
9
-
10
- use WPStaging\Backend\Modules\Jobs\Interfaces\JobInterface;
11
- use WPStaging\Utils\Logger;
12
- use WPStaging\WPStaging;
13
- use WPStaging\Utils\Cache;
14
- use WPStaging\Utils\Multisite;
15
-
16
- /**
17
- * Class Job
18
- * @package WPStaging\Backend\Modules\Jobs
19
- */
20
- abstract class Job implements JobInterface {
21
-
22
- const EXECUTION_TIME_RATIO = 0.8;
23
- const MAX_MEMORY_RATIO = 0.8;
24
-
25
- /**
26
- * @var Cache
27
- */
28
- protected $cache;
29
-
30
- /**
31
- * @var Logger
32
- */
33
- protected $logger;
34
-
35
- /**
36
- * @var bool
37
- */
38
- protected $hasLoggedFileNameSet = false;
39
-
40
- /**
41
- * @var object
42
- */
43
- protected $options;
44
-
45
- /**
46
- * @var object
47
- */
48
- protected $settings;
49
-
50
- /**
51
- * System total maximum memory consumption
52
- * @var int
53
- */
54
- protected $maxMemoryLimit;
55
-
56
- /**
57
- * Script maximum memory consumption
58
- * @var int
59
- */
60
- protected $memoryLimit;
61
-
62
- /**
63
- * @var int
64
- */
65
- protected $maxExecutionTime;
66
-
67
- /**
68
- * @var int
69
- */
70
- protected $executionLimit;
71
-
72
- /**
73
- * @var int
74
- */
75
- protected $totalRecursion;
76
-
77
- /**
78
- * @var int
79
- */
80
- protected $maxRecursionLimit;
81
-
82
- /**
83
- * Multisite Home Url
84
- * @var string
85
- */
86
- protected $multisiteHomeUrl;
87
-
88
- /**
89
- * @var int
90
- */
91
- protected $start;
92
-
93
- /**
94
- * Job constructor.
95
- */
96
- public function __construct() {
97
- // Get max limits
98
- $this->start = $this->time();
99
- $this->maxMemoryLimit = $this->getMemoryInBytes( @ini_get( "memory_limit" ) );
100
-
101
- $multisite = new Multisite;
102
- $this->multisiteHomeUrl = $multisite->getHomeURL();
103
-
104
- //$this->maxExecutionTime = (int) ini_get("max_execution_time");
105
- $this->maxExecutionTime = ( int ) 30;
106
-
107
- // if ($this->maxExecutionTime < 1 || $this->maxExecutionTime > 30)
108
- // {
109
- // $this->maxExecutionTime = 30;
110
- // }
111
- // Services
112
- $this->cache = new Cache( -1, \WPStaging\WPStaging::getContentDir() );
113
- $this->logger = WPStaging::getInstance()->get( "logger" );
114
-
115
- // Settings and Options
116
- $this->options = $this->cache->get( "clone_options" );
117
-
118
- $this->settings = ( object ) get_option( "wpstg_settings", array() );
119
-
120
- if( !$this->options ) {
121
- $this->options = new \stdClass();
122
- }
123
-
124
- if( isset( $this->options->existingClones ) && is_object( $this->options->existingClones ) ) {
125
- $this->options->existingClones = json_decode( json_encode( $this->options->existingClones ), true );
126
- }
127
-
128
- // check default options
129
- if( !isset( $this->settings ) ||
130
- !isset( $this->settings->queryLimit ) ||
131
- !isset( $this->settings->querySRLimit ) ||
132
- !isset( $this->settings->batchSize ) ||
133
- !isset( $this->settings->cpuLoad ) ||
134
- !isset( $this->settings->maxFileSize ) ||
135
- !isset( $this->settings->fileLimit )
136
- ) {
137
- $this->settings = new \stdClass();
138
- $this->setDefaultSettings();
139
- }
140
-
141
- // Set limits accordingly to CPU LIMITS
142
- $this->setLimits();
143
-
144
- $this->maxRecursionLimit = ( int ) ini_get( "xdebug.max_nesting_level" );
145
-
146
- /*
147
- * This is needed to make sure that maxRecursionLimit = -1
148
- * if xdebug is not used in production env.
149
- * For using xdebug, maxRecursionLimit must be larger
150
- * otherwise xdebug is throwing an error 500 while debugging
151
- */
152
- if( $this->maxRecursionLimit < 1 ) {
153
- $this->maxRecursionLimit = -1;
154
- } else {
155
- $this->maxRecursionLimit = $this->maxRecursionLimit - 50; // just to make sure
156
- }
157
-
158
- if( method_exists( $this, "initialize" ) ) {
159
- $this->initialize();
160
- }
161
- }
162
-
163
- /**
164
- * Job destructor
165
- */
166
- public function __destruct() {
167
- // Commit logs
168
- $this->logger->commit();
169
- }
170
-
171
- /**
172
- * Set default settings
173
- */
174
- protected function setDefaultSettings() {
175
- $this->settings->queryLimit = "20000";
176
- $this->settings->querySRLimit = "5000";
177
- $this->settings->fileLimit = "1";
178
- $this->settings->batchSize = "2";
179
- $this->settings->cpuLoad = 'medium';
180
- $this->settings->maxFileSize = 8;
181
- update_option( 'wpstg_settings', $this->settings );
182
- }
183
-
184
- /**
185
- * Set limits accordingly to
186
- */
187
- protected function setLimits() {
188
-
189
- if( !isset( $this->settings->cpuLoad ) ) {
190
- $this->settings->cpuLoad = "medium";
191
- }
192
-
193
- $memoryLimit = self::MAX_MEMORY_RATIO;
194
- $timeLimit = self::EXECUTION_TIME_RATIO;
195
-
196
- switch ( $this->settings->cpuLoad ) {
197
- case "medium":
198
- //$memoryLimit= $memoryLimit / 2; // 0.4
199
- $timeLimit = $timeLimit / 2;
200
- break;
201
- case "low":
202
- //$memoryLimit= $memoryLimit / 4; // 0.2
203
- $timeLimit = $timeLimit / 4;
204
- break;
205
-
206
- case "fast": // 0.8
207
- default:
208
- break;
209
- }
210
-
211
- $this->memoryLimit = $this->maxMemoryLimit * $memoryLimit;
212
- $this->executionLimit = $this->maxExecutionTime * $timeLimit;
213
- }
214
-
215
- /**
216
- * Save options
217
- * @param null|array|object $options
218
- * @return bool
219
- */
220
- protected function saveOptions( $options = null ) {
221
- // Get default options
222
- if( null === $options ) {
223
- $options = $this->options;
224
- }
225
-
226
- // Ensure that it is an object
227
- $options = json_decode( json_encode( $options ) );
228
- return $this->cache->save( "clone_options", $options );
229
- }
230
-
231
- /**
232
- * @return object
233
- */
234
- public function getOptions() {
235
- return $this->options;
236
- }
237
-
238
- /**
239
- * @param string $memory
240
- * @return int
241
- */
242
- protected function getMemoryInBytes( $memory ) {
243
- // Handle unlimited ones
244
- if( 1 > ( int ) $memory ) {
245
- //return (int) $memory;
246
- // 128 MB default value
247
- return ( int ) 134217728;
248
- }
249
-
250
- $bytes = ( int ) $memory; // grab only the number
251
- $size = trim( str_replace( $bytes, null, strtolower( $memory ) ) ); // strip away number and lower-case it
252
- // Actual calculation
253
- switch ( $size ) {
254
- case 'k':
255
- $bytes *= 1024;
256
- break;
257
- case 'm':
258
- $bytes *= (1024 * 1024);
259
- break;
260
- case 'g':
261
- $bytes *= (1024 * 1024 * 1024);
262
- break;
263
- }
264
-
265
- return $bytes;
266
- }
267
-
268
- /**
269
- * Format bytes into ini_set favorable form
270
- * @param int $bytes
271
- * @return string
272
- */
273
- protected function formatBytes( $bytes ) {
274
- if( ( int ) $bytes < 1 ) {
275
- return '';
276
- }
277
-
278
- $units = array('B', 'K', 'M', 'G'); // G since PHP 5.1.x so we are good!
279
-
280
- $bytes = ( int ) $bytes;
281
- $base = log( $bytes ) / log( 1000 );
282
- $pow = pow( 1000, $base - floor( $base ) );
283
-
284
- return round( $pow, 0 ) . $units[( int ) floor( $base )];
285
- }
286
-
287
- /**
288
- * Get current time in seconds
289
- * @return float
290
- */
291
- protected function time() {
292
- $time = microtime();
293
- $time = explode( ' ', $time );
294
- $time = $time[1] + $time[0];
295
- return $time;
296
- }
297
-
298
- /**
299
- * @return bool
300
- */
301
- protected function isOverThreshold() {
302
- // Check if the memory is over threshold
303
- $usedMemory = ( int ) @memory_get_usage( true );
304
-
305
- $this->debugLog( 'Used Memory: ' . $this->formatBytes( $usedMemory ) . ' Max Memory Limit: ' . $this->formatBytes( $this->maxMemoryLimit ) . ' Max Script Memory Limit: ' . $this->formatBytes( $this->memoryLimit ), Logger::TYPE_DEBUG );
306
-
307
- if( $usedMemory >= $this->memoryLimit ) {
308
- $this->log( 'Used Memory: ' . $this->formatBytes( $usedMemory ) . ' Memory Limit: ' . $this->formatBytes( $this->maxMemoryLimit ) . ' Max Script memory limit: ' . $this->formatBytes( $this->memoryLimit ), Logger::TYPE_ERROR );
309
- //$this->resetMemory();
310
- return true;
311
- }
312
-
313
- if( $this->isRecursionLimit() ) {
314
- //$this->log('RESET RECURSION');
315
- return true;
316
- }
317
-
318
- // Check if execution time is over threshold
319
- ///$time = round($this->start + $this->time(), 4);
320
- $time = round( $this->time() - $this->start, 4 );
321
-
322
- if( $time >= $this->executionLimit ) {
323
- $this->debugLog( 'RESET TIME: current time: ' . $time . ', Start Time: ' . $this->start . ', exec time limit: ' . $this->executionLimit );
324
- return true;
325
- }
326
-
327
- return false;
328
- }
329
-
330
- /**
331
- * Attempt to reset memory
332
- * @return bool
333
- * memory
334
- */
335
- // protected function resetMemory()
336
- // {
337
- // $newMemoryLimit = $this->maxMemoryLimit * 2;
338
- //
339
- // // Failed to set
340
- // if (false === ini_set("memory_limit", $this->formatBytes($newMemoryLimit)))
341
- // {
342
- // $this->log('Can not free some memory', Logger::TYPE_CRITICAL);
343
- // return false;
344
- // }
345
- //
346
- // // Double checking
347
- // $newMemoryLimit = $this->getMemoryInBytes(@ini_get("memory_limit"));
348
- // if ($newMemoryLimit <= $this->maxMemoryLimit)
349
- // {
350
- // return false;
351
- // }
352
- //
353
- // // Set the new Maximum memory limit
354
- // $this->maxMemoryLimit = $newMemoryLimit;
355
- //
356
- // // Calculate threshold limit
357
- // $this->memoryLimit = $newMemoryLimit * self::MAX_MEMORY_RATIO;
358
- //
359
- // return true;
360
- // }
361
-
362
- /**
363
- * Attempt to reset time
364
- * @return bool
365
- *
366
- * @deprecated since version 2.0.0
367
-
368
- */
369
- // protected function resetTime()
370
- // {
371
- // // Attempt to reset timeout
372
- // if (!@set_time_limit($this->maxExecutionTime))
373
- // {
374
- // return false;
375
- // }
376
- //
377
- // // Increase execution limit
378
- // $this->executionLimit = $this->executionLimit * 2;
379
- //
380
- // return true;
381
- // }
382
-
383
- /**
384
- * Reset time limit and memory
385
- * @return bool
386
- *
387
- * @deprecated since version 2.0.0
388
- */
389
- // protected function reset()
390
- // {
391
- // // Attempt to reset time
392
- // if (!$this->resetTime())
393
- // {
394
- // return false;
395
- // }
396
- //
397
- // // Attempt to reset memory
398
- // if (!$this->resetMemory())
399
- // {
400
- // return false;
401
- // }
402
- //
403
- // return true;
404
- // }
405
-
406
- /**
407
- * Checks if calls are over recursion limit
408
- * @return bool
409
- */
410
- protected function isRecursionLimit() {
411
- return ($this->maxRecursionLimit > 0 && $this->totalRecursion >= $this->maxRecursionLimit);
412
- }
413
-
414
- /**
415
- * @param string $msg
416
- * @param string $type
417
- */
418
- protected function log( $msg, $type = Logger::TYPE_INFO ) {
419
-
420
- if( !isset( $this->options->clone ) ) {
421
- $this->options->clone = date( DATE_ATOM, mktime( 0, 0, 0, 7, 1, 2000 ) );
422
- }
423
-
424
- if( false === $this->hasLoggedFileNameSet && 0 < strlen( $this->options->clone ) ) {
425
- $this->logger->setFileName( $this->options->clone );
426
- $this->hasLoggedFileNameSet = true;
427
- }
428
-
429
- $this->logger->add( $msg, $type );
430
- }
431
-
432
- /**
433
- * @param string $msg
434
- * @param string $type
435
- */
436
- protected function debugLog( $msg, $type = Logger::TYPE_INFO ) {
437
-
438
- if( !isset( $this->options->clone ) ) {
439
- $this->options->clone = date( DATE_ATOM, mktime( 0, 0, 0, 7, 1, 2000 ) );
440
- }
441
-
442
- if( false === $this->hasLoggedFileNameSet && 0 < strlen( $this->options->clone ) ) {
443
- $this->logger->setFileName( $this->options->clone );
444
- $this->hasLoggedFileNameSet = true;
445
- }
446
-
447
-
448
- if( isset( $this->settings->debugMode ) ) {
449
- $this->logger->add( $msg, $type );
450
- }
451
- }
452
-
453
- /**
454
- * Throw a errror message via json and stop further execution
455
- * @param string $message
456
- */
457
- protected function returnException( $message = '' ) {
458
- wp_die( json_encode( array(
459
- 'job' => isset( $this->options->currentJob ) ? $this->options->currentJob : '',
460
- 'status' => false,
461
- 'message' => $message,
462
- 'error' => true
463
- ) ) );
464
- }
465
-
466
- }
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs;
4
+
5
+ // No Direct Access
6
+ if( !defined( "WPINC" ) ) {
7
+ die;
8
+ }
9
+
10
+ use WPStaging\Backend\Modules\Jobs\Interfaces\JobInterface;
11
+ use WPStaging\Utils\Logger;
12
+ use WPStaging\WPStaging;
13
+ use WPStaging\Utils\Cache;
14
+ use WPStaging\Utils\Multisite;
15
+
16
+ /**
17
+ * Class Job
18
+ * @package WPStaging\Backend\Modules\Jobs
19
+ */
20
+ abstract class Job implements JobInterface {
21
+
22
+ const EXECUTION_TIME_RATIO = 0.8;
23
+ const MAX_MEMORY_RATIO = 0.8;
24
+
25
+ /**
26
+ * @var Cache
27
+ */
28
+ protected $cache;
29
+
30
+ /**
31
+ * @var Logger
32
+ */
33
+ protected $logger;
34
+
35
+ /**
36
+ * @var bool
37
+ */
38
+ protected $hasLoggedFileNameSet = false;
39
+
40
+ /**
41
+ * @var object
42
+ */
43
+ protected $options;
44
+
45
+ /**
46
+ * @var object
47
+ */
48
+ protected $settings;
49
+
50
+ /**
51
+ * System total maximum memory consumption
52
+ * @var int
53
+ */
54
+ protected $maxMemoryLimit;
55
+
56
+ /**
57
+ * Script maximum memory consumption
58
+ * @var int
59
+ */
60
+ protected $memoryLimit;
61
+
62
+ /**
63
+ * @var int
64
+ */
65
+ protected $maxExecutionTime;
66
+
67
+ /**
68
+ * @var int
69
+ */
70
+ protected $executionLimit;
71
+
72
+ /**
73
+ * @var int
74
+ */
75
+ protected $totalRecursion;
76
+
77
+ /**
78
+ * @var int
79
+ */
80
+ protected $maxRecursionLimit;
81
+
82
+ /**
83
+ * Multisite Home Url
84
+ * @var string
85
+ */
86
+ protected $multisiteHomeUrl;
87
+
88
+ /**
89
+ * @var int
90
+ */
91
+ protected $start;
92
+
93
+ /**
94
+ * Job constructor.
95
+ */
96
+ public function __construct() {
97
+ // Get max limits
98
+ $this->start = $this->time();
99
+ $this->maxMemoryLimit = $this->getMemoryInBytes( @ini_get( "memory_limit" ) );
100
+
101
+ $multisite = new Multisite;
102
+ $this->multisiteHomeUrl = $multisite->getHomeURL();
103
+
104
+ //$this->maxExecutionTime = (int) ini_get("max_execution_time");
105
+ $this->maxExecutionTime = ( int ) 30;
106
+
107
+ // if ($this->maxExecutionTime < 1 || $this->maxExecutionTime > 30)
108
+ // {
109
+ // $this->maxExecutionTime = 30;
110
+ // }
111
+ // Services
112
+ $this->cache = new Cache( -1, \WPStaging\WPStaging::getContentDir() );
113
+ $this->logger = WPStaging::getInstance()->get( "logger" );
114
+
115
+ // Settings and Options
116
+ $this->options = $this->cache->get( "clone_options" );
117
+
118
+ $this->settings = ( object ) get_option( "wpstg_settings", array() );
119
+
120
+ if( !$this->options ) {
121
+ $this->options = new \stdClass();
122
+ }
123
+
124
+ if( isset( $this->options->existingClones ) && is_object( $this->options->existingClones ) ) {
125
+ $this->options->existingClones = json_decode( json_encode( $this->options->existingClones ), true );
126
+ }
127
+
128
+ // check default options
129
+ if( !isset( $this->settings ) ||
130
+ !isset( $this->settings->queryLimit ) ||
131
+ !isset( $this->settings->querySRLimit ) ||
132
+ !isset( $this->settings->batchSize ) ||
133
+ !isset( $this->settings->cpuLoad ) ||
134
+ !isset( $this->settings->maxFileSize ) ||
135
+ !isset( $this->settings->fileLimit )
136
+ ) {
137
+ $this->settings = new \stdClass();
138
+ $this->setDefaultSettings();
139
+ }
140
+
141
+ // Set limits accordingly to CPU LIMITS
142
+ $this->setLimits();
143
+
144
+ $this->maxRecursionLimit = ( int ) ini_get( "xdebug.max_nesting_level" );
145
+
146
+ /*
147
+ * This is needed to make sure that maxRecursionLimit = -1
148
+ * if xdebug is not used in production env.
149
+ * For using xdebug, maxRecursionLimit must be larger
150
+ * otherwise xdebug is throwing an error 500 while debugging
151
+ */
152
+ if( $this->maxRecursionLimit < 1 ) {
153
+ $this->maxRecursionLimit = -1;
154
+ } else {
155
+ $this->maxRecursionLimit = $this->maxRecursionLimit - 50; // just to make sure
156
+ }
157
+
158
+ if( method_exists( $this, "initialize" ) ) {
159
+ $this->initialize();
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Job destructor
165
+ */
166
+ public function __destruct() {
167
+ // Commit logs
168
+ $this->logger->commit();
169
+ }
170
+
171
+ /**
172
+ * Set default settings
173
+ */
174
+ protected function setDefaultSettings() {
175
+ $this->settings->queryLimit = "20000";
176
+ $this->settings->querySRLimit = "5000";
177
+ $this->settings->fileLimit = "1";
178
+ $this->settings->batchSize = "2";
179
+ $this->settings->cpuLoad = 'medium';
180
+ $this->settings->maxFileSize = 8;
181
+ update_option( 'wpstg_settings', $this->settings );
182
+ }
183
+
184
+ /**
185
+ * Set limits accordingly to
186
+ */
187
+ protected function setLimits() {
188
+
189
+ if( !isset( $this->settings->cpuLoad ) ) {
190
+ $this->settings->cpuLoad = "medium";
191
+ }
192
+
193
+ $memoryLimit = self::MAX_MEMORY_RATIO;
194
+ $timeLimit = self::EXECUTION_TIME_RATIO;
195
+
196
+ switch ( $this->settings->cpuLoad ) {
197
+ case "medium":
198
+ //$memoryLimit= $memoryLimit / 2; // 0.4
199
+ $timeLimit = $timeLimit / 2;
200
+ break;
201
+ case "low":
202
+ //$memoryLimit= $memoryLimit / 4; // 0.2
203
+ $timeLimit = $timeLimit / 4;
204
+ break;
205
+
206
+ case "fast": // 0.8
207
+ default:
208
+ break;
209
+ }
210
+
211
+ $this->memoryLimit = $this->maxMemoryLimit * $memoryLimit;
212
+ $this->executionLimit = $this->maxExecutionTime * $timeLimit;
213
+ }
214
+
215
+ /**
216
+ * Save options
217
+ * @param null|array|object $options
218
+ * @return bool
219
+ */
220
+ protected function saveOptions( $options = null ) {
221
+ // Get default options
222
+ if( null === $options ) {
223
+ $options = $this->options;
224
+ }
225
+
226
+ // Ensure that it is an object
227
+ $options = json_decode( json_encode( $options ) );
228
+ return $this->cache->save( "clone_options", $options );
229
+ }
230
+
231
+ /**
232
+ * @return object
233
+ */
234
+ public function getOptions() {
235
+ return $this->options;
236
+ }
237
+
238
+ /**
239
+ * @param string $memory
240
+ * @return int
241
+ */
242
+ protected function getMemoryInBytes( $memory ) {
243
+ // Handle unlimited ones
244
+ if( 1 > ( int ) $memory ) {
245
+ //return (int) $memory;
246
+ // 128 MB default value
247
+ return ( int ) 134217728;
248
+ }
249
+
250
+ $bytes = ( int ) $memory; // grab only the number
251
+ $size = trim( str_replace( $bytes, null, strtolower( $memory ) ) ); // strip away number and lower-case it
252
+ // Actual calculation
253
+ switch ( $size ) {
254
+ case 'k':
255
+ $bytes *= 1024;
256
+ break;
257
+ case 'm':
258
+ $bytes *= (1024 * 1024);
259
+ break;
260
+ case 'g':
261
+ $bytes *= (1024 * 1024 * 1024);
262
+ break;
263
+ }
264
+
265
+ return $bytes;
266
+ }
267
+
268
+ /**
269
+ * Format bytes into ini_set favorable form
270
+ * @param int $bytes
271
+ * @return string
272
+ */
273
+ protected function formatBytes( $bytes ) {
274
+ if( ( int ) $bytes < 1 ) {
275
+ return '';
276
+ }
277
+
278
+ $units = array('B', 'K', 'M', 'G'); // G since PHP 5.1.x so we are good!
279
+
280
+ $bytes = ( int ) $bytes;
281
+ $base = log( $bytes ) / log( 1000 );
282
+ $pow = pow( 1000, $base - floor( $base ) );
283
+
284
+ return round( $pow, 0 ) . $units[( int ) floor( $base )];
285
+ }
286
+
287
+ /**
288
+ * Get current time in seconds
289
+ * @return float
290
+ */
291
+ protected function time() {
292
+ $time = microtime();
293
+ $time = explode( ' ', $time );
294
+ $time = $time[1] + $time[0];
295
+ return $time;
296
+ }
297
+
298
+ /**
299
+ * @return bool
300
+ */
301
+ protected function isOverThreshold() {
302
+ // Check if the memory is over threshold
303
+ $usedMemory = ( int ) @memory_get_usage( true );
304
+
305
+ $this->debugLog( 'Used Memory: ' . $this->formatBytes( $usedMemory ) . ' Max Memory Limit: ' . $this->formatBytes( $this->maxMemoryLimit ) . ' Max Script Memory Limit: ' . $this->formatBytes( $this->memoryLimit ), Logger::TYPE_DEBUG );
306
+
307
+ if( $usedMemory >= $this->memoryLimit ) {
308
+ $this->log( 'Used Memory: ' . $this->formatBytes( $usedMemory ) . ' Memory Limit: ' . $this->formatBytes( $this->maxMemoryLimit ) . ' Max Script memory limit: ' . $this->formatBytes( $this->memoryLimit ), Logger::TYPE_ERROR );
309
+ //$this->resetMemory();
310
+ return true;
311
+ }
312
+
313
+ if( $this->isRecursionLimit() ) {
314
+ //$this->log('RESET RECURSION');
315
+ return true;
316
+ }
317
+
318
+ // Check if execution time is over threshold
319
+ ///$time = round($this->start + $this->time(), 4);
320
+ $time = round( $this->time() - $this->start, 4 );
321
+
322
+ if( $time >= $this->executionLimit ) {
323
+ $this->debugLog( 'RESET TIME: current time: ' . $time . ', Start Time: ' . $this->start . ', exec time limit: ' . $this->executionLimit );
324
+ return true;
325
+ }
326
+
327
+ return false;
328
+ }
329
+
330
+ /**
331
+ * Attempt to reset memory
332
+ * @return bool
333
+ * memory
334
+ */
335
+ // protected function resetMemory()
336
+ // {
337
+ // $newMemoryLimit = $this->maxMemoryLimit * 2;
338
+ //
339
+ // // Failed to set
340
+ // if (false === ini_set("memory_limit", $this->formatBytes($newMemoryLimit)))
341
+ // {
342
+ // $this->log('Can not free some memory', Logger::TYPE_CRITICAL);
343
+ // return false;
344
+ // }
345
+ //
346
+ // // Double checking
347
+ // $newMemoryLimit = $this->getMemoryInBytes(@ini_get("memory_limit"));
348
+ // if ($newMemoryLimit <= $this->maxMemoryLimit)
349
+ // {
350
+ // return false;
351
+ // }
352
+ //
353
+ // // Set the new Maximum memory limit
354
+ // $this->maxMemoryLimit = $newMemoryLimit;
355
+ //
356
+ // // Calculate threshold limit
357
+ // $this->memoryLimit = $newMemoryLimit * self::MAX_MEMORY_RATIO;
358
+ //
359
+ // return true;
360
+ // }
361
+
362
+ /**
363
+ * Attempt to reset time
364
+ * @return bool
365
+ *
366
+ * @deprecated since version 2.0.0
367
+
368
+ */
369
+ // protected function resetTime()
370
+ // {
371
+ // // Attempt to reset timeout
372
+ // if (!@set_time_limit($this->maxExecutionTime))
373
+ // {
374
+ // return false;
375
+ // }
376
+ //
377
+ // // Increase execution limit
378
+ // $this->executionLimit = $this->executionLimit * 2;
379
+ //
380
+ // return true;
381
+ // }
382
+
383
+ /**
384
+ * Reset time limit and memory
385
+ * @return bool
386
+ *
387
+ * @deprecated since version 2.0.0
388
+ */
389
+ // protected function reset()
390
+ // {
391
+ // // Attempt to reset time
392
+ // if (!$this->resetTime())
393
+ // {
394
+ // return false;
395
+ // }
396
+ //
397
+ // // Attempt to reset memory
398
+ // if (!$this->resetMemory())
399
+ // {
400
+ // return false;
401
+ // }
402
+ //
403
+ // return true;
404
+ // }
405
+
406
+ /**
407
+ * Checks if calls are over recursion limit
408
+ * @return bool
409
+ */
410
+ protected function isRecursionLimit() {
411
+ return ($this->maxRecursionLimit > 0 && $this->totalRecursion >= $this->maxRecursionLimit);
412
+ }
413
+
414
+ /**
415
+ * @param string $msg
416
+ * @param string $type
417
+ */
418
+ protected function log( $msg, $type = Logger::TYPE_INFO ) {
419
+
420
+ if( !isset( $this->options->clone ) ) {
421
+ $this->options->clone = date( DATE_ATOM, mktime( 0, 0, 0, 7, 1, 2000 ) );
422
+ }
423
+
424
+ if( false === $this->hasLoggedFileNameSet && 0 < strlen( $this->options->clone ) ) {
425
+ $this->logger->setFileName( $this->options->clone );
426
+ $this->hasLoggedFileNameSet = true;
427
+ }
428
+
429
+ $this->logger->add( $msg, $type );
430
+ }
431
+
432
+ /**
433
+ * @param string $msg
434
+ * @param string $type
435
+ */
436
+ protected function debugLog( $msg, $type = Logger::TYPE_INFO ) {
437
+
438
+ if( !isset( $this->options->clone ) ) {
439
+ $this->options->clone = date( DATE_ATOM, mktime( 0, 0, 0, 7, 1, 2000 ) );
440
+ }
441
+
442
+ if( false === $this->hasLoggedFileNameSet && 0 < strlen( $this->options->clone ) ) {
443
+ $this->logger->setFileName( $this->options->clone );
444
+ $this->hasLoggedFileNameSet = true;
445
+ }
446
+
447
+
448
+ if( isset( $this->settings->debugMode ) ) {
449
+ $this->logger->add( $msg, $type );
450
+ }
451
+ }
452
+
453
+ /**
454
+ * Throw a errror message via json and stop further execution
455
+ * @param string $message
456
+ */
457
+ protected function returnException( $message = '' ) {
458
+ wp_die( json_encode( array(
459
+ 'job' => isset( $this->options->currentJob ) ? $this->options->currentJob : '',
460
+ 'status' => false,
461
+ 'message' => $message,
462
+ 'error' => true
463
+ ) ) );
464
+ }
465
+
466
+ }
apps/Backend/Modules/Jobs/Scan.php CHANGED
@@ -1,403 +1,403 @@
1
- <?php
2
-
3
- namespace WPStaging\Backend\Modules\Jobs;
4
-
5
- // No Direct Access
6
- if( !defined( "WPINC" ) ) {
7
- die;
8
- }
9
-
10
- use WPStaging\Utils\Directories;
11
- use WPStaging\WPStaging;
12
-
13
- /**
14
- * Class Scan
15
- * @package WPStaging\Backend\Modules\Jobs
16
- */
17
- class Scan extends Job {
18
-
19
- /**
20
- * @var array
21
- */
22
- private $directories = array();
23
-
24
- /**
25
- * @var Directories
26
- */
27
- private $objDirectories;
28
-
29
- /**
30
- * Upon class initialization
31
- */
32
- protected function initialize() {
33
- $this->objDirectories = new Directories();
34
-
35
- // Database Tables
36
- $this->getTables();
37
-
38
- // Get directories
39
- $this->directories();
40
-
41
- $this->db = WPStaging::getInstance()->get( 'wpdb' );
42
- $this->prefix = $this->db->prefix;
43
- }
44
-
45
- /**
46
- * Start Module
47
- * @return $this
48
- */
49
- public function start() {
50
- // Basic Options
51
- $this->options->root = str_replace( array("\\", '/'), DIRECTORY_SEPARATOR, ABSPATH );
52
- $this->options->existingClones = get_option( "wpstg_existing_clones_beta", array() );
53
- $this->options->current = null;
54
-
55
- if( isset( $_POST["clone"] ) && array_key_exists( $_POST["clone"], $this->options->existingClones ) ) {
56
- $this->options->current = $_POST["clone"];
57
- }
58
-
59
- // Tables
60
- //$this->options->excludedTables = array();
61
- $this->options->clonedTables = array();
62
-
63
- // Files
64
- $this->options->totalFiles = 0;
65
- $this->options->totalFileSize = 0;
66
- $this->options->copiedFiles = 0;
67
-
68
-
69
- // Directories
70
- $this->options->includedDirectories = array();
71
- $this->options->includedExtraDirectories = array();
72
- $this->options->excludedDirectories = array();
73
- $this->options->extraDirectories = array();
74
- $this->options->directoriesToCopy = array();
75
- $this->options->scannedDirectories = array();
76
-
77
- // Job
78
- $this->options->currentJob = "database";
79
- $this->options->currentStep = 0;
80
- $this->options->totalSteps = 0;
81
-
82
- // Define mainJob to differentiate between cloning, updating and pushing
83
- $this->options->mainJob = 'cloning';
84
-
85
- // Delete previous cached files
86
- $this->cache->delete( "files_to_copy" );
87
- $this->cache->delete( "clone_options" );
88
-
89
- // Save options
90
- $this->saveOptions();
91
-
92
- return $this;
93
- }
94
-
95
- /**
96
- * Format bytes into human readable form
97
- * @param int $bytes
98
- * @param int $precision
99
- * @return string
100
- */
101
- public function formatSize( $bytes, $precision = 2 ) {
102
- if( ( double ) $bytes < 1 ) {
103
- return '';
104
- }
105
-
106
- $units = array('B', "KB", "MB", "GB", "TB");
107
-
108
- $bytes = ( double ) $bytes;
109
- $base = log( $bytes ) / log( 1000 ); // 1024 would be for MiB KiB etc
110
- $pow = pow( 1000, $base - floor( $base ) ); // Same rule for 1000
111
-
112
- return round( $pow, $precision ) . ' ' . $units[( int ) floor( $base )];
113
- }
114
-
115
- /**
116
- * @param null|string $directories
117
- * @param bool $forceDisabled
118
- * @return string
119
- */
120
- public function directoryListing( $directories = null, $forceDisabled = false ) {
121
- if( null == $directories ) {
122
- $directories = $this->directories;
123
- }
124
-
125
- // Sort results
126
- uksort( $directories, 'strcasecmp' );
127
-
128
- $output = '';
129
- foreach ( $directories as $name => $directory ) {
130
- // Not a directory, possibly a symlink, therefore we will skip it
131
- if( !is_array( $directory ) ) {
132
- continue;
133
- }
134
-
135
- // Need to preserve keys so no array_shift()
136
- $data = reset( $directory );
137
- unset( $directory[key( $directory )] );
138
-
139
- $isChecked = (
140
- empty( $this->options->includedDirectories ) ||
141
- in_array( $data["path"], $this->options->includedDirectories )
142
- );
143
-
144
- //$isDisabled = ($this->options->existingClones && isset($this->options->existingClones[$name]));
145
- // Include wp core folders and their sub dirs.
146
- // Exclude all other folders (default setting)
147
- $isDisabled = ($name !== 'wp-admin' &&
148
- $name !== 'wp-includes' &&
149
- $name !== 'wp-content') &&
150
- false === strpos( strrev( $data["path"] ), strrev( "wp-admin" ) ) &&
151
- false === strpos( strrev( $data["path"] ), strrev( "wp-includes" ) ) &&
152
- false === strpos( strrev( $data["path"] ), strrev( "wp-content" ) ) ? true : false;
153
-
154
- // Extra class to differentiate between wp core and non core folders
155
- $class = !$isDisabled ? 'wpstg-root' : 'wpstg-extra';
156
-
157
-
158
- $output .= "<div class='wpstg-dir'>";
159
- $output .= "<input type='checkbox' class='wpstg-check-dir " . $class . "'";
160
-
161
- if( $isChecked && !$isDisabled && !$forceDisabled )
162
- $output .= " checked";
163
- //if ($forceDisabled || $isDisabled) $output .= " disabled";
164
-
165
- $output .= " name='selectedDirectories[]' value='{$data["path"]}'>";
166
-
167
- $output .= "<a href='#' class='wpstg-expand-dirs ";
168
- if( !$isChecked || $isDisabled )
169
- $output .= " disabled";
170
- $output .= "'>{$name}";
171
- $output .= "</a>";
172
-
173
- $output .= "<span class='wpstg-size-info'>{$this->formatSize( $data["size"] )}</span>";
174
-
175
- if( !empty( $directory ) ) {
176
- $output .= "<div class='wpstg-dir wpstg-subdir'>";
177
- $output .= $this->directoryListing( $directory, $isDisabled );
178
- $output .= "</div>";
179
- }
180
-
181
- $output .= "</div>";
182
- }
183
-
184
- return $output;
185
- }
186
-
187
- /**
188
- * Checks if there is enough free disk space to create staging site
189
- * Returns null when can't run disk_free_space function one way or another
190
- * @return bool|null
191
- */
192
- public function hasFreeDiskSpace() {
193
- if( !function_exists( "disk_free_space" ) ) {
194
- return null;
195
- }
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();
206
- }
207
-
208
-
209
- $data = array(
210
- 'freespace' => $this->formatSize( $freeSpace ),
211
- 'usedspace' => $this->formatSize( $this->getDirectorySizeInclSubdirs( ABSPATH ) )
212
- );
213
-
214
- echo json_encode( $data );
215
- die();
216
- }
217
-
218
- /**
219
- * Get Database Tables
220
- */
221
- protected function getTables() {
222
- $wpDB = WPStaging::getInstance()->get( "wpdb" );
223
-
224
- if( strlen( $wpDB->prefix ) > 0 ) {
225
- //$prefix = str_replace('_', '', $wpDB->prefix);
226
- $sql = "SHOW TABLE STATUS LIKE '{$wpDB->prefix}%'";
227
- } else {
228
- $sql = "SHOW TABLE STATUS";
229
- }
230
-
231
- $tables = $wpDB->get_results( $sql );
232
-
233
- $currentTables = array();
234
-
235
- // Reset excluded Tables than loop through all tables
236
- $this->options->excludedTables = array();
237
- foreach ( $tables as $table ) {
238
-
239
- // Exclude WP Staging Tables
240
- // if (0 === strpos($table->Name, "wpstg"))
241
- // {
242
- // continue;
243
- // }
244
- // Create array of unchecked tables
245
- if( !empty( $wpDB->prefix ) && 0 !== strpos( $table->Name, $wpDB->prefix ) ) {
246
- $this->options->excludedTables[] = $table->Name;
247
- }
248
-
249
-
250
- $currentTables[] = array(
251
- "name" => $table->Name,
252
- "size" => ($table->Data_length + $table->Index_length)
253
- );
254
- }
255
-
256
- $this->options->tables = json_decode( json_encode( $currentTables ) );
257
- }
258
-
259
- /**
260
- * Get directories and main meta data about'em recursively
261
- */
262
- protected function directories() {
263
- $directories = new \DirectoryIterator( ABSPATH );
264
-
265
- foreach ( $directories as $directory ) {
266
- // Not a valid directory
267
- if( false === ($path = $this->getPath( $directory )) ) {
268
- continue;
269
- }
270
-
271
- $this->handleDirectory( $path );
272
-
273
- // Get Sub-directories
274
- $this->getSubDirectories( $directory->getRealPath() );
275
- }
276
-
277
- // Gather Plugins
278
- $this->getSubDirectories( WP_PLUGIN_DIR );
279
-
280
- // Gather Themes
281
- $this->getSubDirectories( WP_CONTENT_DIR . DIRECTORY_SEPARATOR . "themes" );
282
-
283
- // Gather Default Uploads Folder
284
- //$this->getSubDirectories(WP_CONTENT_DIR . DIRECTORY_SEPARATOR . "uploads");
285
- // Gather Custom Uploads Folder if there is one
286
- $this->getSubDirectories( $this->getUploadDir() );
287
- }
288
-
289
- /**
290
- * @param string $path
291
- */
292
- protected function getSubDirectories( $path ) {
293
- $directories = new \DirectoryIterator( $path );
294
-
295
- foreach ( $directories as $directory ) {
296
- // Not a valid directory
297
- if( false === ($path = $this->getPath( $directory )) ) {
298
- continue;
299
- }
300
-
301
- $this->handleDirectory( $path );
302
- }
303
- }
304
-
305
- /**
306
- * Get Path from $directory
307
- * @param \SplFileInfo $directory
308
- * @return string|false
309
- */
310
- protected function getPath( $directory ) {
311
-
312
- /*
313
- * Do not follow root path like src/web/..
314
- * This must be done before \SplFileInfo->isDir() is used!
315
- * Prevents open base dir restriction fatal errors
316
- */
317
- if( strpos( $directory->getRealPath(), ABSPATH ) !== 0 ) {
318
- return false;
319
- }
320
- $path = str_replace( ABSPATH, null, $directory->getRealPath() );
321
-
322
- // Using strpos() for symbolic links as they could create nasty stuff in nix stuff for directory structures
323
- if( !$directory->isDir() || strlen( $path ) < 1 ) {
324
- return false;
325
- }
326
-
327
- return $path;
328
- }
329
-
330
- /**
331
- * Organizes $this->directories
332
- * @param string $path
333
- */
334
- protected function handleDirectory( $path ) {
335
-
336
- $directoryArray = explode( DIRECTORY_SEPARATOR, $path );
337
- $total = (is_array($directoryArray) || $directoryArray instanceof Countable ) ? count( $directoryArray ) : 0;
338
-
339
- if( $total < 1 ) {
340
- return;
341
- }
342
-
343
- $total = $total - 1;
344
- $currentArray = &$this->directories;
345
-
346
- for ( $i = 0; $i <= $total; $i++ ) {
347
- if( !isset( $currentArray[$directoryArray[$i]] ) ) {
348
- $currentArray[$directoryArray[$i]] = array();
349
- }
350
-
351
- $currentArray = &$currentArray[$directoryArray[$i]];
352
-
353
- // Attach meta data to the end
354
- if( $i < $total ) {
355
- continue;
356
- }
357
-
358
- $fullPath = ABSPATH . $path;
359
- $size = $this->getDirectorySize( $fullPath );
360
-
361
- $currentArray["metaData"] = array(
362
- "size" => $size,
363
- "path" => ABSPATH . $path,
364
- );
365
- }
366
- }
367
-
368
- /**
369
- * Gets size of given directory
370
- * @param string $path
371
- * @return int|null
372
- */
373
- protected function getDirectorySize( $path ) {
374
- if( !isset( $this->settings->checkDirectorySize ) || '1' !== $this->settings->checkDirectorySize ) {
375
- return null;
376
- }
377
-
378
- return $this->objDirectories->size( $path );
379
- }
380
-
381
- /**
382
- * Get total size of a directory including all its subdirectories
383
- * @param string $dir
384
- * @return int
385
- */
386
- function getDirectorySizeInclSubdirs( $dir ) {
387
- $size = 0;
388
- foreach ( glob( rtrim( $dir, '/' ) . '/*', GLOB_NOSORT ) as $each ) {
389
- $size += is_file( $each ) ? filesize( $each ) : $this->getDirectorySizeInclSubdirs( $each );
390
- }
391
- return $size;
392
- }
393
-
394
- /**
395
- * Get relative WP uploads path
396
- * @return string
397
- */
398
- protected function getUploadDir() {
399
- $uploads = wp_upload_dir();
400
- return $uploads['basedir'];
401
- }
402
-
403
- }
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs;
4
+
5
+ // No Direct Access
6
+ if( !defined( "WPINC" ) ) {
7
+ die;
8
+ }
9
+
10
+ use WPStaging\Utils\Directories;
11
+ use WPStaging\WPStaging;
12
+
13
+ /**
14
+ * Class Scan
15
+ * @package WPStaging\Backend\Modules\Jobs
16
+ */
17
+ class Scan extends Job {
18
+
19
+ /**
20
+ * @var array
21
+ */
22
+ private $directories = array();
23
+
24
+ /**
25
+ * @var Directories
26
+ */
27
+ private $objDirectories;
28
+
29
+ /**
30
+ * Upon class initialization
31
+ */
32
+ protected function initialize() {
33
+ $this->objDirectories = new Directories();
34
+
35
+ // Database Tables
36
+ $this->getTables();
37
+
38
+ // Get directories
39
+ $this->directories();
40
+
41
+ $this->db = WPStaging::getInstance()->get( 'wpdb' );
42
+ $this->prefix = $this->db->prefix;
43
+ }
44
+
45
+ /**
46
+ * Start Module
47
+ * @return $this
48
+ */
49
+ public function start() {
50
+ // Basic Options
51
+ $this->options->root = str_replace( array("\\", '/'), DIRECTORY_SEPARATOR, ABSPATH );
52
+ $this->options->existingClones = get_option( "wpstg_existing_clones_beta", array() );
53
+ $this->options->current = null;
54
+
55
+ if( isset( $_POST["clone"] ) && array_key_exists( $_POST["clone"], $this->options->existingClones ) ) {
56
+ $this->options->current = $_POST["clone"];
57
+ }
58
+
59
+ // Tables
60
+ //$this->options->excludedTables = array();
61
+ $this->options->clonedTables = array();
62
+
63
+ // Files
64
+ $this->options->totalFiles = 0;
65
+ $this->options->totalFileSize = 0;
66
+ $this->options->copiedFiles = 0;
67
+
68
+
69
+ // Directories
70
+ $this->options->includedDirectories = array();
71
+ $this->options->includedExtraDirectories = array();
72
+ $this->options->excludedDirectories = array();
73
+ $this->options->extraDirectories = array();
74
+ $this->options->directoriesToCopy = array();
75
+ $this->options->scannedDirectories = array();
76
+
77
+ // Job
78
+ $this->options->currentJob = "database";
79
+ $this->options->currentStep = 0;
80
+ $this->options->totalSteps = 0;
81
+
82
+ // Define mainJob to differentiate between cloning, updating and pushing
83
+ $this->options->mainJob = 'cloning';
84
+
85
+ // Delete previous cached files
86
+ $this->cache->delete( "files_to_copy" );
87
+ $this->cache->delete( "clone_options" );
88
+
89
+ // Save options
90
+ $this->saveOptions();
91
+
92
+ return $this;
93
+ }
94
+
95
+ /**
96
+ * Format bytes into human readable form
97
+ * @param int $bytes
98
+ * @param int $precision
99
+ * @return string
100
+ */
101
+ public function formatSize( $bytes, $precision = 2 ) {
102
+ if( ( double ) $bytes < 1 ) {
103
+ return '';
104
+ }
105
+
106
+ $units = array('B', "KB", "MB", "GB", "TB");
107
+
108
+ $bytes = ( double ) $bytes;
109
+ $base = log( $bytes ) / log( 1000 ); // 1024 would be for MiB KiB etc
110
+ $pow = pow( 1000, $base - floor( $base ) ); // Same rule for 1000
111
+
112
+ return round( $pow, $precision ) . ' ' . $units[( int ) floor( $base )];
113
+ }
114
+
115
+ /**
116
+ * @param null|string $directories
117
+ * @param bool $forceDisabled
118
+ * @return string
119
+ */
120
+ public function directoryListing( $directories = null, $forceDisabled = false ) {
121
+ if( null == $directories ) {
122
+ $directories = $this->directories;
123
+ }
124
+
125
+ // Sort results
126
+ uksort( $directories, 'strcasecmp' );
127
+
128
+ $output = '';
129
+ foreach ( $directories as $name => $directory ) {
130
+ // Not a directory, possibly a symlink, therefore we will skip it
131
+ if( !is_array( $directory ) ) {
132
+ continue;
133
+ }
134
+
135
+ // Need to preserve keys so no array_shift()
136
+ $data = reset( $directory );
137
+ unset( $directory[key( $directory )] );
138
+
139
+ $isChecked = (
140
+ empty( $this->options->includedDirectories ) ||
141
+ in_array( $data["path"], $this->options->includedDirectories )
142
+ );
143
+
144
+ //$isDisabled = ($this->options->existingClones && isset($this->options->existingClones[$name]));
145
+ // Include wp core folders and their sub dirs.
146
+ // Exclude all other folders (default setting)
147
+ $isDisabled = ($name !== 'wp-admin' &&
148
+ $name !== 'wp-includes' &&
149
+ $name !== 'wp-content') &&
150
+ false === strpos( strrev( $data["path"] ), strrev( "wp-admin" ) ) &&
151
+ false === strpos( strrev( $data["path"] ), strrev( "wp-includes" ) ) &&
152
+ false === strpos( strrev( $data["path"] ), strrev( "wp-content" ) ) ? true : false;
153
+
154
+ // Extra class to differentiate between wp core and non core folders
155
+ $class = !$isDisabled ? 'wpstg-root' : 'wpstg-extra';
156
+
157
+
158
+ $output .= "<div class='wpstg-dir'>";
159
+ $output .= "<input type='checkbox' class='wpstg-check-dir " . $class . "'";
160
+
161
+ if( $isChecked && !$isDisabled && !$forceDisabled )
162
+ $output .= " checked";
163
+ //if ($forceDisabled || $isDisabled) $output .= " disabled";
164
+
165
+ $output .= " name='selectedDirectories[]' value='{$data["path"]}'>";
166
+
167
+ $output .= "<a href='#' class='wpstg-expand-dirs ";
168
+ if( !$isChecked || $isDisabled )
169
+ $output .= " disabled";
170
+ $output .= "'>{$name}";
171
+ $output .= "</a>";
172
+
173
+ $output .= "<span class='wpstg-size-info'>{$this->formatSize( $data["size"] )}</span>";
174
+
175
+ if( !empty( $directory ) ) {
176
+ $output .= "<div class='wpstg-dir wpstg-subdir'>";
177
+ $output .= $this->directoryListing( $directory, $isDisabled );
178
+ $output .= "</div>";
179
+ }
180
+
181
+ $output .= "</div>";
182
+ }
183
+
184
+ return $output;
185
+ }
186
+
187
+ /**
188
+ * Checks if there is enough free disk space to create staging site
189
+ * Returns null when can't run disk_free_space function one way or another
190
+ * @return bool|null
191
+ */
192
+ public function hasFreeDiskSpace() {
193
+ if( !function_exists( "disk_free_space" ) ) {
194
+ return null;
195
+ }
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();
206
+ }
207
+
208
+
209
+ $data = array(
210
+ 'freespace' => $this->formatSize( $freeSpace ),
211
+ 'usedspace' => $this->formatSize( $this->getDirectorySizeInclSubdirs( ABSPATH ) )
212
+ );
213
+
214
+ echo json_encode( $data );
215
+ die();
216
+ }
217
+
218
+ /**
219
+ * Get Database Tables
220
+ */
221
+ protected function getTables() {
222
+ $wpDB = WPStaging::getInstance()->get( "wpdb" );
223
+
224
+ if( strlen( $wpDB->prefix ) > 0 ) {
225
+ //$prefix = str_replace('_', '', $wpDB->prefix);
226
+ $sql = "SHOW TABLE STATUS LIKE '{$wpDB->prefix}%'";
227
+ } else {
228
+ $sql = "SHOW TABLE STATUS";
229
+ }
230
+
231
+ $tables = $wpDB->get_results( $sql );
232
+
233
+ $currentTables = array();
234
+
235
+ // Reset excluded Tables than loop through all tables
236
+ $this->options->excludedTables = array();
237
+ foreach ( $tables as $table ) {
238
+
239
+ // Exclude WP Staging Tables
240
+ // if (0 === strpos($table->Name, "wpstg"))
241
+ // {
242
+ // continue;
243
+ // }
244
+ // Create array of unchecked tables
245
+ if( !empty( $wpDB->prefix ) && 0 !== strpos( $table->Name, $wpDB->prefix ) ) {
246
+ $this->options->excludedTables[] = $table->Name;
247
+ }
248
+
249
+
250
+ $currentTables[] = array(
251
+ "name" => $table->Name,
252
+ "size" => ($table->Data_length + $table->Index_length)
253
+ );
254
+ }
255
+
256
+ $this->options->tables = json_decode( json_encode( $currentTables ) );
257
+ }
258
+
259
+ /**
260
+ * Get directories and main meta data about'em recursively
261
+ */
262
+ protected function directories() {
263
+ $directories = new \DirectoryIterator( ABSPATH );
264
+
265
+ foreach ( $directories as $directory ) {
266
+ // Not a valid directory
267
+ if( false === ($path = $this->getPath( $directory )) ) {
268
+ continue;
269
+ }
270
+
271
+ $this->handleDirectory( $path );
272
+
273
+ // Get Sub-directories
274
+ $this->getSubDirectories( $directory->getRealPath() );
275
+ }
276
+
277
+ // Gather Plugins
278
+ $this->getSubDirectories( WP_PLUGIN_DIR );
279
+
280
+ // Gather Themes
281
+ $this->getSubDirectories( WP_CONTENT_DIR . DIRECTORY_SEPARATOR . "themes" );
282
+
283
+ // Gather Default Uploads Folder
284
+ //$this->getSubDirectories(WP_CONTENT_DIR . DIRECTORY_SEPARATOR . "uploads");
285
+ // Gather Custom Uploads Folder if there is one
286
+ $this->getSubDirectories( $this->getUploadDir() );
287
+ }
288
+
289
+ /**
290
+ * @param string $path
291
+ */
292
+ protected function getSubDirectories( $path ) {
293
+ $directories = new \DirectoryIterator( $path );
294
+
295
+ foreach ( $directories as $directory ) {
296
+ // Not a valid directory
297
+ if( false === ($path = $this->getPath( $directory )) ) {
298
+ continue;
299
+ }
300
+
301
+ $this->handleDirectory( $path );
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Get Path from $directory
307
+ * @param \SplFileInfo $directory
308
+ * @return string|false
309
+ */
310
+ protected function getPath( $directory ) {
311
+
312
+ /*
313
+ * Do not follow root path like src/web/..
314
+ * This must be done before \SplFileInfo->isDir() is used!
315
+ * Prevents open base dir restriction fatal errors
316
+ */
317
+ if( strpos( $directory->getRealPath(), ABSPATH ) !== 0 ) {
318
+ return false;
319
+ }
320
+ $path = str_replace( ABSPATH, null, $directory->getRealPath() );
321
+
322
+ // Using strpos() for symbolic links as they could create nasty stuff in nix stuff for directory structures
323
+ if( !$directory->isDir() || strlen( $path ) < 1 ) {
324
+ return false;
325
+ }
326
+
327
+ return $path;
328
+ }
329
+
330
+ /**
331
+ * Organizes $this->directories
332
+ * @param string $path
333
+ */
334
+ protected function handleDirectory( $path ) {
335
+
336
+ $directoryArray = explode( DIRECTORY_SEPARATOR, $path );
337
+ $total = (is_array($directoryArray) || $directoryArray instanceof Countable ) ? count( $directoryArray ) : 0;
338
+
339
+ if( $total < 1 ) {
340
+ return;
341
+ }
342
+
343
+ $total = $total - 1;
344
+ $currentArray = &$this->directories;
345
+
346
+ for ( $i = 0; $i <= $total; $i++ ) {
347
+ if( !isset( $currentArray[$directoryArray[$i]] ) ) {
348
+ $currentArray[$directoryArray[$i]] = array();
349
+ }
350
+
351
+ $currentArray = &$currentArray[$directoryArray[$i]];
352
+
353
+ // Attach meta data to the end
354
+ if( $i < $total ) {
355
+ continue;
356
+ }
357
+
358
+ $fullPath = ABSPATH . $path;
359
+ $size = $this->getDirectorySize( $fullPath );
360
+
361
+ $currentArray["metaData"] = array(
362
+ "size" => $size,
363
+ "path" => ABSPATH . $path,
364
+ );
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Gets size of given directory
370
+ * @param string $path
371
+ * @return int|null
372
+ */
373
+ protected function getDirectorySize( $path ) {
374
+ if( !isset( $this->settings->checkDirectorySize ) || '1' !== $this->settings->checkDirectorySize ) {
375
+ return null;
376
+ }
377
+
378
+ return $this->objDirectories->size( $path );
379
+ }
380
+
381
+ /**
382
+ * Get total size of a directory including all its subdirectories
383
+ * @param string $dir
384
+ * @return int
385
+ */
386
+ function getDirectorySizeInclSubdirs( $dir ) {
387
+ $size = 0;
388
+ foreach ( glob( rtrim( $dir, '/' ) . '/*', GLOB_NOSORT ) as $each ) {
389
+ $size += is_file( $each ) ? filesize( $each ) : $this->getDirectorySizeInclSubdirs( $each );
390
+ }
391
+ return $size;
392
+ }
393
+
394
+ /**
395
+ * Get relative WP uploads path
396
+ * @return string
397
+ */
398
+ protected function getUploadDir() {
399
+ $uploads = wp_upload_dir();
400
+ return $uploads['basedir'];
401
+ }
402
+
403
+ }
apps/Backend/Modules/Jobs/SearchReplace.php CHANGED
@@ -1,699 +1,694 @@
1
- <?php
2
-
3
- namespace WPStaging\Backend\Modules\Jobs;
4
-
5
- // No Direct Access
6
- if( !defined( "WPINC" ) ) {
7
- die;
8
- }
9
-
10
- use WPStaging\WPStaging;
11
- use WPStaging\Utils\Strings;
12
- use WPStaging\Utils\Helper;
13
-
14
- /**
15
- * Class Database
16
- * @package WPStaging\Backend\Modules\Jobs
17
- */
18
- class SearchReplace extends JobExecutable {
19
-
20
- /**
21
- * @var int
22
- */
23
- private $total = 0;
24
-
25
- /**
26
- * @var \WPDB
27
- */
28
- public $db;
29
-
30
- /**
31
- *
32
- * @var string
33
- */
34
- private $homeUrl;
35
-
36
- /**
37
- * The prefix of the new database tables which are used for the live site after updating tables
38
- * @var string
39
- */
40
- public $tmpPrefix;
41
-
42
- /**
43
- * Initialize
44
- */
45
- public function initialize() {
46
- $this->total = count( $this->options->tables );
47
- $this->db = WPStaging::getInstance()->get( "wpdb" );
48
- //$this->tmpPrefix = 'wpstgtmp_';
49
- $this->tmpPrefix = $this->options->prefix;
50
- $helper = new Helper();
51
- $this->homeUrl = $helper->get_home_url();
52
- }
53
-
54
- public function start() {
55
- // Skip job. Nothing to do
56
- if( $this->options->totalSteps === 0 ) {
57
- $this->prepareResponse( true, false );
58
- }
59
-
60
- $this->run();
61
-
62
- // Save option, progress
63
- $this->saveOptions();
64
-
65
- return ( object ) $this->response;
66
- }
67
-
68
- /**
69
- * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
70
- * @return void
71
- */
72
- protected function calculateTotalSteps() {
73
- $this->options->totalSteps = $this->total;
74
- }
75
-
76
- /**
77
- * Execute the Current Step
78
- * Returns false when over threshold limits are hit or when the job is done, true otherwise
79
- * @return bool
80
- */
81
- protected function execute() {
82
- // Over limits threshold
83
- if( $this->isOverThreshold() ) {
84
- // Prepare response and save current progress
85
- $this->prepareResponse( false, false );
86
- $this->saveOptions();
87
- return false;
88
- }
89
-
90
- // No more steps, finished
91
- if( $this->options->currentStep > $this->total || !isset( $this->options->tables[$this->options->currentStep] ) ) {
92
- $this->prepareResponse( true, false );
93
- return false;
94
- }
95
-
96
- // Table is excluded
97
- if( in_array( $this->options->tables[$this->options->currentStep], $this->options->excludedTables ) ) {
98
- $this->prepareResponse();
99
- return true;
100
- }
101
-
102
- // Search & Replace
103
- if( !$this->stopExecution() && !$this->updateTable( $this->options->tables[$this->options->currentStep] ) ) {
104
- // Prepare Response
105
- $this->prepareResponse( false, false );
106
-
107
- // Not finished
108
- return true;
109
- }
110
-
111
-
112
- // Prepare Response
113
- $this->prepareResponse();
114
-
115
- // Not finished
116
- return true;
117
- }
118
-
119
- // private function convertExcludedTables() {
120
- // $tmp = array();
121
- // foreach ( $this->options->excludedTables as $table ) {
122
- // $tmp[] = str_replace( $this->options->prefix, $this->tmpPrefix, $table );
123
- // }
124
- // $this->options->excludedTables = $tmp;
125
- // }
126
-
127
- /**
128
- * Stop Execution immediately
129
- * return mixed bool | json
130
- */
131
- private function stopExecution() {
132
- if( $this->db->prefix == $this->tmpPrefix ) {
133
- $this->returnException( 'Fatal Error 9: Prefix ' . $this->db->prefix . ' is used for the live site hence it can not be used for the staging site as well. Please ask support@wp-staging.com how to resolve this.' );
134
- }
135
- return false;
136
- }
137
-
138
- /**
139
- * Copy Tables
140
- * @param string $tableName
141
- * @return bool
142
- */
143
- private function updateTable( $tableName ) {
144
- $strings = new Strings();
145
- $table = $strings->str_replace_first( $this->db->prefix, '', $tableName );
146
- $newTableName = $this->tmpPrefix . $table;
147
-
148
- // Save current job
149
- $this->setJob( $newTableName );
150
-
151
- // Beginning of the job
152
- if( !$this->startJob( $newTableName, $tableName ) ) {
153
- return true;
154
- }
155
- // Copy data
156
- $this->startReplace( $newTableName );
157
-
158
- // Finis the step
159
- return $this->finishStep();
160
- }
161
-
162
- /**
163
- * Start search replace job
164
- * @param string $new
165
- * @param string $old
166
- */
167
- private function startReplace( $new ) {
168
- $rows = $this->options->job->start + $this->settings->querySRLimit;
169
- $this->log(
170
- "DB Processing: Table {$new} {$this->options->job->start} to {$rows} records"
171
- );
172
-
173
- // Search & Replace
174
- $this->searchReplace( $new, $rows, array() );
175
-
176
- // Set new offset
177
- $this->options->job->start += $this->settings->querySRLimit;
178
- }
179
-
180
- /**
181
- * Returns the number of pages in a table.
182
- * @access public
183
- * @return int
184
- */
185
- private function get_pages_in_table( $table ) {
186
- $table = esc_sql( $table );
187
- $rows = $this->db->get_var( "SELECT COUNT(*) FROM $table" );
188
- $pages = ceil( $rows / $this->settings->querySRLimit );
189
- return absint( $pages );
190
- }
191
-
192
- /**
193
- * Gets the columns in a table.
194
- * @access public
195
- * @param string $table The table to check.
196
- * @return array
197
- */
198
- private function get_columns( $table ) {
199
- $primary_key = null;
200
- $columns = array();
201
- $fields = $this->db->get_results( 'DESCRIBE ' . $table );
202
- if( is_array( $fields ) ) {
203
- foreach ( $fields as $column ) {
204
- $columns[] = $column->Field;
205
- if( $column->Key == 'PRI' ) {
206
- $primary_key = $column->Field;
207
- }
208
- }
209
- }
210
- return array($primary_key, $columns);
211
- }
212
-
213
- /**
214
- * Adapated from interconnect/it's search/replace script, adapted from Better Search Replace
215
- *
216
- * Modified to use WordPress wpdb functions instead of PHP's native mysql/pdo functions,
217
- * and to be compatible with batch processing.
218
- *
219
- * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
220
- *
221
- * @access public
222
- * @param string $table The table to run the replacement on.
223
- * @param int $page The page/block to begin the query on.
224
- * @param array $args An associative array containing arguements for this run.
225
- * @return array
226
- */
227
- private function searchReplace( $table, $page, $args ) {
228
-
229
-
230
- // Load up the default settings for this chunk.
231
- $table = esc_sql( $table );
232
- $current_page = $this->options->job->start + $this->settings->querySRLimit;
233
- $pages = $this->get_pages_in_table( $table );
234
- //$done = false;
235
-
236
-
237
- if( $this->isSubDir() ) {
238
- // Search URL example.com/staging and root path to staging site /var/www/htdocs/staging
239
- $args['search_for'] = array(
240
- rtrim( $this->homeUrl, "/" ) . $this->getSubDir(),
241
- rtrim( ABSPATH, '/' ),
242
- str_replace('/', '\/', rtrim( $this->homeUrl, '/' )) . str_replace('/', '\/', $this->getSubDir()) // // Used by revslider and several visual editors
243
-
244
- );
245
-
246
- $args['replace_with'] = array(
247
- rtrim( $this->homeUrl, "/" ) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName,
248
- rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName,
249
- str_replace('/', '\/', rtrim( $this->homeUrl, "/" )) . str_replace('/', '\/', $this->getSubDir()) . '\/' . $this->options->cloneDirectoryName, // Used by revslider and several visual editors
250
- );
251
- } else {
252
- $args['search_for'] = array(
253
- rtrim( $this->homeUrl, '/' ),
254
- rtrim( ABSPATH, '/' ),
255
- str_replace('/', '\/' , rtrim( $this->homeUrl, '/' ))
256
- );
257
- $args['replace_with'] = array(
258
- rtrim( $this->homeUrl, '/' ) . '/' . $this->options->cloneDirectoryName,
259
- rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName,
260
- str_replace('/', '\/', rtrim( $this->homeUrl, '/' )) . '\/' . $this->options->cloneDirectoryName,
261
- );
262
- }
263
-
264
-
265
- $args['replace_guids'] = 'off';
266
- $args['dry_run'] = 'off';
267
- $args['case_insensitive'] = false;
268
- $args['replace_guids'] = 'off';
269
-
270
- // Get a list of columns in this table.
271
- list( $primary_key, $columns ) = $this->get_columns( $table );
272
-
273
- // Bail out early if there isn't a primary key.
274
- // We commented this to search & replace through tables which have no primary keys like wp_revslider_slides
275
- // @todo test this carefully. If it causes (performance) issues we need to activate it again!
276
- // @since 2.4.4
277
-
278
- // if( null === $primary_key ) {
279
- // return false;
280
- // }
281
-
282
- $current_row = 0;
283
- $start = $this->options->job->start;
284
- $end = $this->settings->querySRLimit;
285
-
286
- // Grab the content of the table.
287
- $data = $this->db->get_results( "SELECT * FROM $table LIMIT $start, $end", ARRAY_A );
288
-
289
- // Filter certain rows (of other plugins)
290
- $filter = array(
291
- 'Admin_custome_login_Slidshow',
292
- 'Admin_custome_login_Social',
293
- 'Admin_custome_login_logo',
294
- 'Admin_custome_login_text',
295
- 'Admin_custome_login_login',
296
- 'Admin_custome_login_top',
297
- 'Admin_custome_login_dashboard',
298
- 'Admin_custome_login_Version',
299
- );
300
-
301
- apply_filters('wpstg_fiter_search_replace_rows', $filter);
302
-
303
- // Loop through the data.
304
- foreach ( $data as $row ) {
305
- $current_row++;
306
- $update_sql = array();
307
- $where_sql = array();
308
- $upd = false;
309
-
310
- // Skip rows below
311
- if (isset($row['option_name']) && in_array($row['option_name'], $filter)){
312
- continue;
313
- }
314
-
315
- // Skip rows with transients (They can store huge data and we need to save memory)
316
- if( isset( $row['option_name'] ) && strpos( $row['option_name'], '_transient' ) === 0 ) {
317
- continue;
318
- }
319
-
320
- foreach ( $columns as $column ) {
321
-
322
- $dataRow = $row[$column];
323
-
324
- if( $column == $primary_key ) {
325
- $where_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
326
- continue;
327
- }
328
-
329
- // Skip GUIDs by default.
330
- if( 'on' !== $args['replace_guids'] && 'guid' == $column ) {
331
- continue;
332
- }
333
-
334
-
335
- // Check options table
336
- if( $this->options->prefix . 'options' === $table ) {
337
-
338
- // Skip certain options
339
- if( isset( $should_skip ) && true === $should_skip ) {
340
- $should_skip = false;
341
- continue;
342
- }
343
-
344
- // Skip this row
345
- if( 'wpstg_existing_clones_beta' === $dataRow ||
346
- 'wpstg_existing_clones' === $dataRow ||
347
- 'wpstg_settings' === $dataRow ||
348
- 'wpstg_license_status' === $dataRow ||
349
- 'siteurl' === $dataRow ||
350
- 'home' === $dataRow
351
- ) {
352
- $should_skip = true;
353
- }
354
- }
355
-
356
- // Run a search replace on the data that'll respect the serialisation.
357
- $i = 0;
358
- foreach ( $args['search_for'] as $replace ) {
359
- $dataRow = $this->recursive_unserialize_replace( $args['search_for'][$i], $args['replace_with'][$i], $dataRow, false, $args['case_insensitive'] );
360
- $i++;
361
- }
362
- unset( $replace );
363
- unset( $i );
364
-
365
- // Something was changed
366
- if( $row[$column] != $dataRow ) {
367
- $update_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
368
- $upd = true;
369
- }
370
- }
371
-
372
- // Determine what to do with updates.
373
- if( $args['dry_run'] === 'on' ) {
374
- // Don't do anything if a dry run
375
- } elseif( $upd && !empty( $where_sql ) ) {
376
- // If there are changes to make, run the query.
377
- $sql = 'UPDATE ' . $table . ' SET ' . implode( ', ', $update_sql ) . ' WHERE ' . implode( ' AND ', array_filter( $where_sql ) );
378
- $result = $this->db->query( $sql );
379
-
380
- if( !$result ) {
381
- $this->log( "Error updating row {$current_row}", \WPStaging\Utils\Logger::TYPE_ERROR );
382
- }
383
- }
384
- } // end row loop
385
- unset( $row );
386
- unset( $update_sql );
387
- unset( $where_sql );
388
- unset( $sql );
389
-
390
-
391
- // DB Flush
392
- $this->db->flush();
393
- return true;
394
- }
395
-
396
- /**
397
- * Adapted from interconnect/it's search/replace script.
398
- *
399
- * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
400
- *
401
- * Take a serialised array and unserialise it replacing elements as needed and
402
- * unserialising any subordinate arrays and performing the replace on those too.
403
- *
404
- * @access private
405
- * @param string $from String we're looking to replace.
406
- * @param string $to What we want it to be replaced with
407
- * @param array $data Used to pass any subordinate arrays back to in.
408
- * @param boolean $serialised Does the array passed via $data need serialising.
409
- * @param sting|boolean $case_insensitive Set to 'on' if we should ignore case, false otherwise.
410
- *
411
- * @return string|array The original array with all elements replaced as needed.
412
- */
413
- // private function recursive_unserialize_replace( $from = '', $to = '', $data = '', $serialised = false, $case_insensitive = false ) {
414
- // try {
415
- //
416
- // if( is_string( $data ) && !is_serialized_string( $data ) && ( $unserialized = $this->unserialize( $data ) ) !== false ) {
417
- // $data = $this->recursive_unserialize_replace( $from, $to, $unserialized, true, $case_insensitive );
418
- // } elseif( is_array( $data ) ) {
419
- // $_tmp = array();
420
- // foreach ( $data as $key => $value ) {
421
- // $_tmp[$key] = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
422
- // }
423
- //
424
- // $data = $_tmp;
425
- // unset( $_tmp );
426
- // }
427
- //
428
- // // Submitted by Tina Matter
429
- // elseif( $this->isValidObject($data) ) {
430
- // $_tmp = $data; // new $data_class( );
431
- // $props = get_object_vars( $data );
432
- // foreach ( $props as $key => $value ) {
433
- // if( $key === '' || ord( $key[0] ) === 0 ) {
434
- // continue;
435
- // }
436
- // $_tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
437
- // }
438
- //
439
- // $data = $_tmp;
440
- // unset($_tmp);
441
- // } elseif (is_serialized_string($data)) {
442
- // if (false !== ($data = $this->unserialize($data)) ) {
443
- // $data = $this->str_replace($from, $to, $data, $case_insensitive);
444
- // $data = serialize($data);
445
- // }
446
- // } else {
447
- // if( is_string( $data ) ) {
448
- // $data = $this->str_replace( $from, $to, $data, $case_insensitive );
449
- // }
450
- // }
451
- //
452
- // if( $serialised ) {
453
- // return serialize( $data );
454
- // }
455
- // } catch ( Exception $error ) {
456
- //
457
- // }
458
- //
459
- // return $data;
460
- // }
461
- private function recursive_unserialize_replace( $from = '', $to = '', $data = '', $serialized = false, $case_insensitive = false ) {
462
- try {
463
- // Some unserialized data cannot be re-serialized eg. SimpleXMLElements
464
- if( is_serialized( $data ) && ( $unserialized = @unserialize( $data ) ) !== false ) {
465
- $data = $this->recursive_unserialize_replace( $from, $to, $unserialized, true, $case_insensitive );
466
- } elseif( is_array( $data ) ) {
467
- $tmp = array();
468
- foreach ( $data as $key => $value ) {
469
- $tmp[$key] = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
470
- }
471
-
472
- $data = $tmp;
473
- unset( $tmp );
474
- } elseif( is_object( $data ) ) {
475
- $tmp = $data;
476
- $props = get_object_vars( $data );
477
- foreach ( $props as $key => $value ) {
478
- if( $key === '' || ord( $key[0] ) === 0 ) {
479
- continue;
480
- }
481
- $tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
482
- }
483
-
484
- $data = $tmp;
485
- unset( $tmp );
486
- } else {
487
- if( is_string( $data ) ) {
488
- if( !empty( $from ) && !empty( $to ) ) {
489
- $data = $this->str_replace( $from, $to, $data, $case_insensitive );
490
- }
491
- }
492
- }
493
-
494
- if( $serialized ) {
495
- return serialize( $data );
496
- }
497
- } catch ( Exception $error ) {
498
-
499
- }
500
-
501
- return $data;
502
- }
503
-
504
- /**
505
- * Check if the object is a valid one and not __PHP_Incomplete_Class_Name
506
- * Can not use is_object alone because in php 7.2 it's returning true even though object is __PHP_Incomplete_Class_Name
507
- * @return boolean
508
- */
509
- // private function isValidObject($data){
510
- // if( !is_object( $data ) || gettype( $data ) != 'object' ) {
511
- // return false;
512
- // }
513
- //
514
- // $invalid_class_props = get_object_vars( $data );
515
- //
516
- // if (!isset($invalid_class_props['__PHP_Incomplete_Class_Name'])){
517
- // // Assume it must be an valid object
518
- // return true;
519
- // }
520
- //
521
- // $invalid_object_class = $invalid_class_props['__PHP_Incomplete_Class_Name'];
522
- //
523
- // if( !empty( $invalid_object_class ) ) {
524
- // return false;
525
- // }
526
- //
527
- // // Assume it must be an valid object
528
- // return true;
529
- // }
530
-
531
- /**
532
- * Mimics the mysql_real_escape_string function. Adapted from a post by 'feedr' on php.net.
533
- * @link http://php.net/manual/en/function.mysql-real-escape-string.php#101248
534
- * @access public
535
- * @param string $input The string to escape.
536
- * @return string
537
- */
538
- private function mysql_escape_mimic( $input ) {
539
- if( is_array( $input ) ) {
540
- return array_map( __METHOD__, $input );
541
- }
542
- if( !empty( $input ) && is_string( $input ) ) {
543
- return str_replace( array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $input );
544
- }
545
-
546
- return $input;
547
- }
548
-
549
- /**
550
- * Return unserialized object or array
551
- *
552
- * @param string $serialized_string Serialized string.
553
- * @param string $method The name of the caller method.
554
- *
555
- * @return mixed, false on failure
556
- */
557
- private static function unserialize( $serialized_string ) {
558
- if( !is_serialized( $serialized_string ) ) {
559
- return false;
560
- }
561
-
562
- $serialized_string = trim( $serialized_string );
563
- $unserialized_string = @unserialize( $serialized_string );
564
-
565
- return $unserialized_string;
566
- }
567
-
568
- /**
569
- * Wrapper for str_replace
570
- *
571
- * @param string $from
572
- * @param string $to
573
- * @param string $data
574
- * @param string|bool $case_insensitive
575
- *
576
- * @return string
577
- */
578
- private function str_replace( $from, $to, $data, $case_insensitive = false ) {
579
- if( 'on' === $case_insensitive ) {
580
- $data = str_ireplace( $from, $to, $data );
581
- } else {
582
- $data = str_replace( $from, $to, $data );
583
- }
584
-
585
- return $data;
586
- }
587
-
588
- /**
589
- * Set the job
590
- * @param string $table
591
- */
592
- private function setJob( $table ) {
593
- if( !empty( $this->options->job->current ) ) {
594
- return;
595
- }
596
-
597
- $this->options->job->current = $table;
598
- $this->options->job->start = 0;
599
- }
600
-
601
- /**
602
- * Start Job
603
- * @param string $new
604
- * @param string $old
605
- * @return bool
606
- */
607
- private function startJob( $new, $old ) {
608
- if( 0 != $this->options->job->start ) {
609
- return true;
610
- }
611
-
612
- $this->options->job->total = ( int ) $this->db->get_var( "SELECT COUNT(1) FROM {$old}" );
613
-
614
- if( 0 == $this->options->job->total ) {
615
- $this->finishStep();
616
- return false;
617
- }
618
-
619
- return true;
620
- }
621
-
622
- /**
623
- * Finish the step
624
- */
625
- private function finishStep() {
626
- // This job is not finished yet
627
- if( $this->options->job->total > $this->options->job->start ) {
628
- return false;
629
- }
630
-
631
- // Add it to cloned tables listing
632
- $this->options->clonedTables[] = $this->options->tables[$this->options->currentStep];
633
-
634
- // Reset job
635
- $this->options->job = new \stdClass();
636
-
637
- return true;
638
- }
639
-
640
- /**
641
- * Drop table if necessary
642
- * @param string $new
643
- */
644
- private function dropTable( $new ) {
645
- $old = $this->db->get_var( $this->db->prepare( "SHOW TABLES LIKE %s", $new ) );
646
-
647
- if( !$this->shouldDropTable( $new, $old ) ) {
648
- return;
649
- }
650
-
651
- $this->log( "DB Processing: {$new} already exists, dropping it first" );
652
- $this->db->query( "DROP TABLE {$new}" );
653
- }
654
-
655
- /**
656
- * Check if table needs to be dropped
657
- * @param string $new
658
- * @param string $old
659
- * @return bool
660
- */
661
- private function shouldDropTable( $new, $old ) {
662
- return (
663
- $old == $new &&
664
- (
665
- !isset( $this->options->job->current ) ||
666
- !isset( $this->options->job->start ) ||
667
- 0 == $this->options->job->start
668
- )
669
- );
670
- }
671
-
672
- /**
673
- * Check if WP is installed in subdir
674
- * @return boolean
675
- */
676
- private function isSubDir(){
677
- if ( get_option( 'siteurl' ) !== get_option( 'home' ) ) {
678
- return true;
679
- }
680
- return false;
681
- }
682
-
683
- /**
684
- * Get the install sub directory if WP is installed in sub directory
685
- * @return string
686
- */
687
- private function getSubDir() {
688
- $home = get_option( 'home' );
689
- $siteurl = get_option( 'siteurl' );
690
-
691
- if( empty( $home ) || empty( $siteurl ) ) {
692
- return '/';
693
- }
694
-
695
- $dir = str_replace( $home, '', $siteurl );
696
- return '/' . str_replace( '/', '', $dir );
697
- }
698
-
699
- }
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Jobs;
4
+
5
+ // No Direct Access
6
+ if( !defined( "WPINC" ) ) {
7
+ die;
8
+ }
9
+
10
+ use WPStaging\WPStaging;
11
+ use WPStaging\Utils\Strings;
12
+ use WPStaging\Utils\Helper;
13
+
14
+ /**
15
+ * Class Database
16
+ * @package WPStaging\Backend\Modules\Jobs
17
+ */
18
+ class SearchReplace extends JobExecutable {
19
+
20
+ /**
21
+ * @var int
22
+ */
23
+ private $total = 0;
24
+
25
+ /**
26
+ * @var \WPDB
27
+ */
28
+ public $db;
29
+
30
+ /**
31
+ *
32
+ * @var string
33
+ */
34
+ private $homeUrl;
35
+
36
+ /**
37
+ * The prefix of the new database tables which are used for the live site after updating tables
38
+ * @var string
39
+ */
40
+ public $tmpPrefix;
41
+
42
+ /**
43
+ * Initialize
44
+ */
45
+ public function initialize() {
46
+ $this->total = count( $this->options->tables );
47
+ $this->db = WPStaging::getInstance()->get( "wpdb" );
48
+ //$this->tmpPrefix = 'wpstgtmp_';
49
+ $this->tmpPrefix = $this->options->prefix;
50
+ $helper = new Helper();
51
+ //$this->homeUrl = $helper->get_home_url();
52
+ $this->homeUrl = $helper->get_home_url_without_scheme();
53
+
54
+ }
55
+
56
+ public function start() {
57
+ // Skip job. Nothing to do
58
+ if( $this->options->totalSteps === 0 ) {
59
+ $this->prepareResponse( true, false );
60
+ }
61
+
62
+ $this->run();
63
+
64
+ // Save option, progress
65
+ $this->saveOptions();
66
+
67
+ return ( object ) $this->response;
68
+ }
69
+
70
+ /**
71
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
72
+ * @return void
73
+ */
74
+ protected function calculateTotalSteps() {
75
+ $this->options->totalSteps = $this->total;
76
+ }
77
+
78
+ /**
79
+ * Execute the Current Step
80
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
81
+ * @return bool
82
+ */
83
+ protected function execute() {
84
+ // Over limits threshold
85
+ if( $this->isOverThreshold() ) {
86
+ // Prepare response and save current progress
87
+ $this->prepareResponse( false, false );
88
+ $this->saveOptions();
89
+ return false;
90
+ }
91
+
92
+ // No more steps, finished
93
+ if( $this->options->currentStep > $this->total || !isset( $this->options->tables[$this->options->currentStep] ) ) {
94
+ $this->prepareResponse( true, false );
95
+ return false;
96
+ }
97
+
98
+ // Table is excluded
99
+ if( in_array( $this->options->tables[$this->options->currentStep], $this->options->excludedTables ) ) {
100
+ $this->prepareResponse();
101
+ return true;
102
+ }
103
+
104
+ // Search & Replace
105
+ if( !$this->stopExecution() && !$this->updateTable( $this->options->tables[$this->options->currentStep] ) ) {
106
+ // Prepare Response
107
+ $this->prepareResponse( false, false );
108
+
109
+ // Not finished
110
+ return true;
111
+ }
112
+
113
+
114
+ // Prepare Response
115
+ $this->prepareResponse();
116
+
117
+ // Not finished
118
+ return true;
119
+ }
120
+
121
+
122
+ /**
123
+ * Stop Execution immediately
124
+ * return mixed bool | json
125
+ */
126
+ private function stopExecution() {
127
+ if( $this->db->prefix == $this->tmpPrefix ) {
128
+ $this->returnException( 'Fatal Error 9: Prefix ' . $this->db->prefix . ' is used for the live site hence it can not be used for the staging site as well. Please ask support@wp-staging.com how to resolve this.' );
129
+ }
130
+ return false;
131
+ }
132
+
133
+ /**
134
+ * Copy Tables
135
+ * @param string $tableName
136
+ * @return bool
137
+ */
138
+ private function updateTable( $tableName ) {
139
+ $strings = new Strings();
140
+ $table = $strings->str_replace_first( $this->db->prefix, '', $tableName );
141
+ $newTableName = $this->tmpPrefix . $table;
142
+
143
+ // Save current job
144
+ $this->setJob( $newTableName );
145
+
146
+ // Beginning of the job
147
+ if( !$this->startJob( $newTableName, $tableName ) ) {
148
+ return true;
149
+ }
150
+ // Copy data
151
+ $this->startReplace( $newTableName );
152
+
153
+ // Finis the step
154
+ return $this->finishStep();
155
+ }
156
+
157
+ /**
158
+ * Start search replace job
159
+ * @param string $new
160
+ * @param string $old
161
+ */
162
+ private function startReplace( $new ) {
163
+ $rows = $this->options->job->start + $this->settings->querySRLimit;
164
+ $this->log(
165
+ "DB Processing: Table {$new} {$this->options->job->start} to {$rows} records"
166
+ );
167
+
168
+ // Search & Replace
169
+ $this->searchReplace( $new, $rows, array() );
170
+
171
+ // Set new offset
172
+ $this->options->job->start += $this->settings->querySRLimit;
173
+ }
174
+
175
+ /**
176
+ * Returns the number of pages in a table.
177
+ * @access public
178
+ * @return int
179
+ */
180
+ private function get_pages_in_table( $table ) {
181
+ $table = esc_sql( $table );
182
+ $rows = $this->db->get_var( "SELECT COUNT(*) FROM $table" );
183
+ $pages = ceil( $rows / $this->settings->querySRLimit );
184
+ return absint( $pages );
185
+ }
186
+
187
+ /**
188
+ * Gets the columns in a table.
189
+ * @access public
190
+ * @param string $table The table to check.
191
+ * @return array
192
+ */
193
+ private function get_columns( $table ) {
194
+ $primary_key = null;
195
+ $columns = array();
196
+ $fields = $this->db->get_results( 'DESCRIBE ' . $table );
197
+ if( is_array( $fields ) ) {
198
+ foreach ( $fields as $column ) {
199
+ $columns[] = $column->Field;
200
+ if( $column->Key == 'PRI' ) {
201
+ $primary_key = $column->Field;
202
+ }
203
+ }
204
+ }
205
+ return array($primary_key, $columns);
206
+ }
207
+
208
+ /**
209
+ * Adapated from interconnect/it's search/replace script, adapted from Better Search Replace
210
+ *
211
+ * Modified to use WordPress wpdb functions instead of PHP's native mysql/pdo functions,
212
+ * and to be compatible with batch processing.
213
+ *
214
+ * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
215
+ *
216
+ * @access public
217
+ * @param string $table The table to run the replacement on.
218
+ * @param int $page The page/block to begin the query on.
219
+ * @param array $args An associative array containing arguements for this run.
220
+ * @return array
221
+ */
222
+ private function searchReplace( $table, $page, $args ) {
223
+
224
+
225
+ // Load up the default settings for this chunk.
226
+ $table = esc_sql( $table );
227
+ $current_page = $this->options->job->start + $this->settings->querySRLimit;
228
+ $pages = $this->get_pages_in_table( $table );
229
+ //$done = false;
230
+
231
+
232
+ if( $this->isSubDir() ) {
233
+ // Search URL example.com/staging and root path to staging site /var/www/htdocs/staging
234
+ $args['search_for'] = array(
235
+ rtrim( $this->homeUrl, "/" ) . $this->getSubDir(),
236
+ rtrim( ABSPATH, '/' ),
237
+ $this->homeUrl . str_replace('/', '\/', $this->getSubDir()) // // Used by revslider and several visual editors
238
+
239
+ );
240
+
241
+ $args['replace_with'] = array(
242
+ rtrim( $this->homeUrl, "/" ) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName,
243
+ rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName,
244
+ $this->homeUrl . str_replace('/', '\/', $this->getSubDir()) . '\/' . $this->options->cloneDirectoryName, // Used by revslider and several visual editors
245
+ );
246
+ } else {
247
+ $args['search_for'] = array(
248
+ rtrim( $this->homeUrl, '/' ),
249
+ rtrim( ABSPATH, '/' ),
250
+ $this->homeUrl . '\/'
251
+ );
252
+ $args['replace_with'] = array(
253
+ rtrim( $this->homeUrl, '/' ) . '/' . $this->options->cloneDirectoryName,
254
+ rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName,
255
+ $this->homeUrl . '\/' . $this->options->cloneDirectoryName,
256
+ );
257
+ }
258
+
259
+
260
+ $args['replace_guids'] = 'off';
261
+ $args['dry_run'] = 'off';
262
+ $args['case_insensitive'] = false;
263
+ $args['replace_guids'] = 'off';
264
+
265
+ // Get a list of columns in this table.
266
+ list( $primary_key, $columns ) = $this->get_columns( $table );
267
+
268
+ // Bail out early if there isn't a primary key.
269
+ // We commented this to search & replace through tables which have no primary keys like wp_revslider_slides
270
+ // @todo test this carefully. If it causes (performance) issues we need to activate it again!
271
+ // @since 2.4.4
272
+
273
+ // if( null === $primary_key ) {
274
+ // return false;
275
+ // }
276
+
277
+ $current_row = 0;
278
+ $start = $this->options->job->start;
279
+ $end = $this->settings->querySRLimit;
280
+
281
+ // Grab the content of the table.
282
+ $data = $this->db->get_results( "SELECT * FROM $table LIMIT $start, $end", ARRAY_A );
283
+
284
+ // Filter certain rows (of other plugins)
285
+ $filter = array(
286
+ 'Admin_custome_login_Slidshow',
287
+ 'Admin_custome_login_Social',
288
+ 'Admin_custome_login_logo',
289
+ 'Admin_custome_login_text',
290
+ 'Admin_custome_login_login',
291
+ 'Admin_custome_login_top',
292
+ 'Admin_custome_login_dashboard',
293
+ 'Admin_custome_login_Version',
294
+ );
295
+
296
+ apply_filters('wpstg_fiter_search_replace_rows', $filter);
297
+
298
+ // Loop through the data.
299
+ foreach ( $data as $row ) {
300
+ $current_row++;
301
+ $update_sql = array();
302
+ $where_sql = array();
303
+ $upd = false;
304
+
305
+ // Skip rows below
306
+ if (isset($row['option_name']) && in_array($row['option_name'], $filter)){
307
+ continue;
308
+ }
309
+
310
+ // Skip rows with transients (They can store huge data and we need to save memory)
311
+ if( isset( $row['option_name'] ) && strpos( $row['option_name'], '_transient' ) === 0 ) {
312
+ continue;
313
+ }
314
+
315
+ foreach ( $columns as $column ) {
316
+
317
+ $dataRow = $row[$column];
318
+
319
+ if( $column == $primary_key ) {
320
+ $where_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
321
+ continue;
322
+ }
323
+
324
+ // Skip GUIDs by default.
325
+ if( 'on' !== $args['replace_guids'] && 'guid' == $column ) {
326
+ continue;
327
+ }
328
+
329
+
330
+ // Check options table
331
+ if( $this->options->prefix . 'options' === $table ) {
332
+
333
+ // Skip certain options
334
+ if( isset( $should_skip ) && true === $should_skip ) {
335
+ $should_skip = false;
336
+ continue;
337
+ }
338
+
339
+ // Skip this row
340
+ if( 'wpstg_existing_clones_beta' === $dataRow ||
341
+ 'wpstg_existing_clones' === $dataRow ||
342
+ 'wpstg_settings' === $dataRow ||
343
+ 'wpstg_license_status' === $dataRow ||
344
+ 'siteurl' === $dataRow ||
345
+ 'home' === $dataRow
346
+ ) {
347
+ $should_skip = true;
348
+ }
349
+ }
350
+
351
+ // Run a search replace on the data that'll respect the serialisation.
352
+ $i = 0;
353
+ foreach ( $args['search_for'] as $replace ) {
354
+ $dataRow = $this->recursive_unserialize_replace( $args['search_for'][$i], $args['replace_with'][$i], $dataRow, false, $args['case_insensitive'] );
355
+ $i++;
356
+ }
357
+ unset( $replace );
358
+ unset( $i );
359
+
360
+ // Something was changed
361
+ if( $row[$column] != $dataRow ) {
362
+ $update_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
363
+ $upd = true;
364
+ }
365
+ }
366
+
367
+ // Determine what to do with updates.
368
+ if( $args['dry_run'] === 'on' ) {
369
+ // Don't do anything if a dry run
370
+ } elseif( $upd && !empty( $where_sql ) ) {
371
+ // If there are changes to make, run the query.
372
+ $sql = 'UPDATE ' . $table . ' SET ' . implode( ', ', $update_sql ) . ' WHERE ' . implode( ' AND ', array_filter( $where_sql ) );
373
+ $result = $this->db->query( $sql );
374
+
375
+ if( !$result ) {
376
+ $this->log( "Error updating row {$current_row}", \WPStaging\Utils\Logger::TYPE_ERROR );
377
+ }
378
+ }
379
+ } // end row loop
380
+ unset( $row );
381
+ unset( $update_sql );
382
+ unset( $where_sql );
383
+ unset( $sql );
384
+
385
+
386
+ // DB Flush
387
+ $this->db->flush();
388
+ return true;
389
+ }
390
+
391
+ /**
392
+ * Adapted from interconnect/it's search/replace script.
393
+ *
394
+ * @link https://interconnectit.com/products/search-and-replace-for-wordpress-databases/
395
+ *
396
+ * Take a serialised array and unserialise it replacing elements as needed and
397
+ * unserialising any subordinate arrays and performing the replace on those too.
398
+ *
399
+ * @access private
400
+ * @param string $from String we're looking to replace.
401
+ * @param string $to What we want it to be replaced with
402
+ * @param array $data Used to pass any subordinate arrays back to in.
403
+ * @param boolean $serialised Does the array passed via $data need serialising.
404
+ * @param sting|boolean $case_insensitive Set to 'on' if we should ignore case, false otherwise.
405
+ *
406
+ * @return string|array The original array with all elements replaced as needed.
407
+ */
408
+ // private function recursive_unserialize_replace( $from = '', $to = '', $data = '', $serialised = false, $case_insensitive = false ) {
409
+ // try {
410
+ //
411
+ // if( is_string( $data ) && !is_serialized_string( $data ) && ( $unserialized = $this->unserialize( $data ) ) !== false ) {
412
+ // $data = $this->recursive_unserialize_replace( $from, $to, $unserialized, true, $case_insensitive );
413
+ // } elseif( is_array( $data ) ) {
414
+ // $_tmp = array();
415
+ // foreach ( $data as $key => $value ) {
416
+ // $_tmp[$key] = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
417
+ // }
418
+ //
419
+ // $data = $_tmp;
420
+ // unset( $_tmp );
421
+ // }
422
+ //
423
+ // // Submitted by Tina Matter
424
+ // elseif( $this->isValidObject($data) ) {
425
+ // $_tmp = $data; // new $data_class( );
426
+ // $props = get_object_vars( $data );
427
+ // foreach ( $props as $key => $value ) {
428
+ // if( $key === '' || ord( $key[0] ) === 0 ) {
429
+ // continue;
430
+ // }
431
+ // $_tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
432
+ // }
433
+ //
434
+ // $data = $_tmp;
435
+ // unset($_tmp);
436
+ // } elseif (is_serialized_string($data)) {
437
+ // if (false !== ($data = $this->unserialize($data)) ) {
438
+ // $data = $this->str_replace($from, $to, $data, $case_insensitive);
439
+ // $data = serialize($data);
440
+ // }
441
+ // } else {
442
+ // if( is_string( $data ) ) {
443
+ // $data = $this->str_replace( $from, $to, $data, $case_insensitive );
444
+ // }
445
+ // }
446
+ //
447
+ // if( $serialised ) {
448
+ // return serialize( $data );
449
+ // }
450
+ // } catch ( Exception $error ) {
451
+ //
452
+ // }
453
+ //
454
+ // return $data;
455
+ // }
456
+ private function recursive_unserialize_replace( $from = '', $to = '', $data = '', $serialized = false, $case_insensitive = false ) {
457
+ try {
458
+ // Some unserialized data cannot be re-serialized eg. SimpleXMLElements
459
+ if( is_serialized( $data ) && ( $unserialized = @unserialize( $data ) ) !== false ) {
460
+ $data = $this->recursive_unserialize_replace( $from, $to, $unserialized, true, $case_insensitive );
461
+ } elseif( is_array( $data ) ) {
462
+ $tmp = array();
463
+ foreach ( $data as $key => $value ) {
464
+ $tmp[$key] = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
465
+ }
466
+
467
+ $data = $tmp;
468
+ unset( $tmp );
469
+ } elseif( is_object( $data ) ) {
470
+ $tmp = $data;
471
+ $props = get_object_vars( $data );
472
+ foreach ( $props as $key => $value ) {
473
+ if( $key === '' || ord( $key[0] ) === 0 ) {
474
+ continue;
475
+ }
476
+ $tmp->$key = $this->recursive_unserialize_replace( $from, $to, $value, false, $case_insensitive );
477
+ }
478
+
479
+ $data = $tmp;
480
+ unset( $tmp );
481
+ } else {
482
+ if( is_string( $data ) ) {
483
+ if( !empty( $from ) && !empty( $to ) ) {
484
+ $data = $this->str_replace( $from, $to, $data, $case_insensitive );
485
+ }
486
+ }
487
+ }
488
+
489
+ if( $serialized ) {
490
+ return serialize( $data );
491
+ }
492
+ } catch ( Exception $error ) {
493
+
494
+ }
495
+
496
+ return $data;
497
+ }
498
+
499
+ /**
500
+ * Check if the object is a valid one and not __PHP_Incomplete_Class_Name
501
+ * Can not use is_object alone because in php 7.2 it's returning true even though object is __PHP_Incomplete_Class_Name
502
+ * @return boolean
503
+ */
504
+ // private function isValidObject($data){
505
+ // if( !is_object( $data ) || gettype( $data ) != 'object' ) {
506
+ // return false;
507
+ // }
508
+ //
509
+ // $invalid_class_props = get_object_vars( $data );
510
+ //
511
+ // if (!isset($invalid_class_props['__PHP_Incomplete_Class_Name'])){
512
+ // // Assume it must be an valid object
513
+ // return true;
514
+ // }
515
+ //
516
+ // $invalid_object_class = $invalid_class_props['__PHP_Incomplete_Class_Name'];
517
+ //
518
+ // if( !empty( $invalid_object_class ) ) {
519
+ // return false;
520
+ // }
521
+ //
522
+ // // Assume it must be an valid object
523
+ // return true;
524
+ // }
525
+
526
+ /**
527
+ * Mimics the mysql_real_escape_string function. Adapted from a post by 'feedr' on php.net.
528
+ * @link http://php.net/manual/en/function.mysql-real-escape-string.php#101248
529
+ * @access public
530
+ * @param string $input The string to escape.
531
+ * @return string
532
+ */
533
+ private function mysql_escape_mimic( $input ) {
534
+ if( is_array( $input ) ) {
535
+ return array_map( __METHOD__, $input );
536
+ }
537
+ if( !empty( $input ) && is_string( $input ) ) {
538
+ return str_replace( array('\\', "\0", "\n", "\r", "'", '"', "\x1a"), array('\\\\', '\\0', '\\n', '\\r', "\\'", '\\"', '\\Z'), $input );
539
+ }
540
+
541
+ return $input;
542
+ }
543
+
544
+ /**
545
+ * Return unserialized object or array
546
+ *
547
+ * @param string $serialized_string Serialized string.
548
+ * @param string $method The name of the caller method.
549
+ *
550
+ * @return mixed, false on failure
551
+ */
552
+ private static function unserialize( $serialized_string ) {
553
+ if( !is_serialized( $serialized_string ) ) {
554
+ return false;
555
+ }
556
+
557
+ $serialized_string = trim( $serialized_string );
558
+ $unserialized_string = @unserialize( $serialized_string );
559
+
560
+ return $unserialized_string;
561
+ }
562
+
563
+ /**
564
+ * Wrapper for str_replace
565
+ *
566
+ * @param string $from
567
+ * @param string $to
568
+ * @param string $data
569
+ * @param string|bool $case_insensitive
570
+ *
571
+ * @return string
572
+ */
573
+ private function str_replace( $from, $to, $data, $case_insensitive = false ) {
574
+ if( 'on' === $case_insensitive ) {
575
+ $data = str_ireplace( $from, $to, $data );
576
+ } else {
577
+ $data = str_replace( $from, $to, $data );
578
+ }
579
+
580
+ return $data;
581
+ }
582
+
583
+ /**
584
+ * Set the job
585
+ * @param string $table
586
+ */
587
+ private function setJob( $table ) {
588
+ if( !empty( $this->options->job->current ) ) {
589
+ return;
590
+ }
591
+
592
+ $this->options->job->current = $table;
593
+ $this->options->job->start = 0;
594
+ }
595
+
596
+ /**
597
+ * Start Job
598
+ * @param string $new
599
+ * @param string $old
600
+ * @return bool
601
+ */
602
+ private function startJob( $new, $old ) {
603
+ if( 0 != $this->options->job->start ) {
604
+ return true;
605
+ }
606
+
607
+ $this->options->job->total = ( int ) $this->db->get_var( "SELECT COUNT(1) FROM {$old}" );
608
+
609
+ if( 0 == $this->options->job->total ) {
610
+ $this->finishStep();
611
+ return false;
612
+ }
613
+
614
+ return true;
615
+ }
616
+
617
+ /**
618
+ * Finish the step
619
+ */
620
+ private function finishStep() {
621
+ // This job is not finished yet
622
+ if( $this->options->job->total > $this->options->job->start ) {
623
+ return false;
624
+ }
625
+
626
+ // Add it to cloned tables listing
627
+ $this->options->clonedTables[] = $this->options->tables[$this->options->currentStep];
628
+
629
+ // Reset job
630
+ $this->options->job = new \stdClass();
631
+
632
+ return true;
633
+ }
634
+
635
+ /**
636
+ * Drop table if necessary
637
+ * @param string $new
638
+ */
639
+ private function dropTable( $new ) {
640
+ $old = $this->db->get_var( $this->db->prepare( "SHOW TABLES LIKE %s", $new ) );
641
+
642
+ if( !$this->shouldDropTable( $new, $old ) ) {
643
+ return;
644
+ }
645
+
646
+ $this->log( "DB Processing: {$new} already exists, dropping it first" );
647
+ $this->db->query( "DROP TABLE {$new}" );
648
+ }
649
+
650
+ /**
651
+ * Check if table needs to be dropped
652
+ * @param string $new
653
+ * @param string $old
654
+ * @return bool
655
+ */
656
+ private function shouldDropTable( $new, $old ) {
657
+ return (
658
+ $old == $new &&
659
+ (
660
+ !isset( $this->options->job->current ) ||
661
+ !isset( $this->options->job->start ) ||
662
+ 0 == $this->options->job->start
663
+ )
664
+ );
665
+ }
666
+
667
+ /**
668
+ * Check if WP is installed in subdir
669
+ * @return boolean
670
+ */
671
+ private function isSubDir(){
672
+ if ( get_option( 'siteurl' ) !== get_option( 'home' ) ) {
673
+ return true;
674
+ }
675
+ return false;
676
+ }
677
+
678
+ /**
679
+ * Get the install sub directory if WP is installed in sub directory
680
+ * @return string
681
+ */
682
+ private function getSubDir() {
683
+ $home = get_option( 'home' );
684
+ $siteurl = get_option( 'siteurl' );
685
+
686
+ if( empty( $home ) || empty( $siteurl ) ) {
687
+ return '/';
688
+ }
689
+
690
+ $dir = str_replace( $home, '', $siteurl );
691
+ return '/' . str_replace( '/', '', $dir );
692
+ }
693
+
694
+ }
 
 
 
 
 
apps/Backend/Modules/Jobs/Updating.php CHANGED
@@ -1,248 +1,248 @@
1
- <?php
2
- namespace WPStaging\Backend\Modules\Jobs;
3
-
4
- use WPStaging\Backend\Modules\Jobs\Exceptions\JobNotFoundException;
5
- use WPStaging\WPStaging;
6
-
7
- /**
8
- * Class Cloning
9
- * @package WPStaging\Backend\Modules\Jobs
10
- */
11
- class Updating 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->includedDirectories = array();
37
- $this->options->excludedDirectories = array();
38
- $this->options->extraDirectories = array();
39
- $this->options->excludedFiles = array('.htaccess', '.DS_Store', '.git', '.svn', '.tmp', 'desktop.ini', '.gitignore', '.log');
40
-
41
- // Define mainJob to differentiate between cloning, updating and pushing
42
- $this->options->mainJob = 'updating';
43
-
44
- // Job
45
- $this->options->job = new \stdClass();
46
-
47
- // Check if clone data already exists and use that one
48
- if (isset($this->options->existingClones[$this->options->clone]) )
49
- {
50
- $this->options->cloneNumber = $this->options->existingClones[$this->options->clone]['number'];
51
- $this->options->prefix = $this->getStagingPrefix();
52
- } else {
53
- wp_die('Fatal Error: Can not update clone because there is no clone data.');
54
- }
55
-
56
-
57
- // Excluded Tables
58
- // if (isset($_POST["excludedTables"]) && is_array($_POST["excludedTables"]))
59
- // {
60
- // $this->options->excludedTables = $_POST["excludedTables"];
61
- // }
62
- // Included Tables
63
- if (isset($_POST["includedTables"]) && is_array($_POST["includedTables"]))
64
- {
65
- $this->options->tables = $_POST["includedTables"];
66
- } else {
67
- $this->options->tables = array();
68
- }
69
-
70
- // Excluded Directories
71
- if (isset($_POST["excludedDirectories"]) && is_array($_POST["excludedDirectories"]))
72
- {
73
- $this->options->excludedDirectories = $_POST["excludedDirectories"];
74
- }
75
-
76
- // Excluded Directories TOTAL
77
- // Do not copy these folders and plugins
78
- $excludedDirectories = array(
79
- ABSPATH . 'wp-content' . DIRECTORY_SEPARATOR . 'cache',
80
- ABSPATH . 'wp-content' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'wps-hide-login'
81
- );
82
-
83
- $this->options->excludedDirectories = array_merge($excludedDirectories, $this->options->excludedDirectories);
84
-
85
- // Included Directories
86
- if (isset($_POST["includedDirectories"]) && is_array($_POST["includedDirectories"]))
87
- {
88
- $this->options->includedDirectories = $_POST["includedDirectories"];
89
- }
90
-
91
- // Extra Directories
92
- if (isset($_POST["extraDirectories"]) && !empty($_POST["extraDirectories"]) )
93
- {
94
- $this->options->extraDirectories = $_POST["extraDirectories"];
95
- }
96
-
97
- // Directories to Copy
98
- $this->options->directoriesToCopy = array_merge(
99
- $this->options->includedDirectories,
100
- $this->options->extraDirectories
101
- );
102
-
103
- array_unshift($this->options->directoriesToCopy, ABSPATH);
104
-
105
- // Delete files to copy listing
106
- $this->cache->delete("files_to_copy");
107
-
108
- return $this->saveOptions();
109
- }
110
-
111
-
112
- /**
113
- * Check and return prefix of the staging site
114
- */
115
- public function getStagingPrefix() {
116
- // prefix not defined! Happens if staging site has ben generated with older version of wpstg
117
- // Try to get staging prefix from wp-config.php of staging site
118
- $this->options->prefix = $this->options->existingClones[$this->options->clone]['prefix'];
119
- if (empty($this->options->prefix)) {
120
- // Throw error if wp-config.php is not readable
121
- $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
122
- if (false === ($content = @file_get_contents($path))) {
123
- $this->log("Can not open {$path}. Can't read contents", Logger::TYPE_ERROR);
124
- $this->returnException("Fatal Error: Can not read {$path} to get correct table prefix. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com");
125
- wp_die("Fatal Error: Can not read {$path} to get correct table prefix. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com");
126
- } else {
127
- // Get prefix from wp-config.php
128
- preg_match("/table_prefix\s*=\s*'(\w*)';/", $content, $matches);
129
-
130
- if (!empty($matches[1])) {
131
- $this->options->prefix = $matches[1];
132
- } else {
133
- $this->returnException("Fatal Error: Can not detect prefix from {$path}. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com");
134
- wp_die("Fatal Error: Can not detect prefix from {$path}. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com");
135
- }
136
- }
137
- }
138
-
139
- // Die() if staging prefix is the same as the live prefix
140
- if ($this->db->prefix == $this->options->prefix) {
141
- $this->log("Fatal Error: Can not updatte staging site. Prefix. '{$this->options->prefix}' is used for the live site. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com");
142
- wp_die("Fatal Error: Can not update staging site. Prefix. '{$this->options->prefix}' is used for the live site. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com");
143
- }
144
-
145
- // Else
146
- return $this->options->prefix;
147
- }
148
-
149
- /**
150
- * Start the cloning job
151
- */
152
- public function start()
153
- {
154
- if (null === $this->options->currentJob)
155
- {
156
- $this->log("Cloning job for {$this->options->clone} finished");
157
- return true;
158
- }
159
-
160
- $methodName = "job" . ucwords($this->options->currentJob);
161
-
162
- if (!method_exists($this, $methodName))
163
- {
164
- $this->log("Can't execute job; Job's method {$methodName} is not found");
165
- throw new JobNotFoundException($methodName);
166
- }
167
-
168
- // Call the job
169
- //$this->log("execute job: Job's method {$methodName}");
170
- return $this->{$methodName}();
171
- }
172
-
173
- /**
174
- * @param object $response
175
- * @param string $nextJob
176
- * @return object
177
- */
178
- private function handleJobResponse($response, $nextJob)
179
- {
180
- // Job is not done
181
- if (true !== $response->status)
182
- {
183
- return $response;
184
- }
185
-
186
- $this->options->currentJob = $nextJob;
187
- $this->options->currentStep = 0;
188
- $this->options->totalSteps = 0;
189
-
190
- // Save options
191
- $this->saveOptions();
192
-
193
- return $response;
194
- }
195
-
196
-
197
-
198
-
199
- /**
200
- * Clone Database
201
- * @return object
202
- */
203
- public function jobDatabase()
204
- {
205
- $database = new Database();
206
- return $this->handleJobResponse($database->start(), "directories");
207
- }
208
-
209
- /**
210
- * Get All Files From Selected Directories Recursively Into a File
211
- * @return object
212
- */
213
- public function jobDirectories()
214
- {
215
- $directories = new Directories();
216
- return $this->handleJobResponse($directories->start(), "files");
217
- }
218
-
219
- /**
220
- * Copy Files
221
- * @return object
222
- */
223
- public function jobFiles()
224
- {
225
- $files = new Files();
226
- return $this->handleJobResponse($files->start(), "data");
227
- }
228
-
229
- /**
230
- * Replace Data
231
- * @return object
232
- */
233
- public function jobData()
234
- {
235
- $data = new Data();
236
- return $this->handleJobResponse($data->start(), "finish");
237
- }
238
-
239
- /**
240
- * Save Clone Data
241
- * @return object
242
- */
243
- public function jobFinish()
244
- {
245
- $finish = new Finish();
246
- return $this->handleJobResponse($finish->start(), '');
247
- }
248
  }
1
+ <?php
2
+ namespace WPStaging\Backend\Modules\Jobs;
3
+
4
+ use WPStaging\Backend\Modules\Jobs\Exceptions\JobNotFoundException;
5
+ use WPStaging\WPStaging;
6
+
7
+ /**
8
+ * Class Cloning
9
+ * @package WPStaging\Backend\Modules\Jobs
10
+ */
11
+ class Updating 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->includedDirectories = array();
37
+ $this->options->excludedDirectories = array();
38
+ $this->options->extraDirectories = array();
39
+ $this->options->excludedFiles = array('.htaccess', '.DS_Store', '.git', '.svn', '.tmp', 'desktop.ini', '.gitignore', '.log');
40
+
41
+ // Define mainJob to differentiate between cloning, updating and pushing
42
+ $this->options->mainJob = 'updating';
43
+
44
+ // Job
45
+ $this->options->job = new \stdClass();
46
+
47
+ // Check if clone data already exists and use that one
48
+ if (isset($this->options->existingClones[$this->options->clone]) )
49
+ {
50
+ $this->options->cloneNumber = $this->options->existingClones[$this->options->clone]['number'];
51
+ $this->options->prefix = $this->getStagingPrefix();
52
+ } else {
53
+ wp_die('Fatal Error: Can not update clone because there is no clone data.');
54
+ }
55
+
56
+
57
+ // Excluded Tables
58
+ // if (isset($_POST["excludedTables"]) && is_array($_POST["excludedTables"]))
59
+ // {
60
+ // $this->options->excludedTables = $_POST["excludedTables"];
61
+ // }
62
+ // Included Tables
63
+ if (isset($_POST["includedTables"]) && is_array($_POST["includedTables"]))
64
+ {
65
+ $this->options->tables = $_POST["includedTables"];
66
+ } else {
67
+ $this->options->tables = array();
68
+ }
69
+
70
+ // Excluded Directories
71
+ if (isset($_POST["excludedDirectories"]) && is_array($_POST["excludedDirectories"]))
72
+ {
73
+ $this->options->excludedDirectories = $_POST["excludedDirectories"];
74
+ }
75
+
76
+ // Excluded Directories TOTAL
77
+ // Do not copy these folders and plugins
78
+ $excludedDirectories = array(
79
+ ABSPATH . 'wp-content' . DIRECTORY_SEPARATOR . 'cache',
80
+ ABSPATH . 'wp-content' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'wps-hide-login'
81
+ );
82
+
83
+ $this->options->excludedDirectories = array_merge($excludedDirectories, $this->options->excludedDirectories);
84
+
85
+ // Included Directories
86
+ if (isset($_POST["includedDirectories"]) && is_array($_POST["includedDirectories"]))
87
+ {
88
+ $this->options->includedDirectories = $_POST["includedDirectories"];
89
+ }
90
+
91
+ // Extra Directories
92
+ if (isset($_POST["extraDirectories"]) && !empty($_POST["extraDirectories"]) )
93
+ {
94
+ $this->options->extraDirectories = $_POST["extraDirectories"];
95
+ }
96
+
97
+ // Directories to Copy
98
+ $this->options->directoriesToCopy = array_merge(
99
+ $this->options->includedDirectories,
100
+ $this->options->extraDirectories
101
+ );
102
+
103
+ array_unshift($this->options->directoriesToCopy, ABSPATH);
104
+
105
+ // Delete files to copy listing
106
+ $this->cache->delete("files_to_copy");
107
+
108
+ return $this->saveOptions();
109
+ }
110
+
111
+
112
+ /**
113
+ * Check and return prefix of the staging site
114
+ */
115
+ public function getStagingPrefix() {
116
+ // prefix not defined! Happens if staging site has ben generated with older version of wpstg
117
+ // Try to get staging prefix from wp-config.php of staging site
118
+ $this->options->prefix = $this->options->existingClones[$this->options->clone]['prefix'];
119
+ if (empty($this->options->prefix)) {
120
+ // Throw error if wp-config.php is not readable
121
+ $path = ABSPATH . $this->options->cloneDirectoryName . "/wp-config.php";
122
+ if (false === ($content = @file_get_contents($path))) {
123
+ $this->log("Can not open {$path}. Can't read contents", Logger::TYPE_ERROR);
124
+ $this->returnException("Fatal Error: Can not read {$path} to get correct table prefix. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com");
125
+ wp_die("Fatal Error: Can not read {$path} to get correct table prefix. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com");
126
+ } else {
127
+ // Get prefix from wp-config.php
128
+ preg_match("/table_prefix\s*=\s*'(\w*)';/", $content, $matches);
129
+
130
+ if (!empty($matches[1])) {
131
+ $this->options->prefix = $matches[1];
132
+ } else {
133
+ $this->returnException("Fatal Error: Can not detect prefix from {$path}. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com");
134
+ wp_die("Fatal Error: Can not detect prefix from {$path}. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com");
135
+ }
136
+ }
137
+ }
138
+
139
+ // Die() if staging prefix is the same as the live prefix
140
+ if ($this->db->prefix == $this->options->prefix) {
141
+ $this->log("Fatal Error: Can not updatte staging site. Prefix. '{$this->options->prefix}' is used for the live site. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com");
142
+ wp_die("Fatal Error: Can not update staging site. Prefix. '{$this->options->prefix}' is used for the live site. Stopping for security reasons. Deleting this staging site and creating a new one could fix this issue. Otherwise contact us support@wp-staging.com");
143
+ }
144
+
145
+ // Else
146
+ return $this->options->prefix;
147
+ }
148
+
149
+ /**
150
+ * Start the cloning job
151
+ */
152
+ public function start()
153
+ {
154
+ if (null === $this->options->currentJob)
155
+ {
156
+ $this->log("Cloning job for {$this->options->clone} finished");
157
+ return true;
158
+ }
159
+
160
+ $methodName = "job" . ucwords($this->options->currentJob);
161
+
162
+ if (!method_exists($this, $methodName))
163
+ {
164
+ $this->log("Can't execute job; Job's method {$methodName} is not found");
165
+ throw new JobNotFoundException($methodName);
166
+ }
167
+
168
+ // Call the job
169
+ //$this->log("execute job: Job's method {$methodName}");
170
+ return $this->{$methodName}();
171
+ }
172
+
173
+ /**
174
+ * @param object $response
175
+ * @param string $nextJob
176
+ * @return object
177
+ */
178
+ private function handleJobResponse($response, $nextJob)
179
+ {
180
+ // Job is not done
181
+ if (true !== $response->status)
182
+ {
183
+ return $response;
184
+ }
185
+
186
+ $this->options->currentJob = $nextJob;
187
+ $this->options->currentStep = 0;
188
+ $this->options->totalSteps = 0;
189
+
190
+ // Save options
191
+ $this->saveOptions();
192
+
193
+ return $response;
194
+ }
195
+
196
+
197
+
198
+
199
+ /**
200
+ * Clone Database
201
+ * @return object
202
+ */
203
+ public function jobDatabase()
204
+ {
205
+ $database = new Database();
206
+ return $this->handleJobResponse($database->start(), "directories");
207
+ }
208
+
209
+ /**
210
+ * Get All Files From Selected Directories Recursively Into a File
211
+ * @return object
212
+ */
213
+ public function jobDirectories()
214
+ {
215
+ $directories = new Directories();
216
+ return $this->handleJobResponse($directories->start(), "files");
217
+ }
218
+
219
+ /**
220
+ * Copy Files
221
+ * @return object
222
+ */
223
+ public function jobFiles()
224
+ {
225
+ $files = new Files();
226
+ return $this->handleJobResponse($files->start(), "data");
227
+ }
228
+
229
+ /**
230
+ * Replace Data
231
+ * @return object
232
+ */
233
+ public function jobData()
234
+ {
235
+ $data = new Data();
236
+ return $this->handleJobResponse($data->start(), "finish");
237
+ }
238
+
239
+ /**
240
+ * Save Clone Data
241
+ * @return object
242
+ */
243
+ public function jobFinish()
244
+ {
245
+ $finish = new Finish();
246
+ return $this->handleJobResponse($finish->start(), '');
247
+ }
248
  }
apps/Backend/Modules/SystemInfo.php CHANGED
@@ -1,502 +1,502 @@
1
- <?php
2
-
3
- namespace WPStaging\Backend\Modules;
4
-
5
- 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" ) ) {
12
- die;
13
- }
14
-
15
- /**
16
- * Class SystemInfo
17
- * @package WPStaging\Backend\Modules
18
- */
19
- class SystemInfo extends InjectionAware {
20
-
21
- /**
22
- * @var bool
23
- */
24
- private $isMultiSite;
25
-
26
- /**
27
- *
28
- * @var obj
29
- */
30
- private $helper;
31
-
32
- /**
33
- * Initialize class
34
- */
35
- public function initialize() {
36
- $this->isMultiSite = is_multisite();
37
- $this->helper = new Utils\Helper();
38
- }
39
-
40
- /**
41
- * Magic method
42
- * @return string
43
- */
44
- public function __toString() {
45
- return $this->get();
46
- }
47
-
48
- /**
49
- * Get System Information as text
50
- * @return string
51
- */
52
- public function get() {
53
- $output = "### Begin System Info ###" . PHP_EOL . PHP_EOL;
54
-
55
- $output .= $this->wpstaging();
56
-
57
- $output .= $this->site();
58
-
59
- $output .= $this->browser();
60
-
61
- $output .= $this->wp();
62
-
63
- $output .= $this->plugins();
64
-
65
- $output .= $this->multiSitePlugins();
66
-
67
- $output .= $this->server();
68
-
69
- $output .= $this->php();
70
-
71
- $output .= $this->phpExtensions();
72
-
73
- $output .= PHP_EOL . "### End System Info ###";
74
-
75
- return $output;
76
- }
77
-
78
- /**
79
- * @param string $string
80
- * @return string
81
- */
82
- public function header( $string ) {
83
- return PHP_EOL . "-- {$string}" . PHP_EOL . PHP_EOL;
84
- }
85
-
86
- /**
87
- * Formating title and the value
88
- * @param string $title
89
- * @param string $value
90
- * @return string
91
- */
92
- public function info( $title, $value ) {
93
- return str_pad( $title, 56, ' ', STR_PAD_RIGHT ) . $value . PHP_EOL;
94
- }
95
-
96
- /**
97
- * Theme Information
98
- * @return string
99
- */
100
- public function theme() {
101
- // Versions earlier than 3.4
102
- if( get_bloginfo( "version" ) < "3.4" ) {
103
- $themeData = get_theme_data( get_stylesheet_directory() . "/style.css" );
104
- return "{$themeData["Name"]} {$themeData["Version"]}";
105
- }
106
-
107
- $themeData = wp_get_theme();
108
- return "{$themeData->Name} {$themeData->Version}";
109
- }
110
-
111
- /**
112
- * Site Information
113
- * @return string
114
- */
115
- public function site() {
116
- $output = "-- Site Info" . PHP_EOL . PHP_EOL;
117
- $output .= $this->info( "Site URL:", site_url() );
118
- $output .= $this->info( "Home URL:", $this->helper->get_home_url() );
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
131
- */
132
- public function wpstaging() {
133
- // Get wpstg settings
134
- $settings = ( object ) get_option( 'wpstg_settings', array() );
135
-
136
- // Clones data < 1.1.6.x
137
- $clones = ( object ) get_option( 'wpstg_existing_clones', array() );
138
- // Clones data version > 2.x
139
- $clonesBeta = get_option( 'wpstg_existing_clones_beta' );
140
-
141
-
142
- $output = "-- WP Staging Settings" . PHP_EOL . PHP_EOL;
143
- $output .= $this->info( "Query Limit:", isset( $settings->queryLimit ) ? $settings->queryLimit : 'undefined' );
144
- $output .= $this->info( "File Copy Limit:", isset( $settings->fileLimit ) ? $settings->fileLimit : 'undefined' );
145
- $output .= $this->info( "Batch Size:", isset( $settings->batchSize ) ? $settings->batchSize : 'undefined' );
146
- $output .= $this->info( "CPU Load:", isset( $settings->cpuLoad ) ? $settings->cpuLoad : 'undefined' );
147
- $output .= $this->info( "WP in Subdir:", isset( $settings->wpSubDirectory ) ? $settings->wpSubDirectory : 'false' );
148
- $output .= $this->info( "Login Custom Link:", isset( $settings->loginSlug ) ? $settings->loginSlug : 'false' );
149
-
150
- $output .= PHP_EOL . PHP_EOL . "-- Available Sites Version < 1.1.6.x" . PHP_EOL . PHP_EOL;
151
-
152
- $i = 1;
153
- foreach ( $clones as $key => $value ) {
154
- $output .= $this->info( "Site name & subfolder :", $value );
155
- }
156
- $output .= PHP_EOL . PHP_EOL . "-- Available Sites Version > 2.0.x" . PHP_EOL . PHP_EOL;
157
-
158
- foreach ( $clonesBeta as $key => $clone ) {
159
- $output .= $this->info( "Number:", isset( $clone['number'] ) ? $clone['number'] : 'undefined' );
160
- $output .= $this->info( "directoryName:", isset( $clone['directoryName'] ) ? $clone['directoryName'] : 'undefined' );
161
- $output .= $this->info( "Path:", isset( $clone['path'] ) ? $clone['path'] : 'undefined' );
162
- $output .= $this->info( "URL:", isset( $clone['url'] ) ? $clone['url'] : 'undefined' );
163
- $output .= $this->info( "DB Prefix:", isset( $clone['prefix'] ) ? $clone['prefix'] : 'undefined' );
164
- $output .= $this->info( "DB Prefix wp-config.php:", $this->getStagingPrefix( $clone ) );
165
- $output .= $this->info( "Version:", isset( $clone['version'] ) ? $clone['version'] : 'undefined' ) . PHP_EOL . PHP_EOL;
166
- }
167
-
168
-
169
- $output .= $this->info( "Raw Clones Data:", json_encode( get_option( 'wpstg_existing_clones_beta', 'undefined' ) ) );
170
-
171
- $output .= '' . PHP_EOL;
172
-
173
-
174
- //$output .= PHP_EOL . PHP_EOL;
175
-
176
- $output .= $this->info( "Plugin Version:", get_option( 'wpstg_version', 'undefined' ) );
177
- $output .= $this->info( "Install Date:", get_option( 'wpstg_installDate', 'undefined' ) );
178
- $output .= $this->info( "Upgraded from:", get_option( 'wpstg_version_upgraded_from', 'undefined' ) );
179
- $output .= $this->info( "Is Staging Site:", get_option( 'wpstg_is_staging_site', 'undefined' ) ) . PHP_EOL . PHP_EOL;
180
-
181
-
182
- return apply_filters( "wpstg_sysinfo_after_wpstaging_info", $output );
183
- }
184
-
185
- /**
186
- * Browser Information
187
- * @return string
188
- */
189
- public function browser() {
190
- $output = $this->header( "User Browser" );
191
- $output .= (new Browser);
192
-
193
- return apply_filters( "wpstg_sysinfo_after_user_browser", $output );
194
- }
195
-
196
- /**
197
- * Frontpage Information when frontpage is set to "page"
198
- * @return string
199
- */
200
- public function frontPage() {
201
- if( get_option( "show_on_front" ) !== "page" ) {
202
- return '';
203
- }
204
-
205
- $frontPageID = get_option( "page_on_front" );
206
- $blogPageID = get_option( "page_for_posts" );
207
-
208
- // Front Page
209
- $pageFront = ($frontPageID != 0) ? get_the_title( $frontPageID ) . " (#{$frontPageID})" : "Unset";
210
- // Blog Page ID
211
- $pageBlog = ($blogPageID != 0) ? get_the_title( $blogPageID ) . " (#{$blogPageID})" : "Unset";
212
-
213
- $output = $this->info( "Page On Front:", $pageFront );
214
- $output .= $this->info( "Page For Posts:", $pageBlog );
215
-
216
- return $output;
217
- }
218
-
219
- /**
220
- * Check wp_remote_post() functionality
221
- * @return string
222
- */
223
- public function wpRemotePost() {
224
- // Make sure wp_remote_post() is working
225
- $wpRemotePost = "wp_remote_post() does not work";
226
-
227
- // Send request
228
- $response = wp_remote_post(
229
- "https://www.paypal.com/cgi-bin/webscr", array(
230
- "sslverify" => false,
231
- "timeout" => 60,
232
- "user-agent" => "WPSTG/" . WPStaging::VERSION,
233
- "body" => array("cmd" => "_notify-validate")
234
- )
235
- );
236
-
237
- // Validate it worked
238
- if( !is_wp_error( $response ) && 200 <= $response["response"]["code"] && 300 > $response["response"]["code"] ) {
239
- $wpRemotePost = "wp_remote_post() works";
240
- }
241
-
242
- return $this->info( "Remote Post:", $wpRemotePost );
243
- }
244
-
245
- /**
246
- * WordPress Configuration
247
- * @return string
248
- */
249
- public function wp() {
250
- $output = $this->header( "WordPress Configuration" );
251
- $output .= $this->info( "Version:", get_bloginfo( "version" ) );
252
- $output .= $this->info( "Language:", (defined( "WPLANG" ) && WPLANG) ? WPLANG : "en_US" );
253
-
254
- $permalinkStructure = get_option( "permalink_structure" );
255
- ;
256
- $output .= $this->info( "Permalink Structure:", ($permalinkStructure) ? $permalinkStructure : "Default" );
257
-
258
- $output .= $this->info( "Active Theme:", $this->theme() );
259
- $output .= $this->info( "Show On Front:", get_option( "show_on_front" ) );
260
-
261
- // Frontpage information
262
- $output .= $this->frontPage();
263
-
264
- // WP Remote Post
265
- $output .= $this->wpRemotePost();
266
-
267
- // Table Prefix
268
- $wpDB = $this->di->get( "wpdb" );
269
- $tablePrefix = "DB Prefix: " . $wpDB->prefix . ' ';
270
- $tablePrefix .= "Length: " . strlen( $wpDB->prefix ) . " Status: ";
271
- $tablePrefix .= (strlen( $wpDB->prefix ) > 16) ? " ERROR: Too long" : " Acceptable";
272
-
273
- $output .= $this->info( "Table Prefix:", $tablePrefix );
274
-
275
- // Constants
276
- $output .= $this->info( "WP Content Path:", WP_CONTENT_DIR );
277
- $output .= $this->info( "WP Plugin Dir:", WP_PLUGIN_DIR );
278
- if (defined('UPLOADS'))
279
- $output .= $this->info( "WP UPLOADS CONST:", UPLOADS );
280
- $uploads = wp_upload_dir();
281
- $output .= $this->info( "WP Uploads Dir:", $uploads['basedir'] );
282
- if (defined('WP_TEMP_DIR'))
283
- $output .= $this->info( "WP Temp Dir:", WP_TEMP_DIR );
284
-
285
- // WP Debug
286
- $output .= $this->info( "WP_DEBUG:", (defined( "WP_DEBUG" )) ? WP_DEBUG ? "Enabled" : "Disabled" : "Not set" );
287
- $output .= $this->info( "Memory Limit:", WP_MEMORY_LIMIT );
288
- $output .= $this->info( "Registered Post Stati:", implode( ", ", \get_post_stati() ) );
289
-
290
- return apply_filters( "wpstg_sysinfo_after_wpstg_config", $output );
291
- }
292
-
293
- /**
294
- * List of Active Plugins
295
- * @param array $plugins
296
- * @param array $activePlugins
297
- * @return string
298
- */
299
- public function activePlugins( $plugins, $activePlugins ) {
300
- $output = $this->header( "WordPress Active Plugins" );
301
-
302
- foreach ( $plugins as $path => $plugin ) {
303
- if( !in_array( $path, $activePlugins ) ) {
304
- continue;
305
- }
306
-
307
- $output .= "{$plugin["Name"]}: {$plugin["Version"]}" . PHP_EOL;
308
- }
309
-
310
- return apply_filters( "wpstg_sysinfo_after_wordpress_plugins", $output );
311
- }
312
-
313
- /**
314
- * List of Inactive Plugins
315
- * @param array $plugins
316
- * @param array $activePlugins
317
- * @return string
318
- */
319
- public function inactivePlugins( $plugins, $activePlugins ) {
320
- $output = $this->header( "WordPress Inactive Plugins" );
321
-
322
- foreach ( $plugins as $path => $plugin ) {
323
- if( in_array( $path, $activePlugins ) ) {
324
- continue;
325
- }
326
-
327
- $output .= "{$plugin["Name"]}: {$plugin["Version"]}" . PHP_EOL;
328
- }
329
-
330
- return apply_filters( "wpstg_sysinfo_after_wordpress_plugins_inactive", $output );
331
- }
332
-
333
- /**
334
- * Get list of active and inactive plugins
335
- * @return string
336
- */
337
- public function plugins() {
338
- // Get plugins and active plugins
339
- $plugins = get_plugins();
340
- $activePlugins = get_option( "active_plugins", array() );
341
-
342
- // Active plugins
343
- $output = $this->activePlugins( $plugins, $activePlugins );
344
- $output .= $this->inactivePlugins( $plugins, $activePlugins );
345
-
346
- return $output;
347
- }
348
-
349
- /**
350
- * Multisite Plugins
351
- * @return string
352
- */
353
- public function multiSitePlugins() {
354
- if( !$this->isMultiSite ) {
355
- return '';
356
- }
357
-
358
- $output = $this->header( "Network Active Plugins" );
359
-
360
- $plugins = wp_get_active_network_plugins();
361
- $activePlugins = get_site_option( "active_sitewide_plugins", array() );
362
-
363
- foreach ( $plugins as $pluginPath ) {
364
- $pluginBase = plugin_basename( $pluginPath );
365
-
366
- if( !array_key_exists( $pluginBase, $activePlugins ) ) {
367
- continue;
368
- }
369
-
370
- $plugin = get_plugin_data( $pluginPath );
371
-
372
- $output .= "{$plugin["Name"]}: {$plugin["Version"]}" . PHP_EOL;
373
- }
374
- unset( $plugins, $activePlugins );
375
-
376
- return $output;
377
- }
378
-
379
- /**
380
- * Server Information
381
- * @return string
382
- */
383
- public function server() {
384
- // Server Configuration
385
- $output = $this->header( "Webserver Configuration" );
386
-
387
- $output .= $this->info( "PHP Version:", PHP_VERSION );
388
- $output .= $this->info( "MySQL Version:", $this->di->get( "wpdb" )->db_version() );
389
- $output .= $this->info( "Webserver Info:", $_SERVER["SERVER_SOFTWARE"] );
390
-
391
- return apply_filters( "wpstg_sysinfo_after_webserver_config", $output );
392
- }
393
-
394
- /**
395
- * PHP Configuration
396
- * @return string
397
- */
398
- public function php() {
399
- $output = $this->header( "PHP Configuration" );
400
- $output .= $this->info( "Safe Mode:", ($this->isSafeModeEnabled() ? "Enabled" : "Disabled" ) );
401
- $output .= $this->info( "Memory Limit:", ini_get( "memory_limit" ) );
402
- $output .= $this->info( "Upload Max Size:", ini_get( "upload_max_filesize" ) );
403
- $output .= $this->info( "Post Max Size:", ini_get( "post_max_size" ) );
404
- $output .= $this->info( "Upload Max Filesize:", ini_get( "upload_max_filesize" ) );
405
- $output .= $this->info( "Time Limit:", ini_get( "max_execution_time" ) );
406
- $output .= $this->info( "Max Input Vars:", ini_get( "max_input_vars" ) );
407
-
408
- $displayErrors = ini_get( "display_errors" );
409
- $output .= $this->info( "Display Errors:", ($displayErrors) ? "On ({$displayErrors})" : "N/A" );
410
-
411
- return apply_filters( "wpstg_sysinfo_after_php_config", $output );
412
- }
413
-
414
- /**
415
- * Check if PHP is on Safe Mode
416
- * @return bool
417
- */
418
- public function isSafeModeEnabled() {
419
- return (
420
- version_compare( PHP_VERSION, "5.4.0", '<' ) &&
421
- @ini_get( "safe_mode" )
422
- );
423
- }
424
-
425
- /**
426
- * Checks if function exists or not
427
- * @param string $functionName
428
- * @return string
429
- */
430
- public function isSupported( $functionName ) {
431
- return (function_exists( $functionName )) ? "Supported" : "Not Supported";
432
- }
433
-
434
- /**
435
- * Checks if class or extension is loaded / exists to determine if it is installed or not
436
- * @param string $name
437
- * @param bool $isClass
438
- * @return string
439
- */
440
- public function isInstalled( $name, $isClass = true ) {
441
- if( true === $isClass ) {
442
- return (class_exists( $name )) ? "Installed" : "Not Installed";
443
- } else {
444
- return (extension_loaded( $name )) ? "Installed" : "Not Installed";
445
- }
446
- }
447
-
448
- /**
449
- * Gets Installed Important PHP Extensions
450
- * @return string
451
- */
452
- public function phpExtensions() {
453
- // Important PHP Extensions
454
- $output = $this->header( "PHP Extensions" );
455
- $output .= $this->info( "cURL:", $this->isSupported( "curl_init" ) );
456
- $output .= $this->info( "fsockopen:", $this->isSupported( "fsockopen" ) );
457
- $output .= $this->info( "SOAP Client:", $this->isInstalled( "SoapClient" ) );
458
- $output .= $this->info( "Suhosin:", $this->isInstalled( "suhosin", false ) );
459
-
460
- return apply_filters( "wpstg_sysinfo_after_php_ext", $output );
461
- }
462
-
463
- /**
464
- * Check if WP is installed in subdir
465
- * @return boolean
466
- */
467
- private function isSubDir() {
468
- if( get_option( 'siteurl' ) !== get_option( 'home' ) ) {
469
- return true;
470
- }
471
- return false;
472
- }
473
-
474
- /**
475
- * Check and return prefix of the staging site
476
- */
477
- /**
478
- * Try to get the staging prefix from wp-config.php of staging site
479
- * @param array $clone
480
- * @return sting
481
- */
482
- private function getStagingPrefix( $clone = array() ) {
483
- // Throw error
484
- $path = ABSPATH . $clone['directoryName'] . "/wp-config.php";
485
- if( false === ($content = @file_get_contents( $path )) ) {
486
- return 'Can\'t find staging wp-config.php';
487
- } else {
488
-
489
- // Get prefix from wp-config.php
490
- //preg_match_all("/table_prefix\s*=\s*'(\w*)';/", $content, $matches);
491
- preg_match( "/table_prefix\s*=\s*'(\w*)';/", $content, $matches );
492
- //wp_die(var_dump($matches));
493
-
494
- if( !empty( $matches[1] ) ) {
495
- return $matches[1];
496
- } else {
497
- return 'No table_prefix in wp-config.php';
498
- }
499
- }
500
- }
501
-
502
- }
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules;
4
+
5
+ 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" ) ) {
12
+ die;
13
+ }
14
+
15
+ /**
16
+ * Class SystemInfo
17
+ * @package WPStaging\Backend\Modules
18
+ */
19
+ class SystemInfo extends InjectionAware {
20
+
21
+ /**
22
+ * @var bool
23
+ */
24
+ private $isMultiSite;
25
+
26
+ /**
27
+ *
28
+ * @var obj
29
+ */
30
+ private $helper;
31
+
32
+ /**
33
+ * Initialize class
34
+ */
35
+ public function initialize() {
36
+ $this->isMultiSite = is_multisite();
37
+ $this->helper = new Utils\Helper();
38
+ }
39
+
40
+ /**
41
+ * Magic method
42
+ * @return string
43
+ */
44
+ public function __toString() {
45
+ return $this->get();
46
+ }
47
+
48
+ /**
49
+ * Get System Information as text
50
+ * @return string
51
+ */
52
+ public function get() {
53
+ $output = "### Begin System Info ###" . PHP_EOL . PHP_EOL;
54
+
55
+ $output .= $this->wpstaging();
56
+
57
+ $output .= $this->site();
58
+
59
+ $output .= $this->browser();
60
+
61
+ $output .= $this->wp();
62
+
63
+ $output .= $this->plugins();
64
+
65
+ $output .= $this->multiSitePlugins();
66
+
67
+ $output .= $this->server();
68
+
69
+ $output .= $this->php();
70
+
71
+ $output .= $this->phpExtensions();
72
+
73
+ $output .= PHP_EOL . "### End System Info ###";
74
+
75
+ return $output;
76
+ }
77
+
78
+ /**
79
+ * @param string $string
80
+ * @return string
81
+ */
82
+ public function header( $string ) {
83
+ return PHP_EOL . "-- {$string}" . PHP_EOL . PHP_EOL;
84
+ }
85
+
86
+ /**
87
+ * Formating title and the value
88
+ * @param string $title
89
+ * @param string $value
90
+ * @return string
91
+ */
92
+ public function info( $title, $value ) {
93
+ return str_pad( $title, 56, ' ', STR_PAD_RIGHT ) . $value . PHP_EOL;
94
+ }
95
+
96
+ /**
97
+ * Theme Information
98
+ * @return string
99
+ */
100
+ public function theme() {
101
+ // Versions earlier than 3.4
102
+ if( get_bloginfo( "version" ) < "3.4" ) {
103
+ $themeData = get_theme_data( get_stylesheet_directory() . "/style.css" );
104
+ return "{$themeData["Name"]} {$themeData["Version"]}";
105
+ }
106
+
107
+ $themeData = wp_get_theme();
108
+ return "{$themeData->Name} {$themeData->Version}";
109
+ }
110
+
111
+ /**
112
+ * Site Information
113
+ * @return string
114
+ */
115
+ public function site() {
116
+ $output = "-- Site Info" . PHP_EOL . PHP_EOL;
117
+ $output .= $this->info( "Site URL:", site_url() );
118
+ $output .= $this->info( "Home URL:", $this->helper->get_home_url() );
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
131
+ */
132
+ public function wpstaging() {
133
+ // Get wpstg settings
134
+ $settings = ( object ) get_option( 'wpstg_settings', array() );
135
+
136
+ // Clones data < 1.1.6.x
137
+ $clones = ( object ) get_option( 'wpstg_existing_clones', array() );
138
+ // Clones data version > 2.x
139
+ $clonesBeta = get_option( 'wpstg_existing_clones_beta' );
140
+
141
+
142
+ $output = "-- WP Staging Settings" . PHP_EOL . PHP_EOL;
143
+ $output .= $this->info( "Query Limit:", isset( $settings->queryLimit ) ? $settings->queryLimit : 'undefined' );
144
+ $output .= $this->info( "File Copy Limit:", isset( $settings->fileLimit ) ? $settings->fileLimit : 'undefined' );
145
+ $output .= $this->info( "Batch Size:", isset( $settings->batchSize ) ? $settings->batchSize : 'undefined' );
146
+ $output .= $this->info( "CPU Load:", isset( $settings->cpuLoad ) ? $settings->cpuLoad : 'undefined' );
147
+ $output .= $this->info( "WP in Subdir:", isset( $settings->wpSubDirectory ) ? $settings->wpSubDirectory : 'false' );
148
+ $output .= $this->info( "Login Custom Link:", isset( $settings->loginSlug ) ? $settings->loginSlug : 'false' );
149
+
150
+ $output .= PHP_EOL . PHP_EOL . "-- Available Sites Version < 1.1.6.x" . PHP_EOL . PHP_EOL;
151
+
152
+ $i = 1;
153
+ foreach ( $clones as $key => $value ) {
154
+ $output .= $this->info( "Site name & subfolder :", $value );
155
+ }
156
+ $output .= PHP_EOL . PHP_EOL . "-- Available Sites Version > 2.0.x" . PHP_EOL . PHP_EOL;
157
+
158
+ foreach ( $clonesBeta as $key => $clone ) {
159
+ $output .= $this->info( "Number:", isset( $clone['number'] ) ? $clone['number'] : 'undefined' );
160
+ $output .= $this->info( "directoryName:", isset( $clone['directoryName'] ) ? $clone['directoryName'] : 'undefined' );
161
+ $output .= $this->info( "Path:", isset( $clone['path'] ) ? $clone['path'] : 'undefined' );
162
+ $output .= $this->info( "URL:", isset( $clone['url'] ) ? $clone['url'] : 'undefined' );
163
+ $output .= $this->info( "DB Prefix:", isset( $clone['prefix'] ) ? $clone['prefix'] : 'undefined' );
164
+ $output .= $this->info( "DB Prefix wp-config.php:", $this->getStagingPrefix( $clone ) );
165
+ $output .= $this->info( "Version:", isset( $clone['version'] ) ? $clone['version'] : 'undefined' ) . PHP_EOL . PHP_EOL;
166
+ }
167
+
168
+
169
+ $output .= $this->info( "Raw Clones Data:", json_encode( get_option( 'wpstg_existing_clones_beta', 'undefined' ) ) );
170
+
171
+ $output .= '' . PHP_EOL;
172
+
173
+
174
+ //$output .= PHP_EOL . PHP_EOL;
175
+
176
+ $output .= $this->info( "Plugin Version:", get_option( 'wpstg_version', 'undefined' ) );
177
+ $output .= $this->info( "Install Date:", get_option( 'wpstg_installDate', 'undefined' ) );
178
+ $output .= $this->info( "Upgraded from:", get_option( 'wpstg_version_upgraded_from', 'undefined' ) );
179
+ $output .= $this->info( "Is Staging Site:", get_option( 'wpstg_is_staging_site', 'undefined' ) ) . PHP_EOL . PHP_EOL;
180
+
181
+
182
+ return apply_filters( "wpstg_sysinfo_after_wpstaging_info", $output );
183
+ }
184
+
185
+ /**
186
+ * Browser Information
187
+ * @return string
188
+ */
189
+ public function browser() {
190
+ $output = $this->header( "User Browser" );
191
+ $output .= (new Browser);
192
+
193
+ return apply_filters( "wpstg_sysinfo_after_user_browser", $output );
194
+ }
195
+
196
+ /**
197
+ * Frontpage Information when frontpage is set to "page"
198
+ * @return string
199
+ */
200
+ public function frontPage() {
201
+ if( get_option( "show_on_front" ) !== "page" ) {
202
+ return '';
203
+ }
204
+
205
+ $frontPageID = get_option( "page_on_front" );
206
+ $blogPageID = get_option( "page_for_posts" );
207
+
208
+ // Front Page
209
+ $pageFront = ($frontPageID != 0) ? get_the_title( $frontPageID ) . " (#{$frontPageID})" : "Unset";
210
+ // Blog Page ID
211
+ $pageBlog = ($blogPageID != 0) ? get_the_title( $blogPageID ) . " (#{$blogPageID})" : "Unset";
212
+
213
+ $output = $this->info( "Page On Front:", $pageFront );
214
+ $output .= $this->info( "Page For Posts:", $pageBlog );
215
+
216
+ return $output;
217
+ }
218
+
219
+ /**
220
+ * Check wp_remote_post() functionality
221
+ * @return string
222
+ */
223
+ public function wpRemotePost() {
224
+ // Make sure wp_remote_post() is working
225
+ $wpRemotePost = "wp_remote_post() does not work";
226
+
227
+ // Send request
228
+ $response = wp_remote_post(
229
+ "https://www.paypal.com/cgi-bin/webscr", array(
230
+ "sslverify" => false,
231
+ "timeout" => 60,
232
+ "user-agent" => "WPSTG/" . WPStaging::VERSION,
233
+ "body" => array("cmd" => "_notify-validate")
234
+ )
235
+ );
236
+
237
+ // Validate it worked
238
+ if( !is_wp_error( $response ) && 200 <= $response["response"]["code"] && 300 > $response["response"]["code"] ) {
239
+ $wpRemotePost = "wp_remote_post() works";
240
+ }
241
+
242
+ return $this->info( "Remote Post:", $wpRemotePost );
243
+ }
244
+
245
+ /**
246
+ * WordPress Configuration
247
+ * @return string
248
+ */
249
+ public function wp() {
250
+ $output = $this->header( "WordPress Configuration" );
251
+ $output .= $this->info( "Version:", get_bloginfo( "version" ) );
252
+ $output .= $this->info( "Language:", (defined( "WPLANG" ) && WPLANG) ? WPLANG : "en_US" );
253
+
254
+ $permalinkStructure = get_option( "permalink_structure" );
255
+ ;
256
+ $output .= $this->info( "Permalink Structure:", ($permalinkStructure) ? $permalinkStructure : "Default" );
257
+
258
+ $output .= $this->info( "Active Theme:", $this->theme() );
259
+ $output .= $this->info( "Show On Front:", get_option( "show_on_front" ) );
260
+
261
+ // Frontpage information
262
+ $output .= $this->frontPage();
263
+
264
+ // WP Remote Post
265
+ $output .= $this->wpRemotePost();
266
+
267
+ // Table Prefix
268
+ $wpDB = $this->di->get( "wpdb" );
269
+ $tablePrefix = "DB Prefix: " . $wpDB->prefix . ' ';
270
+ $tablePrefix .= "Length: " . strlen( $wpDB->prefix ) . " Status: ";
271
+ $tablePrefix .= (strlen( $wpDB->prefix ) > 16) ? " ERROR: Too long" : " Acceptable";
272
+
273
+ $output .= $this->info( "Table Prefix:", $tablePrefix );
274
+
275
+ // Constants
276
+ $output .= $this->info( "WP Content Path:", WP_CONTENT_DIR );
277
+ $output .= $this->info( "WP Plugin Dir:", WP_PLUGIN_DIR );
278
+ if (defined('UPLOADS'))
279
+ $output .= $this->info( "WP UPLOADS CONST:", UPLOADS );
280
+ $uploads = wp_upload_dir();
281
+ $output .= $this->info( "WP Uploads Dir:", $uploads['basedir'] );
282
+ if (defined('WP_TEMP_DIR'))
283
+ $output .= $this->info( "WP Temp Dir:", WP_TEMP_DIR );
284
+
285
+ // WP Debug
286
+ $output .= $this->info( "WP_DEBUG:", (defined( "WP_DEBUG" )) ? WP_DEBUG ? "Enabled" : "Disabled" : "Not set" );
287
+ $output .= $this->info( "Memory Limit:", WP_MEMORY_LIMIT );
288
+ $output .= $this->info( "Registered Post Stati:", implode( ", ", \get_post_stati() ) );
289
+
290
+ return apply_filters( "wpstg_sysinfo_after_wpstg_config", $output );
291
+ }
292
+
293
+ /**
294
+ * List of Active Plugins
295
+ * @param array $plugins
296
+ * @param array $activePlugins
297
+ * @return string
298
+ */
299
+ public function activePlugins( $plugins, $activePlugins ) {
300
+ $output = $this->header( "WordPress Active Plugins" );
301
+
302
+ foreach ( $plugins as $path => $plugin ) {
303
+ if( !in_array( $path, $activePlugins ) ) {
304
+ continue;
305
+ }
306
+
307
+ $output .= "{$plugin["Name"]}: {$plugin["Version"]}" . PHP_EOL;
308
+ }
309
+
310
+ return apply_filters( "wpstg_sysinfo_after_wordpress_plugins", $output );
311
+ }
312
+
313
+ /**
314
+ * List of Inactive Plugins
315
+ * @param array $plugins
316
+ * @param array $activePlugins
317
+ * @return string
318
+ */
319
+ public function inactivePlugins( $plugins, $activePlugins ) {
320
+ $output = $this->header( "WordPress Inactive Plugins" );
321
+
322
+ foreach ( $plugins as $path => $plugin ) {
323
+ if( in_array( $path, $activePlugins ) ) {
324
+ continue;
325
+ }
326
+
327
+ $output .= "{$plugin["Name"]}: {$plugin["Version"]}" . PHP_EOL;
328
+ }
329
+
330
+ return apply_filters( "wpstg_sysinfo_after_wordpress_plugins_inactive", $output );
331
+ }
332
+
333
+ /**
334
+ * Get list of active and inactive plugins
335
+ * @return string
336
+ */
337
+ public function plugins() {
338
+ // Get plugins and active plugins
339
+ $plugins = get_plugins();
340
+ $activePlugins = get_option( "active_plugins", array() );
341
+
342
+ // Active plugins
343
+ $output = $this->activePlugins( $plugins, $activePlugins );
344
+ $output .= $this->inactivePlugins( $plugins, $activePlugins );
345
+
346
+ return $output;
347
+ }
348
+
349
+ /**
350
+ * Multisite Plugins
351
+ * @return string
352
+ */
353
+ public function multiSitePlugins() {
354
+ if( !$this->isMultiSite ) {
355
+ return '';
356
+ }
357
+
358
+ $output = $this->header( "Network Active Plugins" );
359
+
360
+ $plugins = wp_get_active_network_plugins();
361
+ $activePlugins = get_site_option( "active_sitewide_plugins", array() );
362
+
363
+ foreach ( $plugins as $pluginPath ) {
364
+ $pluginBase = plugin_basename( $pluginPath );
365
+
366
+ if( !array_key_exists( $pluginBase, $activePlugins ) ) {
367
+ continue;
368
+ }
369
+
370
+ $plugin = get_plugin_data( $pluginPath );
371
+
372
+ $output .= "{$plugin["Name"]}: {$plugin["Version"]}" . PHP_EOL;
373
+ }
374
+ unset( $plugins, $activePlugins );
375
+
376
+ return $output;
377
+ }
378
+
379
+ /**
380
+ * Server Information
381
+ * @return string
382
+ */
383
+ public function server() {
384
+ // Server Configuration
385
+ $output = $this->header( "Webserver Configuration" );
386
+
387
+ $output .= $this->info( "PHP Version:", PHP_VERSION );
388
+ $output .= $this->info( "MySQL Version:", $this->di->get( "wpdb" )->db_version() );
389
+ $output .= $this->info( "Webserver Info:", $_SERVER["SERVER_SOFTWARE"] );
390
+
391
+ return apply_filters( "wpstg_sysinfo_after_webserver_config", $output );
392
+ }
393
+
394
+ /**
395
+ * PHP Configuration
396
+ * @return string
397
+ */
398
+ public function php() {
399
+ $output = $this->header( "PHP Configuration" );
400
+ $output .= $this->info( "Safe Mode:", ($this->isSafeModeEnabled() ? "Enabled" : "Disabled" ) );
401
+ $output .= $this->info( "Memory Limit:", ini_get( "memory_limit" ) );
402
+ $output .= $this->info( "Upload Max Size:", ini_get( "upload_max_filesize" ) );
403
+ $output .= $this->info( "Post Max Size:", ini_get( "post_max_size" ) );
404
+ $output .= $this->info( "Upload Max Filesize:", ini_get( "upload_max_filesize" ) );
405
+ $output .= $this->info( "Time Limit:", ini_get( "max_execution_time" ) );
406
+ $output .= $this->info( "Max Input Vars:", ini_get( "max_input_vars" ) );
407
+
408
+ $displayErrors = ini_get( "display_errors" );
409
+ $output .= $this->info( "Display Errors:", ($displayErrors) ? "On ({$displayErrors})" : "N/A" );
410
+
411
+ return apply_filters( "wpstg_sysinfo_after_php_config", $output );
412
+ }
413
+
414
+ /**
415
+ * Check if PHP is on Safe Mode
416
+ * @return bool
417
+ */
418
+ public function isSafeModeEnabled() {
419
+ return (
420
+ version_compare( PHP_VERSION, "5.4.0", '<' ) &&
421
+ @ini_get( "safe_mode" )
422
+ );
423
+ }
424
+
425
+ /**
426
+ * Checks if function exists or not
427
+ * @param string $functionName
428
+ * @return string
429
+ */
430
+ public function isSupported( $functionName ) {
431
+ return (function_exists( $functionName )) ? "Supported" : "Not Supported";
432
+ }
433
+
434
+ /**
435
+ * Checks if class or extension is loaded / exists to determine if it is installed or not
436
+ * @param string $name
437
+ * @param bool $isClass
438
+ * @return string
439
+ */
440
+ public function isInstalled( $name, $isClass = true ) {
441
+ if( true === $isClass ) {
442
+ return (class_exists( $name )) ? "Installed" : "Not Installed";
443
+ } else {
444
+ return (extension_loaded( $name )) ? "Installed" : "Not Installed";
445
+ }
446
+ }
447
+
448
+ /**
449
+ * Gets Installed Important PHP Extensions
450
+ * @return string
451
+ */
452
+ public function phpExtensions() {
453
+ // Important PHP Extensions
454
+ $output = $this->header( "PHP Extensions" );
455
+ $output .= $this->info( "cURL:", $this->isSupported( "curl_init" ) );
456
+ $output .= $this->info( "fsockopen:", $this->isSupported( "fsockopen" ) );
457
+ $output .= $this->info( "SOAP Client:", $this->isInstalled( "SoapClient" ) );
458
+ $output .= $this->info( "Suhosin:", $this->isInstalled( "suhosin", false ) );
459
+
460
+ return apply_filters( "wpstg_sysinfo_after_php_ext", $output );
461
+ }
462
+
463
+ /**
464
+ * Check if WP is installed in subdir
465
+ * @return boolean
466
+ */
467
+ private function isSubDir() {
468
+ if( get_option( 'siteurl' ) !== get_option( 'home' ) ) {
469
+ return true;
470
+ }
471
+ return false;
472
+ }
473
+
474
+ /**
475
+ * Check and return prefix of the staging site
476
+ */
477
+ /**
478
+ * Try to get the staging prefix from wp-config.php of staging site
479
+ * @param array $clone
480
+ * @return sting
481
+ */
482
+ private function getStagingPrefix( $clone = array() ) {
483
+ // Throw error
484
+ $path = ABSPATH . $clone['directoryName'] . "/wp-config.php";
485
+ if( false === ($content = @file_get_contents( $path )) ) {
486
+ return 'Can\'t find staging wp-config.php';
487
+ } else {
488
+
489
+ // Get prefix from wp-config.php
490
+ //preg_match_all("/table_prefix\s*=\s*'(\w*)';/", $content, $matches);
491
+ preg_match( "/table_prefix\s*=\s*'(\w*)';/", $content, $matches );
492
+ //wp_die(var_dump($matches));
493
+
494
+ if( !empty( $matches[1] ) ) {
495
+ return $matches[1];
496
+ } else {
497
+ return 'No table_prefix in wp-config.php';
498
+ }
499
+ }
500
+ }
501
+
502
+ }
apps/Backend/Modules/Views/Forms/Settings.php CHANGED
@@ -1,237 +1,237 @@
1
- <?php
2
-
3
- namespace WPStaging\Backend\Modules\Views\Forms;
4
-
5
- use WPStaging\Forms\Elements\Check;
6
- use WPStaging\Forms\Elements\Numerical;
7
- use WPStaging\Forms\Elements\Select;
8
- use WPStaging\Forms\Elements\SelectMultiple;
9
- use WPStaging\Forms\Elements\Text;
10
- use WPStaging\Forms\Form;
11
- use WPStaging\Backend\Modules\Views\Tabs\Tabs;
12
-
13
- /**
14
- * Class Settings
15
- * @package WPStaging\Backend\Modules\Views\Forms
16
- */
17
- class Settings {
18
-
19
- /**
20
- * @var array
21
- */
22
- private $form = array();
23
-
24
- /**
25
- * @var Tabs
26
- */
27
- private $tabs;
28
-
29
- /**
30
- * Settings constructor.
31
- * @param Tabs $tabs
32
- */
33
- public function __construct( $tabs ) {
34
- $this->tabs = $tabs;
35
-
36
- foreach ( $this->tabs->get() as $id => $name ) {
37
- if( !method_exists( $this, $id ) ) {
38
- continue;
39
- }
40
-
41
- $this->{$id}();
42
- }
43
- }
44
-
45
- private function general() {
46
- $this->form["general"] = new Form();
47
-
48
- $settings = json_decode( json_encode( get_option( "wpstg_settings", array() ) ) );
49
-
50
- // DB Copy Query Limit
51
- $element = new Numerical(
52
- "wpstg_settings[queryLimit]", array(
53
- "class" => "medium-text",
54
- "step" => 1,
55
- "max" => 999999,
56
- "min" => 0
57
- )
58
- );
59
-
60
- $this->form["general"]->add(
61
- $element->setLabel( "DB Copy Query Limit" )
62
- ->setDefault( isset( $settings->queryLimit ) ? $settings->queryLimit : 5000 )
63
- );
64
- // DB Search & Replace Query Limit
65
- $element = new Numerical(
66
- "wpstg_settings[querySRLimit]", array(
67
- "class" => "medium-text",
68
- "step" => 1,
69
- "max" => 999999,
70
- "min" => 0
71
- )
72
- );
73
-
74
- $this->form["general"]->add(
75
- $element->setLabel( "DB Search & Replace Limit" )
76
- ->setDefault( isset( $settings->querySRLimit ) ? $settings->querySRLimit : 5000 )
77
- );
78
-
79
- $options = array('1' => '1', '10' => '10', '50' => '50', '250' => '250', '500' => '500', '1000' => '1000');
80
- // DB Copy Query Limit
81
- $element = new Select(
82
- "wpstg_settings[fileLimit]", $options, array(
83
- "class" => "medium-text",
84
- "step" => 1,
85
- "max" => 999999,
86
- "min" => 0
87
- )
88
- );
89
-
90
- $this->form["general"]->add(
91
- $element->setLabel( "File Copy Limit" )->setDefault( isset( $settings->fileLimit ) ? $settings->fileLimit : 1 )
92
- );
93
-
94
-
95
- // File Copy Batch Size
96
- $element = new Numerical(
97
- "wpstg_settings[maxFileSize]", array(
98
- "class" => "medium-text",
99
- "step" => 1,
100
- "max" => 999999,
101
- "min" => 0
102
- )
103
- );
104
-
105
- $this->form["general"]->add(
106
- $element->setLabel( "Maximum File Size (MB)" )
107
- ->setDefault( isset( $settings->maxFileSize ) ? $settings->maxFileSize : 8 )
108
- );
109
-
110
- // File Copy Batch Size
111
- $element = new Numerical(
112
- "wpstg_settings[batchSize]", array(
113
- "class" => "medium-text",
114
- "step" => 1,
115
- "max" => 999999,
116
- "min" => 0
117
- )
118
- );
119
-
120
- $this->form["general"]->add(
121
- $element->setLabel( "File Copy Batch Size" )
122
- ->setDefault( isset( $settings->batchSize ) ? $settings->batchSize : 2 )
123
- );
124
-
125
- // CPU load priority
126
- $element = new Select(
127
- "wpstg_settings[cpuLoad]", array(
128
- "high" => "High (fast)",
129
- "medium" => "Medium (average)",
130
- "low" => "Low (slow)"
131
- )
132
- );
133
-
134
- $this->form["general"]->add(
135
- $element->setLabel( "CPU Load Priority" )
136
- ->setDefault( isset( $settings->cpuLoad ) ? $settings->cpuLoad : "fast" )
137
- );
138
-
139
-
140
- // Optimizer
141
- $element = new Check(
142
- "wpstg_settings[optimizer]", array('1' => "")
143
- );
144
-
145
- $this->form["general"]->add(
146
- $element->setLabel( "Optimizer" )
147
- ->setDefault( (isset( $settings->optimizer )) ? $settings->optimizer : null )
148
- );
149
-
150
- // Plugins
151
- $plugins = array();
152
-
153
- foreach ( get_plugins() as $key => $data ) {
154
- if( "wp-staging/wp-staging.php" === $key ) {
155
- continue;
156
- }
157
-
158
- $plugins[$key] = $data["Name"];
159
- }
160
-
161
- $element = new Select(
162
- "wpstg_settings[blackListedPlugins][]", $plugins, array(
163
- "multiple" => "multiple",
164
- "style" => "min-height:400px;"
165
- )
166
- );
167
-
168
- $this->form["general"]->add(
169
- $element->setDefault( (isset( $settings->blackListedPlugins )) ? $settings->blackListedPlugins : null )
170
- );
171
-
172
- // Disable admin authorization
173
- $element = new Check(
174
- "wpstg_settings[disableAdminLogin]", array('1' => '')
175
- );
176
-
177
- $this->form["general"]->add(
178
- $element->setLabel( "Disable admin authorization" )
179
- ->setDefault( (isset( $settings->disableAdminLogin )) ? $settings->disableAdminLogin : null )
180
- );
181
-
182
- // WordPress in subdirectory
183
- $element = new Check(
184
- "wpstg_settings[wpSubDirectory]", array('1' => '')
185
- );
186
-
187
- $this->form["general"]->add(
188
- $element->setLabel( "Wordpress in subdirectory" )
189
- ->setDefault( (isset( $settings->wpSubDirectory )) ? $settings->wpSubDirectory : null )
190
- );
191
-
192
- // Debug Mode
193
- $element = new Check(
194
- "wpstg_settings[debugMode]", array('1' => '')
195
- );
196
-
197
- $this->form["general"]->add(
198
- $element->setLabel( "Debug Mode" )
199
- ->setDefault( (isset( $settings->debugMode )) ? $settings->debugMode : null )
200
- );
201
-
202
- // Remove Data on Uninstall?
203
- $element = new Check(
204
- "wpstg_settings[unInstallOnDelete]", array('1' => '')
205
- );
206
-
207
- $this->form["general"]->add(
208
- $element->setLabel( "Remove Data on Uninstall?" )
209
- ->setDefault( (isset( $settings->unInstallOnDelete )) ? $settings->unInstallOnDelete : null )
210
- );
211
-
212
- // Check Directory Sizes
213
- $element = new Check(
214
- "wpstg_settings[checkDirectorySize]", array('1' => '')
215
- );
216
-
217
- $this->form["general"]->add(
218
- $element->setLabel( "Check Directory Size" )
219
- ->setDefault( (isset( $settings->checkDirectorySize )) ? $settings->checkDirectorySize : null )
220
- );
221
-
222
- // Set login post id
223
- $element = new Text( 'wpstg_settings[loginSlug]', array() );
224
- $this->form["general"]->add(
225
- $element->setLabel( "Login Custom Link" )->setDefault( isset( $settings->loginSlug ) ? $settings->loginSlug : '' )
226
- );
227
- }
228
-
229
- /**
230
- * @param string $name
231
- * @return array|Form
232
- */
233
- public function get( $name = null ) {
234
- return (null === $name) ? $this->form : $this->form[$name];
235
- }
236
-
237
- }
1
+ <?php
2
+
3
+ namespace WPStaging\Backend\Modules\Views\Forms;
4
+
5
+ use WPStaging\Forms\Elements\Check;
6
+ use WPStaging\Forms\Elements\Numerical;
7
+ use WPStaging\Forms\Elements\Select;
8
+ use WPStaging\Forms\Elements\SelectMultiple;
9
+ use WPStaging\Forms\Elements\Text;
10
+ use WPStaging\Forms\Form;
11
+ use WPStaging\Backend\Modules\Views\Tabs\Tabs;
12
+
13
+ /**
14
+ * Class Settings
15
+ * @package WPStaging\Backend\Modules\Views\Forms
16
+ */
17
+ class Settings {
18
+
19
+ /**
20
+ * @var array
21
+ */
22
+ private $form = array();
23
+
24
+ /**
25
+ * @var Tabs
26
+ */
27
+ private $tabs;
28
+
29
+ /**
30
+ * Settings constructor.
31
+ * @param Tabs $tabs
32
+ */
33
+ public function __construct( $tabs ) {
34
+ $this->tabs = $tabs;
35
+
36
+ foreach ( $this->tabs->get() as $id => $name ) {
37
+ if( !method_exists( $this, $id ) ) {
38
+ continue;
39
+ }
40
+
41
+ $this->{$id}();
42
+ }
43
+ }
44
+
45
+ private function general() {
46
+ $this->form["general"] = new Form();
47
+
48
+ $settings = json_decode( json_encode( get_option( "wpstg_settings", array() ) ) );
49
+
50
+ // DB Copy Query Limit
51
+ $element = new Numerical(
52
+ "wpstg_settings[queryLimit]", array(
53
+ "class" => "medium-text",
54
+ "step" => 1,
55
+ "max" => 999999,
56
+ "min" => 0
57
+ )
58
+ );
59
+
60
+ $this->form["general"]->add(
61
+ $element->setLabel( "DB Copy Query Limit" )
62
+ ->setDefault( isset( $settings->queryLimit ) ? $settings->queryLimit : 5000 )
63
+ );
64
+ // DB Search & Replace Query Limit
65
+ $element = new Numerical(
66
+ "wpstg_settings[querySRLimit]", array(
67
+ "class" => "medium-text",
68
+ "step" => 1,
69
+ "max" => 999999,
70
+ "min" => 0
71
+ )
72
+ );
73
+
74
+ $this->form["general"]->add(
75
+ $element->setLabel( "DB Search & Replace Limit" )
76
+ ->setDefault( isset( $settings->querySRLimit ) ? $settings->querySRLimit : 5000 )
77
+ );
78
+
79
+ $options = array('1' => '1', '10' => '10', '50' => '50', '250' => '250', '500' => '500', '1000' => '1000');
80
+ // DB Copy Query Limit
81
+ $element = new Select(
82
+ "wpstg_settings[fileLimit]", $options, array(
83
+ "class" => "medium-text",
84
+ "step" => 1,
85
+ "max" => 999999,
86
+ "min" => 0
87
+ )
88
+ );
89
+
90
+ $this->form["general"]->add(
91
+ $element->setLabel( "File Copy Limit" )->setDefault( isset( $settings->fileLimit ) ? $settings->fileLimit : 1 )
92
+ );
93
+
94
+
95
+ // File Copy Batch Size
96
+ $element = new Numerical(
97
+ "wpstg_settings[maxFileSize]", array(
98
+ "class" => "medium-text",
99
+ "step" => 1,
100
+ "max" => 999999,
101
+ "min" => 0
102
+ )
103
+ );
104
+
105
+ $this->form["general"]->add(
106
+ $element->setLabel( "Maximum File Size (MB)" )
107
+ ->setDefault( isset( $settings->maxFileSize ) ? $settings->maxFileSize : 8 )
108
+ );
109
+
110
+ // File Copy Batch Size
111
+ $element = new Numerical(
112
+ "wpstg_settings[batchSize]", array(
113
+ "class" => "medium-text",
114
+ "step" => 1,
115
+ "max" => 999999,
116
+ "min" => 0
117
+ )
118
+ );
119
+
120
+ $this->form["general"]->add(
121
+ $element->setLabel( "File Copy Batch Size" )
122
+ ->setDefault( isset( $settings->batchSize ) ? $settings->batchSize : 2 )
123
+ );
124
+
125
+ // CPU load priority
126
+ $element = new Select(
127
+ "wpstg_settings[cpuLoad]", array(
128
+ "high" => "High (fast)",
129
+ "medium" => "Medium (average)",
130
+ "low" => "Low (slow)"
131
+ )
132
+ );
133
+
134
+ $this->form["general"]->add(
135
+ $element->setLabel( "CPU Load Priority" )
136
+ ->setDefault( isset( $settings->cpuLoad ) ? $settings->cpuLoad : "fast" )
137
+ );
138
+
139
+
140
+ // Optimizer
141
+ $element = new Check(
142
+ "wpstg_settings[optimizer]", array('1' => "")
143
+ );
144
+
145
+ $this->form["general"]->add(
146
+ $element->setLabel( "Optimizer" )
147
+ ->setDefault( (isset( $settings->optimizer )) ? $settings->optimizer : null )
148
+ );
149
+
150
+ // Plugins
151
+ $plugins = array();
152
+
153
+ foreach ( get_plugins() as $key => $data ) {
154
+ if( "wp-staging/wp-staging.php" === $key ) {
155
+ continue;
156
+ }
157
+
158
+ $plugins[$key] = $data["Name"];
159
+ }
160
+
161
+ $element = new Select(
162
+ "wpstg_settings[blackListedPlugins][]", $plugins, array(
163
+ "multiple" => "multiple",
164
+ "style" => "min-height:400px;"
165
+ )
166
+ );
167
+
168
+ $this->form["general"]->add(
169
+ $element->setDefault( (isset( $settings->blackListedPlugins )) ? $settings->blackListedPlugins : null )
170
+ );
171
+
172
+ // Disable admin authorization
173
+ $element = new Check(
174
+ "wpstg_settings[disableAdminLogin]", array('1' => '')
175
+ );
176
+
177
+ $this->form["general"]->add(
178
+ $element->setLabel( "Disable admin authorization" )
179
+ ->setDefault( (isset( $settings->disableAdminLogin )) ? $settings->disableAdminLogin : null )
180
+ );
181
+
182
+ // WordPress in subdirectory
183
+ $element = new Check(
184
+ "wpstg_settings[wpSubDirectory]", array('1' => '')
185
+ );
186
+
187
+ $this->form["general"]->add(
188
+ $element->setLabel( "Wordpress in subdirectory" )
189
+ ->setDefault( (isset( $settings->wpSubDirectory )) ? $settings->wpSubDirectory : null )
190
+ );
191
+
192
+ // Debug Mode
193
+ $element = new Check(
194
+ "wpstg_settings[debugMode]", array('1' => '')
195
+ );
196
+
197
+ $this->form["general"]->add(
198
+ $element->setLabel( "Debug Mode" )
199
+ ->setDefault( (isset( $settings->debugMode )) ? $settings->debugMode : null )
200
+ );
201
+
202
+ // Remove Data on Uninstall?
203
+ $element = new Check(
204
+ "wpstg_settings[unInstallOnDelete]", array('1' => '')
205
+ );
206
+
207
+ $this->form["general"]->add(
208
+ $element->setLabel( "Remove Data on Uninstall?" )
209
+ ->setDefault( (isset( $settings->unInstallOnDelete )) ? $settings->unInstallOnDelete : null )
210
+ );
211
+
212
+ // Check Directory Sizes
213
+ $element = new Check(
214
+ "wpstg_settings[checkDirectorySize]", array('1' => '')
215
+ );
216
+
217
+ $this->form["general"]->add(
218
+ $element->setLabel( "Check Directory Size" )
219
+ ->setDefault( (isset( $settings->checkDirectorySize )) ? $settings->checkDirectorySize : null )
220
+ );
221
+
222
+ // Set login post id
223
+ $element = new Text( 'wpstg_settings[loginSlug]', array() );
224
+ $this->form["general"]->add(
225
+ $element->setLabel( "Login Custom Link" )->setDefault( isset( $settings->loginSlug ) ? $settings->loginSlug : '' )
226
+ );
227
+ }
228
+
229
+ /**
230
+ * @param string $name
231
+ * @return array|Form
232
+ */
233
+ public function get( $name = null ) {
234
+ return (null === $name) ? $this->form : $this->form[$name];
235
+ }
236
+
237
+ }
apps/Backend/views/clone/ajax/update.php CHANGED
@@ -1,33 +1,33 @@
1
- <div class=successfullying-section">
2
- <h2 id="wpstg-processing-header"><?php echo __("Processing, please wait...", "wpstg")?></h2>
3
- <div class="wpstg-progress-bar">
4
- <div class="wpstg-progress" id="wpstg-progress-db" style="width:0;overflow: hidden;"></div>
5
- <div class="wpstg-progress" id="wpstg-progress-sr" style="width:0;background-color:#3c9ee4;overflow: hidden;"></div>
6
- <div class="wpstg-progress" id="wpstg-progress-dirs" style="width:0;background-color:#3a96d7;overflow: hidden;"></div>
7
- <div class="wpstg-progress" id="wpstg-progress-files" style="width:0;background-color:#378cc9;overflow: hidden;"></div>
8
- </div>
9
- <div style="clear:both;">
10
- <div id="wpstg-processing-status"></div>
11
- <div id="wpstg-processing-timer"></div>
12
- </div>
13
- <div style="clear: both;"></div>
14
- </div>
15
-
16
- <button type="button" id="wpstg-cancel-cloning-update" class="wpstg-link-btn button-primary">
17
- <?php echo __("Cancel Update", "wpstg")?>
18
- </button>
19
-
20
- <button type="button" id="wpstg-show-log-button" class="button" data-clone="<?php echo $cloning->getOptions()->clone?>" style="margin-top: 5px;display:none;">
21
- <?php _e('Display working log', 'wpstg')?>
22
- </button>
23
-
24
- <div>
25
- <span id="wpstg-cloning-result"></span>
26
- </div>
27
-
28
-
29
- <div id="wpstg-error-wrapper">
30
- <div id="wpstg-error-details"></div>
31
- </div>
32
-
33
  <div id="wpstg-log-details"></div>
1
+ <div class=successfullying-section">
2
+ <h2 id="wpstg-processing-header"><?php echo __("Processing, please wait...", "wpstg")?></h2>
3
+ <div class="wpstg-progress-bar">
4
+ <div class="wpstg-progress" id="wpstg-progress-db" style="width:0;overflow: hidden;"></div>
5
+ <div class="wpstg-progress" id="wpstg-progress-sr" style="width:0;background-color:#3c9ee4;overflow: hidden;"></div>
6
+ <div class="wpstg-progress" id="wpstg-progress-dirs" style="width:0;background-color:#3a96d7;overflow: hidden;"></div>
7
+ <div class="wpstg-progress" id="wpstg-progress-files" style="width:0;background-color:#378cc9;overflow: hidden;"></div>
8
+ </div>
9
+ <div style="clear:both;">
10
+ <div id="wpstg-processing-status"></div>
11
+ <div id="wpstg-processing-timer"></div>
12
+ </div>
13
+ <div style="clear: both;"></div>
14
+ </div>
15
+
16
+ <button type="button" id="wpstg-cancel-cloning-update" class="wpstg-link-btn button-primary">
17
+ <?php echo __("Cancel Update", "wpstg")?>
18
+ </button>
19
+
20
+ <button type="button" id="wpstg-show-log-button" class="button" data-clone="<?php echo $cloning->getOptions()->clone?>" style="margin-top: 5px;display:none;">
21
+ <?php _e('Display working log', 'wpstg')?>
22
+ </button>
23
+
24
+ <div>
25
+ <span id="wpstg-cloning-result"></span>
26
+ </div>
27
+
28
+
29
+ <div id="wpstg-error-wrapper">
30
+ <div id="wpstg-error-details"></div>
31
+ </div>
32
+
33
  <div id="wpstg-log-details"></div>
apps/Backend/views/clone/includes/footer.php CHANGED
@@ -1,4 +1,4 @@
1
- <div style="clear:both;">Something not working? Open a <a href="https://wp-staging.com/support" target="_blank" rel="external nofollow"> support ticket</a> and we help you to fix it quickly.</div>
2
- <div id="wpstg-error-wrapper">
3
- <div id="wpstg-error-details"></div>
4
  </div>
1
+ <div style="clear:both;">Something not working? Open a <a href="https://wp-staging.com/support" target="_blank" rel="external nofollow"> support ticket</a> and we help you to fix it quickly.</div>
2
+ <div id="wpstg-error-wrapper">
3
+ <div id="wpstg-error-details"></div>
4
  </div>
apps/Backend/views/settings/index.php CHANGED
@@ -154,8 +154,24 @@
154
  <tr class="row">
155
  <td class="row th">
156
  <div class="col-title">
157
- <?php echo $form->label( "wpstg_settings[batchSize]" ) ?>
158
  <span class="description">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  Buffer size for the file copy process in megabyte.
160
  The higher the value the faster large files will be copied.
161
  To find out the highest possible values try a high one and lower it until
@@ -163,7 +179,7 @@
163
  with the memory consumption of php so make sure that
164
  it does not exceed any php.ini max_memory limits.
165
  <br>
166
- <strong>Default:</strong> 2
167
  </span>
168
  </div>
169
  </td>
154
  <tr class="row">
155
  <td class="row th">
156
  <div class="col-title">
157
+ <?php echo $form->label("wpstg_settings[maxFileSize]")?>
158
  <span class="description">
159
+ Maximum size of the files which are allowed to copy. All files larger than this value will be skipped.
160
+ Note: Increase this option only if you have a good reason. Files larger than a few megabytes are in 99% of all cases logging and backup files which are not needed on a staging site.
161
+ <br>
162
+ <strong>Default:</strong> 8 MB
163
+ </span>
164
+ </div>
165
+ </td>
166
+ <td>
167
+ <?php echo $form->render("wpstg_settings[maxFileSize]")?>
168
+ </td>
169
+ </tr>
170
+ <tr class="row">
171
+ <td class="row th">
172
+ <div class="col-title">
173
+ <?php echo $form->label("wpstg_settings[batchSize]")?>
174
+ <span class="description">
175
  Buffer size for the file copy process in megabyte.
176
  The higher the value the faster large files will be copied.
177
  To find out the highest possible values try a high one and lower it until
179
  with the memory consumption of php so make sure that
180
  it does not exceed any php.ini max_memory limits.
181
  <br>
182
+ <strong>Default:</strong> 2 MB
183
  </span>
184
  </div>
185
  </td>
apps/Core/Utils/Helper.php CHANGED
@@ -1,74 +1,81 @@
1
- <?php
2
-
3
- namespace WPStaging\Utils;
4
-
5
- // No Direct Access
6
- if (!defined("WPINC"))
7
- {
8
- die;
9
- }
10
-
11
- class Helper {
12
-
13
-
14
- /**
15
- * Retrieves the URL for a given site where the front end is accessible.
16
- * This is from WordPress source 4.9.5/src/wp-includes/link-template.php
17
- * home_url filter has been removed here to make sure that wpml can not overwrite that value!
18
- *
19
- * Returns the 'home' option with the appropriate protocol. The protocol will be 'https'
20
- * if is_ssl() evaluates to true; otherwise, it will be the same as the 'home' option.
21
- * If `$scheme` is 'http' or 'https', is_ssl() is overridden.
22
- *
23
- * @since 3.0.0
24
- *
25
- * @global string $pagenow
26
- *
27
- * @param int $blog_id Optional. Site ID. Default null (current site).
28
- * @param string $path Optional. Path relative to the home URL. Default empty.
29
- * @param string|null $scheme Optional. Scheme to give the home URL context. Accepts
30
- * 'http', 'https', 'relative', 'rest', or null. Default null.
31
- * @return string Home URL link with optional path appended.
32
- */
33
- public function get_home_url( $blog_id = null, $path = '', $scheme = null ) {
34
- global $pagenow;
35
-
36
- $orig_scheme = $scheme;
37
-
38
- if( empty( $blog_id ) || !is_multisite() ) {
39
- $url = get_option( 'home' );
40
- } else {
41
- switch_to_blog( $blog_id );
42
- $url = get_option( 'home' );
43
- restore_current_blog();
44
- }
45
-
46
- if( !in_array( $scheme, array('http', 'https', 'relative') ) ) {
47
- if( is_ssl() && !is_admin() && 'wp-login.php' !== $pagenow )
48
- $scheme = 'https';
49
- else
50
- $scheme = parse_url( $url, PHP_URL_SCHEME );
51
- }
52
-
53
- $url = set_url_scheme( $url, $scheme );
54
-
55
- if( $path && is_string( $path ) )
56
- $url .= '/' . ltrim( $path, '/' );
57
-
58
- /**
59
- * Filters the home URL.
60
- *
61
- * @since 3.0.0
62
- *
63
- * @param string $url The complete home URL including scheme and path.
64
- * @param string $path Path relative to the home URL. Blank string if no path is specified.
65
- * @param string|null $orig_scheme Scheme to give the home URL context. Accepts 'http', 'https',
66
- * 'relative', 'rest', or null.
67
- * @param int|null $blog_id Site ID, or null for the current site.
68
- */
69
- return $url;
70
- }
71
-
72
-
73
-
74
- }
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace WPStaging\Utils;
4
+
5
+ // No Direct Access
6
+ if (!defined("WPINC"))
7
+ {
8
+ die;
9
+ }
10
+
11
+ class Helper {
12
+
13
+
14
+ /**
15
+ * Retrieves the URL for a given site where the front end is accessible.
16
+ * This is from WordPress source 4.9.5/src/wp-includes/link-template.php
17
+ * home_url filter has been removed here to make sure that wpml can not overwrite that value!
18
+ *
19
+ * Returns the 'home' option with the appropriate protocol. The protocol will be 'https'
20
+ * if is_ssl() evaluates to true; otherwise, it will be the same as the 'home' option.
21
+ * If `$scheme` is 'http' or 'https', is_ssl() is overridden.
22
+ *
23
+ * @since 3.0.0
24
+ *
25
+ * @global string $pagenow
26
+ *
27
+ * @param int $blog_id Optional. Site ID. Default null (current site).
28
+ * @param string $path Optional. Path relative to the home URL. Default empty.
29
+ * @param string|null $scheme Optional. Scheme to give the home URL context. Accepts
30
+ * 'http', 'https', 'relative', 'rest', or null. Default null.
31
+ * @return string Home URL link with optional path appended.
32
+ */
33
+ public function get_home_url( $blog_id = null, $path = '', $scheme = null ) {
34
+ global $pagenow;
35
+
36
+ $orig_scheme = $scheme;
37
+
38
+ if( empty( $blog_id ) || !is_multisite() ) {
39
+ $url = get_option( 'home' );
40
+ } else {
41
+ switch_to_blog( $blog_id );
42
+ $url = get_option( 'home' );
43
+ restore_current_blog();
44
+ }
45
+
46
+ if( !in_array( $scheme, array('http', 'https', 'relative') ) ) {
47
+ if( is_ssl() && !is_admin() && 'wp-login.php' !== $pagenow )
48
+ $scheme = 'https';
49
+ else
50
+ $scheme = parse_url( $url, PHP_URL_SCHEME );
51
+ }
52
+
53
+ $url = set_url_scheme( $url, $scheme );
54
+
55
+ if( $path && is_string( $path ) )
56
+ $url .= '/' . ltrim( $path, '/' );
57
+
58
+ /**
59
+ * Filters the home URL.
60
+ *
61
+ * @since 3.0.0
62
+ *
63
+ * @param string $url The complete home URL including scheme and path.
64
+ * @param string $path Path relative to the home URL. Blank string if no path is specified.
65
+ * @param string|null $orig_scheme Scheme to give the home URL context. Accepts 'http', 'https',
66
+ * 'relative', 'rest', or null.
67
+ * @param int|null $blog_id Site ID, or null for the current site.
68
+ */
69
+ return $url;
70
+ }
71
+
72
+ /**
73
+ * Return home url without scheme
74
+ * @param string $str
75
+ * @return string
76
+ */
77
+ public function get_home_url_without_scheme() {
78
+ return preg_replace( '#^https?://#', '', rtrim( $this->get_home_url(), '/' ) );
79
+ }
80
+
81
+ }
apps/Core/Utils/Multisite.php CHANGED
@@ -1,26 +1,26 @@
1
- <?php
2
-
3
- namespace WPStaging\Utils;
4
-
5
- use WPStaging\Utils\Helper;
6
-
7
- // No Direct Access
8
- if( !defined( "WPINC" ) ) {
9
- die;
10
- }
11
-
12
- class Multisite {
13
-
14
- /**
15
- * Get multisite main site homeurl
16
- * @return string
17
- */
18
- public function getHomeURL() {
19
- $helper = new Helper();
20
-
21
- $url = $helper->get_home_url();
22
- $result = parse_url( $url );
23
- return $result['scheme'] . "://" . $result['host'];
24
- }
25
-
26
- }
1
+ <?php
2
+
3
+ namespace WPStaging\Utils;
4
+
5
+ use WPStaging\Utils\Helper;
6
+
7
+ // No Direct Access
8
+ if( !defined( "WPINC" ) ) {
9
+ die;
10
+ }
11
+
12
+ class Multisite {
13
+
14
+ /**
15
+ * Get multisite main site homeurl
16
+ * @return string
17
+ */
18
+ public function getHomeURL() {
19
+ $helper = new Helper();
20
+
21
+ $url = $helper->get_home_url();
22
+ $result = parse_url( $url );
23
+ return $result['scheme'] . "://" . $result['host'];
24
+ }
25
+
26
+ }
apps/Core/WPStaging.php CHANGED
@@ -1,429 +1,429 @@
1
- <?php
2
-
3
- namespace WPStaging;
4
-
5
- // No Direct Access
6
- if( !defined( "WPINC" ) ) {
7
- die;
8
- }
9
-
10
- // Ensure to include autoloader class
11
- require_once __DIR__ . DIRECTORY_SEPARATOR . "Utils" . DIRECTORY_SEPARATOR . "Autoloader.php";
12
-
13
- use WPStaging\Backend\Administrator;
14
- use WPStaging\DTO\Settings;
15
- use WPStaging\Frontend\Frontend;
16
- use WPStaging\Utils\Autoloader;
17
- use WPStaging\Utils\Cache;
18
- use WPStaging\Utils\Loader;
19
- use WPStaging\Utils\Logger;
20
- use WPStaging\DI\InjectionAware;
21
- use WPStaging\Cron\Cron;
22
-
23
- /**
24
- * Class WPStaging
25
- * @package WPStaging
26
- */
27
- final class WPStaging {
28
-
29
- /**
30
- * Plugin version
31
- */
32
- const VERSION = "2.3.0";
33
-
34
- /**
35
- * Plugin name
36
- */
37
- const NAME = "WP Staging";
38
-
39
- /**
40
- * Plugin slug
41
- */
42
- const SLUG = "wp-staging";
43
-
44
- /**
45
- * Compatible WP Version
46
- */
47
- const WP_COMPATIBLE = "4.9.6";
48
-
49
- /**
50
- * Slug: Either wp-staging or wp-staging-pro
51
- * @var string
52
- */
53
- public $slug;
54
-
55
- /**
56
- * Absolute plugin path
57
- * @var string
58
- */
59
- public $pluginPath;
60
-
61
- /**
62
- * Services
63
- * @var array
64
- */
65
- private $services;
66
-
67
- /**
68
- * Singleton instance
69
- * @var WPStaging
70
- */
71
- private static $instance;
72
-
73
- /**
74
- * WPStaging constructor.
75
- */
76
- private function __construct() {
77
-
78
- $file = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . self::SLUG . DIRECTORY_SEPARATOR . self::SLUG . ".php";
79
-
80
- $this->registerMain();
81
- $this->registerNamespaces();
82
- $this->loadLanguages();
83
- $this->loadDependencies();
84
- $this->defineHooks();
85
- // Licensing stuff be loaded in wpstg core to make cron hook available from frontpage
86
- $this->initLicensing();
87
- }
88
-
89
- /**
90
- * Method to be executed upon activation of the plugin
91
- */
92
- public function onActivation() {
93
- $Activation = new \WPStaging\Backend\Activation\Activation();
94
- $Activation->install_dependancies();
95
- }
96
-
97
- public function registerMain() {
98
- // Slug of the plugin
99
- $this->slug = plugin_basename( dirname( dirname( dirname( __FILE__ ) ) ) );
100
-
101
- // absolute path to the main plugin dir
102
- $this->pluginPath = plugin_dir_path( dirname( dirname( __FILE__ ) ) );
103
-
104
- // URL to apps folder
105
- $this->url = plugin_dir_url( dirname( __FILE__ ) );
106
-
107
- // URL to backend public folder folder
108
- $this->backend_url = plugin_dir_url( dirname( __FILE__ ) ) . "Backend/public/";
109
-
110
- // URL to frontend public folder folder
111
- $this->frontend_url = plugin_dir_url( dirname( __FILE__ ) ) . "Frontend/public/";
112
- }
113
-
114
- /**
115
- * Define Hooks
116
- */
117
- public function defineHooks() {
118
- $loader = $this->get( "loader" );
119
- $loader->addAction( "admin_enqueue_scripts", $this, "enqueueElements", 100 );
120
- $loader->addAction( "wp_enqueue_scripts", $this, "enqueueElements", 100 );
121
- $this->addIntervals();
122
- }
123
-
124
- /**
125
- * Add new cron time event "weekly"
126
- */
127
- public function addIntervals(){
128
- $interval = new Cron();
129
- }
130
-
131
- /**
132
- * Scripts and Styles
133
- * @param string $hook
134
- */
135
- public function enqueueElements( $hook ) {
136
-
137
- // Load this css file on frontend and backend on all pages if current site is a staging site
138
- if( $this->isStagingSite() ) {
139
- wp_enqueue_style( "wpstg-admin-bar", $this->backend_url . "css/wpstg-admin-bar.css", $this->getVersion() );
140
- }
141
-
142
- $availablePages = array(
143
- "toplevel_page_wpstg_clone",
144
- "wp-staging_page_wpstg-settings",
145
- "wp-staging_page_wpstg-tools",
146
- "wp-staging_page_wpstg-license",
147
- "wp-staging_page_wpstg-welcome",
148
- );
149
-
150
- // Load these css and js files only on wp staging admin pages
151
- if( !in_array( $hook, $availablePages ) || !is_admin() ) {
152
- return;
153
- }
154
-
155
- // Load admin js files
156
- wp_enqueue_script(
157
- "wpstg-admin-script", $this->backend_url . "js/wpstg-admin.js", array("jquery"), $this->getVersion(), false
158
- );
159
-
160
- // Load admin css files
161
- wp_enqueue_style(
162
- "wpstg-admin", $this->backend_url . "css/wpstg-admin.css", $this->getVersion()
163
- );
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
-
187
- /**
188
- * Get table prefix of the current site
189
- * @return string
190
- */
191
- public static function getTablePrefix(){
192
- $wpDB = WPStaging::getInstance()->get("wpdb");
193
- return $wpDB->prefix;
194
- }
195
-
196
- /**
197
- * Caching and logging folder
198
- *
199
- * @return string
200
- */
201
- public static function getContentDir() {
202
- $wp_upload_dir = wp_upload_dir();
203
- $path = $wp_upload_dir['basedir'] . '/wp-staging';
204
- wp_mkdir_p( $path );
205
- return apply_filters( 'wpstg_get_upload_dir', $path . DIRECTORY_SEPARATOR );
206
- }
207
-
208
- /**
209
- * Register used namespaces
210
- */
211
- private function registerNamespaces() {
212
- $autoloader = new Autoloader();
213
- $this->set( "autoloader", $autoloader );
214
-
215
- // Autoloader
216
- $autoloader->registerNamespaces( array(
217
- "WPStaging" => array(
218
- $this->pluginPath . 'apps' . DIRECTORY_SEPARATOR,
219
- $this->pluginPath . 'apps' . DIRECTORY_SEPARATOR . 'Core' . DIRECTORY_SEPARATOR,
220
- )
221
- ) );
222
-
223
- // Register namespaces
224
- $autoloader->register();
225
- }
226
-
227
- /**
228
- * Get Instance
229
- * @return WPStaging
230
- */
231
- public static function getInstance() {
232
- if( null === static::$instance ) {
233
- static::$instance = new static();
234
- }
235
-
236
- return static::$instance;
237
- }
238
-
239
- /**
240
- * Prevent cloning
241
- * @return void
242
- */
243
- private function __clone() {
244
-
245
- }
246
-
247
- /**
248
- * Prevent unserialization
249
- * @return void
250
- */
251
- private function __wakeup() {
252
-
253
- }
254
-
255
- /**
256
- * Load Dependencies
257
- */
258
- private function loadDependencies() {
259
- // Set loader
260
- $this->set( "loader", new Loader() );
261
-
262
- // Set cache
263
- $this->set( "cache", new Cache() );
264
-
265
- // Set logger
266
- $this->set( "logger", new Logger() );
267
-
268
- // Set settings
269
- $this->set( "settings", new Settings() );
270
-
271
- // Set Administrator
272
- if( is_admin() ) {
273
- new Administrator( $this );
274
- } else {
275
- new Frontend( $this );
276
- }
277
- }
278
-
279
- /**
280
- * Execute Plugin
281
- */
282
- public function run() {
283
- $this->get( "loader" )->run();
284
- }
285
-
286
- /**
287
- * Set a variable to DI with given name
288
- * @param string $name
289
- * @param mixed $variable
290
- * @return $this
291
- */
292
- public function set( $name, $variable ) {
293
- // It is a function
294
- if( is_callable( $variable ) )
295
- $variable = $variable();
296
-
297
- // Add it to services
298
- $this->services[$name] = $variable;
299
-
300
- return $this;
301
- }
302
-
303
- /**
304
- * Get given name index from DI
305
- * @param string $name
306
- * @return mixed|null
307
- */
308
- public function get( $name ) {
309
- return (isset( $this->services[$name] )) ? $this->services[$name] : null;
310
- }
311
-
312
- /**
313
- * @return string
314
- */
315
- public function getVersion() {
316
- return self::VERSION;
317
- }
318
-
319
- /**
320
- * @return string
321
- */
322
- public function getName() {
323
- return self::NAME;
324
- }
325
-
326
- /**
327
- * @return string
328
- */
329
- public static function getSlug() {
330
- return plugin_basename( dirname( dirname( dirname( __FILE__ ) ) ) );
331
- }
332
-
333
- /**
334
- * Get path to main plugin file
335
- * @return string
336
- */
337
- public function getPath() {
338
- return dirname( dirname( __FILE__ ) );
339
- }
340
-
341
- /**
342
- * Get main plugin url
343
- * @return type
344
- */
345
- public function getUrl() {
346
- return plugin_dir_url( dirname( __FILE__ ) );
347
- }
348
-
349
- /**
350
- * @return array|mixed|object
351
- */
352
- public function getCPULoadSetting() {
353
- $options = $this->get( "settings" );
354
- $setting = $options->getCpuLoad();
355
-
356
- switch ( $setting ) {
357
- case "high":
358
- $cpuLoad = 0;
359
- break;
360
-
361
- case "medium":
362
- $cpuLoad = 1000;
363
- break;
364
-
365
- case "low":
366
- $cpuLoad = 1500;
367
- break;
368
-
369
- case "default":
370
- default:
371
- $cpuLoad = 1000;
372
- }
373
-
374
- return $cpuLoad;
375
- }
376
-
377
- /**
378
- * Load language file
379
- */
380
- public function loadLanguages() {
381
- $languagesDirectory = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . self::SLUG . DIRECTORY_SEPARATOR;
382
- $languagesDirectory.= "vars" . DIRECTORY_SEPARATOR . "languages" . DIRECTORY_SEPARATOR;
383
-
384
- // Set filter for plugins languages directory
385
- $languagesDirectory = apply_filters( "wpstg_languages_directory", $languagesDirectory );
386
-
387
- // Traditional WP plugin locale filter
388
- $locale = apply_filters( "plugin_locale", get_locale(), "wpstg" );
389
- $moFile = sprintf( '%1$s-%2$s.mo', "wpstg", $locale );
390
-
391
- // Setup paths to current locale file
392
- $moFileLocal = $languagesDirectory . $moFile;
393
- $moFileGlobal = WP_LANG_DIR . DIRECTORY_SEPARATOR . "wpstg" . DIRECTORY_SEPARATOR . $moFile;
394
-
395
- // Global file (/wp-content/languages/WPSTG)
396
- if( file_exists( $moFileGlobal ) ) {
397
- load_textdomain( "wpstg", $moFileGlobal );
398
- }
399
- // Local file (/wp-content/plugins/wp-staging/languages/)
400
- elseif( file_exists( $moFileLocal ) ) {
401
- load_textdomain( "wpstg", $moFileGlobal );
402
- }
403
- // Default file
404
- else {
405
- load_plugin_textdomain( "wpstg", false, $languagesDirectory );
406
- }
407
- }
408
-
409
- /**
410
- * Check if it is a staging site
411
- * @return bool
412
- */
413
- private function isStagingSite() {
414
- return ("true" === get_option( "wpstg_is_staging_site" ));
415
- }
416
-
417
- /**
418
- * Initialize licensing functions
419
- * @return boolean
420
- */
421
- public function initLicensing() {
422
- // Add licensing stuff if class exists
423
- if( class_exists( 'WPStaging\Backend\Pro\Licensing\Licensing' ) ) {
424
- $licensing = new Backend\Pro\Licensing\Licensing();
425
- }
426
- return false;
427
- }
428
-
429
- }
1
+ <?php
2
+
3
+ namespace WPStaging;
4
+
5
+ // No Direct Access
6
+ if( !defined( "WPINC" ) ) {
7
+ die;
8
+ }
9
+
10
+ // Ensure to include autoloader class
11
+ require_once __DIR__ . DIRECTORY_SEPARATOR . "Utils" . DIRECTORY_SEPARATOR . "Autoloader.php";
12
+
13
+ use WPStaging\Backend\Administrator;
14
+ use WPStaging\DTO\Settings;
15
+ use WPStaging\Frontend\Frontend;
16
+ use WPStaging\Utils\Autoloader;
17
+ use WPStaging\Utils\Cache;
18
+ use WPStaging\Utils\Loader;
19
+ use WPStaging\Utils\Logger;
20
+ use WPStaging\DI\InjectionAware;
21
+ use WPStaging\Cron\Cron;
22
+
23
+ /**
24
+ * Class WPStaging
25
+ * @package WPStaging
26
+ */
27
+ final class WPStaging {
28
+
29
+ /**
30
+ * Plugin version
31
+ */
32
+ const VERSION = "2.3.1";
33
+
34
+ /**
35
+ * Plugin name
36
+ */
37
+ const NAME = "WP Staging";
38
+
39
+ /**
40
+ * Plugin slug
41
+ */
42
+ const SLUG = "wp-staging";
43
+
44
+ /**
45
+ * Compatible WP Version
46
+ */
47
+ const WP_COMPATIBLE = "4.9.6";
48
+
49
+ /**
50
+ * Slug: Either wp-staging or wp-staging-pro
51
+ * @var string
52
+ */
53
+ public $slug;
54
+
55
+ /**
56
+ * Absolute plugin path
57
+ * @var string
58
+ */
59
+ public $pluginPath;
60
+
61
+ /**
62
+ * Services
63
+ * @var array
64
+ */
65
+ private $services;
66
+
67
+ /**
68
+ * Singleton instance
69
+ * @var WPStaging
70
+ */
71
+ private static $instance;
72
+
73
+ /**
74
+ * WPStaging constructor.
75
+ */
76
+ private function __construct() {
77
+
78
+ $file = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . self::SLUG . DIRECTORY_SEPARATOR . self::SLUG . ".php";
79
+
80
+ $this->registerMain();
81
+ $this->registerNamespaces();
82
+ $this->loadLanguages();
83
+ $this->loadDependencies();
84
+ $this->defineHooks();
85
+ // Licensing stuff be loaded in wpstg core to make cron hook available from frontpage
86
+ $this->initLicensing();
87
+ }
88
+
89
+ /**
90
+ * Method to be executed upon activation of the plugin
91
+ */
92
+ public function onActivation() {
93
+ $Activation = new \WPStaging\Backend\Activation\Activation();
94
+ $Activation->install_dependancies();
95
+ }
96
+
97
+ public function registerMain() {
98
+ // Slug of the plugin
99
+ $this->slug = plugin_basename( dirname( dirname( dirname( __FILE__ ) ) ) );
100
+
101
+ // absolute path to the main plugin dir
102
+ $this->pluginPath = plugin_dir_path( dirname( dirname( __FILE__ ) ) );
103
+
104
+ // URL to apps folder
105
+ $this->url = plugin_dir_url( dirname( __FILE__ ) );
106
+
107
+ // URL to backend public folder folder
108
+ $this->backend_url = plugin_dir_url( dirname( __FILE__ ) ) . "Backend/public/";
109
+
110
+ // URL to frontend public folder folder
111
+ $this->frontend_url = plugin_dir_url( dirname( __FILE__ ) ) . "Frontend/public/";
112
+ }
113
+
114
+ /**
115
+ * Define Hooks
116
+ */
117
+ public function defineHooks() {
118
+ $loader = $this->get( "loader" );
119
+ $loader->addAction( "admin_enqueue_scripts", $this, "enqueueElements", 100 );
120
+ $loader->addAction( "wp_enqueue_scripts", $this, "enqueueElements", 100 );
121
+ $this->addIntervals();
122
+ }
123
+
124
+ /**
125
+ * Add new cron time event "weekly"
126
+ */
127
+ public function addIntervals(){
128
+ $interval = new Cron();
129
+ }
130
+
131
+ /**
132
+ * Scripts and Styles
133
+ * @param string $hook
134
+ */
135
+ public function enqueueElements( $hook ) {
136
+
137
+ // Load this css file on frontend and backend on all pages if current site is a staging site
138
+ if( $this->isStagingSite() ) {
139
+ wp_enqueue_style( "wpstg-admin-bar", $this->backend_url . "css/wpstg-admin-bar.css", $this->getVersion() );
140
+ }
141
+
142
+ $availablePages = array(
143
+ "toplevel_page_wpstg_clone",
144
+ "wp-staging_page_wpstg-settings",
145
+ "wp-staging_page_wpstg-tools",
146
+ "wp-staging_page_wpstg-license",
147
+ "wp-staging_page_wpstg-welcome",
148
+ );
149
+
150
+ // Load these css and js files only on wp staging admin pages
151
+ if( !in_array( $hook, $availablePages ) || !is_admin() ) {
152
+ return;
153
+ }
154
+
155
+ // Load admin js files
156
+ wp_enqueue_script(
157
+ "wpstg-admin-script", $this->backend_url . "js/wpstg-admin.js", array("jquery"), $this->getVersion(), false
158
+ );
159
+
160
+ // Load admin css files
161
+ wp_enqueue_style(
162
+ "wpstg-admin", $this->backend_url . "css/wpstg-admin.css", $this->getVersion()
163
+ );
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
+
187
+ /**
188
+ * Get table prefix of the current site
189
+ * @return string
190
+ */
191
+ public static function getTablePrefix(){
192
+ $wpDB = WPStaging::getInstance()->get("wpdb");
193
+ return $wpDB->prefix;
194
+ }
195
+
196
+ /**
197
+ * Caching and logging folder
198
+ *
199
+ * @return string
200
+ */
201
+ public static function getContentDir() {
202
+ $wp_upload_dir = wp_upload_dir();
203
+ $path = $wp_upload_dir['basedir'] . '/wp-staging';
204
+ wp_mkdir_p( $path );
205
+ return apply_filters( 'wpstg_get_upload_dir', $path . DIRECTORY_SEPARATOR );
206
+ }
207
+
208
+ /**
209
+ * Register used namespaces
210
+ */
211
+ private function registerNamespaces() {
212
+ $autoloader = new Autoloader();
213
+ $this->set( "autoloader", $autoloader );
214
+
215
+ // Autoloader
216
+ $autoloader->registerNamespaces( array(
217
+ "WPStaging" => array(
218
+ $this->pluginPath . 'apps' . DIRECTORY_SEPARATOR,
219
+ $this->pluginPath . 'apps' . DIRECTORY_SEPARATOR . 'Core' . DIRECTORY_SEPARATOR,
220
+ )
221
+ ) );
222
+
223
+ // Register namespaces
224
+ $autoloader->register();
225
+ }
226
+
227
+ /**
228
+ * Get Instance
229
+ * @return WPStaging
230
+ */
231
+ public static function getInstance() {
232
+ if( null === static::$instance ) {
233
+ static::$instance = new static();
234
+ }
235
+
236
+ return static::$instance;
237
+ }
238
+
239
+ /**
240
+ * Prevent cloning
241
+ * @return void
242
+ */
243
+ private function __clone() {
244
+
245
+ }
246
+
247
+ /**
248
+ * Prevent unserialization
249
+ * @return void
250
+ */
251
+ private function __wakeup() {
252
+
253
+ }
254
+
255
+ /**
256
+ * Load Dependencies
257
+ */
258
+ private function loadDependencies() {
259
+ // Set loader
260
+ $this->set( "loader", new Loader() );
261
+
262
+ // Set cache
263
+ $this->set( "cache", new Cache() );
264
+
265
+ // Set logger
266
+ $this->set( "logger", new Logger() );
267
+
268
+ // Set settings
269
+ $this->set( "settings", new Settings() );
270
+
271
+ // Set Administrator
272
+ if( is_admin() ) {
273
+ new Administrator( $this );
274
+ } else {
275
+ new Frontend( $this );
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Execute Plugin
281
+ */
282
+ public function run() {
283
+ $this->get( "loader" )->run();
284
+ }
285
+
286
+ /**
287
+ * Set a variable to DI with given name
288
+ * @param string $name
289
+ * @param mixed $variable
290
+ * @return $this
291
+ */
292
+ public function set( $name, $variable ) {
293
+ // It is a function
294
+ if( is_callable( $variable ) )
295
+ $variable = $variable();
296
+
297
+ // Add it to services
298
+ $this->services[$name] = $variable;
299
+
300
+ return $this;
301
+ }
302
+
303
+ /**
304
+ * Get given name index from DI
305
+ * @param string $name
306
+ * @return mixed|null
307
+ */
308
+ public function get( $name ) {
309
+ return (isset( $this->services[$name] )) ? $this->services[$name] : null;
310
+ }
311
+
312
+ /**
313
+ * @return string
314
+ */
315
+ public function getVersion() {
316
+ return self::VERSION;
317
+ }
318
+
319
+ /**
320
+ * @return string
321
+ */
322
+ public function getName() {
323
+ return self::NAME;
324
+ }
325
+
326
+ /**
327
+ * @return string
328
+ */
329
+ public static function getSlug() {
330
+ return plugin_basename( dirname( dirname( dirname( __FILE__ ) ) ) );
331
+ }
332
+
333
+ /**
334
+ * Get path to main plugin file
335
+ * @return string
336
+ */
337
+ public function getPath() {
338
+ return dirname( dirname( __FILE__ ) );
339
+ }
340
+
341
+ /**
342
+ * Get main plugin url
343
+ * @return type
344
+ */
345
+ public function getUrl() {
346
+ return plugin_dir_url( dirname( __FILE__ ) );
347
+ }
348
+
349
+ /**
350
+ * @return array|mixed|object
351
+ */
352
+ public function getCPULoadSetting() {
353
+ $options = $this->get( "settings" );
354
+ $setting = $options->getCpuLoad();
355
+
356
+ switch ( $setting ) {
357
+ case "high":
358
+ $cpuLoad = 0;
359
+ break;
360
+
361
+ case "medium":
362
+ $cpuLoad = 1000;
363
+ break;
364
+
365
+ case "low":
366
+ $cpuLoad = 1500;
367
+ break;
368
+
369
+ case "default":
370
+ default:
371
+ $cpuLoad = 1000;
372
+ }
373
+
374
+ return $cpuLoad;
375
+ }
376
+
377
+ /**
378
+ * Load language file
379
+ */
380
+ public function loadLanguages() {
381
+ $languagesDirectory = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . self::SLUG . DIRECTORY_SEPARATOR;
382
+ $languagesDirectory.= "vars" . DIRECTORY_SEPARATOR . "languages" . DIRECTORY_SEPARATOR;
383
+
384
+ // Set filter for plugins languages directory
385
+ $languagesDirectory = apply_filters( "wpstg_languages_directory", $languagesDirectory );
386
+
387
+ // Traditional WP plugin locale filter
388
+ $locale = apply_filters( "plugin_locale", get_locale(), "wpstg" );
389
+ $moFile = sprintf( '%1$s-%2$s.mo', "wpstg", $locale );
390
+
391
+ // Setup paths to current locale file
392
+ $moFileLocal = $languagesDirectory . $moFile;
393
+ $moFileGlobal = WP_LANG_DIR . DIRECTORY_SEPARATOR . "wpstg" . DIRECTORY_SEPARATOR . $moFile;
394
+
395
+ // Global file (/wp-content/languages/WPSTG)
396
+ if( file_exists( $moFileGlobal ) ) {
397
+ load_textdomain( "wpstg", $moFileGlobal );
398
+ }
399
+ // Local file (/wp-content/plugins/wp-staging/languages/)
400
+ elseif( file_exists( $moFileLocal ) ) {
401
+ load_textdomain( "wpstg", $moFileGlobal );
402
+ }
403
+ // Default file
404
+ else {
405
+ load_plugin_textdomain( "wpstg", false, $languagesDirectory );
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Check if it is a staging site
411
+ * @return bool
412
+ */
413
+ private function isStagingSite() {
414
+ return ("true" === get_option( "wpstg_is_staging_site" ));
415
+ }
416
+
417
+ /**
418
+ * Initialize licensing functions
419
+ * @return boolean
420
+ */
421
+ public function initLicensing() {
422
+ // Add licensing stuff if class exists
423
+ if( class_exists( 'WPStaging\Backend\Pro\Licensing\Licensing' ) ) {
424
+ $licensing = new Backend\Pro\Licensing\Licensing();
425
+ }
426
+ return false;
427
+ }
428
+
429
+ }
apps/Frontend/Frontend.php CHANGED
@@ -140,8 +140,10 @@ class Frontend extends InjectionAware {
140
 
141
  // Allow access for user role administrator in any case
142
  if( current_user_can( 'administrator' ) ) {
 
143
  return false;
144
  }
 
145
 
146
  return (
147
  (!isset( $this->settings->disableAdminLogin ) || '1' !== $this->settings->disableAdminLogin) &&
140
 
141
  // Allow access for user role administrator in any case
142
  if( current_user_can( 'administrator' ) ) {
143
+ //wp_die('is admin');
144
  return false;
145
  }
146
+
147
 
148
  return (
149
  (!isset( $this->settings->disableAdminLogin ) || '1' !== $this->settings->disableAdminLogin) &&
readme.txt CHANGED
@@ -1,284 +1,289 @@
1
- === WP Staging - DB & File Duplicator & Migration ===
2
-
3
- Author URL: https://wordpress.org/plugins/wp-staging
4
- Plugin URL: https://wordpress.org/plugins/wp-staging
5
- Contributors: ReneHermi, WP-Staging
6
- Donate link: https://wordpress.org/plugins/wp-staging
7
- License: GPLv2 or later
8
- 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.0
13
- Requires PHP: 5.3
14
-
15
- A duplicator plugin! Clone, duplicate and migrate live sites to independent staging and development sites that are available only to administrators.
16
-
17
- == Description ==
18
-
19
- <strong>This cloning and staging plugin is well tested but work in progress. <br><br>
20
- If you find any issue, please open a [support ticket](https://wp-staging.com/support/ "support ticket").
21
- </strong>
22
- <br /><br />
23
- <strong>Note: </strong> For pushing & migrating plugins and theme files to live site, check out [https://wp-staging.com/](https://wp-staging.com/ "WP Staging Pro")
24
- <br /><br />
25
- <blockquote>
26
- <h4> WP Staging for WordPress Migration </h4>
27
- This duplicator plugin allows you to create an staging or development environment in seconds* <br /> <br />
28
- It creates a clone of your website into a subfolder of your main WordPress installation including an entire copy of your database.
29
- This sounds pretty simple and yes it is! All the hard time-consumptive database and file copying stuff including url replacements is done in the background.
30
- <br /> <br />
31
- I created this plugin because all other solutions are way too complex, overloaded with dozens of options or having server requirements which are not available on most shared hosting solutions.
32
- All these reasons prevent user from testing new plugins and updates first before installing them on their live website, so its time to release a plugin which has the potential to be merged into everyone´s wordpress workflow.
33
- <br /><br />
34
- <p><small><em>* Time of creation depends on size of your database and file size</em></small></p>
35
- </blockquote>
36
-
37
- WP Staging helps you to prevent your website from being broken or unavailable because of installing untested plugin updates!
38
- <p>Note: WordPress 5.0 will be shipped with a new visual editor called Gutenberg. Use WP Staging to check if Gutenberg editor is working as intended on your website and that all used plugins are compatible with that new editor.</p>
39
-
40
-
41
- [youtube https://www.youtube.com/watch?v=Ye3fC6cdB3A]
42
-
43
- = Main Features =
44
-
45
- * <strong>Easy: </strong> Staging migration applicable for everyone. No configuration needed!
46
- * <strong>Fast: </strong> Migration process lasts only a few seconds or minutes, depending on the site's size and server I/O power
47
- * <strong>Safe: </strong> Access to staging site is granted for administrators only.
48
- <br /><br />
49
- <strong>More safe:</strong>
50
- <br>
51
- * Admin bar reflects that you are working on a staging site
52
- * Extensive logging if duplication or migration process should fail.
53
- * New: Compatible to All In One WP Security & Firewall
54
-
55
- = What does not work or is not tested when running wordpress migration? =
56
-
57
- * Wordpress migration of wordpress multisites (not tested)
58
- * WordPress duplicating process on windows server (not tested but will probably work)
59
- Edit: Duplication on windows server seems to be working well: [Read more](https://wordpress.org/support/topic/wont-copy-files?replies=5 "Read more")
60
-
61
-
62
- <strong>Change your workflow of updating themes and plugins data:</strong>
63
-
64
- 1. Use WP Staging for migration of a production website to a clone site for staging purposes
65
- 2. Customize theme, configuration and plugins or install new plugins
66
- 3. Test everything on your staging site first
67
- 4. Everything running as expected? You are on the save side for migration of all these modifications to your production site!
68
-
69
-
70
- <h3> Why should i use a staging website? </h3>
71
-
72
- Plugin updates and theme customizations should be tested on a staging platform first. Its recommended to have the staging platform on the same server where the production website is located.
73
- When you run a plugin update or plan to install a new one, it is a necessary task to check first the modifications on a clone of your production website.
74
- This makes sure that any modifications is working on your website without throwing unexpected errors or preventing your site from loading. (Better known as the wordpress blank page error)
75
-
76
- Testing a plugin update before installing it in live environment isn´t done very often by most user because existing staging solutions are too complex and need a lot of time to create a
77
- up-to-date copy of your website.
78
-
79
- Some people are also afraid of installing plugins updates because they follow the rule "never touch a running system" with having in mind that untested updates are increasing the risk of breaking their site.
80
- I totally understand this and i am guilty as well here, but unfortunately this leads to one of the main reasons why WordPress installations are often outdated, not updated at all and unsecure due to this non-update behavior.
81
-
82
- <strong> I think its time to change this, so i created "WP Staging" for WordPress migration of staging sites</strong>
83
-
84
- <h3> Can´t i just use my local wordpress development copy for testing like xampp / lampp? </h3>
85
-
86
- Nope! If your local hardware and software environment is not a 100% exact clone of your production server there is NO guarantee that every aspect
87
- of your local copy is working on your live website exactely as you would expect it.
88
- There are some obvious things like differences in the config of php and the server you are running but even such non obvious settings like the amount of ram or the
89
- the cpu performance can lead to unexpected results on your production website.
90
- There are dozens of other possible cause of failure which can not be handled well when you are testing your changes on a local staging platform.
91
-
92
- This is were WP Staging steps in... Site cloning and staging site creation simplified!
93
-
94
- <h3>I just want to migrate the database from one installation to another</h3>
95
- If you want to migrate your local database to a already existing production site you can use a tool like WP Migrate DB.
96
- WP Staging is only for creating a staging site with latest data from your production site. So it goes the opposite way of WP Migrate DB.
97
- Both tools are excellent cooperating eachother.
98
-
99
- <h3>What are the benefits compared to a plugin like Duplicator?</h3>
100
- At first, i love the [Duplicator plugin](https://wordpress.org/plugins/duplicator/ "Duplicator plugin"). Duplicator is a great tool for migrating from development site to production one or from production site to development one.
101
- The downside is that Duplicator needs adjustments, manually interventions and prerequirements for this. Duplicator also needs some skills to be able to create a development / staging site, where WP Staging does not need more than a click from you.
102
- However, Duplicator is best placed to be a tool for first-time creation of your production site. This is something where it is very handy and powerful.
103
-
104
- So, if you have created a local or webhosted development site and you need to migrate this site the first time to your production domain than you are doing nothing wrong with using
105
- the Duplicator plugin! If you need all you latest production data like posts, updated plugins, theme data and styles in a testing environment than i recommend to use WP Staging instead!
106
-
107
- = I need you feedback =
108
- This plugin has been done in hundreds of hours to work on even the smallest shared webhosting package but i am limited in testing this only on a handful of different server so i need your help:
109
- Please open a [support request](https://wordpress.org/support/plugin/wp-staging/ "support request") and describe your problem exactely. In wp-content/wp-staging/logs you find extended logfiles. Have a look at them and let me know the error-thrown lines.
110
-
111
-
112
- = Important =
113
-
114
- Permalinks are disabled on the staging site because the staging site is cloned into a subfolder and permalinks are not working on all systems
115
- without doing changes to the .htaccess (Apache server) or nginx.conf (Nginx Server).
116
- [Read here](https://wp-staging.com/docs/activate-permalinks-staging-site/ "activate permalinks on staging site") how to activate permalinks on the staging site.
117
-
118
-
119
- = How to install and setup? =
120
- Install it via the admin dashboard and to 'Plugins', click 'Add New' and search the plugins for 'Staging'. Install the plugin with 'Install Now'.
121
- After installation goto the settings page 'Staging' and do your adjustments there.
122
-
123
-
124
- == Frequently Asked Questions ==
125
-
126
- * I can not login to the staging site
127
- If you are using a security plugin like All In One WP Security & Firewall you need to install latest version of WP Staging.
128
- Go to WP Staging > Settings and add the slug to the custom login page which you set up in All In One WP Security & Firewall plugin.
129
-
130
-
131
-
132
- == Official Site ==
133
- https://wp-staging.com
134
-
135
- == Installation ==
136
- 1. Download the file "wp-staging.zip":
137
- 2. Upload and install it via the WordPress plugin backend wp-admin > plugins > add new > uploads
138
- 3. Activate the plugin through the 'Plugins' menu in WordPress.
139
-
140
- == Screenshots ==
141
-
142
- 1. Step 1. Create new WordPress staging site
143
- 2. Step 2. Scanning your website for files and database tables
144
- 3. Step 3. Wordpress Staging site creation in progress
145
- 4. Finish!
146
-
147
- == Changelog ==
148
-
149
- = 2.3.0 =
150
- * Security: Do not allow to create a new staging site into a subfolder which already exists
151
- * New: Option to allow adjustment of the allowed maximum size of files that are going to be copied while cloning.
152
- * New: Add multisite informations in system info log
153
- * New: Option to allow adjustment of the allowed maximum size of files that are going to be copied while cloning.
154
- * New: Use the new progress bar for clone updating process
155
- * Fix: Progress bar for step 'database' is not filling up to 100%
156
- * Fix: If cloning update process is interupted it may happen that staging site is not available any longer. (Updating the clone does not copy index.php to staging site again)
157
- * Fix: Progress bar not shown as intented for clone updating process
158
- * Fix: Can not open upload folder in file selection menu
159
- * Fix: Undefined object $this->tables
160
- * Fix: wp-config.php not copied when previous clone updating process has been failed
161
- * Fix: Parameter must be an array or an object that implements Callable
162
- * Fix: Skip search & replace for objects where key is null
163
- * Fix: Search & Replace not working if serialized object contains __PHP_Incomplete_Class_Name
164
- * Tweaks: remove term "error" from several log entries
165
- * Tweak: Remove certain debugging notices from the default log window
166
-
167
- = 2.2.9 =
168
- * Fix: Missing trailingslash results to wrong absolute paths in database after Search & Replace operation
169
-
170
- = 2.2.8 =
171
- * New: Add filter 'wpstg_filter_options_replace' to exclude certain tables from updating while cloning
172
- * New: Exclude tables for plugin wp_mail_smtp
173
- * New: Support for custom upload folder. For instance, if upload folder has been renamed and removed outsite wp-content folder
174
- * New: Add datetime timestamp internally to clone. (Used in WP Staging pro)
175
- * New: Add filter 'wpstg_fiter_search_replace_rows' to exclude certain tables from search & replace
176
- * New: Supports search & replace for revslider image slider and several visual editors which are using non default serialized data
177
- * New: Add new setting which allow to specify the search & replace processing query limit
178
- * New: Compatible to WordPress 4.9.6
179
-
180
-
181
- = 2.2.7 =
182
- * Fix: Serialize replace is not working properly for serialized strings
183
- * Fix: WP_SITEURL & WP_HOME not replaced if constant contains php generated string
184
-
185
-
186
- = 2.2.6 =
187
- * Fix: If WPML is used the live site is not reachable
188
- * Fix: Can not disable optimizer
189
- * Fix: Stop cloning if wp_usermeta or wp_options can not be adapted
190
- * Fix: All methods should be private in class SearchReplace
191
- * Fix: PHP 7.2 is not countable warning
192
- * Fix: PHP 7.2 can not replace data in objects when object is incomplete (__PHP_Incomplete_Class_Name)
193
- * New: Use fully custom login form to prevent access denied issues on sites where access to wp-login.php is denied or redirection plugins are used
194
- * New: Link to support section
195
-
196
-
197
- = 2.2.5 =
198
- * New: Compatible to WP 4.9.5
199
- * New: Allow to select and copy extra folders that are on the root level
200
- * New: Use fully custom login form to prevent access denied issues on sites where access to wp-login.php is denied or redirection plugins are used
201
- * Fix: Incorrect login path to staging site if WordPress is installed in subdirectory
202
- * Fix: Login url is wrong if WP is installed in subfolder
203
- * Fix: If PHP 5.6.34 is used, the cloning process could be unfinished due to use of private member in protected class
204
- * Tweak: Only wp root folders are pre selected before cloning is starting
205
- * Tweak: Change WP_HOME or WP_SITEURL constants of staging site if they are defined in wp-config.php
206
-
207
-
208
- = 2.2.4 =
209
- * New: Replace even hardcoded links and server path by using search & replace through all staging site database tables
210
- * New: New and improved progress bar with elapsed time
211
- * Fix: Cancel cloning does not clean up unused tables and leads to duplicate tables
212
- * Tweak: Wordings in rating admin notice
213
- * Tweak: Better error messages
214
- * Tweak: Open staging site in same window from login request
215
- * Fix: Set meta noindex for staging site to make it non indexable for search engines
216
-
217
-
218
- = 2.2.3 =
219
- * Fix: Change default login link to wp-admin
220
- * Fix: Unneccessary duplicates of wpstg tables in db
221
-
222
- = 2.2.2 =
223
- * Fix: Undefined property: stdClass::$loginSlug
224
-
225
- = 2.2.1 =
226
- * New: Option to set Custom Login Link if there is one
227
- * New: Set meta noindex for staging site to make it non indexable for search engines
228
- * New: Better multiple folder selection. Allows to unselect a parent folder without collapsing all child folders
229
- * New: Sorted list of folders to copy
230
- * Fix: Can not login to staging site if plugin All In One WP Security & Firewall is used
231
- * Fix: Staging site not reachable because permalinks are not disabled under certain conditions
232
-
233
- = 2.2.0 =
234
- * Fix: Old staging site is not listed and pushing is not working properly if plugin is updated from wp staging version 1.6 and lower
235
-
236
- = 2.1.9 =
237
- * New: Performance improvement increase db query limit to 5000
238
- * New: Detect automatically if WordPress is installed in sub folder
239
- * Tweak: Tested up to WP 4.9.4
240
- * Fix: Updating from an old version 1.1.6 < to latest version deletes the staging sites listing table
241
- * Fix: Reduce memory size of the logging window to prevent browser timeouts
242
- * Fix: Can not copy db table if table name contains the db prefix multiple times
243
- * Fix: Some excluded folders are not ignored during copy process
244
- * Fix: mod_security is causing script termination
245
- * Fix: Skip directory listings for symlinks
246
-
247
- = 2.1.8 =
248
- * Fix: Increase the max memory consumption
249
-
250
- = 2.1.7 =
251
- * Tweak: Return more human readable error notices
252
- * Fix: Cloning process stops due to file permission issue
253
- * Fix: Exclude WP Super Cache from copying process because of bug in WP Super Cache, see https://github.com/Automattic/wp-super-cache/issues/505
254
-
255
- = 2.1.6 =
256
- * New: increased speed for cloning process by factor 5, using new method of file agregation
257
- * New: Skip files larger than 8MB
258
- * Fix: Additional checks to ensure that the root path is never deleted
259
- * New: Compatible up to WP 4.9.1
260
-
261
- = 2.1.5 =
262
- * Fix. Change link to support
263
- * Fix: Missing files in clone site if copy file limit is higher than 1
264
-
265
- = 2.1.4 =
266
- * Fix: Link to the staging site is missing a slash if WordPress is installed in subdir
267
- * Tweak: Allow file copy limit 1 to prevent copy timeouts
268
-
269
- = 2.1.3 =
270
- * New: Add more details to tools->system info log for better debugging
271
- * New: Add buttons to select all default wp tables with one click
272
- * New: Show used db table in list of staging sites
273
- * Fix: Delete staging site not possible if db prefix is same as one of the live site
274
- * Fix: Edit/Update clone function is duplicating tables.
275
- * Fix: Other staging site can be overwritten when Edit/Update clone function is executed
276
- * Fix: Several improvements to improve reliability and prevent timeouts and fatal errors during cloning
277
-
278
- Complete changelog: [https://wp-staging.com/changelog.txt](https://wp-staging.com/changelog.txt)
279
-
280
- == Upgrade Notice ==
281
-
282
- = 2.2.8 =
283
- * New: Compatible to WordPress 4.9.6
284
-
 
 
 
 
 
1
+ === WP Staging - DB & File Duplicator & Migration ===
2
+
3
+ Author URL: https://wordpress.org/plugins/wp-staging
4
+ Plugin URL: https://wordpress.org/plugins/wp-staging
5
+ Contributors: ReneHermi, WP-Staging
6
+ Donate link: https://wordpress.org/plugins/wp-staging
7
+ License: GPLv2 or later
8
+ 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.1
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.
16
+
17
+ == Description ==
18
+
19
+ <strong>This cloning and staging plugin is well tested but work in progress. <br><br>
20
+ If you find any issue, please open a [support ticket](https://wp-staging.com/support/ "support ticket").
21
+ </strong>
22
+ <br /><br />
23
+ <strong>Note: </strong> For pushing & migrating plugins and theme files to live site, check out [https://wp-staging.com/](https://wp-staging.com/ "WP Staging Pro")
24
+ <br /><br />
25
+ <blockquote>
26
+ <h4> WP Staging for WordPress Migration </h4>
27
+ This duplicator plugin allows you to create an staging or development environment in seconds* <br /> <br />
28
+ It creates a clone of your website into a subfolder of your main WordPress installation including an entire copy of your database.
29
+ This sounds pretty simple and yes it is! All the hard time-consumptive database and file copying stuff including url replacements is done in the background.
30
+ <br /> <br />
31
+ I created this plugin because all other solutions are way too complex, overloaded with dozens of options or having server requirements which are not available on most shared hosting solutions.
32
+ All these reasons prevent user from testing new plugins and updates first before installing them on their live website, so its time to release a plugin which has the potential to be merged into everyone´s wordpress workflow.
33
+ <br /><br />
34
+ <p><small><em>* Time of creation depends on size of your database and file size</em></small></p>
35
+ </blockquote>
36
+
37
+ WP Staging helps you to prevent your website from being broken or unavailable because of installing untested plugin updates!
38
+ <p>Note: WordPress 5.0 will be shipped with a new visual editor called Gutenberg. Use WP Staging to check if Gutenberg editor is working as intended on your website and that all used plugins are compatible with that new editor.</p>
39
+
40
+
41
+ [youtube https://www.youtube.com/watch?v=Ye3fC6cdB3A]
42
+
43
+ = Main Features =
44
+
45
+ * <strong>Easy: </strong> Staging migration applicable for everyone. No configuration needed!
46
+ * <strong>Fast: </strong> Migration process lasts only a few seconds or minutes, depending on the site's size and server I/O power
47
+ * <strong>Safe: </strong> Access to staging site is granted for administrators only.
48
+ <br /><br />
49
+ <strong>More safe:</strong>
50
+ <br>
51
+ * Admin bar reflects that you are working on a staging site
52
+ * Extensive logging if duplication or migration process should fail.
53
+ * New: Compatible to All In One WP Security & Firewall
54
+
55
+ = What does not work or is not tested when running wordpress migration? =
56
+
57
+ * Wordpress migration of wordpress multisites (not tested)
58
+ * WordPress duplicating process on windows server (not tested but will probably work)
59
+ Edit: Duplication on windows server seems to be working well: [Read more](https://wordpress.org/support/topic/wont-copy-files?replies=5 "Read more")
60
+
61
+
62
+ <strong>Change your workflow of updating themes and plugins data:</strong>
63
+
64
+ 1. Use WP Staging for migration of a production website to a clone site for staging purposes
65
+ 2. Customize theme, configuration and plugins or install new plugins
66
+ 3. Test everything on your staging site first
67
+ 4. Everything running as expected? You are on the save side for migration of all these modifications to your production site!
68
+
69
+
70
+ <h3> Why should i use a staging website? </h3>
71
+
72
+ Plugin updates and theme customizations should be tested on a staging platform first. Its recommended to have the staging platform on the same server where the production website is located.
73
+ When you run a plugin update or plan to install a new one, it is a necessary task to check first the modifications on a clone of your production website.
74
+ This makes sure that any modifications is working on your website without throwing unexpected errors or preventing your site from loading. (Better known as the wordpress blank page error)
75
+
76
+ Testing a plugin update before installing it in live environment isn´t done very often by most user because existing staging solutions are too complex and need a lot of time to create a
77
+ up-to-date copy of your website.
78
+
79
+ Some people are also afraid of installing plugins updates because they follow the rule "never touch a running system" with having in mind that untested updates are increasing the risk of breaking their site.
80
+ I totally understand this and i am guilty as well here, but unfortunately this leads to one of the main reasons why WordPress installations are often outdated, not updated at all and unsecure due to this non-update behavior.
81
+
82
+ <strong> I think its time to change this, so i created "WP Staging" for WordPress migration of staging sites</strong>
83
+
84
+ <h3> Can´t i just use my local wordpress development copy for testing like xampp / lampp? </h3>
85
+
86
+ Nope! If your local hardware and software environment is not a 100% exact clone of your production server there is NO guarantee that every aspect
87
+ of your local copy is working on your live website exactely as you would expect it.
88
+ There are some obvious things like differences in the config of php and the server you are running but even such non obvious settings like the amount of ram or the
89
+ the cpu performance can lead to unexpected results on your production website.
90
+ There are dozens of other possible cause of failure which can not be handled well when you are testing your changes on a local staging platform.
91
+
92
+ This is were WP Staging steps in... Site cloning and staging site creation simplified!
93
+
94
+ <h3>I just want to migrate the database from one installation to another</h3>
95
+ If you want to migrate your local database to a already existing production site you can use a tool like WP Migrate DB.
96
+ WP Staging is only for creating a staging site with latest data from your production site. So it goes the opposite way of WP Migrate DB.
97
+ Both tools are excellent cooperating eachother.
98
+
99
+ <h3>What are the benefits compared to a plugin like Duplicator?</h3>
100
+ At first, i love the [Duplicator plugin](https://wordpress.org/plugins/duplicator/ "Duplicator plugin"). Duplicator is a great tool for migrating from development site to production one or from production site to development one.
101
+ The downside is that Duplicator needs adjustments, manually interventions and prerequirements for this. Duplicator also needs some skills to be able to create a development / staging site, where WP Staging does not need more than a click from you.
102
+ However, Duplicator is best placed to be a tool for first-time creation of your production site. This is something where it is very handy and powerful.
103
+
104
+ So, if you have created a local or webhosted development site and you need to migrate this site the first time to your production domain than you are doing nothing wrong with using
105
+ the Duplicator plugin! If you need all you latest production data like posts, updated plugins, theme data and styles in a testing environment than i recommend to use WP Staging instead!
106
+
107
+ = I need you feedback =
108
+ This plugin has been done in hundreds of hours to work on even the smallest shared webhosting package but i am limited in testing this only on a handful of different server so i need your help:
109
+ Please open a [support request](https://wordpress.org/support/plugin/wp-staging/ "support request") and describe your problem exactely. In wp-content/wp-staging/logs you find extended logfiles. Have a look at them and let me know the error-thrown lines.
110
+
111
+
112
+ = Important =
113
+
114
+ Permalinks are disabled on the staging site because the staging site is cloned into a subfolder and permalinks are not working on all systems
115
+ without doing changes to the .htaccess (Apache server) or nginx.conf (Nginx Server).
116
+ [Read here](https://wp-staging.com/docs/activate-permalinks-staging-site/ "activate permalinks on staging site") how to activate permalinks on the staging site.
117
+
118
+
119
+ = How to install and setup? =
120
+ Install it via the admin dashboard and to 'Plugins', click 'Add New' and search the plugins for 'Staging'. Install the plugin with 'Install Now'.
121
+ After installation goto the settings page 'Staging' and do your adjustments there.
122
+
123
+
124
+ == Frequently Asked Questions ==
125
+
126
+ * I can not login to the staging site
127
+ If you are using a security plugin like All In One WP Security & Firewall you need to install latest version of WP Staging.
128
+ Go to WP Staging > Settings and add the slug to the custom login page which you set up in All In One WP Security & Firewall plugin.
129
+
130
+
131
+
132
+ == Official Site ==
133
+ https://wp-staging.com
134
+
135
+ == Installation ==
136
+ 1. Download the file "wp-staging.zip":
137
+ 2. Upload and install it via the WordPress plugin backend wp-admin > plugins > add new > uploads
138
+ 3. Activate the plugin through the 'Plugins' menu in WordPress.
139
+
140
+ == Screenshots ==
141
+
142
+ 1. Step 1. Create new WordPress staging site
143
+ 2. Step 2. Scanning your website for files and database tables
144
+ 3. Step 3. Wordpress Staging site creation in progress
145
+ 4. Finish!
146
+
147
+ == Changelog ==
148
+
149
+ = 2.3.1 =
150
+ * Fix: Creating a new clone resets the custom wp staging user settings. E.g. ignores authentication setting
151
+ * Fix: Search & replace path not correct if scheme http/https is not identical in wp-config and db wp_options
152
+ * New: Setting to specify the maximum allowed file size to copy
153
+
154
+ = 2.3.0 =
155
+ * Security: Do not allow to create a new staging site into a subfolder which already exists
156
+ * New: Option to allow adjustment of the allowed maximum size of files that are going to be copied while cloning.
157
+ * New: Add multisite informations in system info log
158
+ * New: Option to allow adjustment of the allowed maximum size of files that are going to be copied while cloning.
159
+ * New: Use the new progress bar for clone updating process
160
+ * Fix: Progress bar for step 'database' is not filling up to 100%
161
+ * Fix: If cloning update process is interupted it may happen that staging site is not available any longer. (Updating the clone does not copy index.php to staging site again)
162
+ * Fix: Progress bar not shown as intented for clone updating process
163
+ * Fix: Can not open upload folder in file selection menu
164
+ * Fix: Undefined object $this->tables
165
+ * Fix: wp-config.php not copied when previous clone updating process has been failed
166
+ * Fix: Parameter must be an array or an object that implements Callable
167
+ * Fix: Skip search & replace for objects where key is null
168
+ * Fix: Search & Replace not working if serialized object contains __PHP_Incomplete_Class_Name
169
+ * Tweaks: remove term "error" from several log entries
170
+ * Tweak: Remove certain debugging notices from the default log window
171
+
172
+ = 2.2.9 =
173
+ * Fix: Missing trailingslash results to wrong absolute paths in database after Search & Replace operation
174
+
175
+ = 2.2.8 =
176
+ * New: Add filter 'wpstg_filter_options_replace' to exclude certain tables from updating while cloning
177
+ * New: Exclude tables for plugin wp_mail_smtp
178
+ * New: Support for custom upload folder. For instance, if upload folder has been renamed and removed outsite wp-content folder
179
+ * New: Add datetime timestamp internally to clone. (Used in WP Staging pro)
180
+ * New: Add filter 'wpstg_fiter_search_replace_rows' to exclude certain tables from search & replace
181
+ * New: Supports search & replace for revslider image slider and several visual editors which are using non default serialized data
182
+ * New: Add new setting which allow to specify the search & replace processing query limit
183
+ * New: Compatible to WordPress 4.9.6
184
+
185
+
186
+ = 2.2.7 =
187
+ * Fix: Serialize replace is not working properly for serialized strings
188
+ * Fix: WP_SITEURL & WP_HOME not replaced if constant contains php generated string
189
+
190
+
191
+ = 2.2.6 =
192
+ * Fix: If WPML is used the live site is not reachable
193
+ * Fix: Can not disable optimizer
194
+ * Fix: Stop cloning if wp_usermeta or wp_options can not be adapted
195
+ * Fix: All methods should be private in class SearchReplace
196
+ * Fix: PHP 7.2 is not countable warning
197
+ * Fix: PHP 7.2 can not replace data in objects when object is incomplete (__PHP_Incomplete_Class_Name)
198
+ * New: Use fully custom login form to prevent access denied issues on sites where access to wp-login.php is denied or redirection plugins are used
199
+ * New: Link to support section
200
+
201
+
202
+ = 2.2.5 =
203
+ * New: Compatible to WP 4.9.5
204
+ * New: Allow to select and copy extra folders that are on the root level
205
+ * New: Use fully custom login form to prevent access denied issues on sites where access to wp-login.php is denied or redirection plugins are used
206
+ * Fix: Incorrect login path to staging site if WordPress is installed in subdirectory
207
+ * Fix: Login url is wrong if WP is installed in subfolder
208
+ * Fix: If PHP 5.6.34 is used, the cloning process could be unfinished due to use of private member in protected class
209
+ * Tweak: Only wp root folders are pre selected before cloning is starting
210
+ * Tweak: Change WP_HOME or WP_SITEURL constants of staging site if they are defined in wp-config.php
211
+
212
+
213
+ = 2.2.4 =
214
+ * New: Replace even hardcoded links and server path by using search & replace through all staging site database tables
215
+ * New: New and improved progress bar with elapsed time
216
+ * Fix: Cancel cloning does not clean up unused tables and leads to duplicate tables
217
+ * Tweak: Wordings in rating admin notice
218
+ * Tweak: Better error messages
219
+ * Tweak: Open staging site in same window from login request
220
+ * Fix: Set meta noindex for staging site to make it non indexable for search engines
221
+
222
+
223
+ = 2.2.3 =
224
+ * Fix: Change default login link to wp-admin
225
+ * Fix: Unneccessary duplicates of wpstg tables in db
226
+
227
+ = 2.2.2 =
228
+ * Fix: Undefined property: stdClass::$loginSlug
229
+
230
+ = 2.2.1 =
231
+ * New: Option to set Custom Login Link if there is one
232
+ * New: Set meta noindex for staging site to make it non indexable for search engines
233
+ * New: Better multiple folder selection. Allows to unselect a parent folder without collapsing all child folders
234
+ * New: Sorted list of folders to copy
235
+ * Fix: Can not login to staging site if plugin All In One WP Security & Firewall is used
236
+ * Fix: Staging site not reachable because permalinks are not disabled under certain conditions
237
+
238
+ = 2.2.0 =
239
+ * Fix: Old staging site is not listed and pushing is not working properly if plugin is updated from wp staging version 1.6 and lower
240
+
241
+ = 2.1.9 =
242
+ * New: Performance improvement increase db query limit to 5000
243
+ * New: Detect automatically if WordPress is installed in sub folder
244
+ * Tweak: Tested up to WP 4.9.4
245
+ * Fix: Updating from an old version 1.1.6 < to latest version deletes the staging sites listing table
246
+ * Fix: Reduce memory size of the logging window to prevent browser timeouts
247
+ * Fix: Can not copy db table if table name contains the db prefix multiple times
248
+ * Fix: Some excluded folders are not ignored during copy process
249
+ * Fix: mod_security is causing script termination
250
+ * Fix: Skip directory listings for symlinks
251
+
252
+ = 2.1.8 =
253
+ * Fix: Increase the max memory consumption
254
+
255
+ = 2.1.7 =
256
+ * Tweak: Return more human readable error notices
257
+ * Fix: Cloning process stops due to file permission issue
258
+ * Fix: Exclude WP Super Cache from copying process because of bug in WP Super Cache, see https://github.com/Automattic/wp-super-cache/issues/505
259
+
260
+ = 2.1.6 =
261
+ * New: increased speed for cloning process by factor 5, using new method of file agregation
262
+ * New: Skip files larger than 8MB
263
+ * Fix: Additional checks to ensure that the root path is never deleted
264
+ * New: Compatible up to WP 4.9.1
265
+
266
+ = 2.1.5 =
267
+ * Fix. Change link to support
268
+ * Fix: Missing files in clone site if copy file limit is higher than 1
269
+
270
+ = 2.1.4 =
271
+ * Fix: Link to the staging site is missing a slash if WordPress is installed in subdir
272
+ * Tweak: Allow file copy limit 1 to prevent copy timeouts
273
+
274
+ = 2.1.3 =
275
+ * New: Add more details to tools->system info log for better debugging
276
+ * New: Add buttons to select all default wp tables with one click
277
+ * New: Show used db table in list of staging sites
278
+ * Fix: Delete staging site not possible if db prefix is same as one of the live site
279
+ * Fix: Edit/Update clone function is duplicating tables.
280
+ * Fix: Other staging site can be overwritten when Edit/Update clone function is executed
281
+ * Fix: Several improvements to improve reliability and prevent timeouts and fatal errors during cloning
282
+
283
+ Complete changelog: [https://wp-staging.com/changelog.txt](https://wp-staging.com/changelog.txt)
284
+
285
+ == Upgrade Notice ==
286
+
287
+ = 2.2.8 =
288
+ * New: Compatible to WordPress 4.9.6
289
+
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.0
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.1
11
  * Text Domain: wpstg
12
  * Domain Path: /languages/
13