Passed
Push — master ( ad93c0...af2985 )
by Stefano
03:02
created

PropertyHelper::dateRange()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 28
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 22
nc 1
nop 2
dl 0
loc 28
rs 9.568
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 type, for others schemas
68
     * @return string
69
     */
70
    public function control(string $name, $value, array $options = [], ?string $type = null): string
71
    {
72
        $controlOptions = $this->Schema->controlOptions($name, $value, $this->schema($name, $type));
73
        $controlOptions['label'] = $this->fieldLabel($name, $type);
74
        if (Hash::get($controlOptions, 'class') === 'json') {
75
            $jsonKeys = (array)Configure::read('_jsonKeys');
76
            Configure::write('_jsonKeys', array_merge($jsonKeys, [$name]));
77
        }
78
        if (Hash::check($controlOptions, 'html')) {
79
            return (string)Hash::get($controlOptions, 'html', '');
80
        }
81
82
        return $this->Form->control($name, array_merge($controlOptions, $options));
83
    }
84
85
    /**
86
     * Return label for field by name and type.
87
     * If there's a config for the field and type, return it.
88
     * Return translation of name, otherwise.
89
     *
90
     * @param string $name The field name
91
     * @param string|null $type The object type
92
     * @return string
93
     */
94
    public function fieldLabel(string $name, ?string $type = null): string
95
    {
96
        $defaultLabel = Translate::get($name);
97
        $t = empty($type) ? $this->getView()->get('objectType') : $type;
98
        if (empty($t)) {
99
            return $defaultLabel;
100
        }
101
        $key = sprintf('Properties.%s.labels.fields.%s', $t, $name);
102
103
        return Configure::read($key, $defaultLabel);
104
    }
105
106
    /**
107
     * JSON Schema array of property name
108
     *
109
     * @param string $name The property name
110
     * @param string|null $type The type, for others schemas
111
     * @return array
112
     */
113
    public function schema(string $name, ?string $type = null): array
114
    {
115
        $schema = (array)$this->_View->get('schema');
116
        if (!empty($type)) {
117
            $schemas = (array)$this->_View->get('schemasByType');
118
            $schema = (array)Hash::get($schemas, $type);
119
        }
120
        $res = (array)Hash::get($schema, sprintf('properties.%s', $name));
121
        $default = array_filter([
122
            'type' => Hash::get(self::SPECIAL_PROPS_TYPE, $name),
123
            $name => Hash::get($schema, sprintf('%s', $name)),
124
        ]);
125
        if (in_array($name, self::SPECIAL_PROPS_TYPE)) {
126
            return $default;
127
        }
128
129
        return !empty($res) ? $res : $default;
130
    }
131
132
    /**
133
     * Get formatted property value of a resource or object.
134
     *
135
     * @param array $resource Resource or object data
136
     * @param string $property Property name
137
     * @return string
138
     */
139
    public function value(array $resource, string $property): string
140
    {
141
        $paths = array_filter([
142
            $property,
143
            sprintf('attributes.%s', $property),
144
            sprintf('meta.%s', $property),
145
            (string)Hash::get(self::RELATED_PATHS, $property),
146
        ]);
147
        $value = '';
148
        foreach ($paths as $path) {
149
            if (Hash::check($resource, $path)) {
150
                $value = (string)Hash::get($resource, $path);
151
                break;
152
            }
153
        }
154
155
        return $this->Schema->format($value, $this->schema($property));
156
    }
157
158
    /**
159
     * Return html for fast create form fields.
160
     *
161
     * @param string $type The object type
162
     * @param string $prefix The prefix
163
     * @return string The html for form fields
164
     */
165
    public function fastCreateFields(string $type, string $prefix): string
166
    {
167
        $cfg = (array)Configure::read(sprintf('Properties.%s.fastCreate', $type));
168
        $fields = (array)Hash::get($cfg, 'all', ['status', 'title', 'description']);
169
        $required = (array)Hash::get($cfg, 'required', ['status', 'title']);
170
        $html = '';
171
        $jsonKeys = [];
172
        $ff = [];
173
        foreach ($fields as $field => $fieldType) {
174
            $field = is_numeric($field) ? $fieldType : $field;
175
            $fieldClass = !in_array($field, $required) ? 'fastCreateField' : 'fastCreateField required';
176
            $fieldOptions = [
177
                'id' => sprintf('%s%s', $prefix, $field),
178
                'class' => $fieldClass,
179
                'data-name' => $field,
180
                'key' => sprintf('%s-%s', $type, $field),
181
            ];
182
            if ($field === 'date_ranges') {
183
                $html .= $this->dateRange($type, $fieldOptions);
184
                continue;
185
            }
186
            if ($fieldType === 'json') {
187
                $jsonKeys[] = $field;
188
            }
189
            $this->prepareFieldOptions($field, $fieldType, $fieldOptions);
190
191
            $html .= $this->control($field, '', $fieldOptions, $type);
192
            $ff[] = $field;
193
        }
194
        $jsonKeys = array_unique(array_merge($jsonKeys, (array)Configure::read('_jsonKeys')));
195
        $jsonKeys = array_intersect($jsonKeys, $ff);
196
197
        if (!empty($jsonKeys)) {
198
            $html .= $this->Form->control('_jsonKeys', ['type' => 'hidden', 'value' => implode(',', $jsonKeys)]);
199
        }
200
201
        return $html;
202
    }
203
204
    /**
205
     * Prepare field options for field.
206
     *
207
     * @param string $field The field name
208
     * @param string|null $fieldType The field type, if any
209
     * @param array $fieldOptions The field options
210
     * @return void
211
     */
212
    public function prepareFieldOptions(string $field, ?string $fieldType, array &$fieldOptions): void
213
    {
214
        $method = '';
215
        if (!empty($fieldType) && in_array($fieldType, Control::CONTROL_TYPES)) {
216
            $methodInfo = (array)Form::getMethod(Control::class, $fieldType);
217
            $className = (string)Hash::get($methodInfo, 0);
218
            $method = (string)Hash::get($methodInfo, 1);
219
            $preserveClass = Hash::get($fieldOptions, 'class', '');
220
            $fieldOptions = array_merge($fieldOptions, $className::$method([]));
221
            $fieldOptions['class'] .= ' ' . $preserveClass;
222
            $fieldOptions['class'] = trim($fieldOptions['class']);
223
        }
224
        if ($field === 'status') {
225
            $fieldOptions['v-model'] = 'object.attributes.status';
226
        }
227
    }
228
229
    /**
230
     * Return html for date range fields.
231
     *
232
     * @param string $type The object type
233
     * @param array $options The options
234
     * @return string The html for date range fields
235
     */
236
    public function dateRange(string $type, array $options): string
237
    {
238
        $optionsFrom = array_merge($options, [
239
            'id' => 'start_date_0',
240
            'name' => 'date_ranges[0][start_date]',
241
            'v-datepicker' => 'true',
242
            'date' => 'true',
243
            'time' => 'true',
244
            'daterange' => 'true',
245
        ]);
246
        $optionsTo = array_merge($options, [
247
            'id' => 'end_date_0',
248
            'name' => 'date_ranges[0][end_date]',
249
            'v-datepicker' => 'true',
250
            'date' => 'true',
251
            'time' => 'true',
252
            'daterange' => 'true',
253
        ]);
254
        $optionsAllDay = array_merge($options, [
255
            'id' => 'all_day_0',
256
            'name' => 'date_ranges[0][params][all_day]',
257
            'type' => 'checkbox',
258
        ]);
259
        $from = $this->control(__('From'), '', $optionsFrom, $type);
260
        $to = $this->control(__('To'), '', $optionsTo, $type);
261
        $allDay = $this->control(__('All day'), '', $optionsAllDay, $type);
262
263
        return sprintf('<div class="date-ranges-item mb-1"><div>%s%s%s</div></div>', $from, $to, $allDay);
264
    }
265
}
266