Completed
Push — master ( b31119...230834 )
by Stefano
23s queued 11s
created

Form::checkboxControl()   A

Complexity

Conditions 6
Paths 7

Size

Total Lines 31
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 19
nc 7
nop 2
dl 0
loc 31
rs 9.0111
c 0
b 0
f 0
1
<?php
2
/**
3
 * BEdita, API-first content management framework
4
 * Copyright 2018 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\Core\Utility;
15
16
use Cake\Core\Configure;
17
use Cake\Utility\Hash;
18
use Cake\Utility\Inflector;
19
20
class Form
21
{
22
    /**
23
     * Schema property types
24
     */
25
    public const SCHEMA_PROPERTY_TYPES = ['string', 'number', 'integer', 'boolean', 'array', 'object'];
26
27
    /**
28
     * Custom controls
29
     */
30
    public const CUSTOM_CONTROLS = [
31
        'confirm-password',
32
        'end_date',
33
        'lang',
34
        'old_password',
35
        'password',
36
        'start_date',
37
        'status',
38
        'title',
39
    ];
40
41
    /**
42
     * Control types
43
     */
44
    public const CONTROL_TYPES = ['json', 'textarea', 'date-time', 'date', 'checkbox', 'enum'];
45
46
    /**
47
     * Get control by schema, control type, and value
48
     *
49
     * @param array $schema Object schema array.
50
     * @param string $type Control type.
51
     * @param mixed|null $value Property value.
52
     * @return array
53
     */
54
    public static function control(array $schema, string $type, $value) : array
55
    {
56
        if (!in_array($type, Form::CONTROL_TYPES)) {
57
            return compact('type', 'value');
58
        }
59
        $method = Form::getMethod(sprintf('%sControl', $type));
60
61
        return call_user_func_array($method, [$value, $schema]);
62
    }
63
64
    /**
65
     * Control for json data
66
     *
67
     * @param mixed|null $value Property value.
68
     * @return array
69
     */
70
    protected static function jsonControl($value) : array
71
    {
72
        return [
73
            'type' => 'textarea',
74
            'v-jsoneditor' => 'true',
75
            'class' => 'json',
76
            'value' => json_encode($value),
77
        ];
78
    }
79
80
    /**
81
     * Control for textarea
82
     *
83
     * @param mixed|null $value Property value.
84
     * @return array
85
     */
86
    protected static function textareaControl($value) : array
87
    {
88
        return [
89
            'type' => 'textarea',
90
            'v-richeditor' => 'true',
91
            'ckconfig' => 'configNormal',
92
            'value' => $value,
93
        ];
94
    }
95
96
    /**
97
     * Control for datetime
98
     *
99
     * @param mixed|null $value Property value.
100
     * @return array
101
     */
102
    protected static function datetimeControl($value) : array
103
    {
104
        return [
105
            'type' => 'text',
106
            'v-datepicker' => 'true',
107
            'date' => 'true',
108
            'time' => 'true',
109
            'value' => $value,
110
        ];
111
    }
112
113
    /**
114
     * Control for date
115
     *
116
     * @param mixed|null $value Property value.
117
     * @return array
118
     */
119
    protected static function dateControl($value) : array
120
    {
121
        return [
122
            'type' => 'text',
123
            'v-datepicker' => 'true',
124
            'date' => 'true',
125
            'time' => 'false',
126
            'value' => $value,
127
        ];
128
    }
129
130
    /**
131
     * Control for checkbox
132
     *
133
     * @param mixed|null $value Property value.
134
     * @param array $schema Object schema array.
135
     * @return array
136
     */
137
    protected static function checkboxControl($value, array $schema) : array
138
    {
139
        if (empty($schema['oneOf'])) {
140
            return [
141
                'type' => 'checkbox',
142
                'checked' => (bool)$value,
143
            ];
144
        }
145
146
        $options = [];
147
        foreach ($schema['oneOf'] as $one) {
148
            if (!empty($one['type']) && ($one['type'] === 'array')) {
149
                $options = array_map(
150
                    function ($value) {
151
                        return ['value' => $value, 'text' => Inflector::humanize($value)];
152
                    },
153
                    (array)Hash::extract($one, 'items.enum')
154
                );
155
            }
156
        }
157
        if (!empty($options)) {
158
            return [
159
                'type' => 'select',
160
                'options' => $options,
161
                'multiple' => 'checkbox',
162
            ];
163
        }
164
165
        return [
166
            'type' => 'checkbox',
167
            'checked' => (bool)$value,
168
        ];
169
    }
170
171
    /**
172
     * Control for enum
173
     *
174
     * @param mixed|null $value Property value.
175
     * @param array $schema Object schema array.
176
     * @return array
177
     */
178
    protected static function enumControl($value, array $schema) : array
179
    {
180
        if (!empty($schema['oneOf'])) {
181
            foreach ($schema['oneOf'] as $one) {
182
                if (!empty($one['enum'])) {
183
                    $schema['enum'] = $one['enum'];
184
                    array_unshift($schema['enum'], '');
185
                }
186
            }
187
        }
188
189
        return [
190
            'type' => 'select',
191
            'options' => array_map(
192
                function ($value) {
193
                    return ['value' => $value, 'text' => Inflector::humanize($value)];
194
                },
195
                $schema['enum']
196
            ),
197
        ];
198
    }
199
200
    /**
201
     * Infer control type from property schema
202
     * Possible return values:
203
     *
204
     *   'text'
205
     *   'date-time'
206
     *   'date'
207
     *   'textarea'
208
     *   'enum'
209
     *   'number'
210
     *   'checkbox'
211
     *   'json'
212
     *
213
     * @param mixed $schema The property schema
214
     * @return string
215
     */
216
    public static function controlTypeFromSchema($schema) : string
217
    {
218
        if (!is_array($schema)) {
219
            return 'text';
220
        }
221
        if (!empty($schema['oneOf'])) {
222
            foreach ($schema['oneOf'] as $subSchema) {
223
                if (!empty($subSchema['type']) && $subSchema['type'] === 'null') {
224
                    continue;
225
                }
226
227
                return Form::controlTypeFromSchema($subSchema);
228
            }
229
        }
230
        if (empty($schema['type']) || !in_array($schema['type'], Form::SCHEMA_PROPERTY_TYPES)) {
231
            return 'text';
232
        }
233
        $method = Form::getMethod(sprintf('typeFrom%s', ucfirst($schema['type'])));
234
235
        return call_user_func_array($method, [$schema]);
236
    }
237
238
    /**
239
     * Get controls option by property name.
240
     *
241
     * @param string $name The field name.
242
     * @return array
243
     */
244
    public static function customControlOptions($name) : array
245
    {
246
        if (!in_array($name, Form::CUSTOM_CONTROLS)) {
247
            return [];
248
        }
249
        $method = Form::getMethod(sprintf('%sOptions', $name));
250
251
        return call_user_func_array($method, []);
252
    }
253
254
    /**
255
     * Options for lang
256
     * If available, use config `Project.I18n.languages`
257
     *
258
     * @return array
259
     */
260
    protected static function langOptions() : array
261
    {
262
        $languages = Configure::read('Project.I18n.languages');
263
        if (empty($languages)) {
264
            return [
265
                'type' => 'text',
266
            ];
267
        }
268
        $options = [];
269
        foreach ($languages as $key => $description) {
270
            $options[] = ['value' => $key, 'text' => __($description)];
271
        }
272
273
        return ['type' => 'select'] + compact('options');
274
    }
275
276
    /**
277
     * Options for `start_date`
278
     *
279
     * @return array
280
     */
281
    protected static function startDateOptions() : array
282
    {
283
        return static::datetimeControl(null);
284
    }
285
286
    /**
287
     * Options for `end_date`
288
     *
289
     * @return array
290
     */
291
    protected static function endDateOptions() : array
292
    {
293
        return static::datetimeControl(null);
294
    }
295
296
    /**
297
     * Options for `status` (radio).
298
     *
299
     *   - On ('on')
300
     *   - Draft ('draft')
301
     *   - Off ('off')
302
     *
303
     * @return array
304
     */
305
    protected static function statusOptions() : array
306
    {
307
        return [
308
            'type' => 'radio',
309
            'options' => [
310
                ['value' => 'on', 'text' => __('On')],
311
                ['value' => 'draft', 'text' => __('Draft')],
312
                ['value' => 'off', 'text' => __('Off')],
313
            ],
314
            'templateVars' => [
315
                'containerClass' => 'status',
316
            ],
317
        ];
318
    }
319
320
    /**
321
     * Options for old password
322
     *
323
     * @return array
324
     */
325
    protected static function oldpasswordOptions() : array
326
    {
327
        return [
328
            'class' => 'password',
329
            'label' => __('Current password'),
330
            'placeholder' => __('current password'),
331
            'autocomplete' => 'current-password',
332
            'type' => 'password',
333
            'default' => '',
334
        ];
335
    }
336
337
    /**
338
     * Options for password
339
     *
340
     * @return array
341
     */
342
    protected static function passwordOptions() : array
343
    {
344
        return [
345
            'class' => 'password',
346
            'placeholder' => __('new password'),
347
            'autocomplete' => 'new-password',
348
            'default' => '',
349
        ];
350
    }
351
352
    /**
353
     * Options for confirm password
354
     *
355
     * @return array
356
     */
357
    protected static function confirmpasswordOptions() : array
358
    {
359
        return [
360
            'label' => __('Retype password'),
361
            'id' => 'confirm_password',
362
            'name' => 'confirm-password',
363
            'class' => 'confirm-password',
364
            'placeholder' => __('confirm password'),
365
            'autocomplete' => 'new-password',
366
            'default' => '',
367
            'type' => 'password',
368
        ];
369
    }
370
371
    /**
372
     * Options for title
373
     *
374
     * @return array
375
     */
376
    protected static function titleOptions() : array
377
    {
378
        return [
379
            'class' => 'title',
380
            'type' => 'text',
381
        ];
382
    }
383
384
    /**
385
     * Get control type per string, according to schema property data.
386
     * Possibilities:
387
     *
388
     *    format 'date-time' => 'date-time'
389
     *    format 'date' => 'date'
390
     *    contentMediaType => 'textarea'
391
     *    schema.enum is an array => 'enum'
392
     *
393
     * Return 'text' otherwise.
394
     *
395
     * @param array $schema The property schema
396
     * @return string
397
     */
398
    protected static function typeFromString(array $schema) : string
399
    {
400
        if (!empty($schema['format']) && in_array($schema['format'], ['date', 'date-time'])) {
401
            return $schema['format'];
402
        }
403
        if (!empty($schema['contentMediaType']) && $schema['contentMediaType'] === 'text/html') {
404
            return 'textarea';
405
        }
406
        if (!empty($schema['enum']) && is_array($schema['enum'])) {
407
            return 'enum';
408
        }
409
410
        return 'text';
411
    }
412
413
    /**
414
     * Return the type for number: 'number'
415
     *
416
     * @param array $schema Object schema array.
417
     * @return string
418
     */
419
    protected static function typeFromNumber(array $schema) : string
420
    {
421
        return 'number';
422
    }
423
424
    /**
425
     * Return the type for integer: 'number'
426
     *
427
     * @param array $schema Object schema array.
428
     * @return string
429
     */
430
    protected static function typeFromInteger(array $schema) : string
431
    {
432
        return 'number';
433
    }
434
435
    /**
436
     * Return the type for boolean: 'checkbox'
437
     *
438
     * @param array $schema Object schema array.
439
     * @return string
440
     */
441
    protected static function typeFromBoolean(array $schema) : string
442
    {
443
        return 'checkbox';
444
    }
445
446
    /**
447
     * Return the type for array: 'checkbox'
448
     *
449
     * @param array $schema Object schema array.
450
     * @return string
451
     */
452
    protected static function typeFromArray(array $schema) : string
453
    {
454
        return 'checkbox';
455
    }
456
457
    /**
458
     * Return the type for object: 'json'
459
     *
460
     * @param array $schema Object schema array.
461
     * @return string
462
     */
463
    protected static function typeFromObject(array $schema) : string
464
    {
465
        return 'json';
466
    }
467
468
    /**
469
     * Return method [Form::class, $methodName], if it's callable.
470
     * Otherwise, throw \InvalidArgumentException
471
     *
472
     * @param string $name The method name
473
     * @return array
474
     */
475
    public static function getMethod(string $name) : array
476
    {
477
        $methodName = Inflector::variable(str_replace('-', '_', $name));
478
        $method = [Form::class, $methodName];
479
        if (!is_callable($method)) {
480
            throw new \InvalidArgumentException(sprintf('Method "%s" is not callable', $methodName));
481
        }
482
483
        return $method;
484
    }
485
}
486