1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace GeminiLabs\SiteReviews\Defaults; |
4
|
|
|
|
5
|
|
|
use GeminiLabs\SiteReviews\Contracts\DefaultsContract; |
6
|
|
|
use GeminiLabs\SiteReviews\Helper; |
7
|
|
|
use GeminiLabs\SiteReviews\Helpers\Arr; |
8
|
|
|
use GeminiLabs\SiteReviews\Helpers\Cast; |
9
|
|
|
use GeminiLabs\SiteReviews\Helpers\Str; |
10
|
|
|
use GeminiLabs\SiteReviews\Modules\Sanitizer; |
11
|
|
|
use ReflectionClass; |
12
|
|
|
use ReflectionException; |
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* @method array dataAttributes(array $values = []) |
16
|
|
|
* @method array defaults(): |
17
|
|
|
* @method array filter(array $values = []) |
18
|
|
|
* @method array merge(array $values = []) |
19
|
|
|
* @method array restrict(array $values = []) |
20
|
|
|
* @method array unguardedDataAttributes(array $values = []) |
21
|
|
|
* @method array unguardedDefaults(): |
22
|
|
|
* @method array unguardedFilter(array $values = []) |
23
|
|
|
* @method array unguardedMerge(array $values = []) |
24
|
|
|
* @method array unguardedRestrict(array $values = []) |
25
|
|
|
*/ |
26
|
|
|
abstract class DefaultsAbstract implements DefaultsContract |
27
|
|
|
{ |
28
|
|
|
/** |
29
|
|
|
* The values that should be cast. |
30
|
|
|
* @var array |
31
|
|
|
*/ |
32
|
|
|
public $casts = []; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* The values that should be concatenated. |
36
|
|
|
* @var string[] |
37
|
|
|
*/ |
38
|
|
|
public $concatenated = []; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* The values that should be guarded. |
42
|
|
|
* @var string[] |
43
|
|
|
*/ |
44
|
|
|
public $guarded = []; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* The keys that should be mapped to other keys. |
48
|
|
|
* Note: Mapped keys should not be included in the defaults! |
49
|
|
|
* @var array |
50
|
|
|
*/ |
51
|
|
|
public $mapped = []; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* The values that should be sanitized. |
55
|
|
|
* @var array |
56
|
|
|
*/ |
57
|
|
|
public $sanitize = []; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* The methods that are callable. |
61
|
|
|
* @var array |
62
|
|
|
*/ |
63
|
|
|
protected $callable = [ |
64
|
|
|
'dataAttributes', 'defaults', 'filter', 'merge', 'restrict', |
65
|
|
|
]; |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* @var string |
69
|
|
|
*/ |
70
|
|
|
protected $called; |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* @var array |
74
|
|
|
*/ |
75
|
|
|
protected $defaults = []; |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* The string that should be used for concatenation. |
79
|
|
|
* @var string |
80
|
|
|
*/ |
81
|
|
|
protected $glue = ''; |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* @var string |
85
|
|
|
*/ |
86
|
|
|
protected $hook; |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* @var string |
90
|
|
|
*/ |
91
|
|
|
protected $method; |
92
|
|
|
|
93
|
25 |
|
public function __construct() |
94
|
|
|
{ |
95
|
25 |
|
$hook = 'defaults/'.$this->currentHook().'/defaults'; |
96
|
25 |
|
$this->defaults = $this->app()->filterArray($hook, $this->defaults()); |
97
|
25 |
|
} |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* @param string $name |
101
|
|
|
* @return array |
102
|
|
|
*/ |
103
|
25 |
|
public function __call($name, array $args = []) |
104
|
|
|
{ |
105
|
25 |
|
$this->called = $name; |
106
|
25 |
|
$this->method = Helper::buildMethodName(Str::removePrefix($name, 'unguarded')); |
107
|
25 |
|
$values = $this->normalize(Arr::consolidate(array_shift($args))); |
108
|
25 |
|
$values = $this->mapKeys($values); |
109
|
25 |
|
array_unshift($args, $values); |
110
|
25 |
|
if (in_array($this->method, $this->callable)) { // this also means that the method exists |
111
|
25 |
|
return $this->callMethod($args); |
112
|
|
|
} |
113
|
|
|
glsr_log()->error("Invalid method [$this->method]."); |
114
|
|
|
return $args; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* @return \GeminiLabs\SiteReviews\Application|\GeminiLabs\SiteReviews\Addons\Addon |
119
|
|
|
*/ |
120
|
25 |
|
protected function app() |
121
|
|
|
{ |
122
|
25 |
|
return glsr(); |
123
|
|
|
} |
124
|
|
|
|
125
|
25 |
|
protected function callMethod(array $args) |
126
|
|
|
{ |
127
|
25 |
|
$this->hook = $this->currentHook(); |
128
|
25 |
|
$this->app()->action('defaults', $this, $this->hook, $this->method); |
129
|
25 |
|
$values = 'defaults' === $this->method |
130
|
11 |
|
? $this->defaults // use the filtered defaults (these have not been normalized!) |
131
|
25 |
|
: call_user_func_array([$this, $this->method], $args); |
132
|
25 |
|
if ('dataAttributes' !== $this->method) { |
133
|
25 |
|
$values = $this->sanitize($values); |
134
|
25 |
|
$values = $this->guard($values); |
135
|
|
|
} |
136
|
25 |
|
$args = array_shift($args); |
137
|
25 |
|
return $this->app()->filterArray('defaults/'.$this->hook, $values, $this->method, $args); |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* @return string |
142
|
|
|
*/ |
143
|
25 |
|
protected function currentHook() |
144
|
|
|
{ |
145
|
25 |
|
$hookName = (new ReflectionClass($this))->getShortName(); |
146
|
25 |
|
$hookName = Str::replaceLast('Defaults', '', $hookName); |
147
|
25 |
|
return Str::dashCase($hookName); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* @return string |
152
|
|
|
*/ |
153
|
18 |
|
protected function concatenate($key, $value) |
154
|
|
|
{ |
155
|
18 |
|
if (in_array($key, $this->property('concatenated'))) { |
156
|
|
|
$default = glsr()->args($this->defaults)->$key; |
157
|
|
|
return trim($default.$this->glue.$value); |
158
|
|
|
} |
159
|
18 |
|
return $value; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* Restrict provided values to defaults, remove empty and unchanged values, |
164
|
|
|
* and return data attribute keys with JSON encoded values. |
165
|
|
|
* @return array |
166
|
|
|
*/ |
167
|
|
|
protected function dataAttributes(array $values = []) |
168
|
|
|
{ |
169
|
|
|
$defaults = $this->flattenArrayValues($this->defaults); |
170
|
|
|
$values = $this->flattenArrayValues(shortcode_atts($defaults, $values)); |
171
|
|
|
$filtered = array_filter(array_diff_assoc($values, $defaults)); // remove all empty values |
172
|
|
|
$filtered = $this->sanitize($filtered); |
173
|
|
|
$filtered = $this->guard($filtered); // this after sanitize for a more unique id |
174
|
|
|
$filteredJson = []; |
175
|
|
|
foreach ($filtered as $key => $value) { |
176
|
|
|
$filteredJson['data-'.$key] = !is_scalar($value) |
177
|
|
|
? json_encode((array) $value, JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) |
178
|
|
|
: $value; |
179
|
|
|
} |
180
|
|
|
return $filteredJson; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* The default values. |
185
|
|
|
* @return array |
186
|
|
|
*/ |
187
|
14 |
|
protected function defaults() |
188
|
|
|
{ |
189
|
14 |
|
return []; |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* Remove empty values from the provided values and merge with the defaults. |
194
|
|
|
* @return array |
195
|
|
|
*/ |
196
|
14 |
|
protected function filter(array $values = []) |
197
|
|
|
{ |
198
|
14 |
|
return $this->merge(array_filter($values, Helper::class.'::isNotEmpty')); |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* @return array |
203
|
|
|
*/ |
204
|
|
|
protected function flattenArrayValues(array $values) |
205
|
|
|
{ |
206
|
|
|
array_walk($values, function (&$value) { |
207
|
|
|
if (is_array($value)) { |
208
|
|
|
$value = implode(',', array_filter($value, 'is_scalar')); |
209
|
|
|
} |
210
|
|
|
}); |
211
|
|
|
return $values; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* Remove guarded keys from the provided values. |
216
|
|
|
* @return array |
217
|
|
|
*/ |
218
|
25 |
|
protected function guard(array $values) |
219
|
|
|
{ |
220
|
25 |
|
if (!Str::startsWith('unguarded', $this->called)) { |
221
|
25 |
|
return array_diff_key($values, array_flip($this->property('guarded'))); |
222
|
|
|
} |
223
|
1 |
|
return $values; |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* Map old or deprecated keys to new keys. |
228
|
|
|
* @return array |
229
|
|
|
*/ |
230
|
25 |
|
protected function mapKeys(array $args) |
231
|
|
|
{ |
232
|
25 |
|
foreach ($this->property('mapped') as $old => $new) { |
233
|
17 |
|
if (!empty($args[$old])) { // old always takes precedence |
234
|
16 |
|
$args[$new] = $args[$old]; |
235
|
|
|
} |
236
|
17 |
|
unset($args[$old]); |
237
|
|
|
} |
238
|
25 |
|
return $args; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* Merge provided values with the defaults. |
243
|
|
|
* @return array |
244
|
|
|
*/ |
245
|
14 |
|
protected function merge(array $values = []) |
246
|
|
|
{ |
247
|
14 |
|
return $this->parse($values, $this->defaults); |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
/** |
251
|
|
|
* Normalize provided values, this always runs first. |
252
|
|
|
* @return array |
253
|
|
|
*/ |
254
|
25 |
|
protected function normalize(array $values = []) |
255
|
|
|
{ |
256
|
25 |
|
return $values; |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* @param mixed $values |
261
|
|
|
* @param mixed $defaults |
262
|
|
|
* @return array |
263
|
|
|
*/ |
264
|
16 |
|
protected function parse($values, $defaults) |
265
|
|
|
{ |
266
|
16 |
|
$values = Cast::toArray($values); |
267
|
16 |
|
if (!is_array($defaults)) { |
268
|
1 |
|
return $values; |
269
|
|
|
} |
270
|
16 |
|
$parsed = $defaults; |
271
|
16 |
|
foreach ($values as $key => $value) { |
272
|
15 |
|
if (!is_scalar($value) && isset($parsed[$key])) { |
273
|
13 |
|
$parsed[$key] = Arr::unique($this->parse($value, $parsed[$key])); |
274
|
13 |
|
continue; |
275
|
|
|
} |
276
|
15 |
|
$parsed[$key] = $this->concatenate($key, $value); |
277
|
|
|
} |
278
|
16 |
|
return $parsed; |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
/** |
282
|
|
|
* @param mixed $values |
283
|
|
|
* @return array |
284
|
|
|
*/ |
285
|
18 |
|
protected function parseRestricted($values) |
286
|
|
|
{ |
287
|
18 |
|
$values = Cast::toArray($values); |
288
|
18 |
|
$parsed = []; |
289
|
18 |
|
foreach ($this->defaults as $key => $default) { |
290
|
18 |
|
if (!array_key_exists($key, $values)) { |
291
|
18 |
|
$parsed[$key] = $default; |
292
|
18 |
|
continue; |
293
|
|
|
} |
294
|
18 |
|
if (is_array($default)) { // if the default value is supposed to be an array |
295
|
9 |
|
$parsed[$key] = $this->parse($values[$key], $default); |
296
|
9 |
|
continue; |
297
|
|
|
} |
298
|
18 |
|
$parsed[$key] = $this->concatenate($key, $values[$key]); |
299
|
|
|
} |
300
|
18 |
|
return $parsed; |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
/** |
304
|
|
|
* @return array|void |
305
|
|
|
*/ |
306
|
25 |
|
protected function property($key) |
307
|
|
|
{ |
308
|
|
|
try { |
309
|
25 |
|
$reflection = new ReflectionClass($this); |
310
|
25 |
|
$property = $reflection->getProperty($key); |
311
|
25 |
|
$value = $property->getValue($this); |
312
|
25 |
|
if ($property->isPublic()) { // all public properties are expected to be an array |
313
|
25 |
|
$hook = 'defaults/'.$this->hook.'/'.$key; |
314
|
25 |
|
return $this->app()->filterArray($hook, $value, $this->method); |
315
|
|
|
} |
316
|
|
|
} catch (ReflectionException $e) { |
317
|
|
|
glsr_log()->error("Invalid or protected property [$key]."); |
318
|
|
|
} |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
/** |
322
|
|
|
* Merge the provided values with the defaults and remove any non-default keys. |
323
|
|
|
* @return array |
324
|
|
|
*/ |
325
|
18 |
|
protected function restrict(array $values = []) |
326
|
|
|
{ |
327
|
18 |
|
return $this->parseRestricted($values); |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
/** |
331
|
|
|
* @return array |
332
|
|
|
*/ |
333
|
25 |
|
protected function sanitize(array $values = []) |
334
|
|
|
{ |
335
|
25 |
|
foreach ($this->property('casts') as $key => $cast) { |
336
|
16 |
|
if (array_key_exists($key, $values)) { |
337
|
16 |
|
$values[$key] = Cast::to($cast, $values[$key]); |
338
|
|
|
} |
339
|
|
|
} |
340
|
25 |
|
return (new Sanitizer($values, $this->property('sanitize')))->run(); |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
/** |
344
|
|
|
* @return array |
345
|
|
|
*/ |
346
|
|
|
protected function unmapKeys(array $args) |
347
|
|
|
{ |
348
|
|
|
foreach ($this->property('mapped') as $old => $new) { |
349
|
|
|
if (array_key_exists($new, $args)) { |
350
|
|
|
$args[$old] = $args[$new]; |
351
|
|
|
unset($args[$new]); |
352
|
|
|
} |
353
|
|
|
} |
354
|
|
|
return $args; |
355
|
|
|
} |
356
|
|
|
} |
357
|
|
|
|