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

Version Description

  • New: Add filter 'wpstg_filter_options_replace' to exclude certain tables from updating while cloning
  • New: Exclude tables for plugin wp_mail_smtp
  • New: Support for custom upload folder. For instance, if upload folder has been renamed and removed outsite wp-content folder
  • New: Add datetime timestamp internally to clone. (Used in WP Staging pro)
  • New: Add filter 'wpstg_fiter_search_replace_rows' to exclude certain tables from search & replace
  • New: Supports search & replace for revslider image slider and several visual editors which are using non default serialized data
  • New: Add new setting which allow to specify the search & replace processing query limit
  • New: Compatible to WordPress 4.9.6
Download this release

Release Info

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

Code changes from version 2.2.7 to 2.2.8

apps/Backend/Modules/Jobs/Data.php CHANGED
@@ -283,7 +283,7 @@ class Data extends JobExecutable {
283
  }
284
 
285
  /**
286
- * Update Table Prefix in meta_keys
287
  * @return bool
288
  */
289
  protected function step4() {
@@ -305,17 +305,35 @@ class Data extends JobExecutable {
305
  return false;
306
  }
307
 
308
- $this->log( "Updating db prefixes in {$this->prefix}options. Error: {$this->db->last_error}" );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
 
310
  $resultUserMeta = $this->db->query(
311
  $this->db->prepare(
312
- "UPDATE {$this->prefix}options SET option_name= replace(option_name, %s, %s) WHERE option_name LIKE %s", $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
313
  )
314
  );
315
 
316
  if( !$resultUserMeta ) {
317
- $this->log( "Preparing Data Step4: Failed to update db prefixes in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_ERROR );
318
- $this->returnException( "Data Crunching Step 4: Failed to update db prefixes in {$this->prefix}options. Error: {$this->db->last_error}" );
319
  return false;
320
  }
321
 
283
  }
284
 
285
  /**
286
+ * Update Table Prefix in wp_usermeta and wp_options
287
  * @return bool
288
  */
289
  protected function step4() {
305
  return false;
306
  }
307
 
308
+ if( false === $this->isTable( $this->prefix . 'options' ) ) {
309
+ return true;
310
+ }
311
+
312
+ $this->log( "Updating db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
313
+
314
+ // Filter the rows below. Do not update them!
315
+ $filters = array(
316
+ 'wp_mail_smtp',
317
+ 'wp_mail_smtp_version',
318
+ 'wp_mail_smtp_debug',
319
+ );
320
+
321
+ $filters = apply_filters('wpstg_filter_options_replace', $filters);
322
+
323
+ $where = "";
324
+ foreach($filters as $filter){
325
+ $where .= " AND option_name <> '" . $filter . "'";
326
+ }
327
 
328
  $resultUserMeta = $this->db->query(
329
  $this->db->prepare(
330
+ "UPDATE IGNORE {$this->prefix}options SET option_name= replace(option_name, %s, %s) WHERE option_name LIKE %s" . $where, $this->db->prefix, $this->prefix, $this->db->prefix . "_%"
331
  )
332
  );
333
 
334
  if( !$resultUserMeta ) {
335
+ $this->log( "Preparing Data Step4: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}", Logger::TYPE_ERROR );
336
+ $this->returnException( "Data Crunching Step 4: Failed to update db option_names in {$this->prefix}options. Error: {$this->db->last_error}" );
337
  return false;
338
  }
339
 
apps/Backend/Modules/Jobs/Directories.php CHANGED
@@ -68,6 +68,8 @@ class Directories extends JobExecutable {
68
  return ( object ) $this->response;
69
  }
70
 
 
 
71
  /**
72
  * Step 0
73
  * Get WP Root files
@@ -125,7 +127,8 @@ class Directories extends JobExecutable {
125
  'cache',
126
  'wps-hide-login',
127
  'node_modules',
128
- 'nbproject'
 
129
  );
130
 
131
  try {
@@ -284,7 +287,7 @@ class Directories extends JobExecutable {
284
 
285
  try {
286
 
287
- // Iterate over wp-admin directory
288
  $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( $folder );
289
 
290
  // Exclude new line file names
@@ -313,7 +316,8 @@ class Directories extends JobExecutable {
313
  // Write path line
314
  foreach ( $iterator as $item ) {
315
  if( $item->isFile() ) {
316
- if( $this->write( $files, $strings->getLastElemAfterString( '/', $folder ) . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
 
317
  $this->options->totalFiles++;
318
  // Add current file size
319
  $this->options->totalFileSize += $iterator->getSize();
68
  return ( object ) $this->response;
69
  }
70
 
71
+
72
+
73
  /**
74
  * Step 0
75
  * Get WP Root files
127
  'cache',
128
  'wps-hide-login',
129
  'node_modules',
130
+ 'nbproject',
131
+ '.idea'
132
  );
133
 
134
  try {
287
 
288
  try {
289
 
290
+ // Iterate over extra directory
291
  $iterator = new \WPStaging\Iterators\RecursiveDirectoryIterator( $folder );
292
 
293
  // Exclude new line file names
316
  // Write path line
317
  foreach ( $iterator as $item ) {
318
  if( $item->isFile() ) {
319
+ //if( $this->write( $files, $strings->getLastElemAfterString( '/', $folder ) . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
320
+ if( $this->write( $files, str_replace( ABSPATH, '', $folder ) . DIRECTORY_SEPARATOR . $iterator->getSubPathName() . PHP_EOL ) ) {
321
  $this->options->totalFiles++;
322
  // Add current file size
323
  $this->options->totalFileSize += $iterator->getSize();
apps/Backend/Modules/Jobs/Finish.php CHANGED
@@ -78,7 +78,9 @@ class Finish extends Job
78
  // Clone data already exists
79
  if (isset($this->options->existingClones[$this->options->clone]))
80
  {
81
- $this->log("Finish: Clone data already exists, no need to update, the job finished");
 
 
82
  return true;
83
  }
84
 
@@ -96,6 +98,7 @@ class Finish extends Job
96
  "version" => \WPStaging\WPStaging::VERSION,
97
  "status" => false,
98
  "prefix" => $this->options->prefix,
 
99
  );
100
 
101
  if (false === update_option("wpstg_existing_clones_beta", $this->options->existingClones))
78
  // Clone data already exists
79
  if (isset($this->options->existingClones[$this->options->clone]))
80
  {
81
+ $this->options->existingClones[$this->options->clone]['datetime'] = time();
82
+ update_option("wpstg_existing_clones_beta", $this->options->existingClones);
83
+ $this->log("Finish: The job finished!");
84
  return true;
85
  }
86
 
98
  "version" => \WPStaging\WPStaging::VERSION,
99
  "status" => false,
100
  "prefix" => $this->options->prefix,
101
+ "datetime" => time()
102
  );
103
 
104
  if (false === update_option("wpstg_existing_clones_beta", $this->options->existingClones))
apps/Backend/Modules/Jobs/Job.php CHANGED
@@ -126,6 +126,7 @@ abstract class Job implements JobInterface
126
  // check default options
127
  if ( !isset($this->settings) ||
128
  !isset($this->settings->queryLimit) ||
 
129
  !isset($this->settings->batchSize) ||
130
  !isset($this->settings->cpuLoad) ||
131
  !isset($this->settings->fileLimit)
@@ -176,6 +177,7 @@ abstract class Job implements JobInterface
176
  */
177
  protected function setDefaultSettings(){
178
  $this->settings->queryLimit = "5000";
 
179
  $this->settings->fileLimit = "1";
180
  $this->settings->batchSize = "2";
181
  $this->settings->cpuLoad = 'medium';
126
  // check default options
127
  if ( !isset($this->settings) ||
128
  !isset($this->settings->queryLimit) ||
129
+ !isset($this->settings->querySRLimit) ||
130
  !isset($this->settings->batchSize) ||
131
  !isset($this->settings->cpuLoad) ||
132
  !isset($this->settings->fileLimit)
177
  */
178
  protected function setDefaultSettings(){
179
  $this->settings->queryLimit = "5000";
180
+ $this->settings->querySRLimit = "5000";
181
  $this->settings->fileLimit = "1";
182
  $this->settings->batchSize = "2";
183
  $this->settings->cpuLoad = 'medium';
apps/Backend/Modules/Jobs/Scan.php CHANGED
@@ -45,6 +45,7 @@ class Scan extends Job
45
  $this->prefix = $this->db->prefix;
46
 
47
 
 
48
  }
49
 
50
  /**
@@ -89,8 +90,6 @@ class Scan extends Job
89
  // Delete previous cached files
90
  $this->cache->delete("files_to_copy");
91
  $this->cache->delete("clone_options");
92
- //$this->cache->delete("files_to_verify");
93
- //$this->cache->delete("files_verified");
94
 
95
  // Save options
96
  $this->saveOptions();
@@ -98,6 +97,25 @@ class Scan extends Job
98
  return $this;
99
  }
100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  /**
102
  * Format bytes into human readable form
103
  * @param int $bytes
@@ -301,8 +319,11 @@ class Scan extends Job
301
  // Gather Themes
302
  $this->getSubDirectories(WP_CONTENT_DIR . DIRECTORY_SEPARATOR . "themes");
303
 
304
- // Gather Uploads
305
- $this->getSubDirectories(WP_CONTENT_DIR . DIRECTORY_SEPARATOR . "uploads");
 
 
 
306
  }
307
 
308
  /**
@@ -360,7 +381,6 @@ class Scan extends Job
360
  $directoryArray = explode(DIRECTORY_SEPARATOR, $path);
361
  $total = is_array($directoryArray) || $directoryArray instanceof Countable ? count($directoryArray) : 0;
362
 
363
-
364
  if ($total < 1)
365
  {
366
  return;
45
  $this->prefix = $this->db->prefix;
46
 
47
 
48
+
49
  }
50
 
51
  /**
90
  // Delete previous cached files
91
  $this->cache->delete("files_to_copy");
92
  $this->cache->delete("clone_options");
 
 
93
 
94
  // Save options
95
  $this->saveOptions();
97
  return $this;
98
  }
99
 
100
+ /**
101
+ * Get relative WP uploads path
102
+ * @return string
103
+ */
104
+ protected function getUploadDir(){
105
+ $uploads = wp_upload_dir();
106
+ return $uploads['basedir'];
107
+ }
108
+
109
+ /**
110
+ * Get WP media folder
111
+ *
112
+ * @return string
113
+ */
114
+ // protected function getUploadFolder(){
115
+ // $uploads = wp_upload_dir();
116
+ // return wp_basedir($uploads['baseurl']);
117
+ // }
118
+
119
  /**
120
  * Format bytes into human readable form
121
  * @param int $bytes
319
  // Gather Themes
320
  $this->getSubDirectories(WP_CONTENT_DIR . DIRECTORY_SEPARATOR . "themes");
321
 
322
+ // Gather Default Uploads Folder
323
+ //$this->getSubDirectories(WP_CONTENT_DIR . DIRECTORY_SEPARATOR . "uploads");
324
+
325
+ // Gather Custom Uploads Folder if there is one
326
+ //$this->getSubDirectories( $this->getUploadDir() );
327
  }
328
 
329
  /**
381
  $directoryArray = explode(DIRECTORY_SEPARATOR, $path);
382
  $total = is_array($directoryArray) || $directoryArray instanceof Countable ? count($directoryArray) : 0;
383
 
 
384
  if ($total < 1)
385
  {
386
  return;
apps/Backend/Modules/Jobs/SearchReplace.php CHANGED
@@ -27,14 +27,12 @@ class SearchReplace extends JobExecutable {
27
  */
28
  public $db;
29
 
30
-
31
  /**
32
  *
33
  * @var string
34
  */
35
  private $homeUrl;
36
 
37
-
38
  /**
39
  * The prefix of the new database tables which are used for the live site after updating tables
40
  * @var string
@@ -55,7 +53,7 @@ class SearchReplace extends JobExecutable {
55
 
56
  public function start() {
57
  // Skip job. Nothing to do
58
- if ($this->options->totalSteps === 0){
59
  $this->prepareResponse( true, false );
60
  }
61
 
@@ -167,7 +165,7 @@ class SearchReplace extends JobExecutable {
167
  * @param string $old
168
  */
169
  private function startReplace( $new ) {
170
- $rows = $this->options->job->start + $this->settings->queryLimit;
171
  $this->log(
172
  "DB Processing: Table {$new} {$this->options->job->start} to {$rows} records"
173
  );
@@ -176,7 +174,7 @@ class SearchReplace extends JobExecutable {
176
  $this->searchReplace( $new, $rows, array() );
177
 
178
  // Set new offset
179
- $this->options->job->start += $this->settings->queryLimit;
180
  }
181
 
182
  /**
@@ -187,7 +185,7 @@ class SearchReplace extends JobExecutable {
187
  private function get_pages_in_table( $table ) {
188
  $table = esc_sql( $table );
189
  $rows = $this->db->get_var( "SELECT COUNT(*) FROM $table" );
190
- $pages = ceil( $rows / $this->settings->queryLimit );
191
  return absint( $pages );
192
  }
193
 
@@ -231,31 +229,35 @@ class SearchReplace extends JobExecutable {
231
 
232
  // Load up the default settings for this chunk.
233
  $table = esc_sql( $table );
234
- $current_page = $this->options->job->start + $this->settings->queryLimit;
235
  $pages = $this->get_pages_in_table( $table );
236
  //$done = false;
237
 
238
 
239
  if( $this->isSubDir() ) {
240
- //$homeUrl = rtrim($this->homeUrl, "/") . $this->getSubDir() . $this->options->cloneDirectoryName;
241
  // Search URL example.com/staging and root path to staging site /var/www/htdocs/staging
242
  $args['search_for'] = array(
243
- rtrim( $this->homeUrl, "/" ) . $this->getSubDir(),
244
- ABSPATH
 
 
245
  );
246
 
247
  $args['replace_with'] = array(
248
  rtrim( $this->homeUrl, "/" ) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName,
249
- rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName
 
250
  );
251
  } else {
252
  $args['search_for'] = array(
253
  rtrim( $this->homeUrl, '/' ),
254
- ABSPATH
 
255
  );
256
  $args['replace_with'] = array(
257
  rtrim( $this->homeUrl, '/' ) . '/' . $this->options->cloneDirectoryName,
258
- rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName
 
259
  );
260
  }
261
 
@@ -279,16 +281,34 @@ class SearchReplace extends JobExecutable {
279
  list( $primary_key, $columns ) = $this->get_columns( $table );
280
 
281
  // Bail out early if there isn't a primary key.
282
- if( null === $primary_key ) {
283
- return false;
284
- }
 
 
 
 
285
 
286
  $current_row = 0;
287
  $start = $this->options->job->start;
288
- $end = $this->settings->queryLimit;
289
 
290
  // Grab the content of the table.
291
  $data = $this->db->get_results( "SELECT * FROM $table LIMIT $start, $end", ARRAY_A );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
 
293
  // Loop through the data.
294
  foreach ( $data as $row ) {
@@ -297,6 +317,16 @@ class SearchReplace extends JobExecutable {
297
  $where_sql = array();
298
  $upd = false;
299
 
 
 
 
 
 
 
 
 
 
 
300
  foreach ( $columns as $column ) {
301
 
302
  $dataRow = $row[$column];
@@ -363,10 +393,10 @@ class SearchReplace extends JobExecutable {
363
  }
364
  } // end row loop
365
  unset( $row );
 
 
 
366
 
367
- // if( $current_page >= $pages - 1 ) {
368
- // $done = true;
369
- // }
370
 
371
  // DB Flush
372
  $this->db->flush();
27
  */
28
  public $db;
29
 
 
30
  /**
31
  *
32
  * @var string
33
  */
34
  private $homeUrl;
35
 
 
36
  /**
37
  * The prefix of the new database tables which are used for the live site after updating tables
38
  * @var string
53
 
54
  public function start() {
55
  // Skip job. Nothing to do
56
+ if( $this->options->totalSteps === 0 ) {
57
  $this->prepareResponse( true, false );
58
  }
59
 
165
  * @param string $old
166
  */
167
  private function startReplace( $new ) {
168
+ $rows = $this->options->job->start + $this->settings->querySRLimit;
169
  $this->log(
170
  "DB Processing: Table {$new} {$this->options->job->start} to {$rows} records"
171
  );
174
  $this->searchReplace( $new, $rows, array() );
175
 
176
  // Set new offset
177
+ $this->options->job->start += $this->settings->querySRLimit;
178
  }
179
 
180
  /**
185
  private function get_pages_in_table( $table ) {
186
  $table = esc_sql( $table );
187
  $rows = $this->db->get_var( "SELECT COUNT(*) FROM $table" );
188
+ $pages = ceil( $rows / $this->settings->querySRLimit );
189
  return absint( $pages );
190
  }
191
 
229
 
230
  // Load up the default settings for this chunk.
231
  $table = esc_sql( $table );
232
+ $current_page = $this->options->job->start + $this->settings->querySRLimit;
233
  $pages = $this->get_pages_in_table( $table );
234
  //$done = false;
235
 
236
 
237
  if( $this->isSubDir() ) {
 
238
  // Search URL example.com/staging and root path to staging site /var/www/htdocs/staging
239
  $args['search_for'] = array(
240
+ rtrim( $this->homeUrl, "/" ) . $this->getSubDir(),
241
+ ABSPATH,
242
+ str_replace('/', '\/', rtrim( $this->homeUrl, '/' )) . str_replace('/', '\/', $this->getSubDir()) // // Used by revslider and several visual editors
243
+
244
  );
245
 
246
  $args['replace_with'] = array(
247
  rtrim( $this->homeUrl, "/" ) . $this->getSubDir() . '/' . $this->options->cloneDirectoryName,
248
+ rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName,
249
+ str_replace('/', '\/', rtrim( $this->homeUrl, "/" )) . str_replace('/', '\/', $this->getSubDir()) . '\/' . $this->options->cloneDirectoryName, // Used by revslider and several visual editors
250
  );
251
  } else {
252
  $args['search_for'] = array(
253
  rtrim( $this->homeUrl, '/' ),
254
+ ABSPATH,
255
+ str_replace('/', '\/' , rtrim( $this->homeUrl, '/' ))
256
  );
257
  $args['replace_with'] = array(
258
  rtrim( $this->homeUrl, '/' ) . '/' . $this->options->cloneDirectoryName,
259
+ rtrim( ABSPATH, '/' ) . '/' . $this->options->cloneDirectoryName,
260
+ str_replace('/', '\/', rtrim( $this->homeUrl, '/' )) . '\/' . $this->options->cloneDirectoryName,
261
  );
262
  }
263
 
281
  list( $primary_key, $columns ) = $this->get_columns( $table );
282
 
283
  // Bail out early if there isn't a primary key.
284
+ // We commented this to search & replace through tables which have no primary keys like wp_revslider_slides
285
+ // @todo test this carefully. If it causes (performance) issues we need to activate it again!
286
+ // @since 2.4.4
287
+
288
+ // if( null === $primary_key ) {
289
+ // return false;
290
+ // }
291
 
292
  $current_row = 0;
293
  $start = $this->options->job->start;
294
+ $end = $this->settings->querySRLimit;
295
 
296
  // Grab the content of the table.
297
  $data = $this->db->get_results( "SELECT * FROM $table LIMIT $start, $end", ARRAY_A );
298
+
299
+ // Filter certain rows (of other plugins)
300
+ $filter = array(
301
+ 'Admin_custome_login_Slidshow',
302
+ 'Admin_custome_login_Social',
303
+ 'Admin_custome_login_logo',
304
+ 'Admin_custome_login_text',
305
+ 'Admin_custome_login_login',
306
+ 'Admin_custome_login_top',
307
+ 'Admin_custome_login_dashboard',
308
+ 'Admin_custome_login_Version',
309
+ );
310
+
311
+ apply_filters('wpstg_fiter_search_replace_rows', $filter);
312
 
313
  // Loop through the data.
314
  foreach ( $data as $row ) {
317
  $where_sql = array();
318
  $upd = false;
319
 
320
+ // Skip rows below
321
+ if (isset($row['option_name']) && in_array($row['option_name'], $filter)){
322
+ continue;
323
+ }
324
+
325
+ // Skip rows with transients (They can store huge data and we need to save memory)
326
+ if( isset( $row['option_name'] ) && strpos( $row['option_name'], '_transient' ) === 0 ) {
327
+ continue;
328
+ }
329
+
330
  foreach ( $columns as $column ) {
331
 
332
  $dataRow = $row[$column];
393
  }
394
  } // end row loop
395
  unset( $row );
396
+ unset( $update_sql );
397
+ unset( $where_sql );
398
+ unset( $sql );
399
 
 
 
 
400
 
401
  // DB Flush
402
  $this->db->flush();
apps/Backend/Modules/Views/Forms/Settings.php CHANGED
@@ -61,6 +61,20 @@ class Settings {
61
  $element->setLabel( "DB Copy Query Limit" )
62
  ->setDefault( isset( $settings->queryLimit ) ? $settings->queryLimit : 5000 )
63
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
  $options = array('1' => '1', '10' => '10', '50' => '50', '250' => '250', '500' => '500', '1000' => '1000');
66
  // DB Copy Query Limit
61
  $element->setLabel( "DB Copy Query Limit" )
62
  ->setDefault( isset( $settings->queryLimit ) ? $settings->queryLimit : 5000 )
63
  );
64
+ // DB Search & Replace Query Limit
65
+ $element = new Numerical(
66
+ "wpstg_settings[querySRLimit]", array(
67
+ "class" => "medium-text",
68
+ "step" => 1,
69
+ "max" => 999999,
70
+ "min" => 0
71
+ )
72
+ );
73
+
74
+ $this->form["general"]->add(
75
+ $element->setLabel( "DB Search & Replace Limit" )
76
+ ->setDefault( isset( $settings->querySRLimit ) ? $settings->querySRLimit : 5000 )
77
+ );
78
 
79
  $options = array('1' => '1', '10' => '10', '50' => '50', '250' => '250', '500' => '500', '1000' => '1000');
80
  // DB Copy Query Limit
apps/Backend/views/settings/index.php CHANGED
@@ -106,6 +106,25 @@
106
  <?php echo $form->render( "wpstg_settings[queryLimit]" ) ?>
107
  </td>
108
  </tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
  <tr class="row">
111
  <td class="row th">
@@ -118,6 +137,10 @@
118
  The higher the value the faster the file copy process.
119
  To find out the highest possible values try a high value like 500 or more. If you get timeout issues, lower it
120
  until you get no more errors during copying process.
 
 
 
 
121
  <br>
122
  <strong> Default: 1 </strong>
123
  </span>
106
  <?php echo $form->render( "wpstg_settings[queryLimit]" ) ?>
107
  </td>
108
  </tr>
109
+ <tr class="row">
110
+ <td class="row th">
111
+ <div class="col-title">
112
+ <?php
113
+ echo $form->label("wpstg_settings[querySRLimit]")
114
+ ?>
115
+ <span class="description">
116
+ Number of DB rows, that will be processed within one ajax request.
117
+ The higher the value the faster the database search & replace process.
118
+ This is a high memory consumptive process. If you get timeouts lower this value!
119
+ <br>
120
+ <strong> Default: 5000 </strong>
121
+ </span>
122
+ </div>
123
+ </td>
124
+ <td>
125
+ <?php echo $form->render("wpstg_settings[querySRLimit]")?>
126
+ </td>
127
+ </tr>
128
 
129
  <tr class="row">
130
  <td class="row th">
137
  The higher the value the faster the file copy process.
138
  To find out the highest possible values try a high value like 500 or more. If you get timeout issues, lower it
139
  until you get no more errors during copying process.
140
+ <br>
141
+ <br>
142
+ <strong>Important:</strong> If CPU Load Priority is Low try a file copy limit value of 10 or higher. Otherwise file copying process takes a lot of time.
143
+ <br>
144
  <br>
145
  <strong> Default: 1 </strong>
146
  </span>
apps/Core/WPStaging.php CHANGED
@@ -29,7 +29,7 @@ final class WPStaging {
29
  /**
30
  * Plugin version
31
  */
32
- const VERSION = "2.2.7";
33
 
34
  /**
35
  * Plugin name
@@ -44,7 +44,7 @@ final class WPStaging {
44
  /**
45
  * Compatible WP Version
46
  */
47
- const WP_COMPATIBLE = "4.9.5";
48
 
49
  /**
50
  * Slug: Either wp-staging or wp-staging-pro
29
  /**
30
  * Plugin version
31
  */
32
+ const VERSION = "2.2.8";
33
 
34
  /**
35
  * Plugin name
44
  /**
45
  * Compatible WP Version
46
  */
47
+ const WP_COMPATIBLE = "4.9.6";
48
 
49
  /**
50
  * Slug: Either wp-staging or wp-staging-pro
readme.txt CHANGED
@@ -9,7 +9,7 @@ License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
  Tags: staging, duplication, cloning, clone, migration, sandbox, test site, testing, backup, post, admin, administration, duplicate posts
10
  Requires at least: 3.6+
11
  Tested up to: 4.9
12
- Stable tag: 2.2.7
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,17 @@ https://wp-staging.com
146
 
147
  == Changelog ==
148
 
 
 
 
 
 
 
 
 
 
 
 
149
  = 2.2.7 =
150
  * Fix: Serialize replace is not working properly for serialized strings
151
  * Fix: WP_SITEURL & WP_HOME not replaced if constant contains php generated string
@@ -247,6 +258,6 @@ Complete changelog: [https://wp-staging.com/changelog.txt](https://wp-staging.co
247
 
248
  == Upgrade Notice ==
249
 
250
- = 2.2.7 =
251
- * Fix: Serialize replace is not working properly for serialized strings
252
- * Fix: WP_SITEURL & WP_HOME not replaced if constant contains php generated string
9
  Tags: staging, duplication, cloning, clone, migration, sandbox, test site, testing, backup, post, admin, administration, duplicate posts
10
  Requires at least: 3.6+
11
  Tested up to: 4.9
12
+ Stable tag: 2.2.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
 
147
  == Changelog ==
148
 
149
+ = 2.2.8 =
150
+ * New: Add filter 'wpstg_filter_options_replace' to exclude certain tables from updating while cloning
151
+ * New: Exclude tables for plugin wp_mail_smtp
152
+ * New: Support for custom upload folder. For instance, if upload folder has been renamed and removed outsite wp-content folder
153
+ * New: Add datetime timestamp internally to clone. (Used in WP Staging pro)
154
+ * New: Add filter 'wpstg_fiter_search_replace_rows' to exclude certain tables from search & replace
155
+ * New: Supports search & replace for revslider image slider and several visual editors which are using non default serialized data
156
+ * New: Add new setting which allow to specify the search & replace processing query limit
157
+ * New: Compatible to WordPress 4.9.6
158
+
159
+
160
  = 2.2.7 =
161
  * Fix: Serialize replace is not working properly for serialized strings
162
  * Fix: WP_SITEURL & WP_HOME not replaced if constant contains php generated string
258
 
259
  == Upgrade Notice ==
260
 
261
+ = 2.2.8 =
262
+ * New: Compatible to WordPress 4.9.6
263
+
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.2.7
11
  * Text Domain: wpstg
12
  * Domain Path: /languages/
13
 
7
  * Author: WP-Staging
8
  * Author URI: https://wp-staging.com
9
  * Contributors: ReneHermi, ilgityildirim
10
+ * Version: 2.2.8
11
  * Text Domain: wpstg
12
  * Domain Path: /languages/
13