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

Version Description

  • Fix: Remove LOCK_EX parameter in file_put_contents(). LOCK_EX is not working on several systems which results in cloning process timeouts
  • Fix: Huge Performance improvement in copying process by removing duplicate file entries in the cache file. This also prevents weird timeout issues on some hosted websites
  • Fix: Error 500 when debug mode is activated
  • Fix: Limit maximum execution time to 30 seconds
  • Fix: Sanitize Clone Names and Keys to fix "clone not found" issue in upgrade routine
  • Fix: Do not clone the plugin wps-hide-login
  • Fix: Staging sites can not be deleted if they are very big
  • Fix: Link to staging site is undefined
  • Tweak: Better admin message for asking for a review
  • Tweak: Remove table wpstg_rmpermalinks_executed when plugin is uninstalled
  • New: New setting to specify the maximum amount of files copied within one ajax call to fix godaddy and bluehost ajax 404 errors. Default 10 per batch
Download this release

Release Info

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

Code changes from version 2.1.1 to 2.1.2

apps/Backend/Modules/Jobs/Cloning.php CHANGED
@@ -1,190 +1,190 @@
1
- <?php
2
- namespace WPStaging\Backend\Modules\Jobs;
3
-
4
- use WPStaging\Backend\Modules\Jobs\Exceptions\JobNotFoundException;
5
-
6
- /**
7
- * Class Cloning
8
- * @package WPStaging\Backend\Modules\Jobs
9
- */
10
- class Cloning extends Job
11
- {
12
-
13
- /**
14
- * Save Chosen Cloning Settings
15
- * @return bool
16
- */
17
- public function save()
18
- {
19
- if (!isset($_POST) || !isset($_POST["cloneID"]))
20
- {
21
- return false;
22
- }
23
-
24
- // Generate Options
25
-
26
- // Clone
27
- $this->options->clone = $_POST["cloneID"];
28
- $this->options->cloneDirectoryName = preg_replace("#\W+#", '-', strtolower($this->options->clone));
29
- $this->options->cloneNumber = 1;
30
- $this->options->includedDirectories = array();
31
- $this->options->excludedDirectories = array();
32
- $this->options->extraDirectories = array();
33
- $this->options->excludedFiles = array('.htaccess', '.DS_Store', '.git', '.svn', '.tmp', 'desktop.ini', '.gitignore', '.log');
34
-
35
- // Job
36
- $this->options->job = new \stdClass();
37
-
38
- if (isset($this->options->existingClones[$this->options->clone]))
39
- {
40
- $this->options->cloneNumber = $this->options->existingClones[$this->options->clone]->number;
41
- }
42
- elseif (!empty($this->options->existingClones))
43
- {
44
- $this->options->cloneNumber = count($this->options->existingClones)+1;
45
- }
46
-
47
- // Excluded Tables
48
- if (isset($_POST["excludedTables"]) && is_array($_POST["excludedTables"]))
49
- {
50
- $this->options->excludedTables = $_POST["excludedTables"];
51
- }
52
-
53
- // Excluded Directories
54
- if (isset($_POST["excludedDirectories"]) && is_array($_POST["excludedDirectories"]))
55
- {
56
- $this->options->excludedDirectories = $_POST["excludedDirectories"];
57
- }
58
-
59
- // Excluded Directories TOTAL
60
- // Do not copy these folders and plugins
61
- $excludedDirectories = array(
62
- ABSPATH . 'wp-content' . DIRECTORY_SEPARATOR . 'cache',
63
- ABSPATH . 'wp-content' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'wps-hide-login'
64
- );
65
-
66
- $this->options->excludedDirectories = array_merge($excludedDirectories, $this->options->excludedDirectories);
67
-
68
- // Included Directories
69
- if (isset($_POST["includedDirectories"]) && is_array($_POST["includedDirectories"]))
70
- {
71
- $this->options->includedDirectories = $_POST["includedDirectories"];
72
- }
73
-
74
- // Extra Directories
75
- if (isset($_POST["extraDirectories"]) && !empty($_POST["extraDirectories"]) )
76
- {
77
- $this->options->extraDirectories = $_POST["extraDirectories"];
78
- }
79
-
80
- // Directories to Copy
81
- $this->options->directoriesToCopy = array_merge(
82
- $this->options->includedDirectories,
83
- $this->options->extraDirectories
84
- );
85
-
86
- array_unshift($this->options->directoriesToCopy, ABSPATH);
87
-
88
- // Delete files to copy listing
89
- $this->cache->delete("files_to_copy");
90
-
91
- return $this->saveOptions();
92
- }
93
-
94
- /**
95
- * Start the cloning job
96
- */
97
- public function start()
98
- {
99
- if (null === $this->options->currentJob)
100
- {
101
- $this->log("Cloning job for {$this->options->clone} finished");
102
- return true;
103
- }
104
-
105
- $methodName = "job" . ucwords($this->options->currentJob);
106
-
107
- if (!method_exists($this, $methodName))
108
- {
109
- $this->log("Can't execute job; Job's method {$methodName} is not found");
110
- throw new JobNotFoundException($methodName);
111
- }
112
-
113
- // Call the job
114
- //$this->log("execute job: Job's method {$methodName}");
115
- return $this->{$methodName}();
116
- }
117
-
118
- /**
119
- * @param object $response
120
- * @param string $nextJob
121
- * @return object
122
- */
123
- private function handleJobResponse($response, $nextJob)
124
- {
125
- // Job is not done
126
- if (true !== $response->status)
127
- {
128
- return $response;
129
- }
130
-
131
- $this->options->currentJob = $nextJob;
132
- $this->options->currentStep = 0;
133
- $this->options->totalSteps = 0;
134
-
135
- // Save options
136
- $this->saveOptions();
137
-
138
- return $response;
139
- }
140
-
141
- /**
142
- * Clone Database
143
- * @return object
144
- */
145
- public function jobDatabase()
146
- {
147
- $database = new Database();
148
- return $this->handleJobResponse($database->start(), "directories");
149
- }
150
-
151
- /**
152
- * Get All Files From Selected Directories Recursively Into a File
153
- * @return object
154
- */
155
- public function jobDirectories()
156
- {
157
- $directories = new Directories();
158
- return $this->handleJobResponse($directories->start(), "files");
159
- }
160
-
161
- /**
162
- * Copy Files
163
- * @return object
164
- */
165
- public function jobFiles()
166
- {
167
- $files = new Files();
168
- return $this->handleJobResponse($files->start(), "data");
169
- }
170
-
171
- /**
172
- * Replace Data
173
- * @return object
174
- */
175
- public function jobData()
176
- {
177
- $data = new Data();
178
- return $this->handleJobResponse($data->start(), "finish");
179
- }
180
-
181
- /**
182
- * Save Clone Data
183
- * @return object
184
- */
185
- public function jobFinish()
186
- {
187
- $finish = new Finish();
188
- return $this->handleJobResponse($finish->start(), '');
189
- }
190
}
1
+ <?php
2
+ namespace WPStaging\Backend\Modules\Jobs;
3
+
4
+ use WPStaging\Backend\Modules\Jobs\Exceptions\JobNotFoundException;
5
+
6
+ /**
7
+ * Class Cloning
8
+ * @package WPStaging\Backend\Modules\Jobs
9
+ */
10
+ class Cloning extends Job
11
+ {
12
+
13
+ /**
14
+ * Save Chosen Cloning Settings
15
+ * @return bool
16
+ */
17
+ public function save()
18
+ {
19
+ if (!isset($_POST) || !isset($_POST["cloneID"]))
20
+ {
21
+ return false;
22
+ }
23
+
24
+ // Generate Options
25
+
26
+ // Clone
27
+ $this->options->clone = $_POST["cloneID"];
28
+ $this->options->cloneDirectoryName = preg_replace("#\W+#", '-', strtolower($this->options->clone));
29
+ $this->options->cloneNumber = 1;
30
+ $this->options->includedDirectories = array();
31
+ $this->options->excludedDirectories = array();
32
+ $this->options->extraDirectories = array();
33
+ $this->options->excludedFiles = array('.htaccess', '.DS_Store', '.git', '.svn', '.tmp', 'desktop.ini', '.gitignore', '.log');
34
+
35
+ // Job
36
+ $this->options->job = new \stdClass();
37
+
38
+ if (isset($this->options->existingClones[$this->options->clone]))
39
+ {
40
+ $this->options->cloneNumber = $this->options->existingClones[$this->options->clone]->number;
41
+ }
42
+ elseif (!empty($this->options->existingClones))
43
+ {
44
+ $this->options->cloneNumber = count($this->options->existingClones)+1;
45
+ }
46
+
47
+ // Excluded Tables
48
+ if (isset($_POST["excludedTables"]) && is_array($_POST["excludedTables"]))
49
+ {
50
+ $this->options->excludedTables = $_POST["excludedTables"];
51
+ }
52
+
53
+ // Excluded Directories
54
+ if (isset($_POST["excludedDirectories"]) && is_array($_POST["excludedDirectories"]))
55
+ {
56
+ $this->options->excludedDirectories = $_POST["excludedDirectories"];
57
+ }
58
+
59
+ // Excluded Directories TOTAL
60
+ // Do not copy these folders and plugins
61
+ $excludedDirectories = array(
62
+ ABSPATH . 'wp-content' . DIRECTORY_SEPARATOR . 'cache',
63
+ ABSPATH . 'wp-content' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'wps-hide-login'
64
+ );
65
+
66
+ $this->options->excludedDirectories = array_merge($excludedDirectories, $this->options->excludedDirectories);
67
+
68
+ // Included Directories
69
+ if (isset($_POST["includedDirectories"]) && is_array($_POST["includedDirectories"]))
70
+ {
71
+ $this->options->includedDirectories = $_POST["includedDirectories"];
72
+ }
73
+
74
+ // Extra Directories
75
+ if (isset($_POST["extraDirectories"]) && !empty($_POST["extraDirectories"]) )
76
+ {
77
+ $this->options->extraDirectories = $_POST["extraDirectories"];
78
+ }
79
+
80
+ // Directories to Copy
81
+ $this->options->directoriesToCopy = array_merge(
82
+ $this->options->includedDirectories,
83
+ $this->options->extraDirectories
84
+ );
85
+
86
+ array_unshift($this->options->directoriesToCopy, ABSPATH);
87
+
88
+ // Delete files to copy listing
89
+ $this->cache->delete("files_to_copy");
90
+
91
+ return $this->saveOptions();
92
+ }
93
+
94
+ /**
95
+ * Start the cloning job
96
+ */
97
+ public function start()
98
+ {
99
+ if (null === $this->options->currentJob)
100
+ {
101
+ $this->log("Cloning job for {$this->options->clone} finished");
102
+ return true;
103
+ }
104
+
105
+ $methodName = "job" . ucwords($this->options->currentJob);
106
+
107
+ if (!method_exists($this, $methodName))
108
+ {
109
+ $this->log("Can't execute job; Job's method {$methodName} is not found");
110
+ throw new JobNotFoundException($methodName);
111
+ }
112
+
113
+ // Call the job
114
+ //$this->log("execute job: Job's method {$methodName}");
115
+ return $this->{$methodName}();
116
+ }
117
+
118
+ /**
119
+ * @param object $response
120
+ * @param string $nextJob
121
+ * @return object
122
+ */
123
+ private function handleJobResponse($response, $nextJob)
124
+ {
125
+ // Job is not done
126
+ if (true !== $response->status)
127
+ {
128
+ return $response;
129
+ }
130
+
131
+ $this->options->currentJob = $nextJob;
132
+ $this->options->currentStep = 0;
133
+ $this->options->totalSteps = 0;
134
+
135
+ // Save options
136
+ $this->saveOptions();
137
+
138
+ return $response;
139
+ }
140
+
141
+ /**
142
+ * Clone Database
143
+ * @return object
144
+ */
145
+ public function jobDatabase()
146
+ {
147
+ $database = new Database();
148
+ return $this->handleJobResponse($database->start(), "directories");
149
+ }
150
+
151
+ /**
152
+ * Get All Files From Selected Directories Recursively Into a File
153
+ * @return object
154
+ */
155
+ public function jobDirectories()
156
+ {
157
+ $directories = new Directories();
158
+ return $this->handleJobResponse($directories->start(), "files");
159
+ }
160
+
161
+ /**
162
+ * Copy Files
163
+ * @return object
164
+ */
165
+ public function jobFiles()
166
+ {
167
+ $files = new Files();
168
+ return $this->handleJobResponse($files->start(), "data");
169
+ }
170
+
171
+ /**
172
+ * Replace Data
173
+ * @return object
174
+ */
175
+ public function jobData()
176
+ {
177
+ $data = new Data();
178
+ return $this->handleJobResponse($data->start(), "finish");
179
+ }
180
+
181
+ /**
182
+ * Save Clone Data
183
+ * @return object
184
+ */
185
+ public function jobFinish()
186
+ {
187
+ $finish = new Finish();
188
+ return $this->handleJobResponse($finish->start(), '');
189
+ }
190
}
apps/Backend/Modules/Jobs/Delete.php CHANGED
@@ -1,426 +1,488 @@
1
- <?php
2
- namespace WPStaging\Backend\Modules\Jobs;
3
-
4
- use WPStaging\Backend\Modules\Jobs\Exceptions\CloneNotFoundException;
5
- //use WPStaging\Utils\Directories;
6
- use WPStaging\Utils\Logger;
7
- use WPStaging\WPStaging;
8
-
9
- /**
10
- * Class Delete
11
- * @package WPStaging\Backend\Modules\Jobs
12
- */
13
- class Delete extends Job
14
- {
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
- * Sets Clone and Table Records
38
- * @param null|array $clone
39
- */
40
- public function setData($clone = null)
41
- {
42
- if (!is_array($clone))
43
- {
44
- $this->getCloneRecords();
45
- }
46
- else
47
- {
48
- $this->clone = (object) $clone;
49
- $this->forceDeleteDirectories = true;
50
- }
51
-
52
- $this->getTableRecords();
53
- }
54
-
55
- /**
56
- * Get clone
57
- * @param null|string $name
58
- * @throws CloneNotFoundException
59
- */
60
- private function getCloneRecords($name = null)
61
- {
62
- if (null === $name && !isset($_POST["clone"]))
63
- {
64
- $this->log("Clone name is not set", Logger::TYPE_FATAL);
65
- throw new CloneNotFoundException();
66
- }
67
-
68
- if (null === $name)
69
- {
70
- $name = $_POST["clone"];
71
- }
72
-
73
- $clones = get_option("wpstg_existing_clones_beta", array());
74
-
75
- if (empty($clones) || !isset($clones[$name]))
76
- {
77
- $this->log("Couldn't find clone name {$name} or no existing clone", Logger::TYPE_FATAL);
78
- //throw new CloneNotFoundException();
79
- }
80
-
81
- $this->clone = $clones[$name];
82
- $this->clone["name"] = $name;
83
-
84
- $this->clone = (object) $this->clone;
85
-
86
- unset($clones);
87
- }
88
-
89
- /**
90
- * Get Tables
91
- */
92
- private function getTableRecords()
93
- {
94
- $wpdb = WPStaging::getInstance()->get("wpdb");
95
-
96
- $tables = $wpdb->get_results("SHOW TABLE STATUS LIKE 'wpstg{$this->clone->number}_%'");
97
-
98
- $this->tables = array();
99
-
100
- foreach ($tables as $table)
101
- {
102
- $this->tables[] = array(
103
- "name" => $table->Name,
104
- "size" => $this->formatSize(($table->Data_length + $table->Index_length))
105
- );
106
- }
107
-
108
- $this->tables = json_decode(json_encode($this->tables));
109
- }
110
-
111
- /**
112
- * Format bytes into human readable form
113
- * @param int $bytes
114
- * @param int $precision
115
- * @return string
116
- */
117
- public function formatSize($bytes, $precision = 2)
118
- {
119
- if ((int) $bytes < 1)
120
- {
121
- return '';
122
- }
123
-
124
- $units = array('B', "KB", "MB", "GB", "TB");
125
-
126
- $bytes = (int) $bytes;
127
- $base = log($bytes) / log(1000); // 1024 would be for MiB KiB etc
128
- $pow = pow(1000, $base - floor($base)); // Same rule for 1000
129
-
130
- return round($pow, $precision) . ' ' . $units[(int) floor($base)];
131
- }
132
-
133
-
134
- /**
135
- * @return false
136
- */
137
- public function getClone()
138
- {
139
- return $this->clone;
140
- }
141
-
142
- /**
143
- * @return null|object
144
- */
145
- public function getTables()
146
- {
147
- return $this->tables;
148
- }
149
-
150
- /**
151
- * Start Module
152
- * @param null|array $clone
153
- * @return bool
154
- */
155
- public function start($clone = null)
156
- {
157
- // Set data
158
- $this->setData($clone);
159
-
160
- // Get the job first
161
- $this->getJob();
162
-
163
- $method = "delete" . ucwords($this->job->current);
164
- return $this->{$method}();
165
- }
166
-
167
- /**
168
- * Get job data
169
- */
170
- private function getJob() {
171
- $this->job = $this->cache->get( "delete_job_{$this->clone->name}" );
172
-
173
-
174
- if( null !== $this->job ) {
175
- return;
176
- }
177
-
178
- // Generate JOB
179
- $this->job = ( object ) array(
180
- "current" => "tables",
181
- "nextDirectoryToDelete" => $this->clone->path,
182
- "name" => $this->clone->name
183
- );
184
-
185
- $this->cache->save( "delete_job_{$this->clone->name}", $this->job );
186
- }
187
-
188
-
189
-
190
- /**
191
- * @return bool
192
- */
193
- private function updateJob()
194
- {
195
- $this->job->nextDirectoryToDelete = trim($this->job->nextDirectoryToDelete);
196
- return $this->cache->save("delete_job_{$this->clone->name}", $this->job);
197
- }
198
-
199
- /**
200
- * @return array
201
- */
202
- private function getTablesToRemove()
203
- {
204
- $tables = $this->getTableNames();
205
-
206
- if (!isset($_POST["excludedTables"]) || !is_array($_POST["excludedTables"]) || empty($_POST["excludedTables"]))
207
- {
208
- return $tables;
209
- }
210
-
211
- return array_diff($tables, $_POST["excludedTables"]);
212
- }
213
-
214
- /**
215
- * @return array
216
- */
217
- private function getTableNames()
218
- {
219
- return (!is_array($this->tables)) ? array() : array_map(function($value) {
220
- return ($value->name);
221
- }, $this->tables);
222
- }
223
-
224
- /**
225
- * Delete Tables
226
- */
227
- public function deleteTables()
228
- {
229
- if ($this->isOverThreshold())
230
- {
231
- return;
232
- }
233
-
234
- $wpdb = WPStaging::getInstance()->get("wpdb");
235
-
236
- foreach ($this->getTablesToRemove() as $table)
237
- {
238
- // PROTECTION: Never delete any table that beginns with wp prefix of live site
239
- if($this->startsWith($table, $wpdb->prefix)){
240
- $this->log("Fatal Error: Trying to delete table {$table} of main WP installation!", Logger::TYPE_CRITICAL);
241
- return false;
242
- } else{
243
- $wpdb->query("DROP TABLE {$table}");
244
- }
245
- }
246
-
247
- // Move on to the next
248
- $this->job->current = "directory";
249
- $this->updateJob();
250
- }
251
-
252
- /**
253
- * Check if a strings start with a specific string
254
- * @param string $haystack
255
- * @param string $needle
256
- * @return bool
257
- */
258
- protected function startsWith($haystack, $needle)
259
- {
260
- $length = strlen($needle);
261
- return (substr($haystack, 0, $length) === $needle);
262
- }
263
-
264
- /**
265
- * Delete a specific directory and all of its subfolders in a native way without using any external caching data
266
- *
267
- * @param array $dir
268
- * @param array $excluded_dirs
269
- * @return boolean false when its finished
270
- */
271
- function deleteDirectoryNative( $dir = '' ) {
272
-
273
- if( !file_exists( $dir ) ) {
274
- return $this->isFinished();
275
- }
276
-
277
- if( !is_dir( $dir ) || is_link( $dir ) ) {
278
- unlink( $dir );
279
- return $this->isFinished();
280
- }
281
- foreach ( scandir( $dir ) as $item ) {
282
- if( $item == '.' || $item == '..' ) {
283
- continue;
284
- }
285
- if( !$this->deleteDirectoryNative( $dir . "/" . $item, false ) ) {
286
- //chmod( $dir . "/" . $item, 0777 );
287
- //if( !$this->deleteDirectoryNative( $dir . "/" . $item, false ) ){
288
- //return false;
289
- //}
290
- }
291
- };
292
-
293
- rmdir( $dir );
294
- return $this->isFinished();
295
- }
296
-
297
-
298
-
299
- /**
300
- * Delete Directories
301
- */
302
- public function deleteDirectory()
303
- {
304
- // No deleting directories or root of this clone is deleted
305
- if ($this->isDirectoryDeletingFinished())
306
- {
307
- $this->job->current = "finish";
308
- $this->updateJob();
309
- return;
310
- }
311
-
312
- $this->processDirectory($this->job->nextDirectoryToDelete);
313
-
314
- return;
315
- }
316
-
317
- /**
318
- * @return bool
319
- */
320
- public function isDirectoryDeletingFinished()
321
- {
322
- return (
323
- !is_dir($this->clone->path) ||
324
- (false === $this->forceDeleteDirectories && (!isset($_POST["deleteDir"]) || '1' !== $_POST["deleteDir"])) ||
325
- ABSPATH === $this->job->nextDirectoryToDelete
326
- );
327
- }
328
-
329
- /**
330
- * Delete contents of the directory if there are no directories in it and then delete itself
331
- * @param string $path
332
- * @return mixed
333
- */
334
- private function processDirectory($path)
335
- {
336
- // We hit the limit, stop
337
- if ($this->shouldStop($path))
338
- {
339
- $this->updateJob();
340
- return false;
341
- }
342
-
343
- $this->totalRecursion++;
344
-
345
- $contents = new \DirectoryIterator($path);
346
-
347
- foreach ($contents as $content)
348
- {
349
- // Skip dots
350
- if ($content->isDot())
351
- {
352
- continue;
353
- }
354
-
355
- // Get into the directory
356
- if (!$content->isLink() && $content->isDir())
357
- {
358
- return $this->processDirectory($content->getRealPath());
359
- }
360
-
361
- // Delete file
362
- if ($content->isFile())
363
- {
364
- @unlink($content->getRealPath());
365
- }
366
- }
367
-
368
- // Delete directory
369
- $this->job->lastDeletedDirectory = realpath($path . "/..");
370
- @rmdir($path);
371
- $this->updateJob();
372
- $this->processDirectory($this->job->nextDirectoryToDelete);
373
- }
374
-
375
- /**
376
- * @param string $path
377
- * @return bool
378
- */
379
- private function shouldStop($path)
380
- {
381
- // Just to make sure the root dir is never deleted!
382
- if ($path === get_home_path()){
383
- $this->log("Fatal Error: Trying to delete root of WP installation!", Logger::TYPE_CRITICAL);
384
- return true;
385
- }
386
-
387
- // Check if threshold is reached and is valid dir
388
- return (
389
- $this->isOverThreshold() ||
390
- !is_dir($path) ||
391
- $this->isDirectoryDeletingFinished()
392
- );
393
- }
394
-
395
- /**
396
- * Finish / Update Existing Clones
397
- */
398
- public function deleteFinish()
399
- {
400
- $existingClones = get_option("wpstg_existing_clones_beta", array());
401
-
402
- // Check if clones still exist
403
- $this->log("Verifying existing clones...");
404
- foreach ($existingClones as $name => $clone)
405
- {
406
- if (!is_dir($clone["path"]))
407
- {
408
- unset($existingClones[$name]);
409
- }
410
- }
411
- $this->log("Existing clones verified!");
412
-
413
- if (false === update_option("wpstg_existing_clones_beta", $existingClones))
414
- {
415
- $this->log("Failed to save {$this->options->clone}'s clone job data to database'");
416
- }
417
-
418
- // Delete cached file
419
- $this->cache->delete("delete_job_{$this->clone->name}");
420
- $this->cache->delete("delete_directories_{$this->clone->name}");
421
-
422
- //return true;
423
- $response = array('delete' => 'finished');
424
- wp_die(json_encode($response));
425
- }
426
- }
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
+
100
+ $stagingPrefix = $this->getStagingPrefix();
101
+
102
+ $tables = $this->wpdb->get_results("SHOW TABLE STATUS LIKE '{$stagingPrefix}%'");
103
+
104
+ $this->tables = array();
105
+
106
+ foreach ($tables as $table) {
107
+ $this->tables[] = array(
108
+ "name" => $table->Name,
109
+ "size" => $this->formatSize(($table->Data_length + $table->Index_length))
110
+ );
111
+ }
112
+
113
+ $this->tables = json_decode(json_encode($this->tables));
114
+ }
115
+
116
+ /**
117
+ * Check and return prefix of the staging site
118
+ */
119
+ public function getStagingPrefix() {
120
+ // prefix not defined! Happens if staging site has ben generated with older version of wpstg
121
+ // Try to get staging prefix from wp-config.php of staging site
122
+ //wp_die($this->clone->directoryName);
123
+ if (empty($this->clone->prefix)) {
124
+ // Throw error
125
+ $path = ABSPATH . $this->clone->directoryName . "/wp-config.php";
126
+ if (false === ($content = @file_get_contents($path))) {
127
+ $this->log("Can not open {$path}. Can't read contents", Logger::TYPE_ERROR);
128
+ // Create a random prefix which hopefully never exists.
129
+ $this->clone->prefix = rand(7, 15) . '_';
130
+ } else {
131
+
132
+ // Get prefix from wp-config.php
133
+ //preg_match_all("/table_prefix\s*=\s*'(\w*)';/", $content, $matches);
134
+ preg_match("/table_prefix\s*=\s*'(\w*)';/", $content, $matches);
135
+ //wp_die(var_dump($matches));
136
+
137
+ if (!empty($matches[1])) {
138
+ $this->clone->prefix = $matches[1];
139
+ } else {
140
+ $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");
141
+ // Create a random prefix which hopefully never exists.
142
+ return $this->clone->prefix = rand(7, 15) . '_';
143
+ }
144
+ }
145
+ }
146
+
147
+ // Check if staging prefix is the same as the live prefix
148
+ if ($this->wpdb->prefix == $this->clone->prefix) {
149
+ $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");
150
+ 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");
151
+ }
152
+
153
+ // Else
154
+ return $this->clone->prefix;
155
+ }
156
+
157
+ /**
158
+ * Format bytes into human readable form
159
+ * @param int $bytes
160
+ * @param int $precision
161
+ * @return string
162
+ */
163
+ public function formatSize($bytes, $precision = 2) {
164
+ if ((int) $bytes < 1) {
165
+ return '';
166
+ }
167
+
168
+ $units = array('B', "KB", "MB", "GB", "TB");
169
+
170
+ $bytes = (int) $bytes;
171
+ $base = log($bytes) / log(1000); // 1024 would be for MiB KiB etc
172
+ $pow = pow(1000, $base - floor($base)); // Same rule for 1000
173
+
174
+ return round($pow, $precision) . ' ' . $units[(int) floor($base)];
175
+ }
176
+
177
+ /**
178
+ * @return false
179
+ */
180
+ public function getClone() {
181
+ return $this->clone;
182
+ }
183
+
184
+ /**
185
+ * @return null|object
186
+ */
187
+ public function getTables() {
188
+ return $this->tables;
189
+ }
190
+
191
+ /**
192
+ * Start Module
193
+ * @param null|array $clone
194
+ * @return bool
195
+ */
196
+ public function start($clone = null) {
197
+ // Set data
198
+ $this->setData($clone);
199
+
200
+ // Get the job first
201
+ $this->getJob();
202
+
203
+ $method = "delete" . ucwords($this->job->current);
204
+ return $this->{$method}();
205
+ }
206
+
207
+ /**
208
+ * Get job data
209
+ */
210
+ private function getJob() {
211
+ $this->job = $this->cache->get("delete_job_{$this->clone->name}");
212
+
213
+
214
+ if (null !== $this->job) {
215
+ return;
216
+ }
217
+
218
+ // Generate JOB
219
+ $this->job = (object) array(
220
+ "current" => "tables",
221
+ "nextDirectoryToDelete" => $this->clone->path,
222
+ "name" => $this->clone->name
223
+ );
224
+
225
+ $this->cache->save("delete_job_{$this->clone->name}", $this->job);
226
+ }
227
+
228
+ /**
229
+ * @return bool
230
+ */
231
+ private function updateJob() {
232
+ $this->job->nextDirectoryToDelete = trim($this->job->nextDirectoryToDelete);
233
+ return $this->cache->save("delete_job_{$this->clone->name}", $this->job);
234
+ }
235
+
236
+ /**
237
+ * @return array
238
+ */
239
+ private function getTablesToRemove() {
240
+ $tables = $this->getTableNames();
241
+
242
+ if (!isset($_POST["excludedTables"]) || !is_array($_POST["excludedTables"]) || empty($_POST["excludedTables"])) {
243
+ return $tables;
244
+ }
245
+
246
+ return array_diff($tables, $_POST["excludedTables"]);
247
+ }
248
+
249
+ /**
250
+ * @return array
251
+ */
252
+ private function getTableNames() {
253
+ return (!is_array($this->tables)) ? array() : array_map(function($value) {
254
+ return ($value->name);
255
+ }, $this->tables);
256
+ }
257
+
258
+ /**
259
+ * Delete Tables
260
+ */
261
+ public function deleteTables() {
262
+ if ($this->isOverThreshold()) {
263
+ return;
264
+ }
265
+
266
+ //$wpdb = WPStaging::getInstance()->get("wpdb");
267
+
268
+ foreach ($this->getTablesToRemove() as $table) {
269
+ // PROTECTION: Never delete any table that beginns with wp prefix of live site
270
+ if ($this->startsWith($table, $this->wpdb->prefix)) {
271
+ $this->log("Fatal Error: Trying to delete table {$table} of main WP installation!", Logger::TYPE_CRITICAL);
272
+ return false;
273
+ } else {
274
+ $this->wpdb->query("DROP TABLE {$table}");
275
+ }
276
+ }
277
+
278
+ // Move on to the next
279
+ $this->job->current = "directory";
280
+ $this->updateJob();
281
+ }
282
+
283
+ /**
284
+ * Check if a strings start with a specific string
285
+ * @param string $haystack
286
+ * @param string $needle
287
+ * @return bool
288
+ */
289
+ protected function startsWith($haystack, $needle) {
290
+ $length = strlen($needle);
291
+ return (substr($haystack, 0, $length) === $needle);
292
+ }
293
+
294
+ /**
295
+ * Delete complete directory including all files and subfolders
296
+ *
297
+ * @throws InvalidArgumentException
298
+ */
299
+ public function deleteDirectory() {
300
+
301
+ // Finished or path does not exist
302
+ if (!is_dir($this->clone->path)) {
303
+ $this->job->current = "finish";
304
+ $this->updateJob();
305
+ return $this->returnFinish();
306
+ }
307
+
308
+ $this->log("Delete staging site: " . $this->clone->path, Logger::TYPE_INFO);
309
+
310
+ // Just to make sure the root dir is never deleted!
311
+ if ($this->clone->path === get_home_path()) {
312
+ $this->log("Fatal Error 8: Trying to delete root of WP installation!", Logger::TYPE_CRITICAL);
313
+ $this->returnException('Fatal Error 8: Trying to delete root of WP installation!');
314
+ }
315
+
316
+ // Check if threshold is reached
317
+ if ($this->isOverThreshold()) {
318
+ //$this->returnException('Maximum PHP execution time exceeded. Run again and repeat the deletion process until it is sucessfully finished.');
319
+ return;
320
+ }
321
+
322
+ $di = new \RecursiveDirectoryIterator($this->clone->path, \FilesystemIterator::SKIP_DOTS);
323
+ $ri = new \RecursiveIteratorIterator($di, \RecursiveIteratorIterator::CHILD_FIRST);
324
+ foreach ($ri as $file) {
325
+ $file->isDir() ? @rmdir($file) : unlink($file);
326
+ if ($this->isOverThreshold()) {
327
+ //$this->returnException('Maximum PHP execution time exceeded. Run again and repeat the deletion process until it is sucessfully finished.');
328
+ return;
329
+ }
330
+ }
331
+ //rmdir($this->clone->path);
332
+ // if (is_dir($this->clone->path)) {
333
+ // return $this->returnException('Unable to delete all files in folder' . $this->clone->path . '<br>Please try to delete the folder manually via FTP. Than run the delete function again.');
334
+ // } else {
335
+ // return $this->returnFinish();
336
+ // }
337
+ if (@rmdir($this->clone->path)){
338
+ return $this->returnFinish();
339
+ }
340
+ return;
341
+
342
+
343
+
344
+ }
345
+
346
+ /**
347
+ * Delete Directories
348
+ */
349
+ public function deleteDirectory_old() {
350
+ // No deleting directories or root of this clone is deleted
351
+ if ($this->isDirectoryDeletingFinished()) {
352
+ $this->job->current = "finish";
353
+ $this->updateJob();
354
+ return;
355
+ }
356
+
357
+ $this->processDirectory($this->job->nextDirectoryToDelete);
358
+
359
+ return;
360
+ }
361
+
362
+ /**
363
+ * @return bool
364
+ */
365
+ public function isDirectoryDeletingFinished() {
366
+ return (
367
+ (false === $this->forceDeleteDirectories && (!isset($_POST["deleteDir"]) || '1' !== $_POST["deleteDir"])) ||
368
+ !is_dir($this->clone->path) || ABSPATH === $this->job->nextDirectoryToDelete
369
+ );
370
+ }
371
+
372
+ /**
373
+ * Delete contents of the directory if there are no directories in it and then delete itself
374
+ * @param string $path
375
+ * @return mixed
376
+ */
377
+ private function processDirectory($path) {
378
+ // We hit the limit, stop
379
+ if ($this->shouldStop($path)) {
380
+ $this->updateJob();
381
+ return false;
382
+ }
383
+
384
+ $this->totalRecursion++;
385
+
386
+ $contents = new \DirectoryIterator($path);
387
+
388
+ foreach ($contents as $content => $value) {
389
+
390
+ // Skip dots
391
+ if ($content->isDot())
392
+
393
+
394
+ // Get into the directory
395
+ if (!$content->isLink() && $content->isDir()) {
396
+ return $this->processDirectory($content->getRealPath());
397
+ }
398
+
399
+ // Delete file
400
+ if ($content->isFile()) {
401
+ @unlink($content->getRealPath());
402
+ }
403
+ }
404
+
405
+ // Delete directory
406
+ $this->job->lastDeletedDirectory = realpath($path . "/..");
407
+ @rmdir($path);
408
+ $this->updateJob();
409
+ $this->processDirectory($this->job->nextDirectoryToDelete);
410
+ }
411
+
412
+ /**
413
+ * @param string $path
414
+ * @return bool
415
+ */
416
+ private function shouldStop($path) {
417
+ // Just to make sure the root dir is never deleted!
418
+ if ($path === get_home_path()) {
419
+ $this->log("Fatal Error: Trying to delete root of WP installation!", Logger::TYPE_CRITICAL);
420
+ return true;
421
+ }
422
+
423
+ // Check if threshold is reached and is valid dir
424
+ return (
425
+ $this->isOverThreshold() ||
426
+ !is_dir($path) ||
427
+ $this->isDirectoryDeletingFinished()
428
+ );
429
+ }
430
+
431
+ /**
432
+ * Finish / Update Existing Clones
433
+ */
434
+ public function deleteFinish() {
435
+ $existingClones = get_option("wpstg_existing_clones_beta", array());
436
+
437
+ // Check if clones still exist
438
+ $this->log("Verifying existing clones...");
439
+ foreach ($existingClones as $name => $clone) {
440
+ if (!is_dir($clone["path"])) {
441
+ unset($existingClones[$name]);
442
+ }
443
+ }
444
+ $this->log("Existing clones verified!");
445
+
446
+ if (false === update_option("wpstg_existing_clones_beta", $existingClones)) {
447
+ $this->log("Failed to save {$this->options->clone}'s clone job data to database'");
448
+ }
449
+
450
+ // Delete cached file
451
+ $this->cache->delete("delete_job_{$this->clone->name}");
452
+ $this->cache->delete("delete_directories_{$this->clone->name}");
453
+
454
+ //return true;
455
+ $response = array('delete' => 'finished');
456
+ wp_die(json_encode($response));
457
+ }
458
+
459
+ /**
460
+ * Get json response
461
+ * return json
462
+ */
463
+ // private function returnException($message = ''){
464
+ // wp_die( json_encode(array(
465
+ // 'job' => 'delete',
466
+ // 'status' => false,
467
+ // 'message' => $message,
468
+ // 'error' => true
469
+ // )));
470
+ // }
471
+ /**
472
+ * Get json response
473
+ * return json
474
+ */
475
+ private function returnFinish($message = '') {
476
+
477
+ $this->deleteFinish();
478
+
479
+ wp_die(json_encode(array(
480
+ 'job' => 'delete',
481
+ 'status' => true,
482
+ 'message' => $message,
483
+ 'error' => false,
484
+ 'delete' => 'finished'
485
+ )));
486
+ }
487
+
488
+ }
apps/Backend/Modules/Jobs/Directories.php CHANGED
@@ -2,16 +2,14 @@
2
3
namespace WPStaging\Backend\Modules\Jobs;
4
5
- //ini_set('display_startup_errors', 1);
6
- //ini_set('display_errors', 1);
7
- //error_reporting(-1);
8
-
9
// No Direct Access
10
if( !defined( "WPINC" ) ) {
11
die;
12
}
13
14
use WPStaging\WPStaging;
15
16
/**
17
* Class Files
@@ -156,6 +154,11 @@ class Directories extends JobExecutable {
156
157
// Add scanned directory listing
158
$this->options->scannedDirectories[] = $dir;
159
}
160
161
$this->saveOptions();
@@ -165,6 +168,7 @@ class Directories extends JobExecutable {
165
}
166
167
/**
168
* @param $directory
169
* @return bool
170
*/
@@ -177,8 +181,13 @@ class Directories extends JobExecutable {
177
foreach ( $files as $file ) {
178
$fullPath = $directory . $file;
179
180
- // It's a readable valid file and not excluded for copying
181
- if( is_file( $fullPath ) && !$this->isExcluded($file) ) {
182
$this->options->totalFiles++;
183
$this->files[] = $fullPath;
184
continue;
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\Logger;
12
+
13
14
/**
15
* Class Files
154
155
// Add scanned directory listing
156
$this->options->scannedDirectories[] = $dir;
157
+
158
+ if( $this->isOverThreshold() ) {
159
+ //$this->saveProgress();
160
+ return false;
161
+ }
162
}
163
164
$this->saveOptions();
168
}
169
170
/**
171
+ * Get files from directory
172
* @param $directory
173
* @return bool
174
*/
181
foreach ( $files as $file ) {
182
$fullPath = $directory . $file;
183
184
+ // Conditions:
185
+ // - Must be valid file
186
+ // - Is readable file
187
+ // - Not collected already
188
+ // - File not excluded by another rule or condition
189
+
190
+ if( is_file( $fullPath ) && is_readable( $fullPath ) && !in_array( $fullPath, $this->files ) && !$this->isExcluded($file) ) {
191
$this->options->totalFiles++;
192
$this->files[] = $fullPath;
193
continue;
apps/Backend/Modules/Jobs/Files.php CHANGED
@@ -1,287 +1,288 @@
1
- <?php
2
- namespace WPStaging\Backend\Modules\Jobs;
3
-
4
- // No Direct Access
5
- use WPStaging\Utils\Logger;
6
-
7
- if (!defined("WPINC"))
8
- {
9
- die;
10
- }
11
-
12
- /**
13
- * Class Files
14
- * @package WPStaging\Backend\Modules\Jobs
15
- */
16
- class Files extends JobExecutable
17
- {
18
-
19
- /**
20
- * @var \SplFileObject
21
- */
22
- private $file;
23
-
24
- /**
25
- * @var int
26
- */
27
- private $maxFilesPerRun = 500;
28
-
29
- /**
30
- * @var string
31
- */
32
- private $destination;
33
-
34
- /**
35
- * Initialization
36
- */
37
- public function initialize()
38
- {
39
- $this->destination = ABSPATH . $this->options->cloneDirectoryName . DIRECTORY_SEPARATOR;
40
-
41
- $filePath = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
42
-
43
- if (is_file($filePath))
44
- {
45
- $this->file = new \SplFileObject($filePath, 'r');
46
- }
47
-
48
- // Informational logs
49
- if (0 == $this->options->currentStep)
50
- {
51
- $this->log("Copying files...");
52
- }
53
-
54
- $this->settings->batchSize = $this->settings->batchSize * 1000000;
55
- }
56
-
57
- /**
58
- * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
59
- * @return void
60
- */
61
- protected function calculateTotalSteps()
62
- {
63
- $this->options->totalSteps = ceil($this->options->totalFiles / $this->maxFilesPerRun);
64
- }
65
-
66
- /**
67
- * Execute the Current Step
68
- * Returns false when over threshold limits are hit or when the job is done, true otherwise
69
- * @return bool
70
- */
71
- protected function execute()
72
- {
73
- // Finished
74
- if ($this->isFinished())
75
- {
76
- $this->log("Copying files finished");
77
- $this->prepareResponse(true, false);
78
- return false;
79
- }
80
-
81
- // Get files and copy'em
82
- if (!$this->getFilesAndCopy())
83
- {
84
- $this->prepareResponse(false, false);
85
- return false;
86
- }
87
-
88
- // Prepare and return response
89
- $this->prepareResponse();
90
-
91
- // Not finished
92
- return true;
93
- }
94
-
95
- /**
96
- * Get files and copy
97
- * @return bool
98
- */
99
- private function getFilesAndCopy()
100
- {
101
- // Over limits threshold
102
- if ($this->isOverThreshold())
103
- {
104
- // Prepare response and save current progress
105
- $this->prepareResponse(false, false);
106
- $this->saveOptions();
107
- return false;
108
- }
109
-
110
- // Skip processed ones
111
- if ($this->options->copiedFiles != 0)
112
- {
113
- $this->file->seek($this->options->copiedFiles);
114
- }
115
-
116
- $this->file->setFlags(\SplFileObject::SKIP_EMPTY | \SplFileObject::READ_AHEAD);
117
-
118
- // One thousand files at a time
119
- for ($i = 0; $i <= $this->maxFilesPerRun; $i++)
120
- {
121
- // End of file
122
- if ($this->file->eof())
123
- {
124
- break;
125
- }
126
-
127
- $this->copyFile($this->file->fgets());
128
- }
129
-
130
- $totalFiles = $this->maxFilesPerRun + $this->options->copiedFiles;
131
- $this->log("Total {$totalFiles} files processed");
132
-
133
- return true;
134
- }
135
-
136
- /**
137
- * Checks Whether There is Any Job to Execute or Not
138
- * @return bool
139
- */
140
- private function isFinished()
141
- {
142
- return (
143
- $this->options->currentStep > $this->options->totalSteps ||
144
- $this->options->copiedFiles >= $this->options->totalFiles
145
- );
146
- }
147
-
148
- /**
149
- * @param string $file
150
- * @return bool
151
- */
152
- private function copyFile($file)
153
- {
154
- $file = trim($file);
155
-
156
- // Increment copied files whatever the result is
157
- // This way we don't get stuck in the same step / files
158
- $this->options->copiedFiles++;
159
-
160
- // Invalid file, skipping it as if succeeded
161
- if (!is_file($file) || !is_readable($file))
162
- {
163
- $this->log("Can't read file or file doesn't exist {$file}");
164
- return true;
165
- }
166
-
167
- // Failed to get destination
168
- if (false === ($destination = $this->getDestination($file)))
169
- {
170
- $this->log("Can't get the destination of {$file}");
171
- return false;
172
- }
173
-
174
- // Good old PHP
175
- return $this->copy($file, $destination);
176
- }
177
-
178
- /**
179
- * Gets destination file and checks if the directory exists, if it does not attempts to create it.
180
- * If creating destination directory fails, it returns false, gives destination full path otherwise
181
- * @param string $file
182
- * @return bool|string
183
- */
184
- private function getDestination($file)
185
- {
186
- $relativePath = str_replace(ABSPATH, null, $file);
187
- $destinationPath = $this->destination . $relativePath;
188
- $destinationDirectory = dirname($destinationPath);
189
-
190
- if (!is_dir($destinationDirectory) && !@mkdir($destinationDirectory, 0775, true))
191
- {
192
- $this->log("Destination directory doesn't exist; {$destinationDirectory}", Logger::TYPE_ERROR);
193
- return false;
194
- }
195
-
196
- return $destinationPath;
197
- }
198
-
199
- /**
200
- * Copy File using PHP
201
- * @param string $file
202
- * @param string $destination
203
- * @return bool
204
- */
205
- private function copy($file, $destination)
206
- {
207
- // Get file size
208
- $fileSize = filesize($file);
209
-
210
- // File is over batch size
211
- if ($fileSize >= $this->settings->batchSize)
212
- {
213
- $this->log("Trying to copy big file {$file} -> {$destination}", Logger::TYPE_INFO);
214
- return $this->copyBig($file, $destination, $this->settings->batchSize);
215
- }
216
-
217
- // Attempt to copy
218
- if (!@copy($file, $destination))
219
- {
220
- $this->log("Failed to copy file to destination: {$file} -> {$destination}", Logger::TYPE_ERROR);
221
- return false;
222
- }
223
-
224
- return true;
225
- }
226
-
227
- /**
228
- * Copy bigger files than $this->settings->batchSize
229
- * @param string $file
230
- * @param string $destination
231
- * @return bool
232
- *
233
- * @deprecated since version 2.0.0 (Supported only in php 5.5.11 and later)
234
- */
235
- // private function copyBig($file, $destination)
236
- // {
237
- // $bytes = 0;
238
- // $fileInput = new \SplFileObject($file, "rb");
239
- // $fileOutput = new \SplFileObject($destination, 'w');
240
- //
241
- // $this->log("Copying big file; {$file} -> {$destination}");
242
- //
243
- // while (!$fileInput->eof())
244
- // {
245
- // $bytes += $fileOutput->fwrite($fileInput->fread($this->settings->batchSize));
246
- // }
247
- //
248
- // $fileInput = null;
249
- // $fileOutput= null;
250
- //
251
- // return ($bytes > 0);
252
- // }
253
-
254
- /**
255
- * Copy bigger files than $this->settings->batchSize
256
- * @param string $src
257
- * @param string $dst
258
- * @param int $buffersize
259
- * @return boolean
260
- */
261
- private function copyBig($src, $dst, $buffersize) {
262
- $src = fopen($src, 'r');
263
- $dest = fopen($dst, 'w');
264
-
265
- // Try first method:
266
- while (! feof($src)){
267
- if (false === fwrite($dest, fread($src, $buffersize))){
268
- $error = true;
269
- }
270
- }
271
- // Try second method if first one failed
272
- if (isset($error) && ($error === true)){
273
- while(!feof($src)){
274
- if (false === stream_copy_to_stream($src, $dest, 1024 )) {
275
- $this->log("Can not copy big file; {$src} -> {$dest}");
276
- fclose($src);
277
- fclose($dest);
278
- return false;
279
- }
280
- }
281
- }
282
- // Close any open handler
283
- fclose($src);
284
- fclose($dest);
285
- return true;
286
- }
287
}
1
+ <?php
2
+ namespace WPStaging\Backend\Modules\Jobs;
3
+
4
+ // No Direct Access
5
+ use WPStaging\Utils\Logger;
6
+
7
+ if (!defined("WPINC"))
8
+ {
9
+ die;
10
+ }
11
+
12
+ /**
13
+ * Class Files
14
+ * @package WPStaging\Backend\Modules\Jobs
15
+ */
16
+ class Files extends JobExecutable
17
+ {
18
+
19
+ /**
20
+ * @var \SplFileObject
21
+ */
22
+ private $file;
23
+
24
+ /**
25
+ * @var int
26
+ */
27
+ private $maxFilesPerRun;
28
+
29
+ /**
30
+ * @var string
31
+ */
32
+ private $destination;
33
+
34
+ /**
35
+ * Initialization
36
+ */
37
+ public function initialize()
38
+ {
39
+ $this->destination = ABSPATH . $this->options->cloneDirectoryName . DIRECTORY_SEPARATOR;
40
+
41
+ $filePath = $this->cache->getCacheDir() . "files_to_copy." . $this->cache->getCacheExtension();
42
+
43
+ if (is_file($filePath))
44
+ {
45
+ $this->file = new \SplFileObject($filePath, 'r');
46
+ }
47
+
48
+ // Informational logs
49
+ if (0 == $this->options->currentStep)
50
+ {
51
+ $this->log("Copying files...");
52
+ }
53
+
54
+ $this->settings->batchSize = $this->settings->batchSize * 1000000;
55
+ $this->maxFilesPerRun = $this->settings->fileLimit;
56
+ }
57
+
58
+ /**
59
+ * Calculate Total Steps in This Job and Assign It to $this->options->totalSteps
60
+ * @return void
61
+ */
62
+ protected function calculateTotalSteps()
63
+ {
64
+ $this->options->totalSteps = ceil($this->options->totalFiles / $this->maxFilesPerRun);
65
+ }
66
+
67
+ /**
68
+ * Execute the Current Step
69
+ * Returns false when over threshold limits are hit or when the job is done, true otherwise
70
+ * @return bool
71
+ */
72
+ protected function execute()
73
+ {
74
+ // Finished
75
+ if ($this->isFinished())
76
+ {
77
+ $this->log("Copying files finished");
78
+ $this->prepareResponse(true, false);
79
+ return false;
80
+ }
81
+
82
+ // Get files and copy'em
83
+ if (!$this->getFilesAndCopy())
84
+ {
85
+ $this->prepareResponse(false, false);
86
+ return false;
87
+ }
88
+
89
+ // Prepare and return response
90
+ $this->prepareResponse();
91
+
92
+ // Not finished
93
+ return true;
94
+ }
95
+
96
+ /**
97
+ * Get files and copy
98
+ * @return bool
99
+ */
100
+ private function getFilesAndCopy()
101
+ {
102
+ // Over limits threshold
103
+ if ($this->isOverThreshold())
104
+ {
105
+ // Prepare response and save current progress
106
+ $this->prepareResponse(false, false);
107
+ $this->saveOptions();
108
+ return false;
109
+ }
110
+
111
+ // Skip processed ones
112
+ if ($this->options->copiedFiles != 0)
113
+ {
114
+ $this->file->seek($this->options->copiedFiles);
115
+ }
116
+
117
+ $this->file->setFlags(\SplFileObject::SKIP_EMPTY | \SplFileObject::READ_AHEAD);
118
+
119
+ // One thousand files at a time
120
+ for ($i = 0; $i <= $this->maxFilesPerRun; $i++)
121
+ {
122
+ // End of file
123
+ if ($this->file->eof())
124
+ {
125
+ break;
126
+ }
127
+
128
+ $this->copyFile($this->file->fgets());
129
+ }
130
+
131
+ $totalFiles = $this->maxFilesPerRun + $this->options->copiedFiles;
132
+ $this->log("Total {$totalFiles} files processed");
133
+
134
+ return true;
135
+ }
136
+
137
+ /**
138
+ * Checks Whether There is Any Job to Execute or Not
139
+ * @return bool
140
+ */
141
+ private function isFinished()
142
+ {
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
+ {
155
+ $file = trim($file);
156
+
157
+ // Increment copied files whatever the result is
158
+ // This way we don't get stuck in the same step / files
159
+ $this->options->copiedFiles++;
160
+
161
+ // Invalid file, skipping it as if succeeded
162
+ if (!is_file($file) || !is_readable($file))
163
+ {
164
+ $this->log("Can't read file or file doesn't exist {$file}");
165
+ return true;
166
+ }
167
+
168
+ // Failed to get destination
169
+ if (false === ($destination = $this->getDestination($file)))
170
+ {
171
+ $this->log("Can't get the destination of {$file}");
172
+ return false;
173
+ }
174
+
175
+ // Good old PHP
176
+ return $this->copy($file, $destination);
177
+ }
178
+
179
+ /**
180
+ * Gets destination file and checks if the directory exists, if it does not attempts to create it.
181
+ * If creating destination directory fails, it returns false, gives destination full path otherwise
182
+ * @param string $file
183
+ * @return bool|string
184
+ */
185
+ private function getDestination($file)
186
+ {
187
+ $relativePath = str_replace(ABSPATH, null, $file);
188
+ $destinationPath = $this->destination . $relativePath;
189
+ $destinationDirectory = dirname($destinationPath);
190
+
191
+ if (!is_dir($destinationDirectory) && !@mkdir($destinationDirectory, 0775, true))
192
+ {
193
+ $this->log("Destination directory doesn't exist; {$destinationDirectory}", Logger::TYPE_ERROR);
194
+ return false;
195
+ }
196
+
197
+ return $destinationPath;
198
+ }
199
+
200
+ /**
201
+ * Copy File using PHP
202
+ * @param string $file
203
+ * @param string $destination
204
+ * @return bool
205
+ */
206
+ private function copy($file, $destination)
207
+ {
208
+ // Get file size
209
+ $fileSize = filesize($file);
210
+
211
+ // File is over batch size
212
+ if ($fileSize >= $this->settings->batchSize)
213
+ {
214
+ $this->log("Trying to copy big file {$file} -> {$destination}", Logger::TYPE_INFO);
215
+ return $this->copyBig($file, $destination, $this->settings->batchSize);
216
+ }
217
+
218
+ // Attempt to copy
219
+ if (!@copy($file, $destination))
220
+ {
221
+ $this->log("Failed to copy file to destination: {$file} -> {$destination}", Logger::TYPE_ERROR);
222
+ return false;
223
+ }
224
+
225
+ return true;
226
+ }
227
+
228
+ /**
229
+ * Copy bigger files than $this->settings->batchSize
230
+ * @param string $file
231
+ * @param string $destination
232
+ * @return bool
233
+ *
234
+ * @deprecated since version 2.0.0 (Supported only in php 5.5.11 and later)
235
+ */
236
+ // private function copyBig($file, $destination)
237
+ // {
238
+ // $bytes = 0;
239
+ // $fileInput = new \SplFileObject($file, "rb");
240
+ // $fileOutput = new \SplFileObject($destination, 'w');
241
+ //
242
+ // $this->log("Copying big file; {$file} -> {$destination}");
243
+ //
244
+ // while (!$fileInput->eof())
245
+ // {
246
+ // $bytes += $fileOutput->fwrite($fileInput->fread($this->settings->batchSize));
247
+ // }
248
+ //
249
+ // $fileInput = null;
250
+ // $fileOutput= null;
251
+ //
252
+ // return ($bytes > 0);
253
+ // }
254
+
255
+ /**
256
+ * Copy bigger files than $this->settings->batchSize
257
+ * @param string $src
258
+ * @param string $dst
259
+ * @param int $buffersize
260
+ * @return boolean
261
+ */
262
+ private function copyBig($src, $dst, $buffersize) {
263
+ $src = fopen($src, 'r');
264
+ $dest = fopen($dst, 'w');
265
+
266
+ // Try first method:
267
+ while (! feof($src)){
268
+ if (false === fwrite($dest, fread($src, $buffersize))){
269
+ $error = true;
270
+ }
271
+ }
272
+ // Try second method if first one failed
273
+ if (isset($error) && ($error === true)){
274
+ while(!feof($src)){
275
+ if (false === stream_copy_to_stream($src, $dest, 1024 )) {
276
+ $this->log("Can not copy big file; {$src} -> {$dest}");
277
+ fclose($src);
278
+ fclose($dest);
279
+ return false;
280
+ }
281
+ }
282
+ }
283
+ // Close any open handler
284
+ fclose($src);
285
+ fclose($dest);
286
+ return true;
287
+ }
288
}
apps/Backend/Modules/Jobs/Finish.php CHANGED
@@ -1,236 +1,262 @@
1
- <?php
2
- namespace WPStaging\Backend\Modules\Jobs;
3
-
4
- use WPStaging\WPStaging;
5
-
6
- //error_reporting( E_ALL );
7
-
8
- /**
9
- * Class Finish
10
- * @package WPStaging\Backend\Modules\Jobs
11
- */
12
- class Finish extends Job
13
- {
14
-
15
- /**
16
- * Start Module
17
- * @return object
18
- */
19
- public function start()
20
- {
21
- // Delete Cache Files
22
- $this->deleteCacheFiles();
23
-
24
- // Prepare .htaccess file for staging site
25
- //$this->protectDirectoriesAndFiles();
26
-
27
- // Prepare clone records & save scanned directories for delete job later
28
- $this->prepareCloneDataRecords();
29
-
30
- return (object) $this->options->existingClones[$this->options->clone];
31
- }
32
-
33
- /**
34
- * Delete Cache Files
35
- */
36
- protected function deleteCacheFiles()
37
- {
38
- $this->log("Deleting clone job's cache files...");
39
-
40
- // Clean cache files
41
- $this->cache->delete("clone_options");
42
- $this->cache->delete("files_to_copy");
43
-
44
- $this->log("Clone job's cache files have been deleted!");
45
- }
46
-
47
- /**
48
- * Get Upload Directory
49
- * @return string
50
- */
51
- private function getUploadDirectory()
52
- {
53
- $wpUploadDirectory = wp_get_upload_dir();
54
- $uploadDirectory = $wpUploadDirectory["basedir"] . DIRECTORY_SEPARATOR . WPStaging::SLUG;
55
-
56
- // Failed to create upload directory
57
- if (!is_dir($uploadDirectory) && !wp_mkdir_p($uploadDirectory))
58
- {
59
- $this->log("Upload directory ({$uploadDirectory}) doesn't exist and failed to create!");
60
- }
61
-
62
- $uploadDirectory = apply_filters("wpstg_get_upload_dir", $uploadDirectory);
63
-
64
- return rtrim($uploadDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
65
- }
66
-
67
- /**
68
- * Get .htaccess rules
69
- * @return string
70
- */
71
- private function getHtaccessRules()
72
- {
73
- // Prevent directory browsing and direct access to all files
74
- $rules = "<Files \"*\">\n";
75
- $rules .= "<IfModule mod_access.c>\n";
76
- $rules .= "Deny from all\n";
77
- $rules .= "</IfModule>\n";
78
- $rules .= "<IfModule !mod_access_compat>\n";
79
- $rules .= "<IfModule mod_authz_host.c>\n";
80
- $rules .= "Deny from all\n";
81
- $rules .= "</IfModule>\n";
82
- $rules .= "</IfModule>\n";
83
- $rules .= "<IfModule mod_access_compat>\n";
84
- $rules .= "Deny from all\n";
85
- $rules .= "</IfModule>\n";
86
- $rules .= "</Files>\n";
87
-
88
- return apply_filters("wpstg_protected_directory_htaccess_rules", $rules);
89
- }
90
-
91
- /**
92
- * Update .htaccess file and its rules
93
- * @param string $file
94
- * @param string $contents
95
- * @return bool
96
- */
97
- private function updateHTAccess($file, $contents)
98
- {
99
- return (
100
- (!$contents || $this->getHtaccessRules() !== $contents) &&
101
- false === @file_put_contents($file, $this->getHtaccessRules())
102
- );
103
- }
104
-
105
- /**
106
- * Save HTAccess file
107
- */
108
- private function saveHTAccess()
109
- {
110
- $uploadDir = $this->getUploadDirectory();
111
- $htAccessFile = $uploadDir . ".htaccess";
112
-
113
- // .htaccess exists
114
- if (file_exists($htAccessFile))
115
- {
116
- $contents = @file_get_contents($htAccessFile);
117
-
118
- // Rules doesn't match, update .htaccess rules
119
- if (false === $this->updateHTAccess($htAccessFile, $contents))
120
- {
121
- $this->log("Failed to update {$htAccessFile}");
122
- }
123
- }
124
- // .htaccess doesn't exists and
125
- else if (wp_is_writable($uploadDir) && false === @file_put_contents($htAccessFile, $this->getHtaccessRules()))
126
- {
127
- $this->log("Failed to create {$htAccessFile}");
128
- }
129
- }
130
-
131
- /**
132
- * Save blank index file
133
- * @return bool
134
- */
135
- private function saveBlankIndex()
136
- {
137
- $uploadDir = $this->getUploadDirectory();
138
- $indexFile = $uploadDir . "index.php";
139
-
140
- if (file_exists($indexFile))
141
- {
142
- return true;
143
- }
144
-
145
- $contents = "<?php" . PHP_EOL . "// WP-Staging protection file";
146
-
147
- if (!wp_is_writable($uploadDir) || false === @file_put_contents($indexFile, $contents))
148
- {
149
- $this->log("{$uploadDir} is not writable or couldn't generate {$indexFile}");
150
- return false;
151
- }
152
-
153
- return true;
154
- }
155
-
156
- /**
157
- * Prepare protect directories and files
158
- * @param bool $force
159
- */
160
- // protected function protectDirectoriesAndFiles($force = false)
161
- // {
162
- // // Don't execute
163
- // if (true !== get_transient("wpstg_check_protection_files") && false === $force)
164
- // {
165
- // return;
166
- // }
167
- //
168
- // // Save .htaccess file
169
- // $this->saveHTAccess();
170
- //
171
- // // Save blank index.php file
172
- // $this->saveBlankIndex();
173
- //
174
- // // TODO put blank index to upload directories?? Why??
175
- //
176
- // // Check files once a day
177
- // set_transient("wpstg_check_protection_files", true, DAY_IN_SECONDS); // 24 hours in seconds
178
- //
179
- //
180
- // }
181
-
182
- /**
183
- * Prepare clone records
184
- * @return bool
185
- */
186
- protected function prepareCloneDataRecords()
187
- {
188
- // Check if clones still exist
189
- $this->log("Verifying existing clones...");
190
-
191
-
192
- // Clone data already exists
193
- if (isset($this->options->existingClones[$this->options->clone]))
194
- {
195
- $this->log("Clone data already exists, no need to update, the job finished");
196
- return true;
197
- }
198
-
199
- // Save new clone data
200
- $this->log("{$this->options->clone}'s clone job's data is not in database, generating data");
201
-
202
-
203
-
204
- $this->options->existingClones[$this->options->clone] = array(
205
- "directoryName" => $this->options->cloneDirectoryName,
206
- "path" => ABSPATH . $this->options->cloneDirectoryName,
207
- "url" => get_site_url() . '/' . $this->options->cloneDirectoryName,
208
- "number" => $this->options->cloneNumber,
209
- "version" => \WPStaging\WPStaging::VERSION,
210
- "status" => false
211
- );
212
-
213
- if (false === update_option("wpstg_existing_clones_beta", $this->options->existingClones))
214
- {
215
- $this->log("Failed to save {$this->options->clone}'s clone job data to database'");
216
- return false;
217
- }
218
-
219
- // Save scanned directories for a delete job
220
- $this->saveScannedDirectories();
221
-
222
- return true;
223
- }
224
-
225
- /**
226
- * Save Scanned Directories for Delete Job Later
227
- */
228
- protected function saveScannedDirectories()
229
- {
230
- // Save scanned directories for delete job
231
- $this->cache->save("delete_directories_" . $this->options->clone, $this->options->scannedDirectories);
232
-
233
- $this->log("Successfully saved {$this->options->clone}'s clone job data to database'");
234
- $this->log("Cloning job has finished!");
235
- }
236
}
1
+ <?php
2
+ namespace WPStaging\Backend\Modules\Jobs;
3
+
4
+ use WPStaging\WPStaging;
5
+
6
+ //error_reporting( E_ALL );
7
+
8
+ /**
9
+ * Class Finish
10
+ * @package WPStaging\Backend\Modules\Jobs
11
+ */
12
+ class Finish extends Job
13
+ {
14
+ /**
15
+ * Clone Key
16
+ * @var string
17
+ */
18
+ private $clone = '';
19
+
20
+ /**
21
+ * Start Module
22
+ * @return object
23
+ */
24
+ public function start()
25
+ {
26
+ // sanitize the clone name before saving
27
+ $this->clone = preg_replace("#\W+#", '-', strtolower($this->options->clone));
28
+
29
+ // Delete Cache Files
30
+ $this->deleteCacheFiles();
31
+
32
+ // Prepare clone records & save scanned directories for delete job later
33
+ $this->prepareCloneDataRecords();
34
+
35
+
36
+ $return = array(
37
+ "directoryName" => $this->options->cloneDirectoryName,
38
+ "path" => ABSPATH . $this->options->cloneDirectoryName,
39
+ "url" => get_site_url() . '/' . $this->options->cloneDirectoryName,
40
+ "number" => $this->options->cloneNumber,
41
+ "version" => \WPStaging\WPStaging::VERSION,
42
+ "status" => false,
43
+ "prefix" => $this->options->prefix,
44
+ "last_msg" => $this->logger->getLastLogMsg(),
45
+ "job" => $this->options->currentJob
46
+ );
47
+
48
+
49
+ //return (object) $this->options->existingClones[$this->options->clone];
50
+ //return (object) $this->options->existingClones[$this->clone];
51
+ return (object) $return;
52
+ }
53
+
54
+ /**
55
+ * Delete Cache Files
56
+ */
57
+ protected function deleteCacheFiles()
58
+ {
59
+ $this->log("Finish: Deleting clone job's cache files...");
60
+
61
+ // Clean cache files
62
+ $this->cache->delete("clone_options");
63
+ $this->cache->delete("files_to_copy");
64
+
65
+ $this->log("Finish: Clone job's cache files have been deleted!");
66
+ }
67
+
68
+ /**
69
+ * Prepare clone records
70
+ * @return bool
71
+ */
72
+ protected function prepareCloneDataRecords()
73
+ {
74
+ // Check if clones still exist
75
+ $this->log("Finish: Verifying existing clones...");
76
+
77
+ // Clone data already exists
78
+ if (isset($this->options->existingClones[$this->options->clone]))
79
+ {
80
+ $this->log("Finish: Clone data already exists, no need to update, the job finished");
81
+ return true;
82
+ }
83
+
84
+ // Save new clone data
85
+ $this->log("Finish: {$this->options->clone}'s clone job's data is not in database, generating data");
86
+
87
+ // sanitize the clone name before saving
88
+ //$clone = preg_replace("#\W+#", '-', strtolower($this->options->clone));
89
+
90
+ $this->options->existingClones[$this->clone] = array(
91
+ "directoryName" => $this->options->cloneDirectoryName,
92
+ "path" => ABSPATH . $this->options->cloneDirectoryName,
93
+ "url" => get_site_url() . '/' . $this->options->cloneDirectoryName,
94
+ "number" => $this->options->cloneNumber,
95
+ "version" => \WPStaging\WPStaging::VERSION,
96
+ "status" => false,
97
+ "prefix" => $this->options->prefix,
98
+ );
99
+
100
+ if (false === update_option("wpstg_existing_clones_beta", $this->options->existingClones))
101
+ {
102
+ $this->log("Finish: Failed to save {$this->options->clone}'s clone job data to database'");
103
+ return false;
104
+ }
105
+
106
+ // Save scanned directories for a delete job
107
+ //$this->saveScannedDirectories();
108
+
109
+ return true;
110
+ }
111
+
112
+ /**
113
+ * Save Scanned Directories for Delete Job Later
114
+ */
115
+ // protected function saveScannedDirectories()
116
+ // {
117
+ // // Save scanned directories for delete job
118
+ // $this->cache->save("delete_directories_" . $this->options->clone, $this->options->scannedDirectories);
119
+ //
120
+ // $this->log("Successfully saved {$this->options->clone}'s clone job data to database'");
121
+ // $this->log("Cloning job has finished!");
122
+ // }
123
+
124
+ /**
125
+ * Get Upload Directory
126
+ * @return string
127
+ */
128
+ // private function getUploadDirectory()
129
+ // {
130
+ // $wpUploadDirectory = wp_get_upload_dir();
131
+ // $uploadDirectory = $wpUploadDirectory["basedir"] . DIRECTORY_SEPARATOR . WPStaging::SLUG;
132
+ //
133
+ // // Failed to create upload directory
134
+ // if (!is_dir($uploadDirectory) && !wp_mkdir_p($uploadDirectory))
135
+ // {
136
+ // $this->log("Upload directory ({$uploadDirectory}) doesn't exist and failed to create!");
137
+ // }
138
+ //
139
+ // $uploadDirectory = apply_filters("wpstg_get_upload_dir", $uploadDirectory);
140
+ //
141
+ // return rtrim($uploadDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
142
+ // }
143
+
144
+
145
+
146
+ /**
147
+ * Get .htaccess rules
148
+ * @return string
149
+ */
150
+ // private function getHtaccessRules()
151
+ // {
152
+ // // Prevent directory browsing and direct access to all files
153
+ // $rules = "<Files \"*\">\n";
154
+ // $rules .= "<IfModule mod_access.c>\n";
155
+ // $rules .= "Deny from all\n";
156
+ // $rules .= "</IfModule>\n";
157
+ // $rules .= "<IfModule !mod_access_compat>\n";
158
+ // $rules .= "<IfModule mod_authz_host.c>\n";
159
+ // $rules .= "Deny from all\n";
160
+ // $rules .= "</IfModule>\n";
161
+ // $rules .= "</IfModule>\n";
162
+ // $rules .= "<IfModule mod_access_compat>\n";
163
+ // $rules .= "Deny from all\n";
164
+ // $rules .= "</IfModule>\n";
165
+ // $rules .= "</Files>\n";
166
+ //
167
+ // return apply_filters("wpstg_protected_directory_htaccess_rules", $rules);
168
+ // }
169
+
170
+ /**
171
+ * Update .htaccess file and its rules
172
+ * @param string $file
173
+ * @param string $contents
174
+ * @return bool
175
+ */
176
+ // private function updateHTAccess($file, $contents)
177
+ // {
178
+ // return (
179
+ // (!$contents || $this->getHtaccessRules() !== $contents) &&
180
+ // false === @file_put_contents($file, $this->getHtaccessRules())
181
+ // );
182
+ // }
183
+
184
+ /**
185
+ * Save HTAccess file
186
+ */
187
+ // private function saveHTAccess()
188
+ // {
189
+ // $uploadDir = $this->getUploadDirectory();
190
+ // $htAccessFile = $uploadDir . ".htaccess";
191
+ //
192
+ // // .htaccess exists
193
+ // if (file_exists($htAccessFile))
194
+ // {
195
+ // $contents = @file_get_contents($htAccessFile);
196
+ //
197
+ // // Rules doesn't match, update .htaccess rules
198
+ // if (false === $this->updateHTAccess($htAccessFile, $contents))
199
+ // {
200
+ // $this->log("Failed to update {$htAccessFile}");
201
+ // }
202
+ // }
203
+ // // .htaccess doesn't exists and
204
+ // else if (wp_is_writable($uploadDir) && false === @file_put_contents($htAccessFile, $this->getHtaccessRules()))
205
+ // {
206
+ // $this->log("Failed to create {$htAccessFile}");
207
+ // }
208
+ // }
209
+
210
+ /**
211
+ * Save blank index file
212
+ * @return bool
213
+ */
214
+ // private function saveBlankIndex()
215
+ // {
216
+ // $uploadDir = $this->getUploadDirectory();
217
+ // $indexFile = $uploadDir . "index.php";
218
+ //
219
+ // if (file_exists($indexFile))
220
+ // {
221
+ // return true;
222
+ // }
223
+ //
224
+ // $contents = "<?php" . PHP_EOL . "// WP-Staging protection file";
225
+ //
226
+ // if (!wp_is_writable($uploadDir) || false === @file_put_contents($indexFile, $contents))
227
+ // {
228
+ // $this->log("{$uploadDir} is not writable or couldn't generate {$indexFile}");
229
+ // return false;
230
+ // }
231
+ //
232
+ // return true;
233
+ // }
234
+
235
+ /**
236
+ * Prepare protect directories and files
237
+ * @param bool $force
238
+ */
239
+ // protected function protectDirectoriesAndFiles($force = false)
240
+ // {
241
+ // // Don't execute
242
+ // if (true !== get_transient("wpstg_check_protection_files") && false === $force)
243
+ // {
244
+ // return;
245
+ // }
246
+ //
247
+ // // Save .htaccess file
248
+ // $this->saveHTAccess();
249
+ //
250
+ // // Save blank index.php file
251
+ // $this->saveBlankIndex();
252
+ //
253
+ // // TODO put blank index to upload directories?? Why??
254
+ //
255
+ // // Check files once a day
256
+ // set_transient("wpstg_check_protection_files", true, DAY_IN_SECONDS); // 24 hours in seconds
257
+ //
258
+ //
259
+ // }
260
+
261
+
262
}
apps/Backend/Modules/Jobs/Job.php CHANGED
@@ -1,473 +1,474 @@
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\Backend\Modules\Jobs\Interfaces\JobInterface;
11
- use WPStaging\Utils\Logger;
12
- use WPStaging\WPStaging;
13
- use WPStaging\Utils\Cache;
14
-
15
- /**
16
- * Class Job
17
- * @package WPStaging\Backend\Modules\Jobs
18
- */
19
- abstract class Job implements JobInterface
20
- {
21
-
22
- const EXECUTION_TIME_RATIO = 0.8;
23
-
24
- const MAX_MEMORY_RATIO = 0.8;
25
-
26
- /**
27
- * @var Cache
28
- */
29
- protected $cache;
30
-
31
- /**
32
- * @var Logger
33
- */
34
- protected $logger;
35
-
36
- /**
37
- * @var bool
38
- */
39
- protected $hasLoggedFileNameSet = false;
40
-
41
- /**
42
- * @var object
43
- */
44
- protected $options;
45
-
46
- /**
47
- * @var object
48
- */
49
- protected $settings;
50
-
51
- /**
52
- * System total maximum memory consumption
53
- * @var int
54
- */
55
- protected $maxMemoryLimit;
56
-
57
- /**
58
- * Script maximum memory consumption
59
- * @var int
60
- */
61
- protected $memoryLimit;
62
-
63
- /**
64
- * @var int
65
- */
66
- protected $maxExecutionTime;
67
-
68
-
69
- /**
70
- * @var int
71
- */
72
- protected $executionLimit;
73
-
74
- /**
75
- * @var int
76
- */
77
- protected $totalRecursion;
78
-
79
- /**
80
- * @var int
81
- */
82
- protected $maxRecursionLimit;
83
-
84
- /**
85
- * @var int
86
- */
87
- protected $start;
88
-
89
- /**
90
- * Job constructor.
91
- */
92
- public function __construct()
93
- {
94
- // Get max limits
95
- $this->start = $this->time();
96
- $this->maxMemoryLimit = $this->getMemoryInBytes(@ini_get("memory_limit"));
97
- $this->maxExecutionTime = (int) ini_get("max_execution_time");
98
- //$this->maxExecutionTime = (int) 30;
99
-
100
- if ($this->maxExecutionTime > 30)
101
- {
102
- $this->maxExecutionTime = 30;
103
- }
104
-
105
- if ($this->maxExecutionTime < 1)
106
- {
107
- $this->maxExecutionTime = 30;
108
- }
109
-
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
- //$this->settings = json_decode(json_encode(get_option("wpstg_settings", array())));
118
- $this->settings = (object)get_option("wpstg_settings", array());
119
-
120
-
121
-
122
- // check default options
123
- if (!$this->settings)
124
- {
125
- $this->options = new \stdClass();
126
- }
127
-
128
- if (isset($this->options->existingClones) && is_object($this->options->existingClones))
129
- {
130
- $this->options->existingClones = json_decode(json_encode($this->options->existingClones), true);
131
- }
132
-
133
- if (!isset($this->settings) || !isset($this->settings->queryLimit) || !isset($this->settings->batchSize) || !isset($this->settings->cpuLoad))
134
- {
135
- $this->settings = new \stdClass();
136
- $this->setDefaultSettings();
137
- }
138
-
139
- // Set limits accordingly to CPU LIMITS
140
- $this->setLimits();
141
-
142
- $this->maxRecursionLimit = (int) ini_get("xdebug.max_nesting_level");
143
-
144
- /*
145
- * This is needed to make sure that maxRecursionLimit = -1
146
- * if xdebug is not used in production env.
147
- * For using xdebug, maxRecursionLimit must be larger
148
- * otherwise xdebug is throwing an error 500 while debugging
149
- */
150
- if ($this->maxRecursionLimit < 1)
151
- {
152
- $this->maxRecursionLimit = -1;
153
- }
154
- else
155
- {
156
- $this->maxRecursionLimit = $this->maxRecursionLimit - 50; // just to make sure
157
- }
158
-
159
- if (method_exists($this, "initialize"))
160
- {
161
- $this->initialize();
162
- }
163
- }
164
-
165
- /**
166
- * Job destructor
167
- */
168
- public function __destruct()
169
- {
170
- // Commit logs
171
- $this->logger->commit();
172
- }
173
-
174
- /**
175
- * Set default settings
176
- */
177
- protected function setDefaultSettings(){
178
- $this->settings->queryLimit = "1000";
179
- $this->settings->batchSize = "2";
180
- $this->settings->cpuLoad = 'medium';
181
- update_option('wpstg_settings', $this->settings);
182
- }
183
-
184
- /**
185
- * Set limits accordingly to
186
- */
187
- protected function setLimits()
188
- {
189
-
190
- if (!isset($this->settings->cpuLoad))
191
- {
192
- $this->settings->cpuLoad = "medium";
193
- }
194
-
195
- $memoryLimit= self::MAX_MEMORY_RATIO;
196
- $timeLimit = self::EXECUTION_TIME_RATIO;
197
-
198
- switch($this->settings->cpuLoad)
199
- {
200
- case "medium":
201
- //$memoryLimit= $memoryLimit / 2; // 0.4
202
- $timeLimit = $timeLimit / 2;
203
- break;
204
- case "low":
205
- //$memoryLimit= $memoryLimit / 4; // 0.2
206
- $timeLimit = $timeLimit / 4;
207
- break;
208
-
209
- case "fast": // 0.8
210
- default:
211
- break;
212
- }
213
-
214
- $this->memoryLimit = $this->maxMemoryLimit * $memoryLimit;
215
- $this->executionLimit = $this->maxExecutionTime * $timeLimit;
216
- }
217
-
218
- /**
219
- * Save options
220
- * @param null|array|object $options
221
- * @return bool
222
- */
223
- protected function saveOptions($options = null)
224
- {
225
- // Get default options
226
- if (null === $options)
227
- {
228
- $options = $this->options;
229
- }
230
-
231
- // Ensure that it is an object
232
- $options = json_decode(json_encode($options));
233
-
234
- return $this->cache->save("clone_options", $options);
235
- }
236
-
237
- /**
238
- * @return object
239
- */
240
- public function getOptions()
241
- {
242
- return $this->options;
243
- }
244
-
245
- /**
246
- * @param string $memory
247
- * @return int
248
- */
249
- protected function getMemoryInBytes($memory)
250
- {
251
- // Handle unlimited ones
252
- if (1 > (int) $memory)
253
- {
254
- return (int) $memory;
255
- }
256
-
257
- $bytes = (int) $memory; // grab only the number
258
- $size = trim(str_replace($bytes, null, strtolower($memory))); // strip away number and lower-case it
259
-
260
- // Actual calculation
261
- switch($size)
262
- {
263
- case 'k':
264
- $bytes *= 1024;
265
- break;
266
- case 'm':
267
- $bytes *= (1024 * 1024);
268
- break;
269
- case 'g':
270
- $bytes *= (1024 * 1024 * 1024);
271
- break;
272
- }
273
-
274
- return $bytes;
275
- }
276
-
277
- /**
278
- * Format bytes into ini_set favorable form
279
- * @param int $bytes
280
- * @return string
281
- */
282
- protected function formatBytes($bytes)
283
- {
284
- if ((int) $bytes < 1)
285
- {
286
- return '';
287
- }
288
-
289
- $units = array('B', 'K', 'M', 'G'); // G since PHP 5.1.x so we are good!
290
-
291
- $bytes = (int) $bytes;
292
- $base = log($bytes) / log(1000);
293
- $pow = pow(1000, $base - floor($base));
294
-
295
- return round($pow, 0) . $units[(int) floor($base)];
296
- }
297
-
298
- /**
299
- * Get current time in seconds
300
- * @return float
301
- */
302
- protected function time()
303
- {
304
- $time = microtime();
305
- $time = explode(' ', $time);
306
- $time = $time[1] + $time[0];
307
- return $time;
308
- }
309
-
310
- /**
311
- * @return bool
312
- */
313
- protected function isOverThreshold()
314
- {
315
- // Check if the memory is over threshold
316
- $usedMemory = (int) @memory_get_usage(true);
317
-
318
- $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 );
319
-
320
- if ($usedMemory >= $this->memoryLimit)
321
- {
322
- $this->log('Used Memory: ' . $this->formatBytes($usedMemory) . ' Memory Limit: ' . $this->formatBytes($this->maxMemoryLimit) . ' Max Script memory limit: ' . $this->formatBytes( $this->memoryLimit ) );
323
- $this->resetMemory();
324
- return true;
325
- }
326
-
327
- if ($this->isRecursionLimit())
328
- {
329
- //$this->log('RESET RECURSION');
330
- return true;
331
- }
332
-
333
- // Check if execution time is over threshold
334
- ///$time = round($this->start + $this->time(), 4);
335
- $time = round($this->time() - $this->start, 4);
336
- $this->debugLog( 'Execution time: ' . $time . ' Execution Limit' . $this->executionLimit );
337
- if ($time >= $this->executionLimit)
338
- {
339
- //$this->log('RESET TIME');
340
- return true;
341
- }
342
-
343
- return false;
344
- }
345
-
346
- /**
347
- * Attempt to reset memory
348
- * @return bool
349
- *
350
- */
351
- protected function resetMemory()
352
- {
353
- $newMemoryLimit = $this->maxMemoryLimit * 2;
354
-
355
- // Failed to set
356
- if (false === ini_set("memory_limit", $this->formatBytes($newMemoryLimit)))
357
- {
358
- $this->log('Can not free some memory', Logger::TYPE_CRITICAL);
359
- return false;
360
- }
361
-
362
- // Double checking
363
- $newMemoryLimit = $this->getMemoryInBytes(@ini_get("memory_limit"));
364
- if ($newMemoryLimit <= $this->maxMemoryLimit)
365
- {
366
- return false;
367
- }
368
-
369
- // Set the new Maximum memory limit
370
- $this->maxMemoryLimit = $newMemoryLimit;
371
-
372
- // Calculate threshold limit
373
- $this->memoryLimit = $newMemoryLimit * self::MAX_MEMORY_RATIO;
374
-
375
- return true;
376
- }
377
-
378
- /**
379
- * Attempt to reset time
380
- * @return bool
381
- *
382
- * @deprecated since version 2.0.0
383
-
384
- */
385
- protected function resetTime()
386
- {
387
- // Attempt to reset timeout
388
- if (!@set_time_limit($this->maxExecutionTime))
389
- {
390
- return false;
391
- }
392
-
393
- // Increase execution limit
394
- $this->executionLimit = $this->executionLimit * 2;
395
-
396
- return true;
397
- }
398
-
399
- /**
400
- * Reset time limit and memory
401
- * @return bool
402
- *
403
- * @deprecated since version 2.0.0
404
- */
405
- protected function reset()
406
- {
407
- // Attempt to reset time
408
- if (!$this->resetTime())
409
- {
410
- return false;
411
- }
412
-
413
- // Attempt to reset memory
414
- if (!$this->resetMemory())
415
- {
416
- return false;
417
- }
418
-
419
- return true;
420
- }
421
-
422
- /**
423
- * Checks if calls are over recursion limit
424
- * @return bool
425
- */
426
- protected function isRecursionLimit()
427
- {
428
- return ($this->maxRecursionLimit > 0 && $this->totalRecursion >= $this->maxRecursionLimit);
429
- }
430
-
431
- /**
432
- * @param string $msg
433
- * @param string $type
434
- */
435
- protected function log($msg, $type = Logger::TYPE_INFO)
436
- {
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
- {
444
- $this->logger->setFileName($this->options->clone);
445
- $this->hasLoggedFileNameSet = true;
446
- }
447
-
448
- $this->logger->add($msg, $type);
449
- }
450
- /**
451
- * @param string $msg
452
- * @param string $type
453
- */
454
- protected function debugLog($msg, $type = Logger::TYPE_INFO)
455
- {
456
-
457
- if (!isset($this->options->clone)){
458
- $this->options->clone = date(DATE_ATOM, mktime(0, 0, 0, 7, 1, 2000));
459
- }
460
-
461
- if (false === $this->hasLoggedFileNameSet && 0 < strlen($this->options->clone))
462
- {
463
- $this->logger->setFileName($this->options->clone);
464
- $this->hasLoggedFileNameSet = true;
465
- }
466
-
467
-
468
- if (isset($this->settings->debugMode)){
469
- $this->logger->add($msg, $type);
470
- }
471
-
472
- }
473
}
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\Backend\Modules\Jobs\Interfaces\JobInterface;
11
+ use WPStaging\Utils\Logger;
12
+ use WPStaging\WPStaging;
13
+ use WPStaging\Utils\Cache;
14
+
15
+ /**
16
+ * Class Job
17
+ * @package WPStaging\Backend\Modules\Jobs
18
+ */
19
+ abstract class Job implements JobInterface
20
+ {
21
+
22
+ const EXECUTION_TIME_RATIO = 0.8;
23
+
24
+ const MAX_MEMORY_RATIO = 0.8;
25
+
26
+ /**
27
+ * @var Cache
28
+ */
29
+ protected $cache;
30
+
31
+ /**
32
+ * @var Logger
33
+ */
34
+ protected $logger;
35
+
36
+ /**
37
+ * @var bool
38
+ */
39
+ protected $hasLoggedFileNameSet = false;
40
+
41
+ /**
42
+ * @var object
43
+ */
44
+ protected $options;
45
+
46
+ /**
47
+ * @var object
48
+ */
49
+ protected $settings;
50
+
51
+ /**
52
+ * System total maximum memory consumption
53
+ * @var int
54
+ */
55
+ protected $maxMemoryLimit;
56
+
57
+ /**
58
+ * Script maximum memory consumption
59
+ * @var int
60
+ */
61
+ protected $memoryLimit;
62
+
63
+ /**
64
+ * @var int
65
+ */
66
+ protected $maxExecutionTime;
67
+
68
+
69
+ /**
70
+ * @var int
71
+ */
72
+ protected $executionLimit;
73
+
74
+ /**
75
+ * @var int
76
+ */
77
+ protected $totalRecursion;
78
+
79
+ /**
80
+ * @var int
81
+ */
82
+ protected $maxRecursionLimit;
83
+
84
+ /**
85
+ * @var int
86
+ */
87
+ protected $start;
88
+
89
+ /**
90
+ * Job constructor.
91
+ */
92
+ public function __construct()
93
+ {
94
+ // Get max limits
95
+ $this->start = $this->time();
96
+ $this->maxMemoryLimit = $this->getMemoryInBytes(@ini_get("memory_limit"));
97
+ //$this->maxExecutionTime = (int) ini_get("max_execution_time");
98
+ $this->maxExecutionTime = (int) 30;
99
+
100