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

Version Description

  • New: Add new db table selection manager
  • Fix: Better warning notices before updating a staging site
  • Fix: Stop delete process if staging site has been deleted manually before
  • Fix: Log file folder does not have correct permission 0755
  • Fix: Continue cloning if siteurl & home in wp_options could not be changed
  • Tweak: Better warning for update method
  • Tweak: DB tables and file verification opened as default option
  • Tweak: Skip rows larger than 5MB for search & replace operations to keep memory consumption low
  • Tweak: clean up search & replace method
  • Tweak: Better FAQ
Download this release

Release Info

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

Code changes from version 2.4.8 to 2.4.9

apps/Backend/Modules/Jobs/Data.php CHANGED
@@ -273,8 +273,8 @@ class Data extends JobExecutable {
273
  return true;
274
  }
275
 
276
- $this->log( "Preparing Data Step1: Failed to update siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
277
- return false;
278
  }
279
 
280
  /**
273
  return true;
274
  }
275
 
276
+ $this->log( "Preparing Data Step1: Failed to update siteurl and homeurl in {$this->prefix}options. Probably already did! {$this->db->last_error}", Logger::TYPE_ERROR );
277
+ return true;
278
  }
279
 
280
  /**
apps/Backend/Modules/Jobs/Delete.php CHANGED
@@ -13,461 +13,462 @@ use WPStaging\WPStaging;
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
- /**
43
- *
44
- * @var bool
45
- */
46
- private $isExternal;
47
-
48
-
49
-
50
- public function __construct( $isExternal = false ) {
51
- parent::__construct();
52
- $this->isExternal = $isExternal;
53
- }
54
-
55
- /**
56
- * Sets Clone and Table Records
57
- * @param null|array $clone
58
- */
59
- public function setData( $clone = null ) {
60
- if( !is_array( $clone ) ) {
61
- $this->getCloneRecords();
62
- } else {
63
- $this->clone = ( object ) $clone;
64
- $this->forceDeleteDirectories = true;
65
- }
66
-
67
- if( $this->isExternalDatabase() ) {
68
- $this->wpdb = $this->getStagingDb();
69
- } else {
70
- $this->wpdb = WPStaging::getInstance()->get( "wpdb" );
71
- }
72
-
73
- $this->getTableRecords();
74
- }
75
-
76
- /**
77
- * Get database object to interact with
78
- */
79
- private function getStagingDb() {
80
- return new \wpdb( $this->clone->databaseUser, $this->clone->databasePassword, $this->clone->databaseDatabase, $this->clone->databaseServer );
81
- }
82
-
83
- /**
84
- * Date database name
85
- * @return type
86
- */
87
- public function getDbName() {
88
- return $this->wpdb->dbname;
89
- }
90
-
91
- /**
92
- * Check if external database is used
93
- * @return boolean
94
- */
95
- private function isExternalDatabase() {
96
- if( $this->isExternal ) {
97
- return true;
98
- }
99
-
100
- if( !empty( $this->clone->databaseUser ) ) {
101
- return true;
102
- }
103
- return false;
104
- }
105
-
106
- /**
107
- * Get clone
108
- * @param null|string $name
109
- * @throws CloneNotFoundException
110
- */
111
- private function getCloneRecords( $name = null ) {
112
- if( null === $name && !isset( $_POST["clone"] ) ) {
113
- $this->log( "Clone name is not set", Logger::TYPE_FATAL );
114
- //throw new CloneNotFoundException();
115
- $this->returnException( "Clone name is not set" );
116
- }
117
-
118
- if( null === $name ) {
119
- $name = $_POST["clone"];
120
- }
121
-
122
- $clones = get_option( "wpstg_existing_clones_beta", array() );
123
-
124
- if( empty( $clones ) || !isset( $clones[$name] ) ) {
125
- $this->log( "Couldn't find clone name {$name} or no existing clone", Logger::TYPE_FATAL );
126
- //throw new CloneNotFoundException();
127
- $this->returnException( "Couldn't find clone name {$name} or no existing clone" );
128
- }
129
-
130
- $this->clone = $clones[$name];
131
- $this->clone["name"] = $name;
132
-
133
- $this->clone = ( object ) $this->clone;
134
-
135
- unset( $clones );
136
- }
137
-
138
- /**
139
- * Get Tables
140
- */
141
- private function getTableRecords() {
142
-
143
- $stagingPrefix = $this->getStagingPrefix();
144
-
145
- $tables = $this->wpdb->get_results( "SHOW TABLE STATUS LIKE '{$stagingPrefix}%'" );
146
-
147
- $this->tables = array();
148
-
149
- foreach ( $tables as $table ) {
150
- $this->tables[] = array(
151
- "name" => $table->Name,
152
- "size" => $this->formatSize( ($table->Data_length + $table->Index_length ) )
153
- );
154
- }
155
-
156
- $this->tables = json_decode( json_encode( $this->tables ) );
157
- }
158
-
159
- /**
160
- * Check and return prefix of the staging site
161
- */
162
- public function getStagingPrefix() {
163
-
164
- if( $this->isExternalDatabase() && !empty($this->clone->prefix) ) {
165
- return $this->clone->prefix;
166
- }
167
-
168
- // Prefix not defined! Happens if staging site has been generated with older version of wpstg
169
- // Try to get staging prefix from wp-config.php of staging site
170
- if( empty( $this->clone->prefix ) ) {
171
- // Throw error
172
- $path = ABSPATH . $this->clone->directoryName . "/wp-config.php";
173
- if( false === ($content = @file_get_contents( $path )) ) {
174
- $this->log( "Can not open {$path}. Can't read contents", Logger::TYPE_ERROR );
175
- } else {
176
- // Try to get prefix from wp-config.php
177
- preg_match( "/table_prefix\s*=\s*'(\w*)';/", $content, $matches );
178
-
179
- if( !empty( $matches[1] ) ) {
180
- $this->clone->prefix = $matches[1];
181
  } else {
182
- $this->returnException( "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" );
 
 
 
 
 
 
 
183
  }
184
- }
185
- }
186
-
187
- if( empty( $this->clone->prefix ) ) {
188
- $this->returnException( "Fatal Error: Can not delete staging site. Can not find table prefix. Contact support@wp-staging.com" );
189
- }
190
-
191
- // Check if staging prefix is the same as the live prefix
192
- if( empty( $this->options->databaseUser ) && $this->wpdb->prefix == $this->clone->prefix ) {
193
- $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" );
194
- $this->returnException( "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" );
195
- }
196
-
197
- // Else
198
- return $this->clone->prefix;
199
- }
200
-
201
- /**
202
- * Format bytes into human readable form
203
- * @param int $bytes
204
- * @param int $precision
205
- * @return string
206
- */
207
- public function formatSize( $bytes, $precision = 2 ) {
208
- if( ( int ) $bytes < 1 ) {
209
- return '';
210
- }
211
-
212
- $units = array('B', "KB", "MB", "GB", "TB");
213
-
214
- $bytes = ( int ) $bytes;
215
- $base = log( $bytes ) / log( 1000 ); // 1024 would be for MiB KiB etc
216
- $pow = pow( 1000, $base - floor( $base ) ); // Same rule for 1000
217
-
218
- return round( $pow, $precision ) . ' ' . $units[( int ) floor( $base )];
219
- }
220
-
221
- /**
222
- * @return false
223
- */
224
- public function getClone() {
225
- return $this->clone;
226
- }
227
-
228
- /**
229
- * @return null|object
230
- */
231
- public function getTables() {
232
- return $this->tables;
233
- }
234
-
235
- /**
236
- * Start Module
237
- * @param null|array $clone
238
- * @return bool
239
- */
240
- public function start( $clone = null ) {
241
- // Set data
242
- $this->setData( $clone );
243
-
244
- // Get the job first
245
- $this->getJob();
246
-
247
- $method = "delete" . ucwords( $this->job->current );
248
- return $this->{$method}();
249
- }
250
-
251
- /**
252
- * Get job data
253
- */
254
- private function getJob() {
255
- $this->job = $this->cache->get( "delete_job_{$this->clone->name}" );
256
-
257
-
258
- if( null !== $this->job ) {
259
- return;
260
- }
261
-
262
- // Generate JOB
263
- $this->job = ( object ) array(
264
- "current" => "tables",
265
- "nextDirectoryToDelete" => $this->clone->path,
266
- "name" => $this->clone->name
267
- );
268
-
269
- $this->cache->save( "delete_job_{$this->clone->name}", $this->job );
270
- }
271
-
272
- /**
273
- * @return bool
274
- */
275
- private function updateJob() {
276
- $this->job->nextDirectoryToDelete = trim( $this->job->nextDirectoryToDelete );
277
- return $this->cache->save( "delete_job_{$this->clone->name}", $this->job );
278
- }
279
-
280
- /**
281
- * @return array
282
- */
283
- private function getTablesToRemove() {
284
- $tables = $this->getTableNames();
285
-
286
- if( !isset( $_POST["excludedTables"] ) || !is_array( $_POST["excludedTables"] ) || empty( $_POST["excludedTables"] ) ) {
287
- return $tables;
288
- }
289
-
290
- return array_diff( $tables, $_POST["excludedTables"] );
291
- }
292
-
293
- /**
294
- * @return array
295
- */
296
- private function getTableNames() {
297
- return (!is_array( $this->tables )) ? array() : array_map( function($value) {
298
- return ($value->name);
299
- }, $this->tables );
300
- }
301
-
302
- /**
303
- * Delete Tables
304
- */
305
- public function deleteTables() {
306
- if( $this->isOverThreshold() ) {
307
- return;
308
- }
309
-
310
- foreach ( $this->getTablesToRemove() as $table ) {
311
- // PROTECTION: Never delete any table that beginns with wp prefix of live site
312
- if( !$this->isExternalDatabase() && $this->startsWith( $table, $this->wpdb->prefix ) ) {
313
- $this->log( "Fatal Error: Trying to delete table {$table} of main WP installation!", Logger::TYPE_CRITICAL );
314
- return false;
315
- } else {
316
- $this->wpdb->query( "DROP TABLE {$table}" );
317
- }
318
- }
319
-
320
- // Move on to the next
321
- $this->job->current = "directory";
322
- $this->updateJob();
323
- }
324
-
325
- /**
326
- * Check if a strings start with a specific string
327
- * @param string $haystack
328
- * @param string $needle
329
- * @return bool
330
- */
331
- protected function startsWith( $haystack, $needle ) {
332
- $length = strlen( $needle );
333
- return (substr( $haystack, 0, $length ) === $needle);
334
- }
335
-
336
- /**
337
- * Delete complete directory including all files and subfolders
338
- *
339
- * @throws InvalidArgumentException
340
- */
341
- public function deleteDirectory() {
342
- if( $this->isFatalError() ) {
343
- $this->returnException( 'Can not delete directory: ' . $this->clone->path . '. This seems to be the root directory. Please contact support@wp-staging.com' );
344
- throw new \Exception( 'Can not delete directory: ' . $this->clone->path . ' This seems to be the root directory. Please contact support@wp-staging.com' );
345
- }
346
- // Finished or path does not exist
347
- if(
348
- empty( $this->clone->path ) ||
349
- $this->clone->path == get_home_path() ||
350
- !is_dir( $this->clone->path ) ) {
351
-
352
- $this->job->current = "finish";
353
- $this->updateJob();
354
- return $this->returnFinish();
355
- }
356
-
357
- $this->log( "Delete staging site: " . $this->clone->path, Logger::TYPE_INFO );
358
-
359
- // Just to make sure the root dir is never deleted!
360
- if( $this->clone->path == get_home_path() ) {
361
- $this->log( "Fatal Error 8: Trying to delete root of WP installation!", Logger::TYPE_CRITICAL );
362
- $this->returnException( 'Fatal Error 8: Trying to delete root of WP installation!' );
363
- }
364
-
365
- // Check if threshold is reached
366
- if( $this->isOverThreshold() ) {
367
- //$this->returnException('Maximum PHP execution time exceeded. Run again and repeat the deletion process until it is sucessfully finished.');
368
- return;
369
- }
370
-
371
- $di = new \RecursiveDirectoryIterator( $this->clone->path, \FilesystemIterator::SKIP_DOTS );
372
- $ri = new \RecursiveIteratorIterator( $di, \RecursiveIteratorIterator::CHILD_FIRST );
373
- foreach ( $ri as $file ) {
374
- //$file->isDir() ? @rmdir($file) : unlink($file);
375
- $this->deleteFile( $file );
376
- if( $this->isOverThreshold() ) {
377
  //$this->returnException('Maximum PHP execution time exceeded. Run again and repeat the deletion process until it is sucessfully finished.');
378
  return;
379
- }
380
- }
381
-
382
- if( @rmdir( $this->clone->path ) ) {
383
- return $this->returnFinish();
384
- }
385
- return;
386
- }
387
-
388
- /**
389
- * Delete file
390
- * @param object iterator $file
391
- */
392
- private function deleteFile( $file ) {
393
- if( $file->isDir() ) {
394
- if( !@rmdir( $file ) ) {
395
- $this->returnException( 'Permission Error: Can not delete folder ' . $file );
396
- }
397
- } elseif( !is_file( $file ) ) {
398
- return false;
399
- } else {
400
- if( !unlink( $file ) ) {
401
- $this->returnException( 'Permission Error: Can not delete file ' . $file );
402
- }
403
- }
404
- }
405
-
406
- /**
407
- * @return bool
408
- */
409
- public function isDirectoryDeletingFinished() {
410
- return (
411
- (false === $this->forceDeleteDirectories && (!isset( $_POST["deleteDir"] ) || '1' !== $_POST["deleteDir"])) ||
412
- !is_dir( $this->clone->path ) || ABSPATH === $this->job->nextDirectoryToDelete
413
- );
414
- }
415
-
416
- /**
417
- *
418
- * @return boolean
419
- */
420
- public function isFatalError() {
421
- $homePath = rtrim( get_home_path(), "/" );
422
- if( rtrim( $this->clone->path, "/" ) == $homePath ) {
423
- return true;
424
- }
425
- return false;
426
- }
427
-
428
- /**
429
- * Finish / Update Existing Clones
430
- */
431
- public function deleteFinish() {
432
- $existingClones = get_option( "wpstg_existing_clones_beta", array() );
433
-
434
- // Check if clones still exist
435
- $this->log( "Verifying existing clones..." );
436
- foreach ( $existingClones as $name => $clone ) {
437
- if( !is_dir( $clone["path"] ) ) {
438
- unset( $existingClones[$name] );
439
- }
440
- }
441
- $this->log( "Existing clones verified!" );
442
-
443
- if( false === update_option( "wpstg_existing_clones_beta", $existingClones ) ) {
444
- $this->log( "Failed to save {$this->options->clone}'s clone job data to database'" );
445
- }
446
-
447
- // Delete cached file
448
- $this->cache->delete( "delete_job_{$this->clone->name}" );
449
- $this->cache->delete( "delete_directories_{$this->clone->name}" );
450
-
451
- //return true;
452
- $response = array('delete' => 'finished');
453
- wp_die( json_encode( $response ) );
454
- }
455
-
456
- /**
457
- * Get json response
458
- * return json
459
- */
460
- private function returnFinish( $message = '' ) {
461
-
462
- $this->deleteFinish();
463
-
464
- wp_die( json_encode( array(
465
- 'job' => 'delete',
466
- 'status' => true,
467
- 'message' => $message,
468
- 'error' => false,
469
- 'delete' => 'finished'
470
- ) ) );
471
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
472
 
473
  }
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
+ /**
43
+ *
44
+ * @var bool
45
+ */
46
+ private $isExternal;
47
+
48
+ public function __construct( $isExternal = false ) {
49
+ parent::__construct();
50
+ $this->isExternal = $isExternal;
51
+ }
52
+
53
+ /**
54
+ * Sets Clone and Table Records
55
+ * @param null|array $clone
56
+ */
57
+ public function setData( $clone = null ) {
58
+ if( !is_array( $clone ) ) {
59
+ $this->getCloneRecords();
60
+ } else {
61
+ $this->clone = ( object ) $clone;
62
+ $this->forceDeleteDirectories = true;
63
+ }
64
+
65
+ if( $this->isExternalDatabase() ) {
66
+ $this->wpdb = $this->getStagingDb();
67
+ } else {
68
+ $this->wpdb = WPStaging::getInstance()->get( "wpdb" );
69
+ }
70
+
71
+ $this->getTableRecords();
72
+ }
73
+
74
+ /**
75
+ * Get database object to interact with
76
+ */
77
+ private function getStagingDb() {
78
+ return new \wpdb( $this->clone->databaseUser, $this->clone->databasePassword, $this->clone->databaseDatabase, $this->clone->databaseServer );
79
+ }
80
+
81
+ /**
82
+ * Date database name
83
+ * @return type
84
+ */
85
+ public function getDbName() {
86
+ return $this->wpdb->dbname;
87
+ }
88
+
89
+ /**
90
+ * Check if external database is used
91
+ * @return boolean
92
+ */
93
+ private function isExternalDatabase() {
94
+ if( $this->isExternal ) {
95
+ return true;
96
+ }
97
+
98
+ if( !empty( $this->clone->databaseUser ) ) {
99
+ return true;
100
+ }
101
+ return false;
102
+ }
103
+
104
+ /**
105
+ * Get clone
106
+ * @param null|string $name
107
+ * @throws CloneNotFoundException
108
+ */
109
+ private function getCloneRecords( $name = null ) {
110
+ if( null === $name && !isset( $_POST["clone"] ) ) {
111
+ $this->log( "Clone name is not set", Logger::TYPE_FATAL );
112
+ //throw new CloneNotFoundException();
113
+ $this->returnException( "Clone name is not set" );
114
+ }
115
+
116
+ if( null === $name ) {
117
+ $name = $_POST["clone"];
118
+ }
119
+
120
+ $clones = get_option( "wpstg_existing_clones_beta", array() );
121
+
122
+ if( empty( $clones ) || !isset( $clones[$name] ) ) {
123
+ $this->log( "Couldn't find clone name {$name} or no existing clone", Logger::TYPE_FATAL );
124
+ //throw new CloneNotFoundException();
125
+ $this->returnException( "Couldn't find clone name {$name} or no existing clone" );
126
+ }
127
+
128
+ $this->clone = $clones[$name];
129
+ $this->clone["name"] = $name;
130
+
131
+ $this->clone = ( object ) $this->clone;
132
+
133
+ unset( $clones );
134
+ }
135
+
136
+ /**
137
+ * Get Tables
138
+ */
139
+ private function getTableRecords() {
140
+
141
+ $stagingPrefix = $this->getStagingPrefix();
142
+
143
+ $tables = $this->wpdb->get_results( "SHOW TABLE STATUS LIKE '{$stagingPrefix}%'" );
144
+
145
+ $this->tables = array();
146
+
147
+ foreach ( $tables as $table ) {
148
+ $this->tables[] = array(
149
+ "name" => $table->Name,
150
+ "size" => $this->formatSize( ($table->Data_length + $table->Index_length ) )
151
+ );
152
+ }
153
+
154
+ $this->tables = json_decode( json_encode( $this->tables ) );
155
+ }
156
+
157
+ /**
158
+ * Check and return prefix of the staging site
159
+ */
160
+ public function getStagingPrefix() {
161
+
162
+ if( $this->isExternalDatabase() && !empty( $this->clone->prefix ) ) {
163
+ return $this->clone->prefix;
164
+ }
165
+
166
+ // Prefix not defined! Happens if staging site has been generated with older version of wpstg
167
+ // Try to get staging prefix from wp-config.php of staging site
168
+ if( empty( $this->clone->prefix ) ) {
169
+ // Throw error
170
+ $path = ABSPATH . $this->clone->directoryName . "/wp-config.php";
171
+ if( false === ($content = @file_get_contents( $path )) ) {
172
+ $this->log( "Can not open {$path}. Can't read contents", Logger::TYPE_ERROR );
 
 
 
 
 
 
 
 
173
  } else {
174
+ // Try to get prefix from wp-config.php
175
+ preg_match( "/table_prefix\s*=\s*'(\w*)';/", $content, $matches );
176
+
177
+ if( !empty( $matches[1] ) ) {
178
+ $this->clone->prefix = $matches[1];
179
+ } else {
180
+ $this->returnException( "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" );
181
+ }
182
  }
183
+ }
184
+
185
+ if( empty( $this->clone->prefix ) ) {
186
+ $this->returnException( "Fatal Error: Can not delete staging site. Can not find table prefix. Contact support@wp-staging.com" );
187
+ }
188
+
189
+ // Check if staging prefix is the same as the live prefix
190
+ if( empty( $this->options->databaseUser ) && $this->wpdb->prefix == $this->clone->prefix ) {
191
+ $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" );
192
+ $this->returnException( "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" );
193
+ }
194
+
195
+ // Else
196
+ return $this->clone->prefix;
197
+ }
198
+
199
+ /**
200
+ * Format bytes into human readable form
201
+ * @param int $bytes
202
+ * @param int $precision
203
+ * @return string
204
+ */
205
+ public function formatSize( $bytes, $precision = 2 ) {
206
+ if( ( int ) $bytes < 1 ) {
207
+ return '';
208
+ }
209
+
210
+ $units = array('B', "KB", "MB", "GB", "TB");
211
+
212
+ $bytes = ( int ) $bytes;
213
+ $base = log( $bytes ) / log( 1000 ); // 1024 would be for MiB KiB etc
214
+ $pow = pow( 1000, $base - floor( $base ) ); // Same rule for 1000
215
+
216
+ return round( $pow, $precision ) . ' ' . $units[( int ) floor( $base )];
217
+ }
218
+
219
+ /**
220
+ * @return false
221
+ */
222
+ public function getClone() {
223
+ return $this->clone;
224
+ }
225
+
226
+ /**
227
+ * @return null|object
228
+ */
229
+ public function getTables() {
230
+ return $this->tables;
231
+ }
232
+
233
+ /**
234
+ * Start Module
235
+ * @param null|array $clone
236
+ * @return bool
237
+ */
238
+ public function start( $clone = null ) {
239
+ // Set data
240
+ $this->setData( $clone );
241
+
242
+ // Get the job first
243
+ $this->getJob();
244
+
245
+ $method = "delete" . ucwords( $this->job->current );
246
+ return $this->{$method}();
247
+ }
248
+
249
+ /**
250
+ * Get job data
251
+ */
252
+ private function getJob() {
253
+ $this->job = $this->cache->get( "delete_job_{$this->clone->name}" );
254
+
255
+
256
+ if( null !== $this->job ) {
257
+ return;
258
+ }
259
+
260
+ // Generate JOB
261
+ $this->job = ( object ) array(
262
+ "current" => "tables",
263
+ "nextDirectoryToDelete" => $this->clone->path,
264
+ "name" => $this->clone->name
265
+ );
266
+
267
+ $this->cache->save( "delete_job_{$this->clone->name}", $this->job );
268
+ }
269
+
270
+ /**
271
+ * @return bool
272
+ */
273
+ private function updateJob() {
274
+ $this->job->nextDirectoryToDelete = trim( $this->job->nextDirectoryToDelete );
275
+ return $this->cache->save( "delete_job_{$this->clone->name}", $this->job );
276
+ }
277
+
278
+ /**
279
+ * @return array
280
+ */
281
+ private function getTablesToRemove() {
282
+ $tables = $this->getTableNames();
283
+
284
+ if( !isset( $_POST["excludedTables"] ) || !is_array( $_POST["excludedTables"] ) || empty( $_POST["excludedTables"] ) ) {
285
+ return $tables;
286
+ }
287
+
288
+ return array_diff( $tables, $_POST["excludedTables"] );
289
+ }
290
+
291
+ /**
292
+ * @return array
293
+ */
294
+ private function getTableNames() {
295
+ return (!is_array( $this->tables )) ? array() : array_map( function($value) {
296
+ return ($value->name);
297
+ }, $this->tables );
298
+ }
299
+
300
+ /**
301
+ * Delete Tables
302
+ */
303
+ public function deleteTables() {
304
+ if( $this->isOverThreshold() ) {
305
+ return;
306
+ }
307
+
308
+ foreach ( $this->getTablesToRemove() as $table ) {
309
+ // PROTECTION: Never delete any table that beginns with wp prefix of live site
310
+ if( !$this->isExternalDatabase() && $this->startsWith( $table, $this->wpdb->prefix ) ) {
311
+ $this->log( "Fatal Error: Trying to delete table {$table} of main WP installation!", Logger::TYPE_CRITICAL );
312
+ return false;
313
+ } else {
314
+ $this->wpdb->query( "DROP TABLE {$table}" );
315
+ }
316
+ }
317
+
318
+ // Move on to the next
319
+ $this->job->current = "directory";
320
+ $this->updateJob();
321
+ }
322
+
323
+ /**
324
+ * Check if a strings start with a specific string
325
+ * @param string $haystack
326
+ * @param string $needle
327
+ * @return bool
328
+ */
329
+ protected function startsWith( $haystack, $needle ) {
330
+ $length = strlen( $needle );
331
+ return (substr( $haystack, 0, $length ) === $needle);
332
+ }
333
+
334
+ /**
335
+ * Delete complete directory including all files and subfolders
336
+ *
337
+ * @throws InvalidArgumentException
338
+ */
339
+ public function deleteDirectory() {
340
+ if( $this->isFatalError() ) {
341
+ $this->returnException( 'Can not delete directory: ' . $this->clone->path . '. This seems to be the root directory. Please contact support@wp-staging.com' );
342
+ throw new \Exception( 'Can not delete directory: ' . $this->clone->path . ' This seems to be the root directory. Please contact support@wp-staging.com' );
343
+ }
344
+ // Finished or path does not exist
345
+ if(
346
+ empty( $this->clone->path ) ||
347
+ $this->clone->path == get_home_path() ||
348
+ !is_dir( $this->clone->path ) ) {
349
+
350
+ $this->job->current = "finish";
351
+ $this->updateJob();
352
+ return $this->returnFinish();
353
+ }
354
+
355
+ $this->log( "Delete staging site: " . $this->clone->path, Logger::TYPE_INFO );
356
+
357
+ // Just to make sure the root dir is never deleted!
358
+ if( $this->clone->path == get_home_path() ) {
359
+ $this->log( "Fatal Error 8: Trying to delete root of WP installation!", Logger::TYPE_CRITICAL );
360
+ $this->returnException( 'Fatal Error 8: Trying to delete root of WP installation!' );
361
+ }
362
+
363
+ // Check if threshold is reached
364
+ if( $this->isOverThreshold() ) {
 
 
 
 
 
 
 
 
 
 
 
365
  //$this->returnException('Maximum PHP execution time exceeded. Run again and repeat the deletion process until it is sucessfully finished.');
366
  return;
367
+ }
368
+
369
+ $di = new \RecursiveDirectoryIterator( $this->clone->path, \FilesystemIterator::SKIP_DOTS );
370
+ $ri = new \RecursiveIteratorIterator( $di, \RecursiveIteratorIterator::CHILD_FIRST );
371
+ foreach ( $ri as $file ) {
372
+ //$file->isDir() ? @rmdir($file) : unlink($file);
373
+ $this->deleteFile( $file );
374
+ if( $this->isOverThreshold() ) {
375
+ //$this->returnException('Maximum PHP execution time exceeded. Run again and repeat the deletion process until it is sucessfully finished.');
376
+ return;
377
+ }
378
+ }
379
+
380
+ if( @rmdir( $this->clone->path ) ) {
381
+ return $this->returnFinish();
382
+ }
383
+ return;
384
+ }
385
+
386
+ /**
387
+ * Delete file
388
+ * @param object iterator $file
389
+ */
390
+ private function deleteFile( $file ) {
391
+ if( !file_exists( $file ) ) {
392
+ return true;
393
+ }
394
+ if( $file->isDir() ) {
395
+ if( !@rmdir( $file ) ) {
396
+ $this->returnException( 'Permission Error: Can not delete folder ' . $file );
397
+ }
398
+ } elseif( !is_file( $file ) ) {
399
+ return false;
400
+ } else {
401
+ if( !unlink( $file ) ) {
402
+ $this->returnException( 'Permission Error: Can not delete file ' . $file );
403
+ }
404
+ }
405
+ }
406
+
407
+ /**
408
+ * @return bool
409
+ */
410
+ public function isDirectoryDeletingFinished() {
411
+ return (
412
+ (false === $this->forceDeleteDirectories && (!isset( $_POST["deleteDir"] ) || '1' !== $_POST["deleteDir"])) ||
413
+ !is_dir( $this->clone->path ) || ABSPATH === $this->job->nextDirectoryToDelete
414
+ );
415
+ }
416
+
417
+ /**
418
+ *
419
+ * @return boolean
420
+ */
421
+ public function isFatalError() {
422
+ $homePath = rtrim( get_home_path(), "/" );
423
+ if( rtrim( $this->clone->path, "/" ) == $homePath ) {
424
+ return true;
425
+ }
426
+ return false;
427
+ }
428
+
429
+ /**
430
+ * Finish / Update Existing Clones
431
+ */
432
+ public function deleteFinish() {
433
+ $existingClones = get_option( "wpstg_existing_clones_beta", array() );
434
+
435
+ // Check if clones still exist
436
+ $this->log( "Verifying existing clones..." );
437
+ foreach ( $existingClones as $name => $clone ) {
438
+ if( !is_dir( $clone["path"] ) ) {
439
+ unset( $existingClones[$name] );
440
+ }
441
+ }
442
+ $this->log( "Existing clones verified!" );
443
+
444
+ if( false === update_option( "wpstg_existing_clones_beta", $existingClones ) ) {
445
+ $this->log( "Failed to save {$this->options->clone}'s clone job data to database'" );
446
+ }
447
+
448
+ // Delete cached file
449
+ $this->cache->delete( "delete_job_{$this->clone->name}" );
450
+ $this->cache->delete( "delete_directories_{$this->clone->name}" );
451
+
452
+ //return true;
453
+ $response = array('delete' => 'finished');
454
+ wp_die( json_encode( $response ) );
455
+ }
456
+
457
+ /**
458
+ * Get json response
459
+ * return json
460
+ */
461
+ private function returnFinish( $message = '' ) {
462
+
463
+ $this->deleteFinish();
464
+
465
+ wp_die( json_encode( array(
466
+ 'job' => 'delete',
467
+ 'status' => true,
468
+ 'message' => $message,
469
+ 'error' => false,
470
+ 'delete' => 'finished'
471
+ ) ) );
472
+ }
473
 
474
  }
apps/Backend/Modules/Jobs/Multisite/Data.php CHANGED
@@ -282,7 +282,7 @@ class Data extends JobExecutable {
282
  }
283
 
284
  $this->log( "Preparing Data Step1: Failed to update siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
285
- return false;
286
  }
287
 
288
  /**
282
  }
283
 
284
  $this->log( "Preparing Data Step1: Failed to update siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
285
+ return true;
286
  }
287
 
288
  /**
apps/Backend/Modules/Jobs/Multisite/DataExternal.php CHANGED
@@ -289,7 +289,7 @@ class DataExternal extends JobExecutable {
289
  }
290
 
291
  $this->log( "Preparing Data Step1: Failed to update siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
292
- return false;
293
  }
294
 
295
  /**
289
  }
290
 
291
  $this->log( "Preparing Data Step1: Failed to update siteurl and homeurl in {$this->prefix}options {$this->db->last_error}", Logger::TYPE_ERROR );
292
+ return true;
293
  }
294
 
295
  /**
apps/Backend/Modules/Jobs/Multisite/SearchReplace.php CHANGED
@@ -327,42 +327,6 @@ class SearchReplace extends JobExecutable {
327
  $this->getImagePathStaging()
328
  );
329
 
330
-
331
- ##########################
332
- // if( $this->isSubDir() ) {
333
- // // Search URL example.com/staging and root path to staging site /var/www/htdocs/staging
334
- // $args['search_for'] = array(
335
- // rtrim( $this->multisiteHomeUrlWithoutScheme, "/" ) . $this->getSubDir(),
336
- // ABSPATH,
337
- // str_replace( '/', '\/', rtrim( $this->multisiteHomeUrlWithoutScheme, '/' ) ) . str_replace( '/', '\/', $this->getSubDir() ), // // Used by revslider and several visual editors
338
- // $this->getImagePathLive()
339
- // );
340
- //
341
- //
342
- // $args['replace_with'] = array(
343
- // rtrim( $this->multisiteDomainWithoutScheme, "/" ) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName,
344
- // rtrim( $this->getAbsDestination(), '/' ) . '/' . $this->options->cloneDirectoryName,
345
- // str_replace( '/', '\/', rtrim( $this->multisiteDomainWithoutScheme, "/" ) ) . str_replace( '/', '\/', $this->getSubDir() ) . '\/' . $this->options->cloneDirectoryName, // Used by revslider and several visual editors
346
- // $this->getImagePathStaging()
347
- // );
348
- // } else {
349
- // $args['search_for'] = array(
350
- // rtrim( $this->multisiteHomeUrlWithoutScheme, '/' ),
351
- // ABSPATH,
352
- // str_replace( '/', '\/', rtrim( $this->multisiteHomeUrlWithoutScheme, '/' ) ),
353
- // $this->getImagePathLive()
354
- // );
355
- // $args['replace_with'] = array(
356
- // rtrim( $this->multisiteDomainWithoutScheme, '/' ) . '/' . $this->options->cloneDirectoryName,
357
- // rtrim( $this->getAbsDestination(), '/' ) . '/' . $this->options->cloneDirectoryName,
358
- // str_replace( '/', '\/', rtrim( $this->multisiteDomainWithoutScheme, '/' ) ) . '\/' . $this->options->cloneDirectoryName,
359
- // $this->getImagePathStaging()
360
- // );
361
- // }
362
-
363
-
364
-
365
-
366
  $args['replace_guids'] = 'off';
367
  $args['dry_run'] = 'off';
368
  $args['case_insensitive'] = false;
@@ -446,10 +410,10 @@ class SearchReplace extends JobExecutable {
446
  if( $this->options->prefix . 'options' === $table ) {
447
 
448
  // Skip certain options
449
- if( isset( $should_skip ) && true === $should_skip ) {
450
- $should_skip = false;
451
- continue;
452
- }
453
 
454
  // Skip this row
455
  if( 'wpstg_existing_clones_beta' === $dataRow ||
@@ -459,7 +423,8 @@ class SearchReplace extends JobExecutable {
459
  'siteurl' === $dataRow ||
460
  'home' === $dataRow
461
  ) {
462
- $should_skip = true;
 
463
  }
464
  }
465
 
327
  $this->getImagePathStaging()
328
  );
329
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
  $args['replace_guids'] = 'off';
331
  $args['dry_run'] = 'off';
332
  $args['case_insensitive'] = false;
410
  if( $this->options->prefix . 'options' === $table ) {
411
 
412
  // Skip certain options
413
+ // if( isset( $should_skip ) && true === $should_skip ) {
414
+ // $should_skip = false;
415
+ // continue;
416
+ // }
417
 
418
  // Skip this row
419
  if( 'wpstg_existing_clones_beta' === $dataRow ||
423
  'siteurl' === $dataRow ||
424
  'home' === $dataRow
425
  ) {
426
+ //$should_skip = true;
427
+ continue;
428
  }
429
  }
430
 
apps/Backend/Modules/Jobs/Multisite/SearchReplaceExternal.php CHANGED
@@ -336,37 +336,6 @@ class SearchReplaceExternal extends JobExecutable {
336
  $this->getImagePathStaging()
337
  );
338
 
339
- // if( $this->isSubDir() ) {
340
- // // Search URL example.com/staging and root path to staging site /var/www/htdocs/staging
341
- // $args['search_for'] = array(
342
- // '//' . rtrim( $this->multisiteHomeUrlWithoutScheme, "/" ) . $this->getSubDir(),
343
- // ABSPATH,
344
- // str_replace( '/', '\/', rtrim( $this->multisiteHomeUrlWithoutScheme, '/' ) ) . str_replace( '/', '\/', $this->getSubDir() ), // // Used by revslider and several visual editors
345
- // $this->getImagePathLive()
346
- // );
347
- //
348
- //
349
- // $args['replace_with'] = array(
350
- // '//' . rtrim( $this->multisiteDomainWithoutScheme, "/" ) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName,
351
- // rtrim( $this->getAbsDestination(), '/' ) . '/' . $this->options->cloneDirectoryName,
352
- // str_replace( '/', '\/', rtrim( $this->multisiteDomainWithoutScheme, "/" ) ) . str_replace( '/', '\/', $this->getSubDir() ) . '\/' . $this->options->cloneDirectoryName, // Used by revslider and several visual editors
353
- // $this->getImagePathStaging()
354
- // );
355
- // } else {
356
- // $args['search_for'] = array(
357
- // '//' . rtrim( $this->multisiteHomeUrlWithoutScheme, '/' ),
358
- // ABSPATH,
359
- // str_replace( '/', '\/', rtrim( $this->multisiteHomeUrlWithoutScheme, '/' ) ),
360
- // $this->getImagePathLive()
361
- // );
362
- // $args['replace_with'] = array(
363
- // '//' . rtrim( $this->multisiteDomainWithoutScheme, '/' ) . '/' . $this->options->cloneDirectoryName,
364
- // rtrim( $this->getAbsDestination(), '/' ) . '/' . $this->options->cloneDirectoryName,
365
- // str_replace( '/', '\/', rtrim( $this->multisiteDomainWithoutScheme, '/' ) ) . '\/' . $this->options->cloneDirectoryName,
366
- // $this->getImagePathStaging()
367
- // );
368
- // }
369
-
370
  $args['replace_guids'] = 'off';
371
  $args['dry_run'] = 'off';
372
  $args['case_insensitive'] = false;
@@ -450,10 +419,10 @@ class SearchReplaceExternal extends JobExecutable {
450
  if( $this->options->prefix . 'options' === $table ) {
451
 
452
  // Skip certain options
453
- if( isset( $should_skip ) && true === $should_skip ) {
454
- $should_skip = false;
455
- continue;
456
- }
457
 
458
  // Skip this row
459
  if( 'wpstg_existing_clones_beta' === $dataRow ||
@@ -463,7 +432,8 @@ class SearchReplaceExternal extends JobExecutable {
463
  'siteurl' === $dataRow ||
464
  'home' === $dataRow
465
  ) {
466
- $should_skip = true;
 
467
  }
468
  }
469
 
336
  $this->getImagePathStaging()
337
  );
338
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
  $args['replace_guids'] = 'off';
340
  $args['dry_run'] = 'off';
341
  $args['case_insensitive'] = false;
419
  if( $this->options->prefix . 'options' === $table ) {
420
 
421
  // Skip certain options
422
+ // if( isset( $should_skip ) && true === $should_skip ) {
423
+ // $should_skip = false;
424
+ // continue;
425
+ // }
426
 
427
  // Skip this row
428
  if( 'wpstg_existing_clones_beta' === $dataRow ||
432
  'siteurl' === $dataRow ||
433
  'home' === $dataRow
434
  ) {
435
+ //$should_skip = true;
436
+ continue;
437
  }
438
  }
439
 
apps/Backend/Modules/Jobs/Scan.php CHANGED
@@ -284,7 +284,6 @@ class Scan extends Job {
284
  if( false === ($path = $this->getPath( $directory )) ) {
285
  continue;
286
  }
287
- //echo $directory . '<br>';
288
 
289
  $this->handleDirectory( $path );
290
 
@@ -338,13 +337,9 @@ class Scan extends Job {
338
  * Prevents open base dir restriction fatal errors
339
  */
340
 
341
- //echo $directory->getRealPath() . '<br>';
342
- //echo 'abspath: ' . \WPStaging\WPStaging::getWPpath() . '<br>';
343
- //if( strpos( $directory->getRealPath(), ABSPATH ) !== 0 ) {
344
  if( strpos( $directory->getRealPath(), \WPStaging\WPStaging::getWPpath() ) !== 0 ) {
345
  return false;
346
  }
347
- //$path = str_replace( ABSPATH, null, $directory->getRealPath() );
348
  $path = str_replace( \WPStaging\WPStaging::getWPpath(), null, $directory->getRealPath() );
349
 
350
  // Using strpos() for symbolic links as they could create nasty stuff in nix stuff for directory structures
284
  if( false === ($path = $this->getPath( $directory )) ) {
285
  continue;
286
  }
 
287
 
288
  $this->handleDirectory( $path );
289
 
337
  * Prevents open base dir restriction fatal errors
338
  */
339
 
 
 
 
340
  if( strpos( $directory->getRealPath(), \WPStaging\WPStaging::getWPpath() ) !== 0 ) {
341
  return false;
342
  }
 
343
  $path = str_replace( \WPStaging\WPStaging::getWPpath(), null, $directory->getRealPath() );
344
 
345
  // Using strpos() for symbolic links as they could create nasty stuff in nix stuff for directory structures
apps/Backend/Modules/Jobs/SearchReplace.php CHANGED
@@ -351,7 +351,7 @@ class SearchReplace extends JobExecutable {
351
  'Admin_custome_login_top',
352
  'Admin_custome_login_dashboard',
353
  'Admin_custome_login_Version',
354
- 'upload_path',
355
  );
356
 
357
  $filter = apply_filters( 'wpstg_clone_searchreplace_excl_rows', $filter );
@@ -372,11 +372,24 @@ class SearchReplace extends JobExecutable {
372
  if( isset( $row['option_name'] ) && 'on' === $args['skip_transients'] && false !== strpos( $row['option_name'], '_transient' ) ) {
373
  continue;
374
  }
 
 
 
 
 
375
 
376
  foreach ( $columns as $column ) {
377
 
378
  $dataRow = $row[$column];
379
 
 
 
 
 
 
 
 
 
380
  if( $column == $primary_key ) {
381
  $where_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
382
  continue;
351
  'Admin_custome_login_top',
352
  'Admin_custome_login_dashboard',
353
  'Admin_custome_login_Version',
354
+ 'upload_path'
355
  );
356
 
357
  $filter = apply_filters( 'wpstg_clone_searchreplace_excl_rows', $filter );
372
  if( isset( $row['option_name'] ) && 'on' === $args['skip_transients'] && false !== strpos( $row['option_name'], '_transient' ) ) {
373
  continue;
374
  }
375
+ // Skip rows with more than 5MB to save memory
376
+ if( isset( $row['option_value'] ) && strlen($row['option_value']) >= 5000000 ) {
377
+ continue;
378
+ }
379
+
380
 
381
  foreach ( $columns as $column ) {
382
 
383
  $dataRow = $row[$column];
384
 
385
+
386
+ // Skip rows larger than 5MB
387
+ $size = strlen($dataRow);
388
+ if ($size >= 5000000){
389
+ continue;
390
+ }
391
+
392
+ // Skip Primary key
393
  if( $column == $primary_key ) {
394
  $where_sql[] = $column . ' = "' . $this->mysql_escape_mimic( $dataRow ) . '"';
395
  continue;
apps/Backend/public/css/wpstg-admin.css CHANGED
@@ -448,6 +448,11 @@
448
  position: relative;
449
  }
450
 
 
 
 
 
 
451
  .wpstg-progress-bar {
452
  /*max-width: 900px;*/
453
  height: 27px;
@@ -993,3 +998,8 @@
993
  background-color: #f2dede;
994
  border-color: #ebccd1;
995
  }
 
 
 
 
 
448
  position: relative;
449
  }
450
 
451
+ #wpstg-removing-clone .wpstg-tab-section{
452
+ display: block;
453
+ }
454
+
455
+
456
  .wpstg-progress-bar {
457
  /*max-width: 900px;*/
458
  height: 27px;
998
  background-color: #f2dede;
999
  border-color: #ebccd1;
1000
  }
1001
+
1002
+ #wpstg_select_tables_cloning {
1003
+ height: 600px;
1004
+ font-size: 13px;
1005
+ }
apps/Backend/public/js/wpstg-admin.js CHANGED
@@ -64,7 +64,7 @@ var WPStaging = (function ($)
64
  var elements = function ()
65
  {
66
  var $workFlow = cache.get("#wpstg-workflow"),
67
- isAllChecked = true,
68
  urlSpinner = ajaxurl.replace("/admin-ajax.php", '') + "/images/spinner",
69
  timer;
70
 
@@ -78,49 +78,97 @@ var WPStaging = (function ($)
78
  ajaxSpinner = "<img src=''" + urlSpinner + "' alt='' class='ajax-spinner general-spinner' />";
79
 
80
  $workFlow
81
- // Check / Un-check Database Tables
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  .on("click", ".wpstg-button-unselect", function (e) {
83
  e.preventDefault();
84
 
85
- if (false === isAllChecked)
 
 
86
  {
87
- cache.get(".wpstg-db-table-checkboxes").prop("checked", true);
88
- cache.get(".wpstg-button-unselect").text("Un-check All");
 
89
  isAllChecked = true;
90
  }
91
  else
92
  {
93
- cache.get(".wpstg-db-table-checkboxes").prop("checked", false);
94
- cache.get(".wpstg-button-unselect").text("Check All");
 
95
  isAllChecked = false;
96
  }
97
  })
 
98
  /**
99
  * Select tables with certain tbl prefix
100
  * @param obj e
101
  * @returns {undefined}
102
  */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  .on("click", ".wpstg-button-select", function (e) {
104
  e.preventDefault();
105
- $(".wpstg-db-table input").each(function () {
106
-
107
  if (wpstg.isMultisite == 1) {
108
  if ($(this).attr('name').match("^" + wpstg.tblprefix + "([^0-9])_*")) {
109
- $(this).prop("checked", true);
110
  } else {
111
- $(this).prop("checked", false);
112
  }
113
  }
114
 
115
  if (wpstg.isMultisite == 0) {
116
  if ($(this).attr('name').match("^" + wpstg.tblprefix)) {
117
- $(this).prop("checked", true);
118
  } else {
119
- $(this).prop("checked", false);
120
  }
121
  }
122
  })
123
  })
 
124
  // Expand Directories
125
  .on("click", ".wpstg-expand-dirs", function (e) {
126
  e.preventDefault();
@@ -332,11 +380,6 @@ var WPStaging = (function ($)
332
  .on("click", ".wpstg-execute-clone", function (e) {
333
  e.preventDefault();
334
 
335
- // if (!confirm("Are you sure you want to update the staging site? All your staging site modifications will be overwritten with the data from the live site. So make sure that your live site is up to date."))
336
- // {
337
- // return false;
338
- // }
339
-
340
  var clone = $(this).data("clone");
341
 
342
  $workFlow.addClass("loading");
@@ -490,7 +533,7 @@ var WPStaging = (function ($)
490
 
491
  if ($this.data("action") === "wpstg_update") {
492
  // Update Clone - confirmed
493
- if (!confirm("Are you sure you want to update the staging site with data from the live site? \n\nEnsure to exclude all tables and folders which you do not want to overwrite, first! \n\nDo not necessarily cancel the updating process! This can break your staging site."))
494
  {
495
  return false;
496
  }
@@ -577,21 +620,29 @@ var WPStaging = (function ($)
577
  {
578
  var includedTables = [];
579
 
580
- $(".wpstg-db-table input:checked").each(function () {
581
- includedTables.push(this.name);
 
 
 
 
582
  });
583
 
584
  return includedTables;
585
  };
586
  /**
587
  * Get Excluded (Unchecked) Database Tables
 
588
  * @returns {Array}
589
  */
590
  var getExcludedTables = function ()
591
  {
592
  var excludedTables = [];
593
 
594
- $(".wpstg-db-table input:not(:checked)").each(function () {
 
 
 
595
  excludedTables.push(this.name);
596
  });
597
 
64
  var elements = function ()
65
  {
66
  var $workFlow = cache.get("#wpstg-workflow"),
67
+ isAllChecked = false,
68
  urlSpinner = ajaxurl.replace("/admin-ajax.php", '') + "/images/spinner",
69
  timer;
70
 
78
  ajaxSpinner = "<img src=''" + urlSpinner + "' alt='' class='ajax-spinner general-spinner' />";
79
 
80
  $workFlow
81
+ // Check / Un-check All Database Tables
82
+ // .on("click", ".wpstg-button-unselect", function (e) {
83
+ // e.preventDefault();
84
+ //
85
+ // if (false === isAllChecked)
86
+ // {
87
+ // cache.get(".wpstg-db-table-checkboxes").prop("checked", true);
88
+ // cache.get(".wpstg-button-unselect").text("Un-check All");
89
+ // isAllChecked = true;
90
+ // }
91
+ // else
92
+ // {
93
+ // cache.get(".wpstg-db-table-checkboxes").prop("checked", false);
94
+ // cache.get(".wpstg-button-unselect").text("Check All");
95
+ // isAllChecked = false;
96
+ // }
97
+ // })
98
+ // Check / Un-check All Database Tables New
99
  .on("click", ".wpstg-button-unselect", function (e) {
100
  e.preventDefault();
101
 
102
+
103
+ //if (typeof(isAllChecked) !== 'defined' || true === isAllChecked )
104
+ if (false === isAllChecked )
105
  {
106
+ console.log('true');
107
+ cache.get("#wpstg_select_tables_cloning .wpstg-db-table").prop("selected", "selected");
108
+ cache.get(".wpstg-button-unselect").text("Unselect All");
109
  isAllChecked = true;
110
  }
111
  else
112
  {
113
+ console.log('false');
114
+ cache.get("#wpstg_select_tables_cloning .wpstg-db-table").prop("selected", false);
115
+ cache.get(".wpstg-button-unselect").text("Select All");
116
  isAllChecked = false;
117
  }
118
  })
119
+
120
  /**
121
  * Select tables with certain tbl prefix
122
  * @param obj e
123
  * @returns {undefined}
124
  */
125
+ // .on("click", ".wpstg-button-select", function (e) {
126
+ // e.preventDefault();
127
+ // $(".wpstg-db-table input").each(function () {
128
+ //
129
+ // if (wpstg.isMultisite == 1) {
130
+ // if ($(this).attr('name').match("^" + wpstg.tblprefix + "([^0-9])_*")) {
131
+ // $(this).prop("checked", true);
132
+ // } else {
133
+ // $(this).prop("checked", false);
134
+ // }
135
+ // }
136
+ //
137
+ // if (wpstg.isMultisite == 0) {
138
+ // if ($(this).attr('name').match("^" + wpstg.tblprefix)) {
139
+ // $(this).prop("checked", true);
140
+ // } else {
141
+ // $(this).prop("checked", false);
142
+ // }
143
+ // }
144
+ // })
145
+ // })
146
+ /**
147
+ * Select tables with certain tbl prefix | NEW
148
+ * @param obj e
149
+ * @returns {undefined}
150
+ */
151
  .on("click", ".wpstg-button-select", function (e) {
152
  e.preventDefault();
153
+ $("#wpstg_select_tables_cloning .wpstg-db-table").each(function () {
 
154
  if (wpstg.isMultisite == 1) {
155
  if ($(this).attr('name').match("^" + wpstg.tblprefix + "([^0-9])_*")) {
156
+ $(this).prop("selected", "selected");
157
  } else {
158
+ $(this).prop("selected", false);
159
  }
160
  }
161
 
162
  if (wpstg.isMultisite == 0) {
163
  if ($(this).attr('name').match("^" + wpstg.tblprefix)) {
164
+ $(this).prop("selected", "selected");
165
  } else {
166
+ $(this).prop("selected", false);
167
  }
168
  }
169
  })
170
  })
171
+
172
  // Expand Directories
173
  .on("click", ".wpstg-expand-dirs", function (e) {
174
  e.preventDefault();
380
  .on("click", ".wpstg-execute-clone", function (e) {
381
  e.preventDefault();
382
 
 
 
 
 
 
383
  var clone = $(this).data("clone");
384
 
385
  $workFlow.addClass("loading");
533
 
534
  if ($this.data("action") === "wpstg_update") {
535
  // Update Clone - confirmed
536
+ if (!confirm("ATTENTION! This will overwrite your staging site with all selected data from the live site! This should be used only if you want to clone again your production site. Are you sure you want to do this? \n\nMake sure to exclude all tables and folders which you do not want to overwrite, first! \n\nDo not necessarily cancel the updating process! This can break your staging site."))
537
  {
538
  return false;
539
  }
620
  {
621
  var includedTables = [];
622
 
623
+ // $(".wpstg-db-table input:checked").each(function () {
624
+ // includedTables.push(this.name);
625
+ // });
626
+ $("#wpstg_select_tables_cloning option:selected").each(function () {
627
+ //console.log(this);
628
+ includedTables.push(this.value);
629
  });
630
 
631
  return includedTables;
632
  };
633
  /**
634
  * Get Excluded (Unchecked) Database Tables
635
+ * Not used anymore!
636
  * @returns {Array}
637
  */
638
  var getExcludedTables = function ()
639
  {
640
  var excludedTables = [];
641
 
642
+ // $(".wpstg-db-table input:not(:checked)").each(function () {
643
+ // excludedTables.push(this.name);
644
+ // });
645
+ $("#wpstg_select_tables_cloning option:not(:selected)").each(function () {
646
  excludedTables.push(this.name);
647
  });
648
 
apps/Backend/views/clone/ajax/delete-confirmation.php CHANGED
@@ -1,7 +1,7 @@
1
  <div class="wpstg-notice-alert wpstg-failed">
2
  <h4 style="margin:0">
3
  <?php
4
- _e("Attention: Check carefully if these database tables and files are safe to delete and do not belong to your live site!", "wp-staging")
5
  ?>
6
  </h4>
7
 
1
  <div class="wpstg-notice-alert wpstg-failed">
2
  <h4 style="margin:0">
3
  <?php
4
+ _e("Attention: Check carefully if the database tables and files from the selection below are safe to delete and do not belong to your live site!", "wp-staging")
5
  ?>
6
  </h4>
7
 
apps/Backend/views/clone/ajax/scan.php CHANGED
@@ -15,7 +15,7 @@
15
  <div class="wpstg-tabs-wrapper">
16
  <a href="#" class="wpstg-tab-header active" data-id="#wpstg-scanning-db">
17
  <span class="wpstg-tab-triangle">&#9658;</span>
18
- <?php echo __( "DB Tables", "wp-staging" ) ?>
19
  </a>
20
 
21
  <div class="wpstg-tab-section" id="wpstg-scanning-db">
@@ -23,29 +23,50 @@
23
  <h4 style="margin:0">
24
  <?php
25
  echo __(
26
- "Uncheck the tables you do not want to copy. Usually you should select tables with prefix '{$scan->prefix}', only.", "wp-staging"
27
- )
 
 
 
 
 
 
28
  ?>
29
  </h4>
30
  <div style="margin-top:10px;margin-bottom:10px;">
31
- <a href="#" class="wpstg-button-unselect button"> None </a>
32
  <a href="#" class="wpstg-button-select button"> <?php _e( WPStaging\WPStaging::getTablePrefix(), 'wp-staging' ); ?> </a>
33
  </div>
 
34
  <?php
35
  foreach ( $options->tables as $table ):
36
- $attributes = in_array( $table->name, $options->excludedTables ) ? '' : "checked";
37
- $attributes .= in_array( $table->name, $options->clonedTables ) ? " disabled" : '';
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  ?>
39
- <div class="wpstg-db-table">
40
  <label>
41
- <input class="wpstg-db-table-checkboxes" type="checkbox" name="<?php echo $table->name ?>" <?php echo $attributes ?>>
42
- <?php echo $table->name ?>
43
  </label>
44
  <span class="wpstg-size-info">
45
- <?php echo $scan->formatSize( $table->size ) ?>
46
  </span>
47
- </div>
48
- <?php endforeach ?>
 
49
  <div style="margin-top:10px;">
50
  <a href="#" class="wpstg-button-unselect button"> None </a>
51
  <a href="#" class="wpstg-button-select button"> <?php _e( WPStaging\WPStaging::getTablePrefix(), 'wp-staging' ); ?> </a>
15
  <div class="wpstg-tabs-wrapper">
16
  <a href="#" class="wpstg-tab-header active" data-id="#wpstg-scanning-db">
17
  <span class="wpstg-tab-triangle">&#9658;</span>
18
+ <?php echo __( "Database Tables", "wp-staging" ) ?>
19
  </a>
20
 
21
  <div class="wpstg-tab-section" id="wpstg-scanning-db">
23
  <h4 style="margin:0">
24
  <?php
25
  echo __(
26
+ "Select the tables you like to clone. All tables beginning with prefix '{$scan->prefix}' have been selected already.", "wp-staging"
27
+ );
28
+ ?>
29
+ <p></p>
30
+ <?php
31
+ echo __(
32
+ "Select multiple tables by pressing left mouse button and moving or by pressing STRG+Left Mouse button. (Mac ⌘+Left Mouse Button)", "wp-staging"
33
+ );
34
  ?>
35
  </h4>
36
  <div style="margin-top:10px;margin-bottom:10px;">
37
+ <a href="#" class="wpstg-button-unselect button"><?php _e('Select All','wp-staging'); ?></a>
38
  <a href="#" class="wpstg-button-select button"> <?php _e( WPStaging\WPStaging::getTablePrefix(), 'wp-staging' ); ?> </a>
39
  </div>
40
+ <select multiple="multiple" id="wpstg_select_tables_cloning">
41
  <?php
42
  foreach ( $options->tables as $table ):
43
+ $attributes = !in_array( $table->name, $options->excludedTables ) && (strpos( $table->name, $db->prefix ) === 0) ? "selected='selected'" : "";
44
+ $attributes .= in_array( $table->name, $options->clonedTables ) ? "disabled" : '';
45
+ ?>
46
+ <option class="wpstg-db-table" value="<?php echo $table->name ?>" name="<?php echo $table->name ?>" <?php echo $attributes ?>>
47
+ <?php echo $table->name ?> - <?php echo $scan->formatSize( $table->size ) ?>
48
+ </option>
49
+ <?php endforeach ?>
50
+ </select>
51
+
52
+
53
+
54
+ <?php
55
+ // foreach ( $options->tables as $table ):
56
+ // $attributes = in_array( $table->name, $options->excludedTables ) ? '' : "checked";
57
+ // $attributes .= in_array( $table->name, $options->clonedTables ) ? " disabled" : '';
58
  ?>
59
+ <!-- <div class="wpstg-db-table">
60
  <label>
61
+ <input class="wpstg-db-table-checkboxes" type="checkbox" name="<?php // echo $table->name ?>" <?php // echo $attributes ?>>
62
+ <?php // echo $table->name ?>
63
  </label>
64
  <span class="wpstg-size-info">
65
+ <?php // echo $scan->formatSize( $table->size ) ?>
66
  </span>
67
+ </div>-->
68
+ <?php // endforeach ?>
69
+
70
  <div style="margin-top:10px;">
71
  <a href="#" class="wpstg-button-unselect button"> None </a>
72
  <a href="#" class="wpstg-button-select button"> <?php _e( WPStaging\WPStaging::getTablePrefix(), 'wp-staging' ); ?> </a>
apps/Backend/views/clone/includes/footer.php CHANGED
@@ -6,6 +6,7 @@
6
  <strong>&nbsp;&nbsp;FAQ</strong><br/>
7
  - <a href="https://wp-staging.com/docs/staging-site-redirects-live-site/" target="_blank" rel="external">Can not login to staging site</a> <br/>
8
  - <a href="https://wp-staging.com/docs/fix-white-or-blank-page-after-pushing-fatal-error-500/" target="_blank" rel="external">Staging site returns blank white page</a> <br/>
 
9
  - <a href="https://wp-staging.com/docs/install-wp-staging/" target="_blank" rel="external">More</a><br/><br/>
10
  <?php echo __('It still does not work?','wp-staging');?>
11
  <br>
6
  <strong>&nbsp;&nbsp;FAQ</strong><br/>
7
  - <a href="https://wp-staging.com/docs/staging-site-redirects-live-site/" target="_blank" rel="external">Can not login to staging site</a> <br/>
8
  - <a href="https://wp-staging.com/docs/fix-white-or-blank-page-after-pushing-fatal-error-500/" target="_blank" rel="external">Staging site returns blank white page</a> <br/>
9
+ - <a href="https://wp-staging.com/docs/activate-permalinks-staging-site/" target="_blank" rel="external">Activate permalinks on staging site</a> <br/>
10
  - <a href="https://wp-staging.com/docs/install-wp-staging/" target="_blank" rel="external">More</a><br/><br/>
11
  <?php echo __('It still does not work?','wp-staging');?>
12
  <br>
apps/Core/Utils/Logger.php CHANGED
@@ -79,7 +79,7 @@ class Logger
79
  }
80
 
81
  // If cache directory doesn't exists, create it
82
- if (!is_dir($this->logDir) && !@mkdir($this->logDir, 0775, true))
83
  {
84
  throw new \Exception("Failed to create log directory!");
85
  }
79
  }
80
 
81
  // If cache directory doesn't exists, create it
82
+ if (!is_dir($this->logDir) && !@mkdir($this->logDir, 0755, true))
83
  {
84
  throw new \Exception("Failed to create log directory!");
85
  }
apps/Core/WPStaging.php CHANGED
@@ -29,7 +29,7 @@ final class WPStaging {
29
  /**
30
  * Plugin version
31
  */
32
- const VERSION = "2.4.8";
33
 
34
  /**
35
  * Plugin name
29
  /**
30
  * Plugin version
31
  */
32
+ const VERSION = "2.4.9";
33
 
34
  /**
35
  * Plugin name
readme.txt CHANGED
@@ -9,7 +9,7 @@ License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
  Tags: staging, duplication, cloning, clone, migration, sandbox, test site, testing, backup, post, admin, administration, duplicate posts
10
  Requires at least: 3.6+
11
  Tested up to: 5.0
12
- Stable tag: 2.4.8
13
  Requires PHP: 5.3
14
 
15
  A duplicator plugin! Clone, duplicate and migrate live sites to independent staging and development sites that are available only to administrators.
@@ -146,6 +146,20 @@ https://wp-staging.com
146
 
147
  == Changelog ==
148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  = 2.4.8 =
150
  * Fix: Prevent throwing error when table prefix of table usermeta can not be changed
151
  * Fix: WP Staging does not run with old WordPress version 3.2
@@ -218,8 +232,8 @@ Complete changelog: [https://wp-staging.com/wp-staging-changelog](https://wp-sta
218
 
219
  == Upgrade Notice ==
220
 
221
- = 2.4.7 =
222
  * Fix: Security, prevent downloading wp staging log files by third party users from uploads folder
223
- * New: Compatible up to WordPress 5.0.2 Gutenberg
224
 
225
 
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: 5.0
12
+ Stable tag: 2.4.9
13
  Requires PHP: 5.3
14
 
15
  A duplicator plugin! Clone, duplicate and migrate live sites to independent staging and development sites that are available only to administrators.
146
 
147
  == Changelog ==
148
 
149
+ = 2.4.9 =
150
+ * New: Add new db table selection manager
151
+ * Fix: Better warning notices before updating a staging site
152
+ * Fix: Stop delete process if staging site has been deleted manually before
153
+ * Fix: Log file folder does not have correct permission 0755
154
+ * Fix: Continue cloning if siteurl & home in wp_options could not be changed
155
+ * Tweak: Better warning for update method
156
+ * Tweak: DB tables and file verification opened as default option
157
+ * Tweak: Skip rows larger than 5MB for search & replace operations to keep memory consumption low
158
+ * Tweak: clean up search & replace method
159
+ * Tweak: Better FAQ
160
+
161
+
162
+
163
  = 2.4.8 =
164
  * Fix: Prevent throwing error when table prefix of table usermeta can not be changed
165
  * Fix: WP Staging does not run with old WordPress version 3.2
232
 
233
  == Upgrade Notice ==
234
 
235
+ = 2.4.9 =
236
  * Fix: Security, prevent downloading wp staging log files by third party users from uploads folder
237
+ * New: Compatible up to WordPress 5.0.3 Gutenberg
238
 
239
 
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.4.8
11
  * Text Domain: wp-staging
12
  * Domain Path: /languages/
13
 
@@ -51,7 +51,7 @@ if( !defined( 'WPSTG_PLUGIN_URL' ) ) {
51
 
52
  // Version
53
  if( !defined( 'WPSTG_VERSION' ) ) {
54
- define( 'WPSTG_VERSION', '2.4.8' );
55
  }
56
 
57
  // Must use version of the optimizer
7
  * Author: WP-Staging
8
  * Author URI: https://wp-staging.com
9
  * Contributors: ReneHermi, ilgityildirim
10
+ * Version: 2.4.9
11
  * Text Domain: wp-staging
12
  * Domain Path: /languages/
13
 
51
 
52
  // Version
53
  if( !defined( 'WPSTG_VERSION' ) ) {
54
+ define( 'WPSTG_VERSION', '2.4.9' );
55
  }
56
 
57
  // Must use version of the optimizer