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