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

Version Description

  • New: Support up to WordPress 5.9.1
  • New: Add filter wpstg.frontend.showLoginForm to allow third party plugin disabling login form for the staging site #1577
  • New: Add labels to distinguish between network and single site clones on multisite
  • Fix: Handle issue when showing staging sites in System Info #1560
  • Fix: Fix Rows Generator for zero or negative values for Primary Key Index #1584
  • Fix: Set option "Keep permalinks" on the staging site when updating a staging site if "keep permalinks" is active on the production site initially #1562
  • Fix: Updating an existing multisite clone converted the clone to a single site #1565 #1589
Download this release

Release Info

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

Code changes from version 2.9.5 to 2.9.6

Backend/Modules/Jobs/SearchReplace.php CHANGED
@@ -333,6 +333,7 @@ class SearchReplace extends CloningProcess
333
  if (defined('WPSTG_DISABLE_SEARCH_REPLACE_GENERATOR') && WPSTG_DISABLE_SEARCH_REPLACE_GENERATOR) {
334
  $data = $this->stagingDb->get_results("SELECT * FROM $table LIMIT $offset, $limit", ARRAY_A);
335
  } else {
 
336
  $data = $this->rowsGenerator($table, $offset, $limit, $this->stagingDb);
337
  }
338
 
@@ -550,7 +551,7 @@ class SearchReplace extends CloningProcess
550
  protected function finishStep()
551
  {
552
  // This job is not finished yet
553
- if ($this->options->job->total > $this->options->job->start) {
554
  return false;
555
  }
556
 
@@ -579,9 +580,10 @@ class SearchReplace extends CloningProcess
579
  $this->processed = absint($processed);
580
 
581
  // If it is a numeric primary key table execution,
582
- // Use last fetched primary key value for the next request
583
  if ($this->executeNumericPrimaryKeyQuery && $this->lastFetchedPrimaryKeyValue !== false) {
584
- $this->options->job->start = $this->lastFetchedPrimaryKeyValue;
 
585
  return;
586
  }
587
 
333
  if (defined('WPSTG_DISABLE_SEARCH_REPLACE_GENERATOR') && WPSTG_DISABLE_SEARCH_REPLACE_GENERATOR) {
334
  $data = $this->stagingDb->get_results("SELECT * FROM $table LIMIT $offset, $limit", ARRAY_A);
335
  } else {
336
+ $this->lastFetchedPrimaryKeyValue = property_exists($this->options->job, 'lastProcessedId') ? $this->options->job->lastProcessedId : false;
337
  $data = $this->rowsGenerator($table, $offset, $limit, $this->stagingDb);
338
  }
339
 
551
  protected function finishStep()
552
  {
553
  // This job is not finished yet
554
+ if (!$this->noResultRows && ($this->options->job->total > $this->options->job->start)) {
555
  return false;
556
  }
557
 
580
  $this->processed = absint($processed);
581
 
582
  // If it is a numeric primary key table execution,
583
+ // Save the last processed primary key value for the next request
584
  if ($this->executeNumericPrimaryKeyQuery && $this->lastFetchedPrimaryKeyValue !== false) {
585
+ $this->options->job->lastProcessedId = $this->lastFetchedPrimaryKeyValue;
586
+ $this->options->job->start += $this->processed;
587
  return;
588
  }
589
 
Backend/Modules/Jobs/Updating.php CHANGED
@@ -135,6 +135,8 @@ class Updating extends Job
135
  $this->options->uploadsSymlinked = isset($this->options->existingClones[strtolower($this->options->clone)]['uploadsSymlinked']) ? $this->options->existingClones[strtolower($this->options->clone)]['uploadsSymlinked'] : false;
136
  $this->options->prefix = $this->options->existingClones[$this->options->clone]['prefix'];
137
  $this->options->emailsAllowed = $this->options->existingClones[$this->options->clone]['emailsAllowed'];
 
 
138
  //$this->options->prefix = $this->getStagingPrefix();
139
  $helper = new Helper();
140
  $this->options->homeHostname = $helper->getHomeUrlWithoutScheme();
135
  $this->options->uploadsSymlinked = isset($this->options->existingClones[strtolower($this->options->clone)]['uploadsSymlinked']) ? $this->options->existingClones[strtolower($this->options->clone)]['uploadsSymlinked'] : false;
136
  $this->options->prefix = $this->options->existingClones[$this->options->clone]['prefix'];
137
  $this->options->emailsAllowed = $this->options->existingClones[$this->options->clone]['emailsAllowed'];
138
+ $this->options->networkClone = isset($this->options->existingClones[strtolower($this->options->clone)]['networkClone']) ? $this->options->existingClones[$this->options->clone]['networkClone'] : false;
139
+ $this->options->networkClone = filter_var($this->options->networkClone, FILTER_VALIDATE_BOOLEAN);
140
  //$this->options->prefix = $this->getStagingPrefix();
141
  $helper = new Helper();
142
  $this->options->homeHostname = $helper->getHomeUrlWithoutScheme();
Backend/Modules/SystemInfo.php CHANGED
@@ -183,17 +183,20 @@ class SystemInfo
183
  // old name wpstg_existing_clones_beta
184
  // New name since version 4.0.3 wpstg_staging_sites
185
  $stagingSites = get_option(Sites::STAGING_SITES_OPTION, []);
186
- foreach ($stagingSites as $key => $clone) {
187
- $path = !empty($clone['path']) ? $clone['path'] : 'undefined';
188
-
189
- $output .= $this->info("Number:", isset($clone['number']) ? $clone['number'] : 'undefined');
190
- $output .= $this->info("directoryName:", isset($clone['directoryName']) ? $clone['directoryName'] : 'undefined');
191
- $output .= $this->info("Path:", $path);
192
- $output .= $this->info("URL:", isset($clone['url']) ? $clone['url'] : 'undefined');
193
- $output .= $this->info("DB Prefix:", isset($clone['prefix']) ? $clone['prefix'] : 'undefined');
194
- $output .= $this->info("DB Prefix wp-config.php:", $this->getStagingPrefix($clone));
195
- $output .= $this->info("WP Staging Version:", isset($clone['version']) ? $clone['version'] : 'undefined');
196
- $output .= $this->info("WP Version:", $this->getStagingWpVersion($path)) . PHP_EOL . PHP_EOL;
 
 
 
197
  }
198
 
199
  $output .= $this->info(Sites::STAGING_SITES_OPTION . ": ", serialize(get_option(Sites::STAGING_SITES_OPTION, [])));
183
  // old name wpstg_existing_clones_beta
184
  // New name since version 4.0.3 wpstg_staging_sites
185
  $stagingSites = get_option(Sites::STAGING_SITES_OPTION, []);
186
+ // make sure $stagingSites is an array
187
+ if (is_array($stagingSites)) {
188
+ foreach ($stagingSites as $key => $clone) {
189
+ $path = !empty($clone['path']) ? $clone['path'] : 'undefined';
190
+
191
+ $output .= $this->info("Number:", isset($clone['number']) ? $clone['number'] : 'undefined');
192
+ $output .= $this->info("directoryName:", isset($clone['directoryName']) ? $clone['directoryName'] : 'undefined');
193
+ $output .= $this->info("Path:", $path);
194
+ $output .= $this->info("URL:", isset($clone['url']) ? $clone['url'] : 'undefined');
195
+ $output .= $this->info("DB Prefix:", isset($clone['prefix']) ? $clone['prefix'] : 'undefined');
196
+ $output .= $this->info("DB Prefix wp-config.php:", $this->getStagingPrefix($clone));
197
+ $output .= $this->info("WP Staging Version:", isset($clone['version']) ? $clone['version'] : 'undefined');
198
+ $output .= $this->info("WP Version:", $this->getStagingWpVersion($path)) . PHP_EOL . PHP_EOL;
199
+ }
200
  }
201
 
202
  $output .= $this->info(Sites::STAGING_SITES_OPTION . ": ", serialize(get_option(Sites::STAGING_SITES_OPTION, [])));
Backend/views/backup/listing-single-backup.php CHANGED
@@ -36,7 +36,7 @@ if (defined('WPSTG_DOWNLOAD_BACKUP_USING_PHP') && WPSTG_DOWNLOAD_BACKUP_USING_PH
36
  <?php echo esc_html($name); ?>
37
  </span>
38
  <div class="wpstg-clone-labels">
39
- <span class="wpstg-clone-label"><?php echo $backup->type === 'single' ? 'Single Site' : 'Multisite' ?></span>
40
  </div>
41
  <div class="wpstg-clone-actions">
42
  <div class="wpstg-dropdown wpstg-action-dropdown">
36
  <?php echo esc_html($name); ?>
37
  </span>
38
  <div class="wpstg-clone-labels">
39
+ <span class="wpstg-clone-label"><?php echo $backup->type === 'single' ? __('Single Site', 'wp-staging') : __('Multisite', 'wp-staging') ?></span>
40
  </div>
41
  <div class="wpstg-clone-actions">
42
  <div class="wpstg-dropdown wpstg-action-dropdown">
Backend/views/clone/ajax/single-overview.php CHANGED
@@ -26,6 +26,11 @@
26
  <a href="<?php echo $urlLogin ?>" class="wpstg-clone-title" target="_blank">
27
  <?php echo isset($data["cloneName"]) ? $data["cloneName"] : $data["directoryName"]; ?>
28
  </a>
 
 
 
 
 
29
  <div class="wpstg-clone-actions">
30
  <div class="wpstg-dropdown wpstg-action-dropdown">
31
  <a href="#" class="wpstg-dropdown-toggler transparent">
26
  <a href="<?php echo $urlLogin ?>" class="wpstg-clone-title" target="_blank">
27
  <?php echo isset($data["cloneName"]) ? $data["cloneName"] : $data["directoryName"]; ?>
28
  </a>
29
+ <?php if (is_multisite()) { ?>
30
+ <div class="wpstg-clone-labels">
31
+ <span class="wpstg-clone-label"><?php echo $data['networkClone'] ? __('Network', 'wp-staging') : __('Site', 'wp-staging') ?></span>
32
+ </div>
33
+ <?php } ?>
34
  <div class="wpstg-clone-actions">
35
  <div class="wpstg-dropdown wpstg-action-dropdown">
36
  <a href="#" class="wpstg-dropdown-toggler transparent">
Core/DTO/Settings.php CHANGED
@@ -153,6 +153,22 @@ class Settings
153
  $this->queryLimit = $queryLimit;
154
  }
155
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  /**
157
  * @return int
158
  */
153
  $this->queryLimit = $queryLimit;
154
  }
155
 
156
+ /**
157
+ * @return int
158
+ */
159
+ public function getQuerySRLimit()
160
+ {
161
+ return $this->querySRLimit;
162
+ }
163
+
164
+ /**
165
+ * @param int $querySRLimit
166
+ */
167
+ public function setQuerySRLimit($querySRLimit)
168
+ {
169
+ $this->querySRLimit = $querySRLimit;
170
+ }
171
+
172
  /**
173
  * @return int
174
  */
Framework/CloningProcess/Data/CloningService.php CHANGED
@@ -94,7 +94,7 @@ abstract class CloningService
94
  }
95
 
96
  /**
97
- * Undocumented function
98
  *
99
  * @return boolean
100
  */
94
  }
95
 
96
  /**
97
+ * Is the current clone network?
98
  *
99
  * @return boolean
100
  */
Framework/CloningProcess/Data/UpdateStagingOptionsTable.php CHANGED
@@ -78,6 +78,14 @@ class UpdateStagingOptionsTable extends DBCloningService
78
  if (!$this->keepPermalinks()) {
79
  $updateOrInsert['rewrite_rules'] = null;
80
  $updateOrInsert['permalink_structure'] = ' ';
 
 
 
 
 
 
 
 
81
  }
82
 
83
  $freemiusHelper = new FreemiusScript();
78
  if (!$this->keepPermalinks()) {
79
  $updateOrInsert['rewrite_rules'] = null;
80
  $updateOrInsert['permalink_structure'] = ' ';
81
+ } else {
82
+ /**
83
+ * if staging site is created with keep permalinks setting off,
84
+ * The below code make sure permalinks settings are kept during update,
85
+ * when later production site has keep permalinks setting on,
86
+ * without the need to also keep permalinks setting on staging site too.
87
+ */
88
+ $updateOrInsert['wpstg_rmpermalinks_executed'] = 'true';
89
  }
90
 
91
  $freemiusHelper = new FreemiusScript();
Framework/CloningProcess/Data/UpdateWpConfigConstants.php CHANGED
@@ -35,13 +35,13 @@ class UpdateWpConfigConstants extends FileCloningService
35
  $replaceOrAdd['DB_NAME'] = sprintf("'%s'", $this->escapeSingleQuotes($this->dto->getExternalDatabaseName()));
36
  }
37
 
 
38
  if ($this->isNetworkClone()) {
39
  $replaceOrAdd['DOMAIN_CURRENT_SITE'] = sprintf("'%s'", $this->escapeSingleQuotes($this->dto->getStagingSiteDomain()));
40
  $replaceOrAdd['PATH_CURRENT_SITE'] = sprintf("'%s'", trailingslashit($this->escapeSingleQuotes($this->dto->getStagingSitePath())));
41
- }
42
-
43
- $replaceOrSkip = [];
44
- if (!$this->isNetworkClone()) {
45
  //It's OK to attempt replacing multi-site constants even in single-site jobs as they will not be present in a single-site wp-config.php
46
  $replaceOrSkip["WP_ALLOW_MULTISITE"] = 'false';
47
  $replaceOrSkip["MULTISITE"] = 'false';
35
  $replaceOrAdd['DB_NAME'] = sprintf("'%s'", $this->escapeSingleQuotes($this->dto->getExternalDatabaseName()));
36
  }
37
 
38
+ $replaceOrSkip = [];
39
  if ($this->isNetworkClone()) {
40
  $replaceOrAdd['DOMAIN_CURRENT_SITE'] = sprintf("'%s'", $this->escapeSingleQuotes($this->dto->getStagingSiteDomain()));
41
  $replaceOrAdd['PATH_CURRENT_SITE'] = sprintf("'%s'", trailingslashit($this->escapeSingleQuotes($this->dto->getStagingSitePath())));
42
+ $replaceOrSkip["WP_ALLOW_MULTISITE"] = 'true';
43
+ $replaceOrSkip["MULTISITE"] = 'true';
44
+ } else {
 
45
  //It's OK to attempt replacing multi-site constants even in single-site jobs as they will not be present in a single-site wp-config.php
46
  $replaceOrSkip["WP_ALLOW_MULTISITE"] = 'false';
47
  $replaceOrSkip["MULTISITE"] = 'false';
Framework/Traits/DbRowsGeneratorTrait.php CHANGED
@@ -31,6 +31,9 @@ trait DbRowsGeneratorTrait
31
  /** @var string */
32
  public $numericPrimaryKey = null;
33
 
 
 
 
34
  /**
35
  * Used by unit tests
36
  * @var bool
@@ -147,11 +150,6 @@ trait DbRowsGeneratorTrait
147
  $batchSize = ceil($batchSize);
148
  $lastFetch = false;
149
 
150
- $this->lastFetchedPrimaryKeyValue = false;
151
- if (!empty($numericPrimaryKey)) {
152
- $this->lastFetchedPrimaryKeyValue = $offset;
153
- }
154
-
155
  do {
156
  if (count($rows) === 0) {
157
  if ($lastFetch) {
@@ -160,10 +158,15 @@ trait DbRowsGeneratorTrait
160
 
161
  // Optimal! We have a Primary Key so it doesn't get slower on large offsets.
162
  if (!empty($numericPrimaryKey)) {
 
 
 
 
 
163
  $query = <<<SQL
164
  SELECT *
165
  FROM `{$table}`
166
- WHERE `{$numericPrimaryKey}` > {$this->lastFetchedPrimaryKeyValue}
167
  ORDER BY `{$numericPrimaryKey}` ASC
168
  LIMIT 0, {$batchSize}
169
  SQL;
@@ -171,6 +174,7 @@ SQL;
171
  $query = "SELECT * FROM `{$table}` LIMIT {$offset}, {$batchSize}";
172
  }
173
 
 
174
  $rows = $db->get_results($query, ARRAY_A);
175
 
176
  // Call to mysql_free_result
@@ -181,7 +185,8 @@ SQL;
181
  }
182
 
183
  // We're done here.
184
- if (empty($rows)) {
 
185
  break;
186
  }
187
 
@@ -202,9 +207,9 @@ SQL;
202
  break;
203
  }
204
 
205
- // save the last fetched primary key value
206
  if (!empty($numericPrimaryKey)) {
207
- $this->lastFetchedPrimaryKeyValue = $row[$this->numericPrimaryKey];
208
  }
209
 
210
  yield $row;
31
  /** @var string */
32
  public $numericPrimaryKey = null;
33
 
34
+ /** @var bool */
35
+ public $noResultRows = false;
36
+
37
  /**
38
  * Used by unit tests
39
  * @var bool
150
  $batchSize = ceil($batchSize);
151
  $lastFetch = false;
152
 
 
 
 
 
 
153
  do {
154
  if (count($rows) === 0) {
155
  if ($lastFetch) {
158
 
159
  // Optimal! We have a Primary Key so it doesn't get slower on large offsets.
160
  if (!empty($numericPrimaryKey)) {
161
+ $whereCondition = '';
162
+ if ($this->lastFetchedPrimaryKeyValue !== false) {
163
+ $whereCondition = "WHERE `{$numericPrimaryKey}` > {$this->lastFetchedPrimaryKeyValue}";
164
+ }
165
+
166
  $query = <<<SQL
167
  SELECT *
168
  FROM `{$table}`
169
+ {$whereCondition}
170
  ORDER BY `{$numericPrimaryKey}` ASC
171
  LIMIT 0, {$batchSize}
172
  SQL;
174
  $query = "SELECT * FROM `{$table}` LIMIT {$offset}, {$batchSize}";
175
  }
176
 
177
+ $this->noResultRows = false;
178
  $rows = $db->get_results($query, ARRAY_A);
179
 
180
  // Call to mysql_free_result
185
  }
186
 
187
  // We're done here.
188
+ if (empty($rows) || count($rows) === 0) {
189
+ $this->noResultRows = true;
190
  break;
191
  }
192
 
207
  break;
208
  }
209
 
210
+ // save the last fetched primary key for next requests
211
  if (!empty($numericPrimaryKey)) {
212
+ $this->lastFetchedPrimaryKeyValue = $row[$numericPrimaryKey];
213
  }
214
 
215
  yield $row;
Frontend/Frontend.php CHANGED
@@ -90,7 +90,12 @@ class Frontend
90
  {
91
  $this->accessDenied = false;
92
 
93
- // Dont show login form for rest requests
 
 
 
 
 
94
  if ((new Rest())->isRestUrl()) {
95
  return false;
96
  }
90
  {
91
  $this->accessDenied = false;
92
 
93
+ // Don't show login form if showLoginForm filter is set to false. Used by Real Cookie Banner plugin
94
+ if (apply_filters('wpstg.frontend.showLoginForm', false)) {
95
+ return false;
96
+ }
97
+
98
+ // Don't show login form for rest requests
99
  if ((new Rest())->isRestUrl()) {
100
  return false;
101
  }
constantsFree.php CHANGED
@@ -2,10 +2,10 @@
2
 
3
  // WP STAGING version number
4
  if (!defined('WPSTG_VERSION')) {
5
- define('WPSTG_VERSION', '2.9.5');
6
  }
7
 
8
  // Compatible up to WordPress Version
9
  if (!defined('WPSTG_COMPATIBLE')) {
10
- define('WPSTG_COMPATIBLE', '5.8.3');
11
  }
2
 
3
  // WP STAGING version number
4
  if (!defined('WPSTG_VERSION')) {
5
+ define('WPSTG_VERSION', '2.9.6');
6
  }
7
 
8
  // Compatible up to WordPress Version
9
  if (!defined('WPSTG_COMPATIBLE')) {
10
+ define('WPSTG_COMPATIBLE', '5.9.1');
11
  }
opcacheBootstrap.php CHANGED
@@ -45,7 +45,7 @@ if (!$canInvalidate) {
45
  *
46
  * We use the "Version" from the headers of the main file of the plugin to compare.
47
  */
48
- $runtimeVersionDifferentFromBuildVersion = get_file_data($pluginFilePath, ['Version' => 'Version'])['Version'] !== '2.9.5';
49
  $lastCheckHappenedAfterInterval = current_time('timestamp') > (int)get_site_transient('wpstg.bootstrap.opcache.lastCleared') + 5 * MINUTE_IN_SECONDS;
50
 
51
  $shouldClearOpCache = apply_filters('wpstg.bootstrap.opcache.shouldClear', $runtimeVersionDifferentFromBuildVersion && $lastCheckHappenedAfterInterval);
45
  *
46
  * We use the "Version" from the headers of the main file of the plugin to compare.
47
  */
48
+ $runtimeVersionDifferentFromBuildVersion = get_file_data($pluginFilePath, ['Version' => 'Version'])['Version'] !== '2.9.6';
49
  $lastCheckHappenedAfterInterval = current_time('timestamp') > (int)get_site_transient('wpstg.bootstrap.opcache.lastCleared') + 5 * MINUTE_IN_SECONDS;
50
 
51
  $shouldClearOpCache = apply_filters('wpstg.bootstrap.opcache.shouldClear', $runtimeVersionDifferentFromBuildVersion && $lastCheckHappenedAfterInterval);
readme.txt CHANGED
@@ -8,8 +8,8 @@ License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
  Tags: backup, cloud backup, database backup, restore, wordpress backup
10
  Requires at least: 3.6+
11
- Tested up to: 5.8
12
- Stable tag: 2.9.5
13
  Requires PHP: 5.6
14
 
15
  Backup & Duplicator Plugin - Clone, move, duplicate & migrate websites to staging, backup, and development sites for authorized users only.
@@ -193,6 +193,15 @@ https://wp-staging.com
193
 
194
  == Changelog ==
195
 
 
 
 
 
 
 
 
 
 
196
  = 2.9.5 =
197
  * New: Create backups and restore of multisites (PRO) #1458
198
  * Fix: Force AnalyticsSender to convert wpstg_settings to array before usage #1559
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
  Tags: backup, cloud backup, database backup, restore, wordpress backup
10
  Requires at least: 3.6+
11
+ Tested up to: 5.9
12
+ Stable tag: 2.9.6
13
  Requires PHP: 5.6
14
 
15
  Backup & Duplicator Plugin - Clone, move, duplicate & migrate websites to staging, backup, and development sites for authorized users only.
193
 
194
  == Changelog ==
195
 
196
+ = 2.9.6 =
197
+ * New: Support up to WordPress 5.9.1
198
+ * New: Add filter wpstg.frontend.showLoginForm to allow third party plugin disabling login form for the staging site #1577
199
+ * New: Add labels to distinguish between network and single site clones on multisite
200
+ * Fix: Handle issue when showing staging sites in System Info #1560
201
+ * Fix: Fix Rows Generator for zero or negative values for Primary Key Index #1584
202
+ * Fix: Set option "Keep permalinks" on the staging site when updating a staging site if "keep permalinks" is active on the production site initially #1562
203
+ * Fix: Updating an existing multisite clone converted the clone to a single site #1565 #1589
204
+
205
  = 2.9.5 =
206
  * New: Create backups and restore of multisites (PRO) #1458
207
  * Fix: Force AnalyticsSender to convert wpstg_settings to array before usage #1559
wp-staging.php CHANGED
@@ -7,7 +7,7 @@
7
  * Author: WP-STAGING
8
  * Author URI: https://wp-staging.com
9
  * Contributors: ReneHermi
10
- * Version: 2.9.5
11
  * Text Domain: wp-staging
12
  * Domain Path: /languages/
13
  *
7
  * Author: WP-STAGING
8
  * Author URI: https://wp-staging.com
9
  * Contributors: ReneHermi
10
+ * Version: 2.9.6
11
  * Text Domain: wp-staging
12
  * Domain Path: /languages/
13
  *