Passed
Push — master ( aeb5d1...e00263 )
by Stefano
01:16
created

PropertyHelper::translationControl()   B

Complexity

Conditions 7
Paths 16

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 14
nc 16
nop 3
dl 0
loc 21
rs 8.8333
c 0
b 0
f 0
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
namespace App\View\Helper;
14
15
use App\Form\Control;
16
use App\Form\Form;
17
use App\Utility\Translate;
18
use Cake\Core\Configure;
19
use Cake\Utility\Hash;
20
use Cake\View\Helper;
21
22
/**
23
 * Helper class to generate properties html
24
 *
25
 * @property \App\View\Helper\SchemaHelper $Schema The schema helper
26
 * @property \Cake\View\Helper\FormHelper $Form The form helper
27
 */
28
class PropertyHelper extends Helper
29
{
30
    /**
31
     * List of helpers used by this helper
32
     *
33
     * @var array
34
     */
35
    public $helpers = ['Form', 'Schema'];
36
37
    /**
38
     * Special paths to retrieve properties from related resources
39
     *
40
     * @var array
41
     */
42
    public const RELATED_PATHS = [
43
        'file_name' => 'relationships.streams.data.0.attributes.file_name',
44
        'mime_type' => 'relationships.streams.data.0.attributes.mime_type',
45
        'file_size' => 'relationships.streams.data.0.meta.file_size',
46
    ];
47
48
    /**
49
     * Special properties having their own custom schema type
50
     *
51
     * @var array
52
     */
53
    public const SPECIAL_PROPS_TYPE = [
54
        'categories' => 'categories',
55
        'relations' => 'relations',
56
        'file_size' => 'byte',
57
    ];
58
59
    /**
60
     * Generates a form control element for an object property by name, value and options.
61
     * Use SchemaHelper (@see \App\View\Helper\SchemaHelper) to get control options by schema model.
62
     * Use FormHelper (@see \Cake\View\Helper\FormHelper::control) to render control.
63
     *
64
     * @param string $name The property name
65
     * @param mixed|null $value The property value
66
     * @param array $options The form element options, if any
67
     * @param string|null $type The object or resource type, for others schemas
68
     * @return string
69
     */
70
    public function control(string $name, $value, array $options = [], ?string $type = null): string
71
    {
72
        $forceReadonly = !empty(Hash::get($options, 'readonly'));
73
        $controlOptions = $this->Schema->controlOptions($name, $value, $this->schema($name, $type));
74
        $controlOptions['label'] = $this->fieldLabel($name, $type);
75
        $readonly = Hash::get($controlOptions, 'readonly') || $forceReadonly;
76
        if ($readonly === true && array_key_exists('html', $controlOptions)) {
77
            $controlOptions['html'] = str_replace('readonly="false"', 'readonly="true"', $controlOptions['html']);
78
            $controlOptions['html'] = str_replace(':readonly=false', ':readonly=true', $controlOptions['html']);
79
        }
80
        if ($readonly === true && array_key_exists('v-datepicker', $controlOptions)) {
81
            unset($controlOptions['v-datepicker']);
82
        }
83
        if (Hash::get($controlOptions, 'class') === 'json' || Hash::get($controlOptions, 'type') === 'json') {
84
            $jsonKeys = (array)Configure::read('_jsonKeys');
85
            Configure::write('_jsonKeys', array_merge($jsonKeys, [$name]));
86
        }
87
        if (Hash::check($controlOptions, 'html')) {
88
            return (string)Hash::get($controlOptions, 'html', '');
89
        }
90
91
        return $this->Form->control($name, array_merge($controlOptions, $options));
92
    }
93
94
    /**
95
     * Generates a form control for translation property
96
     *
97
     * @param string $name The property name
98
     * @param mixed|null $value The property value
99
     * @param array $options The form element options, if any
100
     * @return string
101
     */
102
    public function translationControl(string $name, $value, array $options = []): string
103
    {
104
        $formControlName = sprintf('translated_fields[%s]', $name);
105
        $controlOptions = $this->Schema->controlOptions($name, $value, $this->schema($name, null));
106
        if (array_key_exists('html', $controlOptions)) {
107
            $controlOptions['html'] = str_replace(sprintf('name="%s"', $name), sprintf('name="%s"', $formControlName), $controlOptions['html']);
108
        }
109
        $controlOptions['label'] = $this->fieldLabel($name, null);
110
        $readonly = Hash::get($controlOptions, 'readonly');
111
        if ($readonly === true && array_key_exists('v-datepicker', $controlOptions)) {
112
            unset($controlOptions['v-datepicker']);
113
        }
114
        if (Hash::get($controlOptions, 'class') === 'json' || Hash::get($controlOptions, 'type') === 'json') {
115
            $jsonKeys = (array)Configure::read('_jsonKeys');
116
            Configure::write('_jsonKeys', array_merge($jsonKeys, [$formControlName]));
117
        }
118
        if (Hash::check($controlOptions, 'html')) {
119
            return (string)Hash::get($controlOptions, 'html', '');
120
        }
121
122
        return $this->Form->control($formControlName, array_merge($controlOptions, $options));
123
    }
124
125
    /**
126
     * Return label for field by name and type.
127
     * If there's a config for the field and type, return it.
128
     * Return translation of name, otherwise.
129
     *
130
     * @param string $name The field name
131
     * @param string|null $type The object type
132
     * @return string
133
     */
134
    public function fieldLabel(string $name, ?string $type = null): string
135
    {
136
        $defaultLabel = Translate::get($name);
137
        $t = empty($type) ? $this->getView()->get('objectType') : $type;
138
        if (empty($t)) {
139
            return $defaultLabel;
140
        }
141
        $key = sprintf('Properties.%s.options.%s.label', $t, $name);
142
143
        return Configure::read($key, $defaultLabel);
144
    }
145
146
    /**
147
     * JSON Schema array of property name
148
     *
149
     * @param string $name The property name
150
     * @param string|null $objectType The object or resource type to use as schema
151
     * @return array|null
152
     */
153
    public function schema(string $name, ?string $objectType = null): ?array
154
    {
155
        $schema = (array)$this->_View->get('schema');
156
        if (!empty($objectType)) {
157
            $schemas = (array)$this->_View->get('schemasByType');
158
            $schema = (array)Hash::get($schemas, $objectType);
159
        }
160
        if (Hash::check(self::SPECIAL_PROPS_TYPE, $name)) {
161
            return array_filter([
162
                'type' => Hash::get(self::SPECIAL_PROPS_TYPE, $name),
163
                $name => Hash::get($schema, sprintf('%s', $name)),
164
            ]);
165
        }
166
        $res = Hash::get($schema, sprintf('properties.%s', $name));
167
168
        return $res === null ? null : (array)$res;
169
    }
170
171
    /**
172
     * Get formatted property value of a resource or object.
173
     *
174
     * @param array $resource Resource or object data
175
     * @param string $property Property name
176
     * @return string
177
     */
178
    public function value(array $resource, string $property): string
179
    {
180
        $paths = array_filter([
181
            $property,
182
            sprintf('attributes.%s', $property),
183
            sprintf('meta.%s', $property),
184
            (string)Hash::get(self::RELATED_PATHS, $property),
185
        ]);
186
        $value = '';
187
        foreach ($paths as $path) {
188
            if (Hash::check($resource, $path)) {
189
                $value = (string)Hash::get($resource, $path);
190
                break;
191
            }
192
        }
193
194
        return $this->Schema->format($value, $this->schema($property));
195
    }
196
197
    /**
198
     * Return html for fast create form fields.
199
     *
200
     * @param string $type The object type
201
     * @param string $prefix The prefix
202
     * @return string The html for form fields
203
     */
204
    public function fastCreateFields(string $type, string $prefix): string
205
    {
206
        $cfg = (array)Configure::read(sprintf('Properties.%s.fastCreate', $type));
207
        $fields = (array)Hash::get($cfg, 'all', ['status', 'title', 'description']);
208
        $required = (array)Hash::get($cfg, 'required', ['status', 'title']);
209
        $html = '';
210
        $jsonKeys = [];
211
        $ff = [];
212
        foreach ($fields as $field => $fieldType) {
213
            $field = is_numeric($field) ? $fieldType : $field;
214
            $fieldClass = !in_array($field, $required) ? 'fastCreateField' : 'fastCreateField required';
215
            $fieldOptions = [
216
                'id' => sprintf('%s%s', $prefix, $field),
217
                'class' => $fieldClass,
218
                'data-name' => $field,
219
                'key' => sprintf('%s-%s', $type, $field),
220
            ];
221
            if ($field === 'date_ranges') {
222
                $html .= $this->dateRange($type, $fieldOptions);
223
                continue;
224
            }
225
            if ($fieldType === 'json') {
226
                $jsonKeys[] = $field;
227
            }
228
            $this->prepareFieldOptions($field, $fieldType, $fieldOptions);
229
230
            $html .= $this->control($field, '', $fieldOptions, $type);
231
            $ff[] = $field;
232
        }
233
        $jsonKeys = array_unique(array_merge($jsonKeys, (array)Configure::read('_jsonKeys')));
234
        $jsonKeys = array_intersect($jsonKeys, $ff);
235
236
        if (!empty($jsonKeys)) {
237
            $html .= $this->Form->control('_jsonKeys', ['type' => 'hidden', 'value' => implode(',', $jsonKeys)]);
238
        }
239
240
        return $html;
241
    }
242
243
    /**
244
     * Prepare field options for field.
245
     *
246
     * @param string $field The field name
247
     * @param string|null $fieldType The field type, if any
248
     * @param array $fieldOptions The field options
249
     * @return void
250
     */
251
    public function prepareFieldOptions(string $field, ?string $fieldType, array &$fieldOptions): void
252
    {
253
        $method = '';
254
        if (!empty($fieldType) && in_array($fieldType, Control::CONTROL_TYPES)) {
255
            $methodInfo = (array)Form::getMethod(Control::class, $fieldType);
256
            $className = (string)Hash::get($methodInfo, 0);
257
            $method = (string)Hash::get($methodInfo, 1);
258
            $preserveClass = Hash::get($fieldOptions, 'class', '');
259
            $fieldOptions = array_merge($fieldOptions, $className::$method([]));
260
            $fieldOptions['class'] .= ' ' . $preserveClass;
261
            $fieldOptions['class'] = trim($fieldOptions['class']);
262
        }
263
        if ($field === 'status') {
264
            $fieldOptions['v-model'] = 'object.attributes.status';
265
        }
266
    }
267
268
    /**
269
     * Return html for date range fields.
270
     *
271
     * @param string $type The object type
272
     * @param array $options The options
273
     * @return string The html for date range fields
274
     */
275
    public function dateRange(string $type, array $options): string
276
    {
277
        $optionsFrom = array_merge($options, [
278
            'id' => 'start_date_0',
279
            'name' => 'date_ranges[0][start_date]',
280
            'v-datepicker' => 'true',
281
            'date' => 'true',
282
            'time' => 'true',
283
            'daterange' => 'true',
284
        ]);
285
        $optionsTo = array_merge($options, [
286
            'id' => 'end_date_0',
287
            'name' => 'date_ranges[0][end_date]',
288
            'v-datepicker' => 'true',
289
            'date' => 'true',
290
            'time' => 'true',
291
            'daterange' => 'true',
292
        ]);
293
        $optionsAllDay = array_merge($options, [
294
            'id' => 'all_day_0',
295
            'name' => 'date_ranges[0][params][all_day]',
296
            'type' => 'checkbox',
297
        ]);
298
        $from = $this->control(__('From'), '', $optionsFrom, $type);
299
        $to = $this->control(__('To'), '', $optionsTo, $type);
300
        $allDay = $this->control(__('All day'), '', $optionsAllDay, $type);
301
302
        return sprintf('<div class="date-ranges-item mb-1"><div>%s%s%s</div></div>', $from, $to, $allDay);
303
    }
304
}
305