Control   A
last analyzed

Complexity

Total Complexity 39

Size/Duplication

Total Lines 389
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 159
dl 0
loc 389
rs 9.28
c 1
b 0
f 0
wmc 39

16 Methods

Rating   Name   Duplication   Size   Complexity  
A oneOf() 0 10 2
A format() 0 3 1
A json() 0 7 1
B control() 0 25 8
A plaintext() 0 5 1
A richtext() 0 12 3
A checkbox() 0 28 4
A date() 0 9 1
A oneOptions() 0 11 2
A email() 0 7 1
A label() 0 12 3
A checkboxNullable() 0 10 1
A datetime() 0 10 1
A enum() 0 27 4
A uri() 0 7 1
A categories() 0 35 5
1
<?php
2
/**
3
 * BEdita, API-first content management framework
4
 * Copyright 2020 ChannelWeb Srl, Chialab Srl
5
 *
6
 * This file is part of BEdita: you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as published
8
 * by the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * See LICENSE.LGPL or <http://gnu.org/licenses/lgpl-3.0.html> for more details.
12
 */
13
14
namespace App\Form;
15
16
use Cake\Core\Configure;
17
use Cake\Utility\Hash;
18
use Cake\Utility\Inflector;
19
20
/**
21
 * Control class provides methods for form controls, customized by control type.
22
 * Used by SchemaHelper to build control options for a property schema (@see \App\View\Helper\SchemaHelper::controlOptions)
23
 */
24
class Control
25
{
26
    /**
27
     * Control types
28
     */
29
    public const CONTROL_TYPES = [
30
        'json',
31
        'richtext',
32
        'plaintext',
33
        'date-time',
34
        'date',
35
        'checkbox',
36
        'checkboxNullable',
37
        'enum',
38
        'categories',
39
    ];
40
41
    /**
42
     * Map JSON schema properties to HTML attributes
43
     */
44
    public const JSON_SCHEMA_HTML_PROPERTIES_MAP = [
45
        'default' => 'value',
46
        'minimum' => 'min',
47
        'maximum' => 'max',
48
    ];
49
50
    /**
51
     * Get control by options
52
     *
53
     * @param array $options The options
54
     * @return array
55
     */
56
    public static function control(array $options): array
57
    {
58
        $type = $options['propertyType'];
59
        $oneOf = self::oneOf((array)$options['schema']);
60
        $format = (string)Hash::get($oneOf, 'format');
61
        $controlOptions = array_intersect_key($options, array_flip(['label', 'disabled', 'readonly', 'step', 'value']));
62
        foreach (self::JSON_SCHEMA_HTML_PROPERTIES_MAP as $key => $attribute) {
63
            if (array_key_exists($key, $oneOf)) {
64
                $controlOptions[$attribute] = $oneOf[$key];
65
            }
66
        }
67
        $defaultValue = Hash::get($controlOptions, 'value');
68
        if ($type === 'text' && in_array($format, ['email', 'uri'])) {
69
            $result = call_user_func_array(Form::getMethod(self::class, $type, $format), [$options]);
70
71
            return array_merge($controlOptions, $result);
72
        }
73
        if (!in_array($type, self::CONTROL_TYPES)) {
74
            $result = compact('type') + ['value' => $options['value'] === null && $defaultValue ? $defaultValue : $options['value']];
75
76
            return array_merge($controlOptions, $result);
77
        }
78
        $result = call_user_func_array(Form::getMethod(self::class, $type), [$options]);
79
80
        return array_merge($controlOptions, $result);
81
    }
82
83
    /**
84
     * Get format from schema
85
     *
86
     * @param array $schema The schema
87
     * @return string
88
     */
89
    public static function format(array $schema): string
90
    {
91
        return (string)Hash::get(self::oneOf($schema), 'format');
92
    }
93
94
    /**
95
     * Get oneOf from schema, excluding null values
96
     *
97
     * @param array $schema The schema
98
     * @return array
99
     */
100
    public static function oneOf(array $schema): array
101
    {
102
        $oneOf = array_filter(
103
            (array)Hash::get($schema, 'oneOf'),
104
            function ($item) {
105
                return !empty($item) && Hash::get($item, 'type') !== 'null';
106
            }
107
        );
108
109
        return (array)Hash::get(array_values($oneOf), 0);
110
    }
111
112
    /**
113
     * Control for json data
114
     *
115
     * @param array $options The options
116
     * @return array
117
     */
118
    public static function json(array $options): array
119
    {
120
        return [
121
            'type' => 'textarea',
122
            'v-jsoneditor' => 'true',
123
            'class' => 'json',
124
            'value' => json_encode(Hash::get($options, 'value')),
125
        ];
126
    }
127
128
    /**
129
     * Control for plaintext
130
     *
131
     * @param array $options The options
132
     * @return array
133
     */
134
    public static function plaintext(array $options): array
135
    {
136
        return [
137
            'type' => 'textarea',
138
            'value' => Hash::get($options, 'value'),
139
        ];
140
    }
141
142
    /**
143
     * Control for richtext
144
     *
145
     * @param array $options The options
146
     * @return array
147
     */
148
    public static function richtext(array $options): array
149
    {
150
        $schema = (array)Hash::get($options, 'schema');
151
        $value = Hash::get($options, 'value');
152
        $richeditorKey = !empty($schema['placeholders']) ? 'v-richeditor.placeholders' : 'v-richeditor';
153
        $override = !empty($options[$richeditorKey]);
154
        $toolbar = $override ? $options[$richeditorKey] : json_encode(Configure::read('RichTextEditor.default.toolbar', ''));
155
156
        return [
157
            'type' => 'textarea',
158
            $richeditorKey => $toolbar,
159
            'value' => $value,
160
        ];
161
    }
162
163
    /**
164
     * Control for datetime
165
     *
166
     * @param array $options The options
167
     * @return array
168
     */
169
    public static function datetime(array $options): array
170
    {
171
        return [
172
            'type' => 'text',
173
            'v-datepicker' => 'true',
174
            'date' => 'true',
175
            'time' => 'true',
176
            'value' => Hash::get($options, 'value'),
177
            'templates' => [
178
                'inputContainer' => '<div class="input datepicker {{type}}{{required}}">{{content}}</div>',
179
            ],
180
        ];
181
    }
182
183
    /**
184
     * Control for date
185
     *
186
     * @param array $options The options
187
     * @return array
188
     */
189
    public static function date(array $options): array
190
    {
191
        return [
192
            'type' => 'text',
193
            'v-datepicker' => 'true',
194
            'date' => 'true',
195
            'value' => Hash::get($options, 'value'),
196
            'templates' => [
197
                'inputContainer' => '<div class="input datepicker {{type}}{{required}}">{{content}}</div>',
198
            ],
199
        ];
200
    }
201
202
    /**
203
     * Control for categories
204
     *
205
     * @param array $options The options
206
     * @return array
207
     */
208
    public static function categories(array $options): array
209
    {
210
        $schema = (array)Hash::get($options, 'schema');
211
        $value = Hash::get($options, 'value');
212
        $categories = array_values(array_filter(
213
            (array)Hash::get($schema, 'categories'),
214
            function ($category) {
215
                return (bool)Hash::get($category, 'enabled');
216
            }
217
        ));
218
        $options = array_map(
219
            function ($category) {
220
                return [
221
                    'value' => Hash::get($category, 'name'),
222
                    'text' => empty($category['label']) ? $category['name'] : $category['label'],
223
                ];
224
            },
225
            $categories
226
        );
227
228
        $checked = [];
229
        if (!empty($value)) {
230
            $names = (array)Hash::extract($value, '{n}.name');
231
            foreach ($categories as $category) {
232
                if (in_array($category['name'], $names)) {
233
                    $checked[] = $category['name'];
234
                }
235
            }
236
        }
237
238
        return [
239
            'type' => 'select',
240
            'options' => $options,
241
            'multiple' => 'checkbox',
242
            'value' => $checked,
243
        ];
244
    }
245
246
    /**
247
     * Control for checkbox
248
     *
249
     * @param array $options The options
250
     * @return array
251
     */
252
    public static function checkbox(array $options): array
253
    {
254
        $schema = (array)Hash::get($options, 'schema');
255
        $value = Hash::get($options, 'value');
256
        if (empty($schema['oneOf'])) {
257
            return [
258
                'type' => 'checkbox',
259
                'checked' => filter_var($value, FILTER_VALIDATE_BOOLEAN),
260
                'value' => '1',
261
            ];
262
        }
263
264
        $options = [];
265
        foreach ($schema['oneOf'] as $one) {
266
            self::oneOptions($options, $one);
267
        }
268
        if (!empty($options)) {
269
            return [
270
                'type' => 'select',
271
                'options' => $options,
272
                'multiple' => 'checkbox',
273
            ];
274
        }
275
276
        return [
277
            'type' => 'checkbox',
278
            'checked' => filter_var($value, FILTER_VALIDATE_BOOLEAN),
279
            'value' => '1',
280
        ];
281
    }
282
283
    /**
284
     * Control for checkbox nullable
285
     *
286
     * @param array $options The options
287
     * @return array
288
     */
289
    public static function checkboxNullable(array $options): array
290
    {
291
        return [
292
            'type' => 'select',
293
            'options' => [
294
                Form::NULL_VALUE => '',
295
                '1' => __('Yes'),
296
                '0' => __('No'),
297
            ],
298
            'value' => Hash::get($options, 'value'),
299
        ];
300
    }
301
302
    /**
303
     * Set options for one of `oneOf` items.
304
     *
305
     * @param array $options The options to update
306
     * @param array $one The one item to check
307
     * @return void
308
     */
309
    public static function oneOptions(array &$options, array $one): void
310
    {
311
        $type = Hash::get($one, 'type');
312
        if ($type !== 'array') {
313
            return;
314
        }
315
        $options = array_map(
316
            function ($item) {
317
                return ['value' => $item, 'text' => Inflector::humanize($item)];
318
            },
319
            (array)Hash::extract($one, 'items.enum')
320
        );
321
    }
322
323
    /**
324
     * Control for enum
325
     *
326
     * @param array $options The options
327
     * @return array
328
     */
329
    public static function enum(array $options): array
330
    {
331
        $schema = (array)Hash::get($options, 'schema');
332
        $value = Hash::get($options, 'value');
333
        $objectType = Hash::get($options, 'objectType');
334
        $property = Hash::get($options, 'property');
335
336
        if (!empty($schema['oneOf'])) {
337
            foreach ($schema['oneOf'] as $one) {
338
                if (!empty($one['enum'])) {
339
                    $schema['enum'] = $one['enum'];
340
                    array_unshift($schema['enum'], '');
341
                }
342
            }
343
        }
344
345
        return [
346
            'type' => 'select',
347
            'options' => array_map(
348
                function (string $value) use ($objectType, $property) {
349
                    $text = self::label((string)$objectType, (string)$property, $value);
350
351
                    return compact('text', 'value');
352
                },
353
                $schema['enum']
354
            ),
355
            'value' => $value,
356
        ];
357
    }
358
359
    /**
360
     * Control for email
361
     *
362
     * @param array $options The options
363
     * @return array
364
     */
365
    public static function email(array $options): array
366
    {
367
        return [
368
            'type' => 'text',
369
            'v-email' => 'true',
370
            'class' => 'email',
371
            'value' => Hash::get($options, 'value'),
372
        ];
373
    }
374
375
    /**
376
     * Control for uri
377
     *
378
     * @param array $options The options
379
     * @return array
380
     */
381
    public static function uri(array $options): array
382
    {
383
        return [
384
            'type' => 'text',
385
            'v-uri' => 'true',
386
            'class' => 'uri',
387
            'value' => Hash::get($options, 'value'),
388
        ];
389
    }
390
391
    /**
392
     * Label for property.
393
     * If set in config Properties.<type>.labels.options.<property>, return it.
394
     * Return humanize of value, otherwise.
395
     *
396
     * @param string $type The object type
397
     * @param string $property The property name
398
     * @param string $value The value
399
     * @return string
400
     */
401
    public static function label(string $type, string $property, string $value): string
402
    {
403
        $label = Configure::read(sprintf('Properties.%s.labels.options.%s', $type, $property));
404
        if (empty($label)) {
405
            return Inflector::humanize($value);
406
        }
407
        $labelVal = (string)Configure::read(sprintf('Properties.%s.labels.options.%s.%s', $type, $property, $value));
408
        if (empty($labelVal)) {
409
            return Inflector::humanize($value);
410
        }
411
412
        return $labelVal;
413
    }
414
}
415