PluginProbe
Metricool – Social media and site statistics / 2.1.1
Metricool – Social media and site statistics v2.1.1
2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.27 trunk
metricool / app / Features / UserSettings / Fields / Field.php

Field.php in Metricool – Social media and site statistics 2.1.1, at app/Features/UserSettings/Fields/Field.php

273 lines 6.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Metricool\Features\UserSettings\Fields;
6
7 use Metricool\Features\UserSettings\Exceptions\ValidatorFailedException;
8 use Metricool\Features\UserSettings\Storage\AbstractStorage;
9 use Metricool\Support\Validation\Rules\AbstractRule;
10 use Metricool\Support\Validation\Validator;
11
12 class Field
13 {
14 /**
15 * Field identifier. Used as key when registering and as default setting name.
16 */
17 public string $name;
18
19 /**
20 * Field data type (boolean|integer|float|string|array|object). Used for
21 * casting and validation.
22 */
23 public string $type;
24
25 /**
26 * Section name within settings UI. Used for grouping fields in the UI.
27 */
28 public ?string $section;
29
30 /**
31 * Storage key name (e.g. `default`). Used to select the storage implementation.
32 */
33 public string $storageName;
34
35 /**
36 * Setting name for persistence; if null, the field {@see $name} is used.
37 */
38 public ?string $settingName;
39
40 /**
41 * @var mixed Default value returned when no stored value exists.
42 */
43 public $defaultValue;
44
45 /**
46 * @var mixed Current value set via setValue(); if null, value is read from
47 * storage.
48 */
49 public $value = null;
50
51 /**
52 * Storage instance used to update/read the field value.
53 */
54 public AbstractStorage $storage;
55
56 /**
57 * Validation rules applied to this field during validate(). Configured
58 * with the 'validators' key in config/user_settings.php, either as rule
59 * strings like 'required' or as {@see AbstractRule} instances.
60 * @var array<string|AbstractRule>
61 */
62 protected array $validators = [];
63
64 public function __construct(string $name)
65 {
66 $this->name = $name;
67 }
68
69 /**
70 * Gets the name of the field. If no {@see $settingName} is set, this value
71 * is used as the setting name.
72 */
73 public function getName(): string
74 {
75 return $this->name;
76 }
77
78 /**
79 * Gets the type of the field
80 */
81 public function getType(): string
82 {
83 return $this->type;
84 }
85
86 /**
87 * Gets the section name of this field
88 */
89 public function getSection(): ?string
90 {
91 return $this->section;
92 }
93
94 /**
95 * Gets the setting name of the field
96 */
97 public function getSettingName(): string
98 {
99 return $this->settingName ?? $this->name;
100 }
101
102 /**
103 * Gets the storage name of the field
104 */
105 public function getStorageName(): string
106 {
107 return $this->storageName;
108 }
109
110 /**
111 * Set the storage object for this field
112 */
113 public function setStorage(AbstractStorage $storage): void
114 {
115 $this->storage = $storage;
116 }
117
118 /**
119 * Gets the storage object for this field
120 */
121 public function getStorage(): AbstractStorage
122 {
123 return $this->storage;
124 }
125
126 /** @return mixed */
127 public function getDefaultValue()
128 {
129 return $this->defaultValue;
130 }
131
132 /**
133 * Sets the value of the field after validating it
134 * @param mixed $value
135 * @param \WP_REST_Request $request Pass the request object for
136 * context-aware validation
137 * @throws \LogicException when storage is not set by developer
138 * @throws ValidatorFailedException when validation fails
139 */
140 public function setValue($value, \WP_REST_Request $request): void
141 {
142 if (empty($this->storage)) {
143 throw new \LogicException('Storage not set for field: ' . esc_html($this->name) . '. First call setStorage() before setValue().');
144 }
145
146 $this->validate($value, $request);
147 $this->value = $value;
148 }
149
150 /**
151 * Retrieves the value of the field from storage but only if {@see setValue}
152 * was not called before. If it was, the set value is returned. Method
153 * returns default value when no value is found in storage.
154 * @return mixed
155 * @throws \LogicException when storage is not set by developer
156 * @throws \Exception
157 */
158 public function getValue()
159 {
160 if (!empty($this->value)) {
161 return $this->castValue($this->value);
162 }
163
164 if (empty($this->storage)) {
165 throw new \LogicException('Storage not set for field: ' . esc_html($this->name) . '. First call setStorage() before getValue().');
166 }
167
168 $value = $this->storage->get($this->getSettingName());
169
170 return is_null($value) ? $this->getDefaultValue() : $this->castValue($value);
171 }
172
173 /**
174 * Casts the value to the type of the field
175 * @param mixed $value
176 * @return mixed
177 */
178 protected function castValue($value)
179 {
180 if ($this->isBoolean()) {
181 return (bool) $value;
182 }
183
184 if ($this->isInteger()) {
185 return (int) $value;
186 }
187
188 if ($this->isFloat()) {
189 return (float) $value;
190 }
191
192 if ($this->isString()) {
193 return (string) $value;
194 }
195
196 if ($this->isArray()) {
197 return (array) $value;
198 }
199
200 if ($this->isObject()) {
201 return (object) $value;
202 }
203
204 return $value;
205 }
206
207 /**
208 * Validates the value of the field against this field's validators using
209 * the {@see Validator}. The request params are passed along as validation
210 * data for context-aware rules like requiredIf.
211 * @param mixed $value
212 * @throws ValidatorFailedException
213 */
214 public function validate($value, \WP_REST_Request $request): void
215 {
216 $data = $request->get_params();
217 $data[$this->getName()] = $value;
218
219 $validator = Validator::make($data, [$this->getName() => $this->validators]);
220
221 if ($validator->fails()) {
222 $messages = $validator->errors()[$this->getName()] ?? [];
223 // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Messages are returned as JSON
224 throw new ValidatorFailedException(implode(' ', $messages));
225 }
226 }
227
228 /**
229 * Apply the configuration array to the field
230 */
231 public function applyConfig(array $config = []): self
232 {
233 $this->type = $config['type'] ?? 'string';
234 $this->section = $config['section'] ?? '';
235 $this->storageName = $config['storage'] ?? 'default';
236 $this->settingName = $config['settingName'] ?? null;
237 $this->defaultValue = $config['defaultValue'] ?? null;
238 $this->validators = $config['validators'] ?? [];
239
240 return $this;
241 }
242
243 public function isBoolean(): bool
244 {
245 return $this->type === 'boolean';
246 }
247
248 public function isInteger(): bool
249 {
250 return $this->type === 'integer';
251 }
252
253 public function isFloat(): bool
254 {
255 return $this->type === 'float';
256 }
257
258 public function isString(): bool
259 {
260 return $this->type === 'string';
261 }
262
263 public function isArray(): bool
264 {
265 return $this->type === 'array';
266 }
267
268 public function isObject(): bool
269 {
270 return $this->type === 'object';
271 }
272 }
273