PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backup, Restore, Migration & Clone / 4.9.2
WP STAGING – WordPress Backup, Restore, Migration & Clone v4.9.2
4.9.2 4.9.1 4.9.0 4.8.1 trunk 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.10.0 3.2.0 3.3.1 3.3.2 3.3.3 3.4.1 3.4.3 3.5.0 3.6.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 4.0.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.8.0
wp-staging / Backup / Storage / Providers.php
wp-staging / Backup / Storage Last commit date
Traits 1 month ago Providers.php 1 month ago
Providers.php
340 lines
1 <?php
2
3 namespace WPStaging\Backup\Storage;
4
5 use WPStaging\Core\WPStaging;
6 use WPStaging\Pro\Backup\Storage\Amazon\S3 as AmazonS3Auth;
7 use WPStaging\Pro\Backup\Storage\DigitalOceanSpaces\Auth as DOSAuth;
8 use WPStaging\Pro\Backup\Storage\GenericS3\Auth as GenericS3Auth;
9 use WPStaging\Pro\Backup\Storage\GoogleDrive\Auth as GoogleDriveAuth;
10 use WPStaging\Pro\Backup\Storage\Dropbox\Auth as DropboxAuth;
11 use WPStaging\Pro\Backup\Storage\OneDrive\Auth as OneDriveAuth;
12 use WPStaging\Pro\Backup\Storage\SFTP\Auth as SftpAuth;
13 use WPStaging\Pro\Backup\Storage\Wasabi\Auth as WasabiAuth;
14 use WPStaging\Pro\Backup\Storage\PCloud\Auth as PCloudAuth;
15 use WPStaging\Backup\Storage\Traits\StorageIdNormalizerTrait;
16
17 /**
18 * Registry of remote-storage providers (Google Drive, Amazon S3, Dropbox, etc.)
19 * available to the plugin for backup upload targets.
20 *
21 * Owns the canonical list of storage definitions (id, display name, auth class,
22 * settings URL) and the legacy-to-current identifier mapping used to migrate
23 * old option names to the new hyphenated format.
24 */
25 class Providers
26 {
27 use StorageIdNormalizerTrait;
28
29 /** @var string */
30 const IDENTIFIER_GOOGLE_DRIVE = 'google-drive';
31
32 /** @var string */
33 const IDENTIFIER_AMAZON_S3 = 'amazon-s3';
34
35 /** @var string */
36 const IDENTIFIER_DROPBOX = 'dropbox';
37
38 /** @var string */
39 const IDENTIFIER_ONE_DRIVE = 'one-drive';
40
41 /** @var string */
42 const IDENTIFIER_PCLOUD = 'pcloud';
43
44 /** @var string */
45 const IDENTIFIER_SFTP = 'sftp';
46
47 /** @var string */
48 const IDENTIFIER_DIGITALOCEAN_SPACES = 'digitalocean-spaces';
49
50 /** @var string */
51 const IDENTIFIER_WASABI_S3 = 'wasabi-s3';
52
53 /** @var string */
54 const IDENTIFIER_GENERIC_S3 = 'generic-s3';
55
56 /**
57 * Map of legacy storage IDs to new hyphenated format for backward compatibility.
58 * Includes legacy storage IDs (camelCase) and legacy identifiers (lowercase).
59 */
60 const LEGACY_ID_MAP = [
61 'googleDrive' => self::IDENTIFIER_GOOGLE_DRIVE,
62 'amazonS3' => self::IDENTIFIER_AMAZON_S3,
63 'googledrive' => self::IDENTIFIER_GOOGLE_DRIVE,
64 'amazons3' => self::IDENTIFIER_AMAZON_S3,
65 ];
66
67 /**
68 * Map of new hyphenated storage IDs to legacy lowercase identifiers.
69 * Used for backward compatibility with option names (e.g. wpstg_googledrive, wpstg_amazons3).
70 */
71 const REVERSE_LEGACY_ID_MAP = [
72 self::IDENTIFIER_GOOGLE_DRIVE => 'googledrive',
73 self::IDENTIFIER_AMAZON_S3 => 'amazons3',
74 ];
75
76 /** Maps hyphenated identifiers to their legacy wpstg_* option names for backward compatibility. */
77 const LEGACY_OPTION_MAP = [
78 self::IDENTIFIER_GOOGLE_DRIVE => 'wpstg_googledrive',
79 self::IDENTIFIER_AMAZON_S3 => 'wpstg_amazons3',
80 ];
81
82 /** Maps hyphenated identifiers to legacy camelCase property names stored in wpstg_tmp_data. */
83 const LEGACY_PROPERTY_MAP = [
84 self::IDENTIFIER_GOOGLE_DRIVE => 'googleDrive',
85 self::IDENTIFIER_AMAZON_S3 => 'amazonS3',
86 self::IDENTIFIER_DIGITALOCEAN_SPACES => 'digitalOceanSpaces',
87 self::IDENTIFIER_WASABI_S3 => 'wasabiS3',
88 self::IDENTIFIER_GENERIC_S3 => 'genericS3',
89 self::IDENTIFIER_ONE_DRIVE => 'oneDrive',
90 self::IDENTIFIER_PCLOUD => 'pCloud',
91 ];
92
93 /** Maps hyphenated identifiers to their display names, used for logging. */
94 const STORAGE_LABELS = [
95 self::IDENTIFIER_GOOGLE_DRIVE => 'Google Drive',
96 self::IDENTIFIER_AMAZON_S3 => 'Amazon S3',
97 self::IDENTIFIER_SFTP => 'sFTP/FTP',
98 self::IDENTIFIER_DIGITALOCEAN_SPACES => 'Digital Ocean Spaces',
99 self::IDENTIFIER_WASABI_S3 => 'Wasabi S3',
100 self::IDENTIFIER_GENERIC_S3 => 'Generic S3',
101 self::IDENTIFIER_DROPBOX => 'Dropbox',
102 self::IDENTIFIER_ONE_DRIVE => 'Microsoft OneDrive',
103 self::IDENTIFIER_PCLOUD => 'pCloud',
104 ];
105
106 protected $storages = [];
107
108 /**
109 * Build the static storage-provider registry used across the plugin.
110 *
111 * Each entry exposes the storage id, CLI slug, display name, an "enabled"
112 * flag, the auth class (filtered so Free builds never reference Pro-only
113 * auth implementations), and the admin settings page URL.
114 */
115 public function __construct()
116 {
117 $this->storages = [
118 [
119 'id' => self::IDENTIFIER_GOOGLE_DRIVE,
120 'cli' => self::IDENTIFIER_GOOGLE_DRIVE,
121 'name' => 'Google Drive',
122 'enabled' => true,
123 'authClass' => $this->filterAuthClassForPro(GoogleDriveAuth::class),
124 'settingsPath' => $this->getStorageAdminPage(self::IDENTIFIER_GOOGLE_DRIVE),
125 ],
126 [
127 'id' => self::IDENTIFIER_AMAZON_S3,
128 'cli' => self::IDENTIFIER_AMAZON_S3,
129 'name' => 'Amazon S3',
130 'enabled' => true,
131 'authClass' => $this->filterAuthClassForPro(AmazonS3Auth::class),
132 'settingsPath' => $this->getStorageAdminPage(self::IDENTIFIER_AMAZON_S3),
133 ],
134 [
135 'id' => self::IDENTIFIER_DROPBOX,
136 'cli' => self::IDENTIFIER_DROPBOX,
137 'name' => 'Dropbox',
138 'enabled' => true,
139 'authClass' => $this->filterAuthClassForPro(DropboxAuth::class),
140 'settingsPath' => $this->getStorageAdminPage(self::IDENTIFIER_DROPBOX),
141 ],
142 [
143 'id' => self::IDENTIFIER_ONE_DRIVE,
144 'cli' => self::IDENTIFIER_ONE_DRIVE,
145 'name' => 'Microsoft OneDrive',
146 'enabled' => true,
147 'authClass' => $this->filterAuthClassForPro(OneDriveAuth::class),
148 'settingsPath' => $this->getStorageAdminPage(self::IDENTIFIER_ONE_DRIVE),
149 ],
150 [
151 'id' => self::IDENTIFIER_PCLOUD,
152 'cli' => self::IDENTIFIER_PCLOUD,
153 'name' => 'pCloud',
154 'enabled' => true,
155 'authClass' => $this->filterAuthClassForPro(PCloudAuth::class),
156 'settingsPath' => $this->getStorageAdminPage(self::IDENTIFIER_PCLOUD),
157 ],
158 [
159 'id' => self::IDENTIFIER_SFTP,
160 'cli' => self::IDENTIFIER_SFTP,
161 'name' => 'FTP / SFTP',
162 'enabled' => true,
163 'authClass' => $this->filterAuthClassForPro(SftpAuth::class),
164 'settingsPath' => $this->getStorageAdminPage(self::IDENTIFIER_SFTP),
165 ],
166 [
167 'id' => self::IDENTIFIER_DIGITALOCEAN_SPACES,
168 'cli' => self::IDENTIFIER_DIGITALOCEAN_SPACES,
169 'name' => 'DigitalOcean Spaces',
170 'enabled' => true,
171 'authClass' => $this->filterAuthClassForPro(DOSAuth::class),
172 'settingsPath' => $this->getStorageAdminPage(self::IDENTIFIER_DIGITALOCEAN_SPACES),
173 ],
174 [
175 'id' => self::IDENTIFIER_WASABI_S3,
176 'cli' => self::IDENTIFIER_WASABI_S3,
177 'name' => 'Wasabi S3',
178 'enabled' => true,
179 'authClass' => $this->filterAuthClassForPro(WasabiAuth::class),
180 'settingsPath' => $this->getStorageAdminPage(self::IDENTIFIER_WASABI_S3),
181 ],
182 [
183 'id' => self::IDENTIFIER_GENERIC_S3,
184 'cli' => self::IDENTIFIER_GENERIC_S3,
185 'name' => 'Generic S3',
186 'enabled' => true,
187 'authClass' => $this->filterAuthClassForPro(GenericS3Auth::class),
188 'settingsPath' => $this->getStorageAdminPage(self::IDENTIFIER_GENERIC_S3),
189 ],
190 ];
191 }
192
193 /**
194 * @param null|bool $isEnabled. Default null
195 * Use null for all storages,
196 * Use true for enabled storages,
197 * Use false for disabled storages
198 *
199 * @return array
200 */
201 public function getStorageIds($isEnabled = null)
202 {
203 return array_map(function ($storage) {
204 return $storage['id'];
205 }, $this->getStorages($isEnabled));
206 }
207
208 /**
209 * @param null|bool $isEnabled. Default null
210 * Use null for all storages,
211 * Use true for enabled storages,
212 * Use false for disabled storages
213 *
214 * @return array
215 */
216 public function getStorages($isEnabled = null)
217 {
218 if ($isEnabled === null) {
219 return $this->storages;
220 }
221
222 return array_filter($this->storages, function ($storage) use ($isEnabled) {
223 return $storage['enabled'] === $isEnabled;
224 });
225 }
226
227 /**
228 * @param string $id
229 * @param string $property
230 * @param null|bool $isEnabled. Default null
231 * Use null for all storages,
232 * Use true for enabled storages,
233 * Use false for disabled storages
234 *
235 * @return mixed
236 */
237 public function getStorageProperty($id, $property, $isEnabled = null)
238 {
239 foreach ($this->getStorages($isEnabled) as $storage) {
240 if ($storage['id'] === $id) {
241 if (array_key_exists($property, $storage)) {
242 return $storage[$property];
243 }
244 }
245 }
246
247 return false;
248 }
249
250 /**
251 * @param string $class
252 * @return bool
253 */
254 public function isActivated($class)
255 {
256 if (empty($class)) {
257 return false;
258 }
259
260 /** @see WPStaging\Backup\Storage\AbstractStorage */
261 $storage = WPStaging::make($class);
262 return $storage->isAuthenticated();
263 }
264
265 /**
266 * @param string $id
267 * @return string
268 */
269 protected function filterAuthClassForPro($id)
270 {
271 if (empty($id) || !WPStaging::isPro()) {
272 return '';
273 }
274
275 return $id;
276 }
277
278 private function getStorageAdminPage($storageTab)
279 {
280 return admin_url('admin.php?page=wpstg-settings&tab=remote-storages&sub-tab=' . $storageTab);
281 }
282
283 /**
284 * Rename each remote-storage wp_options entry from its legacy camelCase or
285 * lowercase key to the current hyphenated key (e.g. wpstg_googleDrive and
286 * wpstg_googledrive -> wpstg_google-drive, wpstg_amazonS3 and wpstg_amazons3
287 * -> wpstg_amazon-s3).
288 *
289 * Idempotent and safe to re-run:
290 * - Skips any storage whose new-format option already exists (even if the
291 * stored value is empty, e.g. after the user revoked credentials) so we
292 * never resurrect old credentials on top of an intentionally cleared one.
293 * - Tracks already-handled new ids in a local map so multi-entry legacy
294 * mappings (camelCase + lowercase pointing at the same new id) only
295 * migrate the first non-empty legacy option found.
296 * - Uses autoload = false when writing the migrated option.
297 *
298 * Called once per install from the Upgrade dispatchers, gated by the
299 * `remote_storage_option_names_migrated` feature flag in UpgradeFlags.
300 *
301 * @return void
302 */
303 public function migrateRemoteStorageOptions()
304 {
305 $migrated = [];
306 foreach (self::LEGACY_ID_MAP as $legacyId => $newId) {
307 if (isset($migrated[$newId])) {
308 continue;
309 }
310
311 $newOptionName = 'wpstg_' . $newId;
312
313 // Check if the new option already exists (even if empty, e.g. after revoking credentials).
314 // Only migrate when the option is truly missing, to avoid resurrecting old credentials.
315 $newValue = get_option($newOptionName);
316 if ($newValue !== false) {
317 $migrated[$newId] = true;
318 continue;
319 }
320
321 $legacyOptionNames = array_unique([
322 'wpstg_' . $legacyId,
323 'wpstg_' . strtolower($legacyId),
324 ]);
325
326 foreach ($legacyOptionNames as $legacyOptionName) {
327 $legacyValue = get_option($legacyOptionName, []);
328 if (empty($legacyValue)) {
329 continue;
330 }
331
332 update_option($newOptionName, $legacyValue, false);
333 break;
334 }
335
336 $migrated[$newId] = true;
337 }
338 }
339 }
340