PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / ajax / Ajax_UpdateOptions.php

Ajax_UpdateOptions.php in 404 Solution trunk, at includes/ajax/Ajax_UpdateOptions.php

117 lines 4.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3
4 if (!defined('ABSPATH')) {
5 exit;
6 }
7
8 require_once __DIR__ . '/AjaxRequestContractValidator.php';
9
10 /**
11 * AJAX adapter for saving plugin settings.
12 */
13 class ABJ_404_Solution_Ajax_UpdateOptions {
14
15 /** @return object */
16 private static function getQueryStringHelperService() {
17 $service = ABJ_404_Solution_Ajax_ServiceResolver::optional('query_string_helper');
18 if (is_object($service) && is_callable(array($service, 'decodeComplicatedData'))) {
19 return $service;
20 }
21
22 $fallback = abj_service('query_string_helper');
23 if (is_object($fallback) && is_callable(array($fallback, 'decodeComplicatedData'))) {
24 return $fallback;
25 }
26
27 throw new \RuntimeException('404 Solution query_string_helper service is unavailable.');
28 }
29
30 /**
31 * @param object $service
32 * @return mixed
33 */
34 private static function decodeComplicatedDataWithService($service, string $encodedData) {
35 $callback = array($service, 'decodeComplicatedData');
36 if (!is_callable($callback)) {
37 throw new \RuntimeException('404 Solution query_string_helper service cannot decode request payloads.');
38 }
39 return call_user_func($callback, $encodedData);
40 }
41
42 /** @return array<mixed, mixed>|null */
43 private static function decodeUpdateOptionsPayload() {
44 if (!isset($_POST['encodedData']) || !is_scalar($_POST['encodedData'])) {
45 ABJ_404_Solution_AjaxRequestContractValidator::requireValidPayload(
46 'ajax-update-options',
47 array()
48 );
49 return null;
50 }
51
52 // wp_magic_quotes() backslash-escapes the payload before any plugin
53 // code sees it. general.js builds the field with encodeURI(), which
54 // percent-encodes " and \\ but leaves ' literal, so every apostrophe a
55 // user typed into a settings field arrives as \\'. Unslash at the
56 // boundary rather than letting the decoder guess.
57 $postData = self::decodeComplicatedDataWithService(
58 self::getQueryStringHelperService(),
59 ABJ_404_Solution_RequestInputNormalizer::normalizeScalar($_POST['encodedData'])
60 );
61 if (!is_array($postData) ||
62 !ABJ_404_Solution_AjaxRequestContractValidator::requireValidLivePayload(
63 'ajax-update-options',
64 $postData
65 )) {
66 return null;
67 }
68
69 return $postData;
70 }
71
72 /**
73 * @param array<mixed, mixed> $postData
74 * @return string
75 */
76 private static function nonceFromDecodedPostData(array $postData) {
77 $nonce = $postData['nonce'] ?? '';
78 return is_string($nonce) ? $nonce : '';
79 }
80
81 /** @return void */
82 public static function updateOptions() {
83 $postData = self::decodeUpdateOptionsPayload();
84 if ($postData === null) {
85 return;
86 }
87
88 /** @var ABJ_404_Solution_PluginLogic $abj404logic */
89 $abj404logic = ABJ_404_Solution_Ajax_ServiceResolver::required('plugin_logic');
90
91 abj_service('ajax_security_gate')->requireAdminWithNonce(
92 'abj404UpdateOptions',
93 'nonce',
94 array('nonce_value' => self::nonceFromDecodedPostData($postData))
95 );
96
97 $result = $abj404logic->settingsUpdate()->updateOptionsFromPOST();
98 if (!is_array($result) || !array_key_exists('success', $result)) {
99 $keys = implode(',', array_keys($result));
100 wp_send_json_error(array('message' => 'Server error (handler result array missing "success" key; keys present: ' . $keys . ')'), 500);
101 return; // @phpstan-ignore deadCode.unreachable
102 }
103
104 if (!$result['success']) {
105 $statusRaw = array_key_exists('status', $result) ? $result['status'] : 400;
106 $status = is_scalar($statusRaw) ? intval($statusRaw) : 400;
107 $messageRaw = array_key_exists('message', $result) ? $result['message'] : 'Server error';
108 $message = is_scalar($messageRaw) ? (string)$messageRaw : 'Server error';
109 wp_send_json_error(array('message' => $message), $status);
110 return; // @phpstan-ignore deadCode.unreachable
111 }
112
113 $data = array_key_exists('data', $result) ? $result['data'] : array();
114 wp_send_json_success($data, 200);
115 }
116 }
117