PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.11.2
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.11.2
4.11.2 4.11.1 4.11.0 4.10.0 4.9.5 4.9.4 4.9.3 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 / Framework / Settings / Settings.php
wp-staging / Framework / Settings Last commit date
DarkMode.php 1 week ago Settings.php 3 days ago SettingsTable.php 1 week ago
Settings.php
482 lines
1 <?php
2
3 namespace WPStaging\Framework\Settings;
4
5 use WPStaging\Core\WPStaging;
6 use WPStaging\Core\DTO\Settings as SettingsDTO;
7 use WPStaging\Framework\Facades\Sanitize as SanitizeFacade;
8 use WPStaging\Framework\BackgroundProcessing\FeatureDetection;
9 use WPStaging\Framework\BackgroundProcessing\Queue;
10 use WPStaging\Framework\Network\HttpBasicAuth;
11 use WPStaging\Framework\SiteInfo;
12 use WPStaging\Framework\Utils\Sanitize;
13 use WPStaging\Backup\BackupScheduler;
14 use WPStaging\Backup\Service\UpdateProtectionSettings;
15 use WPStaging\Framework\Security\Auth;
16 use WPStaging\Framework\Security\DataEncryption;
17 use WPStaging\Notifications\Notifications;
18
19
20
21
22 class Settings
23 {
24 use HttpBasicAuth;
25
26
27 const ACTION_WPSTG_PRO_SETTINGS = 'wpstg.views.pro.settings';
28
29
30
31
32
33 private $optionsToSanitize = [
34 'queryLimit' => 'sanitizeInt',
35 'querySRLimit' => 'sanitizeInt',
36 'fileLimit' => 'sanitizeInt',
37 'maxFileSize' => 'sanitizeInt',
38 'batchSize' => 'sanitizeInt',
39 'delayRequest' => 'sanitizeInt',
40 'cpuLoad' => 'sanitizeString',
41 'unInstallOnDelete' => 'sanitizeBool',
42 'optimizer' => 'sanitizeBool',
43 'disableAdminLogin' => 'sanitizeBool',
44 'keepPermalinks' => 'sanitizeBool',
45 'debugMode' => 'sanitizeBool',
46 'enableBackupBeforeUpdate' => 'sanitizeBool',
47 ];
48
49
50
51
52 private $siteInfo;
53
54
55
56
57 private $sanitize;
58
59
60 private $queue;
61
62
63 private $auth;
64
65
66 private $dataEncryption;
67
68
69
70
71
72
73
74
75 public function __construct(SiteInfo $siteInfo, Sanitize $sanitize, Queue $queue, Auth $auth, DataEncryption $dataEncryption)
76 {
77 $this->siteInfo = $siteInfo;
78 $this->sanitize = $sanitize;
79 $this->queue = $queue;
80 $this->auth = $auth;
81 $this->dataEncryption = $dataEncryption;
82 }
83
84
85
86
87 public function registerSettings()
88 {
89 register_setting("wpstg_settings", "wpstg_settings", [$this, "sanitizeOptions"]);
90 }
91
92
93
94
95
96
97
98
99
100
101
102 public function sanitizeOptions($data = []): array
103 {
104 if (!is_array($data) && !is_object($data)) {
105 return (array)get_option('wpstg_settings', []);
106 }
107
108 $data = $this->toArrayDeep($data);
109 $isFormSubmission = $this->isSettingsFormSubmission();
110 $showErrorToggleStagingSiteCloning = false;
111
112 if ($isFormSubmission) {
113 $showErrorToggleStagingSiteCloning = $this->applySideEffects($data);
114 }
115
116 $sanitized = $this->sanitizeData($data);
117
118 if ($isFormSubmission && function_exists('add_settings_error')) {
119 if ($showErrorToggleStagingSiteCloning) {
120 add_settings_error("wpstg-notices", '', __("Settings updated. But unable to activate/deactivate the site cloneable status!", "wp-staging"), "warning");
121 } else {
122 add_settings_error("wpstg-notices", '', __("Settings updated.", "wp-staging"), "updated");
123 }
124 }
125
126 return $sanitized;
127 }
128
129
130
131
132 public function ajaxPurgeQueueTable()
133 {
134 if ($this->auth->isAuthenticatedRequest() === false) {
135 wp_send_json([
136 'success' => false,
137 'message' => esc_html__('Error 403: Unauthorized Request', 'wp-staging'),
138 ]);
139 }
140
141 $result = $this->queue->purgeQueueTable();
142
143 if ($result === false) {
144 wp_send_json([
145 'success' => false,
146 'message' => esc_html__('Unable to purge queue table', 'wp-staging'),
147 ]);
148 }
149
150 if ($result === 0) {
151 wp_send_json([
152 'success' => true,
153 'message' => sprintf(esc_html__('Table %s is already empty.', 'wp-staging'), esc_html($this->queue->getTableName())),
154 ]);
155 }
156
157 wp_send_json([
158 'success' => true,
159 'message' => sprintf(esc_html__('Purged queue table! Removed %s action(s)', 'wp-staging'), esc_html((string)$result)),
160 ]);
161
162 return null;
163 }
164
165
166
167
168
169
170
171
172
173
174 public function ajaxHttpAuthPing()
175 {
176 wp_send_json_success(['ping' => true]);
177 }
178
179
180
181
182
183
184
185 public function ajaxTestHttpAuth()
186 {
187 if ($this->auth->isAuthenticatedRequest() === false) {
188 wp_send_json_error(['message' => esc_html__('Error 403: Unauthorized Request', 'wp-staging')]);
189 return;
190 }
191
192 $headers = $this->getHttpAuthHeaders();
193 if (empty($headers)) {
194 wp_send_json_error(['message' => esc_html__('No HTTP Basic Auth credentials are saved yet. Save your settings first, then test the connection.', 'wp-staging')]);
195 return;
196 }
197
198 $url = admin_url('admin-ajax.php');
199
200 $response = wp_remote_post($url, [
201 'timeout' => 15,
202 'sslverify' => apply_filters(FeatureDetection::FILTER_HTTPS_LOCAL_SSL_VERIFY, false),
203 'headers' => $headers,
204 'body' => [
205 'action' => 'wpstg_http_auth_ping',
206 ],
207 ]);
208
209 if (is_wp_error($response)) {
210 wp_send_json_error([
211 'message' => sprintf(
212 esc_html__('Connection failed: %s', 'wp-staging'),
213 esc_html($response->get_error_message())
214 ),
215 ]);
216 return;
217 }
218
219 $statusCode = wp_remote_retrieve_response_code($response);
220 $body = json_decode(wp_remote_retrieve_body($response), true);
221
222 if ($statusCode === 401) {
223 wp_send_json_error(['message' => esc_html__('Authentication failed (401). The username or password is incorrect.', 'wp-staging')]);
224 return;
225 }
226
227 if ($statusCode === 403) {
228 wp_send_json_error(['message' => esc_html__('Access denied (403). The request was blocked, possibly by a firewall or security plugin.', 'wp-staging')]);
229 return;
230 }
231
232 if ($statusCode !== 200 || empty($body['success'])) {
233 wp_send_json_error([
234 'message' => sprintf(
235 esc_html__('Unexpected response (HTTP %s). The loopback request did not succeed.', 'wp-staging'),
236 esc_html((string)$statusCode)
237 ),
238 ]);
239 return;
240 }
241
242 wp_send_json_success(['message' => esc_html__('Connection successful! Background tasks will be able to reach wp-admin.', 'wp-staging')]);
243 }
244
245
246
247
248
249 private function toArrayDeep($data): array
250 {
251 if (is_object($data)) {
252 $data = get_object_vars($data);
253 }
254
255 if (!is_array($data)) {
256 return [];
257 }
258
259 foreach ($data as $key => $value) {
260 if (is_object($value) || is_array($value)) {
261 $data[$key] = $this->toArrayDeep($value);
262 }
263 }
264
265 return $data;
266 }
267
268
269
270
271
272 protected function sanitizeData(array $data = []): array
273 {
274 $sanitized = [];
275
276 foreach ($data as $key => $value) {
277 if (is_array($value)) {
278 $sanitized[$key] = $this->sanitizeData($value);
279 continue;
280 }
281
282 $val = htmlspecialchars($value);
283 if (array_key_exists($key, $this->optionsToSanitize)) {
284 $sanitizeMethod = $this->optionsToSanitize[$key];
285 $val = $this->sanitize->$sanitizeMethod($val);
286 }
287
288 $sanitized[$key] = wp_filter_nohtml_kses($val);
289 }
290
291 return $sanitized;
292 }
293
294
295
296
297
298
299
300
301 protected function toggleStagingSiteCloning(bool $isCloneable): bool
302 {
303 if ($isCloneable && $this->siteInfo->enableStagingSiteCloning()) {
304 return true;
305 }
306
307 if (!$isCloneable && $this->siteInfo->disableStagingSiteCloning()) {
308 return true;
309 }
310
311 return false;
312 }
313
314
315
316
317
318
319
320
321
322
323
324
325
326 protected function setErrorReportOptions(
327 string $optionBackupScheduleErrorReport,
328 string $optionBackupScheduleWarningReport,
329 string $optionBackupScheduleGeneralReport,
330 string $optionBackupScheduleReportEmail,
331 string $optionBackupScheduleSlackErrorReport,
332 string $optionBackupScheduleReportSlackWebhook,
333 string $optionSendEmailAsHTML
334 ) {
335 if (!class_exists('WPStaging\Backup\BackupScheduler')) {
336 return;
337 }
338
339 update_option(BackupScheduler::OPTION_BACKUP_SCHEDULE_ERROR_REPORT, $optionBackupScheduleErrorReport, false);
340 update_option(BackupScheduler::OPTION_BACKUP_SCHEDULE_WARNING_REPORT, $optionBackupScheduleWarningReport, false);
341 update_option(BackupScheduler::OPTION_BACKUP_SCHEDULE_GENERAL_REPORT, $optionBackupScheduleGeneralReport, false);
342 update_option(Notifications::OPTION_BACKUP_SCHEDULE_REPORT_EMAIL, $optionBackupScheduleReportEmail);
343 update_option(BackupScheduler::OPTION_BACKUP_SCHEDULE_SLACK_ERROR_REPORT, $optionBackupScheduleSlackErrorReport, false);
344 update_option(BackupScheduler::OPTION_BACKUP_SCHEDULE_REPORT_SLACK_WEBHOOK, $optionBackupScheduleReportSlackWebhook, false);
345 update_option(Notifications::OPTION_SEND_EMAIL_AS_HTML, $optionSendEmailAsHTML);
346 }
347
348
349
350
351
352
353
354
355
356 protected function saveHttpAuthCredentials(array $data)
357 {
358 $username = isset($data['httpAuthUsername'])
359 ? $this->sanitize->sanitizeString($data['httpAuthUsername'])
360 : '';
361
362 if (empty($username)) {
363 update_option(Queue::OPTION_HTTP_AUTH_CREDENTIALS, ['username' => '', 'password' => ''], false);
364 return;
365 }
366
367 $submittedPassword = isset($data['httpAuthPassword'])
368 ? $this->sanitize->sanitizePassword($data['httpAuthPassword'])
369 : '';
370
371 if (!empty($submittedPassword)) {
372 $password = $this->dataEncryption->encrypt($submittedPassword);
373 } else {
374 $existing = get_option(Queue::OPTION_HTTP_AUTH_CREDENTIALS, []);
375 $password = !empty($existing['password']) ? $existing['password'] : '';
376 }
377
378 update_option(Queue::OPTION_HTTP_AUTH_CREDENTIALS, [
379 'username' => $username,
380 'password' => $password,
381 ], false);
382 }
383
384
385
386
387
388
389
390
391
392 public function restoreDefaults()
393 {
394 delete_option('wpstg_settings');
395 (new SettingsDTO())->setDefault();
396
397 $data = [];
398 $this->applySideEffects($data);
399 }
400
401
402
403
404
405
406
407
408 private function applySideEffects(array &$data): bool
409 {
410 $showErrorToggleStagingSiteCloning = false;
411 if ($this->siteInfo->isStagingSite()) {
412 $isStagingCloneable = isset($data['isStagingSiteCloneable']) ? $data['isStagingSiteCloneable'] : 'false';
413 unset($data['isStagingSiteCloneable']);
414 $showErrorToggleStagingSiteCloning = !$this->toggleStagingSiteCloning($isStagingCloneable === 'true');
415 }
416
417 $optionBackupScheduleErrorReport = isset($data['schedulesErrorReport']) ? 'true' : '';
418 $optionBackupScheduleWarningReport = isset($data['schedulesWarningReport']) ? 'true' : '';
419 $optionBackupScheduleGeneralReport = isset($data['schedulesGeneralReport']) ? 'true' : '';
420 $optionBackupScheduleReportEmail = !empty($data['schedulesReportEmail']) ? $this->sanitize->sanitizeEmail($data['schedulesReportEmail']) : '';
421
422 if (empty($optionBackupScheduleReportEmail)) {
423 $optionBackupScheduleErrorReport = '';
424 }
425
426 unset($data['schedulesErrorReport'], $data['schedulesReportEmail']);
427
428 $optionBackupScheduleSlackErrorReport = isset($data['schedulesSlackErrorReport']) ? 'true' : '';
429 $optionBackupScheduleReportSlackWebhook = !empty($data['schedulesReportSlackWebhook']) ? $this->sanitize->sanitizeUrl($data['schedulesReportSlackWebhook']) : '';
430 $optionSendEmailAsHTML = isset($data['emailAsHTML']) ? 'true' : '';
431
432 if (empty($optionBackupScheduleReportSlackWebhook)) {
433 $optionBackupScheduleSlackErrorReport = '';
434 }
435
436 unset($data['schedulesErrorSlackReport'], $data['schedulesReportSlackWebhook']);
437
438 $this->setErrorReportOptions(
439 $optionBackupScheduleErrorReport,
440 $optionBackupScheduleWarningReport,
441 $optionBackupScheduleGeneralReport,
442 $optionBackupScheduleReportEmail,
443 $optionBackupScheduleSlackErrorReport,
444 $optionBackupScheduleReportSlackWebhook,
445 $optionSendEmailAsHTML
446 );
447
448 $this->saveHttpAuthCredentials($data);
449 unset($data['httpAuthUsername'], $data['httpAuthPassword']);
450
451 $data['enableBackupBeforeUpdate'] = $data['enableBackupBeforeUpdate'] ?? '0';
452
453 if ($data['enableBackupBeforeUpdate'] === '0') {
454 WPStaging::make(UpdateProtectionSettings::class)->forgetMode();
455 }
456
457 return $showErrorToggleStagingSiteCloning;
458 }
459
460
461
462
463
464
465
466
467
468
469 private function isSettingsFormSubmission(): bool
470 {
471 if (!isset($_POST['option_page']) || !isset($_POST['_wpnonce'])) {
472 return false;
473 }
474
475 if (SanitizeFacade::sanitizeString(wp_unslash($_POST['option_page'])) !== 'wpstg_settings') {
476 return false;
477 }
478
479 return (bool)wp_verify_nonce(SanitizeFacade::sanitizeString(wp_unslash($_POST['_wpnonce'])), 'wpstg_settings-options');
480 }
481 }
482