PluginProbe
Extendify / 3.1.5
Extendify v3.1.5
3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 0.7.0 All 126 releases
extendify / app / Agent / Controllers / OptionsController.php

OptionsController.php in Extendify 3.1.5, at app/Agent/Controllers/OptionsController.php

157 lines 4.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Extendify\Agent\Controllers;
4
5 defined('ABSPATH') || die('No direct access.');
6
7 class OptionsController
8 {
9 // phpcs:disable PSR12.Properties.ConstantVisibility.NotFound
10 const MAX_MATCHES = 25;
11
12 const MAX_VALUE_BYTES = 8000;
13
14 const MAX_BYTES = 20000;
15 // phpcs:enable PSR12.Properties.ConstantVisibility.NotFound
16
17 /**
18 * @param \WP_REST_Request $request The REST API request object.
19 * @return \WP_REST_Response
20 */
21 public static function handle(\WP_REST_Request $request)
22 {
23 $params = $request->get_json_params();
24 $params = is_array($params) ? $params : [];
25 $search = self::text($params, 'search');
26
27 $names = self::requested($params);
28 if ($search !== '') {
29 $names = array_merge($names, self::matching($search));
30 }
31 $names = array_values(array_unique($names));
32
33 if (!$names && $search === '') {
34 return new \WP_REST_Response(
35 ['error' => 'name, names or search must say what to read'],
36 400
37 );
38 }
39
40 if (!$names) {
41 return new \WP_REST_Response(['options' => [], 'searched' => $search]);
42 }
43
44 return new \WP_REST_Response(self::read($names));
45 }
46
47 /**
48 * @param array $params The array to read from.
49 * @param string $key The key to read.
50 * @param string $default What to use when the key is absent.
51 * @return string
52 */
53 private static function text($params, $key, $default = '')
54 {
55 return isset($params[$key]) && is_string($params[$key]) ? $params[$key] : $default;
56 }
57
58 /**
59 * @param array $params The request body.
60 * @return array The option names the caller spelled out.
61 */
62 private static function requested($params)
63 {
64 $names = is_array($params['names'] ?? null) ? $params['names'] : [];
65 array_unshift($names, self::text($params, 'name'));
66
67 return array_values(array_filter($names, function ($name) {
68 return is_string($name) && $name !== '';
69 }));
70 }
71
72 /**
73 * A search for where a setting lives would otherwise fill up with cache entries.
74 *
75 * @param string $search Part of an option name.
76 * @return array The option names holding it.
77 */
78 private static function matching($search)
79 {
80 global $wpdb;
81
82 return $wpdb->get_col($wpdb->prepare(
83 "SELECT option_name FROM {$wpdb->options}
84 WHERE option_name LIKE %s
85 AND option_name NOT LIKE %s
86 AND option_name NOT LIKE %s
87 ORDER BY option_name
88 LIMIT %d",
89 '%' . $wpdb->esc_like($search) . '%',
90 $wpdb->esc_like('_transient_') . '%',
91 $wpdb->esc_like('_site_transient_') . '%',
92 self::MAX_MATCHES
93 ));
94 }
95
96 /**
97 * @param array $names The option names to read.
98 * @return array The response body.
99 */
100 private static function read($names)
101 {
102 $options = [];
103 $spent = 0;
104 foreach ($names as $name) {
105 $option = self::option($name);
106 $spent += self::bytes($option);
107 if ($spent > self::MAX_BYTES && $options) {
108 return [
109 'options' => $options,
110 'truncated' => (count($names) - count($options)) . ' more options were not returned.',
111 ];
112 }
113 $options[] = $option;
114 }
115
116 return ['options' => $options];
117 }
118
119 /**
120 * Escaped JSON counts one Japanese character as six, shrinking every cap to a sixth.
121 *
122 * @param mixed $value Anything the response can carry.
123 * @return integer The bytes it takes as UTF-8.
124 */
125 private static function bytes($value)
126 {
127 return strlen((string) wp_json_encode($value, JSON_UNESCAPED_UNICODE));
128 }
129
130 /**
131 * @param string $name An option name.
132 * @return array The value, a cut of it, or the reason for neither.
133 */
134 private static function option($name)
135 {
136 $value = \get_option($name, null);
137 if ($value === null) {
138 return ['name' => $name, 'error' => "Nothing is stored under {$name}"];
139 }
140
141 $size = is_string($value) ? strlen($value) : self::bytes($value);
142 if ($size <= self::MAX_VALUE_BYTES) {
143 return ['name' => $name, 'value' => $value];
144 }
145
146 if (!is_string($value)) {
147 return ['name' => $name, 'error' => "{$name} holds {$size} bytes, too much to return"];
148 }
149
150 return [
151 'name' => $name,
152 'value' => mb_strcut($value, 0, self::MAX_VALUE_BYTES),
153 'truncated' => 'Cut after ' . self::MAX_VALUE_BYTES . ' bytes, of ' . $size . ' the option holds.',
154 ];
155 }
156 }
157