Passed
Pull Request — master (#556)
by Stefano
04:41 queued 02:11
created

SchemaHelper::formatByte()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * BEdita, API-first content management framework
4
 * Copyright 2019 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\View\Helper;
15
16
use App\Form\Control;
17
use App\Form\ControlType;
18
use App\Form\Options;
19
use Cake\Core\Configure;
20
use Cake\I18n\Number;
21
use Cake\Utility\Hash;
22
use Cake\Utility\Inflector;
23
use Cake\View\Helper;
24
25
/**
26
 * Schema helper
27
 *
28
 * @property \Cake\View\Helper\TimeHelper $Time
29
 */
30
class SchemaHelper extends Helper
31
{
32
    /**
33
     * {@inheritDoc}
34
     *
35
     * @var array
36
     */
37
    public $helpers = ['Time'];
38
39
    /**
40
     * Default translatable fields to be prepended in translations
41
     *
42
     * @var array
43
     */
44
    public const DEFAULT_TRANSLATABLE = ['title', 'description', 'body'];
45
46
    /**
47
     * Translatable media types
48
     *
49
     * @var array
50
     */
51
    public const TRANSLATABLE_MEDIATYPES = ['text/html', 'text/plain'];
52
53
    /**
54
     * Get control options for a property schema.
55
     *
56
     * @param string $name Property name.
57
     * @param mixed $value Property value.
58
     * @param array $schema Object schema array.
59
     * @return array
60
     */
61
    public function controlOptions(string $name, $value, $schema = []): array
62
    {
63
        $options = Options::customControl($name, $value);
64
        if (!empty($options)) {
65
            return $options;
66
        }
67
        // verify if there's an handler by $type.$name
68
        $objectType = (string)$this->_View->get('objectType');
69
        if (!empty(Configure::read(sprintf('Control.handlers.%s.%s', $objectType, $name)))) {
70
            $handler = Configure::read(sprintf('Control.handlers.%s.%s', $objectType, $name));
71
72
            return call_user_func_array([$handler['class'], $handler['method']], [$value, $schema]);
73
        }
74
        $type = ControlType::fromSchema((array)$schema);
75
76
        return Control::control((array)$schema, $type, $value);
77
    }
78
79
    /**
80
     * Display a formatted property value using schema.
81
     *
82
     * @param mixed $value Property value.
83
     * @param array $schema Property schema array.
84
     * @return string
85
     */
86
    public function format($value, $schema = []): string
87
    {
88
        $type = static::typeFromSchema((array)$schema);
89
        $type = Inflector::variable(str_replace('-', '_', $type));
90
        $methodName = sprintf('format%s', ucfirst($type));
91
        if (method_exists($this, $methodName) && $value !== null) {
92
            return call_user_func_array([$this, $methodName], [$value]);
93
        }
94
        if (is_array($value)) {
95
            return json_encode($value);
96
        }
97
98
        return (string)$value;
99
    }
100
101
    /**
102
     * Format byte value
103
     *
104
     * @param mixed $value Property value.
105
     * @return string
106
     */
107
    protected function formatByte($value): string
108
    {
109
        return (string)Number::toReadableSize((int)$value);
110
    }
111
112
    /**
113
     * Format boolean value
114
     *
115
     * @param mixed $value Property value.
116
     * @return string
117
     */
118
    protected function formatBoolean($value): string
119
    {
120
        $res = filter_var($value, FILTER_VALIDATE_BOOLEAN);
121
122
        return (string)($res ? __('Yes') : __('No'));
123
    }
124
125
    /**
126
     * Format date value
127
     *
128
     * @param mixed $value Property value.
129
     * @return string
130
     */
131
    protected function formatDate($value): string
132
    {
133
        return (string)$this->Time->format($value);
134
    }
135
136
    /**
137
     * Format date-time value
138
     *
139
     * @param mixed $value Property value.
140
     * @return string
141
     */
142
    protected function formatDateTime($value): string
143
    {
144
        return (string)$this->Time->format($value);
145
    }
146
147
    /**
148
     * Infer type from property schema in JSON-SCHEMA format
149
     * Possible return values:
150
     *
151
     *   'string'
152
     *   'number'
153
     *   'integer'
154
     *   'boolean'
155
     *   'array'
156
     *   'object'
157
     *   'date-time'
158
     *   'date'
159
     *
160
     * @param mixed $schema The property schema
161
     * @return string
162
     */
163
    public static function typeFromSchema(array $schema): string
164
    {
165
        if (!empty($schema['oneOf'])) {
166
            foreach ($schema['oneOf'] as $subSchema) {
167
                if (!empty($subSchema['type']) && $subSchema['type'] === 'null') {
168
                    continue;
169
                }
170
171
                return static::typeFromSchema($subSchema);
172
            }
173
        }
174
        if (empty($schema['type']) || !in_array($schema['type'], ControlType::SCHEMA_PROPERTY_TYPES)) {
175
            return 'string';
176
        }
177
        $format = Hash::get($schema, 'format');
178
        if ($schema['type'] === 'string' && in_array($format, ['date', 'date-time'])) {
179
            return $format;
180
        }
181
182
        return $schema['type'];
183
    }
184
185
    /**
186
     * Provides list of translatable fields per schema properties
187
     *
188
     * @param array $properties The array of schema properties
189
     * @return array
190
     */
191
    public function translatableFields(array $properties): array
192
    {
193
        if (empty($properties)) {
194
            return [];
195
        }
196
197
        $fields = [];
198
        foreach ($properties as $name => $property) {
199
            if ($this->translatableType($property)) {
200
                $fields[] = $name;
201
            }
202
        }
203
204
        // put specific fields at the beginning of the fields array
205
        foreach (array_reverse(static::DEFAULT_TRANSLATABLE) as $field) {
206
            if (in_array($field, $fields)) {
207
                unset($fields[array_search($field, $fields)]);
208
                array_unshift($fields, $field);
209
            }
210
        }
211
212
        return $fields;
213
    }
214
215
    /**
216
     * Helper recursive method to check if a property is translatable checking its JSON SCHEMA
217
     *
218
     * @param array $schema Property schema
219
     * @return bool
220
     */
221
    protected function translatableType(array $schema): bool
222
    {
223
        if (!empty($schema['oneOf'])) {
224
            return array_reduce(
225
                (array)$schema['oneOf'],
226
                function ($carry, $item) {
227
                    if ($carry) {
228
                        return true;
229
                    }
230
231
                    return $this->translatableType((array)$item);
232
                }
233
            );
234
        }
235
        // accept as translatable 'string' type having text/html or tex/plain 'contentMediaType'
236
        $type = (string)Hash::get($schema, 'type');
237
        $contentMediaType = Hash::get($schema, 'contentMediaType');
238
239
        return $type === 'string' &&
240
            in_array($contentMediaType, static::TRANSLATABLE_MEDIATYPES);
241
    }
242
243
    /**
244
     * Verify field's schema, return true if field is sortable.
245
     *
246
     * @param string $field The field to check
247
     * @return bool
248
     */
249
    public function sortable(string $field): bool
250
    {
251
        // exception 'date_ranges' default sortable
252
        if ($field === 'date_ranges') {
253
            return true;
254
        }
255
        $schema = (array)$this->_View->get('schema');
256
        $schema = Hash::get($schema, sprintf('properties.%s', $field), []);
257
258
        // empty schema, then not sortable
259
        if (empty($schema)) {
260
            return false;
261
        }
262
        $type = self::typeFromSchema($schema);
263
264
        // not sortable: 'array', 'object'
265
        if (in_array($type, ['array', 'object'])) {
266
            return false;
267
        }
268
        // other types are sortable: 'string', 'number', 'integer', 'boolean', 'date-time', 'date'
269
270
        return true;
271
    }
272
}
273