PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.11.0
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.11.0
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 day ago Settings.php 1 day ago SettingsTable.php 1 day ago
Settings.php
449 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 public function sanitizeOptions(array $data = []): array
98 {
99 $isFormSubmission = $this->isSettingsFormSubmission();
100 $showErrorToggleStagingSiteCloning = false;
101
102 if ($isFormSubmission) {
103 $showErrorToggleStagingSiteCloning = $this->applySideEffects($data);
104 }
105
106 $sanitized = $this->sanitizeData($data);
107
108 if ($isFormSubmission && function_exists('add_settings_error')) {
109 if ($showErrorToggleStagingSiteCloning) {
110 add_settings_error("wpstg-notices", '', __("Settings updated. But unable to activate/deactivate the site cloneable status!", "wp-staging"), "warning");
111 } else {
112 add_settings_error("wpstg-notices", '', __("Settings updated.", "wp-staging"), "updated");
113 }
114 }
115
116 return $sanitized;
117 }
118
119
120
121
122 public function ajaxPurgeQueueTable()
123 {
124 if ($this->auth->isAuthenticatedRequest() === false) {
125 wp_send_json([
126 'success' => false,
127 'message' => esc_html__('Error 403: Unauthorized Request', 'wp-staging'),
128 ]);
129 }
130
131 $result = $this->queue->purgeQueueTable();
132
133 if ($result === false) {
134 wp_send_json([
135 'success' => false,
136 'message' => esc_html__('Unable to purge queue table', 'wp-staging'),
137 ]);
138 }
139
140 if ($result === 0) {
141 wp_send_json([
142 'success' => true,
143 'message' => sprintf(esc_html__('Table %s is already empty.', 'wp-staging'), esc_html($this->queue->getTableName())),
144 ]);
145 }
146
147 wp_send_json([
148 'success' => true,
149 'message' => sprintf(esc_html__('Purged queue table! Removed %s action(s)', 'wp-staging'), esc_html((string)$result)),
150 ]);
151
152 return null;
153 }
154
155
156
157
158
159
160
161
162
163
164 public function ajaxHttpAuthPing()
165 {
166 wp_send_json_success(['ping' => true]);
167 }
168
169
170
171
172
173
174
175 public function ajaxTestHttpAuth()
176 {
177 if ($this->auth->isAuthenticatedRequest() === false) {
178 wp_send_json_error(['message' => esc_html__('Error 403: Unauthorized Request', 'wp-staging')]);
179 return;
180 }
181
182 $headers = $this->getHttpAuthHeaders();
183 if (empty($headers)) {
184 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')]);
185 return;
186 }
187
188 $url = admin_url('admin-ajax.php');
189
190 $response = wp_remote_post($url, [
191 'timeout' => 15,
192 'sslverify' => apply_filters(FeatureDetection::FILTER_HTTPS_LOCAL_SSL_VERIFY, false),
193 'headers' => $headers,
194 'body' => [
195 'action' => 'wpstg_http_auth_ping',
196 ],
197 ]);
198
199 if (is_wp_error($response)) {
200 wp_send_json_error([
201 'message' => sprintf(
202 esc_html__('Connection failed: %s', 'wp-staging'),
203 esc_html($response->get_error_message())
204 ),
205 ]);
206 return;
207 }
208
209 $statusCode = wp_remote_retrieve_response_code($response);
210 $body = json_decode(wp_remote_retrieve_body($response), true);
211
212 if ($statusCode === 401) {
213 wp_send_json_error(['message' => esc_html__('Authentication failed (401). The username or password is incorrect.', 'wp-staging')]);
214 return;
215 }
216
217 if ($statusCode === 403) {
218 wp_send_json_error(['message' => esc_html__('Access denied (403). The request was blocked, possibly by a firewall or security plugin.', 'wp-staging')]);
219 return;
220 }
221
222 if ($statusCode !== 200 || empty($body['success'])) {
223 wp_send_json_error([
224 'message' => sprintf(
225 esc_html__('Unexpected response (HTTP %s). The loopback request did not succeed.', 'wp-staging'),
226 esc_html((string)$statusCode)
227 ),
228 ]);
229 return;
230 }
231
232 wp_send_json_success(['message' => esc_html__('Connection successful! Background tasks will be able to reach wp-admin.', 'wp-staging')]);
233 }
234
235
236
237
238
239 protected function sanitizeData(array $data = []): array
240 {
241 $sanitized = [];
242
243 foreach ($data as $key => $value) {
244 if (is_array($value)) {
245 $sanitized[$key] = $this->sanitizeData($value);
246 continue;
247 }
248
249 $val = htmlspecialchars($value);
250 if (array_key_exists($key, $this->optionsToSanitize)) {
251 $sanitizeMethod = $this->optionsToSanitize[$key];
252 $val = $this->sanitize->$sanitizeMethod($val);
253 }
254
255 $sanitized[$key] = wp_filter_nohtml_kses($val);
256 }
257
258 return $sanitized;
259 }
260
261
262
263
264
265
266
267
268 protected function toggleStagingSiteCloning(bool $isCloneable): bool
269 {
270 if ($isCloneable && $this->siteInfo->enableStagingSiteCloning()) {
271 return true;
272 }
273
274 if (!$isCloneable && $this->siteInfo->disableStagingSiteCloning()) {
275 return true;
276 }
277
278 return false;
279 }
280
281
282
283
284
285
286
287
288
289
290
291
292
293 protected function setErrorReportOptions(
294 string $optionBackupScheduleErrorReport,
295 string $optionBackupScheduleWarningReport,
296 string $optionBackupScheduleGeneralReport,
297 string $optionBackupScheduleReportEmail,
298 string $optionBackupScheduleSlackErrorReport,
299 string $optionBackupScheduleReportSlackWebhook,
300 string $optionSendEmailAsHTML
301 ) {
302 if (!class_exists('WPStaging\Backup\BackupScheduler')) {
303 return;
304 }
305
306 update_option(BackupScheduler::OPTION_BACKUP_SCHEDULE_ERROR_REPORT, $optionBackupScheduleErrorReport, false);
307 update_option(BackupScheduler::OPTION_BACKUP_SCHEDULE_WARNING_REPORT, $optionBackupScheduleWarningReport, false);
308 update_option(BackupScheduler::OPTION_BACKUP_SCHEDULE_GENERAL_REPORT, $optionBackupScheduleGeneralReport, false);
309 update_option(Notifications::OPTION_BACKUP_SCHEDULE_REPORT_EMAIL, $optionBackupScheduleReportEmail);
310 update_option(BackupScheduler::OPTION_BACKUP_SCHEDULE_SLACK_ERROR_REPORT, $optionBackupScheduleSlackErrorReport, false);
311 update_option(BackupScheduler::OPTION_BACKUP_SCHEDULE_REPORT_SLACK_WEBHOOK, $optionBackupScheduleReportSlackWebhook, false);
312 update_option(Notifications::OPTION_SEND_EMAIL_AS_HTML, $optionSendEmailAsHTML);
313 }
314
315
316
317
318
319
320
321
322
323 protected function saveHttpAuthCredentials(array $data)
324 {
325 $username = isset($data['httpAuthUsername'])
326 ? $this->sanitize->sanitizeString($data['httpAuthUsername'])
327 : '';
328
329 if (empty($username)) {
330 update_option(Queue::OPTION_HTTP_AUTH_CREDENTIALS, ['username' => '', 'password' => ''], false);
331 return;
332 }
333
334 $submittedPassword = isset($data['httpAuthPassword'])
335 ? $this->sanitize->sanitizePassword($data['httpAuthPassword'])
336 : '';
337
338 if (!empty($submittedPassword)) {
339 $password = $this->dataEncryption->encrypt($submittedPassword);
340 } else {
341 $existing = get_option(Queue::OPTION_HTTP_AUTH_CREDENTIALS, []);
342 $password = !empty($existing['password']) ? $existing['password'] : '';
343 }
344
345 update_option(Queue::OPTION_HTTP_AUTH_CREDENTIALS, [
346 'username' => $username,
347 'password' => $password,
348 ], false);
349 }
350
351
352
353
354
355
356
357
358
359 public function restoreDefaults()
360 {
361 delete_option('wpstg_settings');
362 (new SettingsDTO())->setDefault();
363
364 $data = [];
365 $this->applySideEffects($data);
366 }
367
368
369
370
371
372
373
374
375 private function applySideEffects(array &$data): bool
376 {
377 $showErrorToggleStagingSiteCloning = false;
378 if ($this->siteInfo->isStagingSite()) {
379 $isStagingCloneable = isset($data['isStagingSiteCloneable']) ? $data['isStagingSiteCloneable'] : 'false';
380 unset($data['isStagingSiteCloneable']);
381 $showErrorToggleStagingSiteCloning = !$this->toggleStagingSiteCloning($isStagingCloneable === 'true');
382 }
383
384 $optionBackupScheduleErrorReport = isset($data['schedulesErrorReport']) ? 'true' : '';
385 $optionBackupScheduleWarningReport = isset($data['schedulesWarningReport']) ? 'true' : '';
386 $optionBackupScheduleGeneralReport = isset($data['schedulesGeneralReport']) ? 'true' : '';
387 $optionBackupScheduleReportEmail = !empty($data['schedulesReportEmail']) ? $this->sanitize->sanitizeEmail($data['schedulesReportEmail']) : '';
388
389 if (empty($optionBackupScheduleReportEmail)) {
390 $optionBackupScheduleErrorReport = '';
391 }
392
393 unset($data['schedulesErrorReport'], $data['schedulesReportEmail']);
394
395 $optionBackupScheduleSlackErrorReport = isset($data['schedulesSlackErrorReport']) ? 'true' : '';
396 $optionBackupScheduleReportSlackWebhook = !empty($data['schedulesReportSlackWebhook']) ? $this->sanitize->sanitizeUrl($data['schedulesReportSlackWebhook']) : '';
397 $optionSendEmailAsHTML = isset($data['emailAsHTML']) ? 'true' : '';
398
399 if (empty($optionBackupScheduleReportSlackWebhook)) {
400 $optionBackupScheduleSlackErrorReport = '';
401 }
402
403 unset($data['schedulesErrorSlackReport'], $data['schedulesReportSlackWebhook']);
404
405 $this->setErrorReportOptions(
406 $optionBackupScheduleErrorReport,
407 $optionBackupScheduleWarningReport,
408 $optionBackupScheduleGeneralReport,
409 $optionBackupScheduleReportEmail,
410 $optionBackupScheduleSlackErrorReport,
411 $optionBackupScheduleReportSlackWebhook,
412 $optionSendEmailAsHTML
413 );
414
415 $this->saveHttpAuthCredentials($data);
416 unset($data['httpAuthUsername'], $data['httpAuthPassword']);
417
418 $data['enableBackupBeforeUpdate'] = $data['enableBackupBeforeUpdate'] ?? '0';
419
420 if ($data['enableBackupBeforeUpdate'] === '0') {
421 WPStaging::make(UpdateProtectionSettings::class)->forgetMode();
422 }
423
424 return $showErrorToggleStagingSiteCloning;
425 }
426
427
428
429
430
431
432
433
434
435
436 private function isSettingsFormSubmission(): bool
437 {
438 if (!isset($_POST['option_page']) || !isset($_POST['_wpnonce'])) {
439 return false;
440 }
441
442 if (SanitizeFacade::sanitizeString(wp_unslash($_POST['option_page'])) !== 'wpstg_settings') {
443 return false;
444 }
445
446 return (bool)wp_verify_nonce(SanitizeFacade::sanitizeString(wp_unslash($_POST['_wpnonce'])), 'wpstg_settings-options');
447 }
448 }
449