Passed
Push — master ( 62b0c9...43b939 )
by Andreas
20:53
created

midgard_admin_asgard_schemadb::_add_copy_fields()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 36
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 24
nc 1
nop 0
dl 0
loc 36
ccs 17
cts 17
cp 1
crap 1
rs 9.536
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package midgard.admin.asgard
4
 * @author CONTENT CONTROL http://www.contentcontrol-berlin.de/
5
 * @copyright CONTENT CONTROL http://www.contentcontrol-berlin.de/
6
 * @license http://www.gnu.org/licenses/gpl.html GNU General Public License
7
 */
8
9
use midcom\datamanager\schemadb;
10
11
/**
12
 * Helper class to create a DM schema from an object via reflection
13
 *
14
 * @package midgard.admin.asgard
15
 */
16
class midgard_admin_asgard_schemadb
17
{
18
    /**
19
     * The object we're working with
20
     *
21
     * @var midcom_core_dbaobject
22
     */
23
    private $_object;
24
25
    /**
26
     * Component config for Asgard
27
     *
28
     * @var midcom_helper_configuration
29
     */
30
    private $_config;
31
32
    /**
33
     * The schema in use
34
     *
35
     * @var array
36
     */
37
    private $schema;
38
39
    /**
40
     * Midgard reflection property instance for the current object's class.
41
     *
42
     * @var midgard_reflection_property
43
     */
44
    private $_reflector;
45
46
    /**
47
     * @var midcom_services_i18n_l10n
48
     */
49
    private $l10n;
50
51
    /**
52
     * Flag that controls if fields used for copying should be added
53
     *
54
     * @var boolean
55
     */
56
    public $add_copy_fields = false;
57
58 31
    public function __construct(midcom_core_dbaobject $object, midcom_helper_configuration $config)
59
    {
60 31
        $this->_object = $object;
61 31
        $this->_reflector = new midgard_reflection_property(midcom_helper_reflector::resolve_baseclass($this->_object));
62 31
        $this->_config = $config;
63 31
        $this->l10n = midcom::get()->i18n->get_l10n('midgard.admin.asgard');
64 31
    }
65
66
    /**
67
     * Generates, loads and prepares the schema database.
68
     *
69
     * The operations are done on all available schemas within the DB.
70
     */
71 9
    public function create($include_fields) : schemadb
72
    {
73 9
        $type = get_class($this->_object);
74 9
        $type_fields = $this->_object->get_properties();
75
76 9
        $this->schema = [
77
            'description' => 'object schema',
78
            'l10n_db'     => 'midgard.admin.asgard',
79
            'fields'      => []
80
        ];
81
82 9
        if ($component = midcom::get()->dbclassloader->get_component_for_class($type)) {
83 9
            $this->schema['l10n_db'] = $component;
84
        }
85
86 9
        if (!empty($include_fields)) {
87
            // Skip the fields that aren't requested, if inclusion list has been defined
88 2
            $type_fields = array_intersect($type_fields, (array) $include_fields);
89
        }
90
91 9
        $type_fields = array_filter($type_fields, [$this, '_filter_schema_fields']);
92
93 9
        usort($type_fields, [$this, 'sort_schema_fields']);
94
95
        // Iterate through object properties
96 9
        foreach ($type_fields as $key) {
97
            // Linked fields should use chooser
98 8
            if ($this->_reflector->is_link($key)) {
99 8
                $this->_add_linked_field($key);
100
                // Skip rest of processing
101 8
                continue;
102
            }
103
104 7
            $field_type = $this->_reflector->get_midgard_type($key);
105 7
            switch ($field_type) {
106
                case MGD_TYPE_GUID:
107
                case MGD_TYPE_STRING:
108 7
                    $this->_add_string_field($key, $type);
109 7
                    break;
110
                case MGD_TYPE_LONGTEXT:
111 7
                    $this->_add_longtext_field($key);
112 7
                    break;
113
                case MGD_TYPE_INT:
114
                case MGD_TYPE_UINT:
115 2
                    $this->_add_int_field($key);
116 2
                    break;
117
                case MGD_TYPE_FLOAT:
118
                    $this->schema['fields'][$key] = [
119
                        'title'       => $key,
120
                        'storage'     => $key,
121
                        'type'        => 'number',
122
                        'widget'      => 'text',
123
                    ];
124
                    break;
125
                case MGD_TYPE_BOOLEAN:
126 5
                    $this->schema['fields'][$key] = [
127 5
                        'title'       => $key,
128 5
                        'storage'     => $key,
129 5
                        'type'        => 'boolean',
130 5
                        'widget'      => 'checkbox',
131
                    ];
132 5
                    break;
133
                case MGD_TYPE_TIMESTAMP:
134
                    $this->schema['fields'][$key] = [
135
                        'title'       => $key,
136
                        'storage'     => $key,
137
                        'type' => 'date',
138
                        'widget' => 'jsdate',
139
                    ];
140
                    break;
141
            }
142
        }
143
144 9
        $this->_add_rcs_field();
145
146 9
        if ($this->add_copy_fields) {
147 2
            $this->_add_copy_fields();
148
        }
149
150 9
        return new schemadb(['default' => $this->schema]);
151
    }
152
153 8
    private function _filter_schema_fields(string $key) : bool
154
    {
155 8
        if (   $key == 'metadata'
156 8
            || in_array($key, $this->_config->get('object_skip_fields'))) {
0 ignored issues
show
Bug introduced by
It seems like $this->_config->get('object_skip_fields') can also be of type false; however, parameter $haystack of in_array() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

156
            || in_array($key, /** @scrutinizer ignore-type */ $this->_config->get('object_skip_fields'))) {
Loading history...
157 7
            return false;
158
        }
159
160 8
        return true;
161
    }
162
163 7
    private function _add_string_field(string $key, string $type)
164
    {
165 7
        if (   $key == 'component'
166 7
            && $type == midcom_db_topic::class) {
167 5
            $this->_add_component_dropdown($key);
168 5
            return;
169
        }
170
171
        // Special name handling, start by checking if given type is same as $this->_object and if not making a dummy copy (we're probably in creation mode then)
172 7
        if ($this->_object instanceof $type) {
173 7
            $name_obj = $this->_object;
174
        } else {
175
            $name_obj = new $type();
176
        }
177
178 7
        if ($key === midcom_helper_reflector::get_name_property($name_obj)) {
179 7
            $this->_add_name_field($key, $name_obj);
180 7
            return;
181
        }
182
183 7
        $this->schema['fields'][$key] = [
184 7
            'title'       => $key,
185 7
            'storage'     => $key,
186 7
            'type'        => 'text',
187 7
            'widget'      => 'text',
188
        ];
189 7
    }
190
191 9
    private function _add_rcs_field()
192
    {
193 9
        $this->schema['fields']['_rcs_message'] = [
194 9
            'title'       => $this->l10n->get('revision comment'),
195
            'storage'     => null,
196 9
            'type'        => 'rcsmessage',
197 9
            'widget'      => 'text',
198
            'start_fieldset' => [
199 9
                'title' => $this->l10n->get('revision'),
200 9
                'css_group' => 'rcs',
201
            ],
202 9
            'end_fieldset' => '',
203
        ];
204 9
    }
205
206 2
    private function _add_int_field(string $key)
207
    {
208 2
        if (in_array($key, ['start', 'end', 'added', 'date'])) {
209
            // We can safely assume that INT fields called start and end store unixtimes
210
            $this->schema['fields'][$key] = [
211
                'title'       => $key,
212
                'storage'     => $key,
213
                'type' => 'date',
214
                'type_config' => [
215
                    'storage_type' => 'UNIXTIME'
216
                    ],
217
                'widget' => 'jsdate',
218
            ];
219
        } else {
220 2
            $this->schema['fields'][$key] = [
221 2
                'title'       => $key,
222 2
                'storage'     => $key,
223 2
                'type'        => 'number',
224 2
                'widget'      => 'text',
225
            ];
226
        }
227 2
    }
228
229 7
    private function _add_longtext_field(string $key)
230
    {
231
        // Figure out nice size for the editing field
232
233 7
        $output_mode = '';
234 7
        $widget = 'textarea';
235 7
        $dm_type = 'text';
236
237 7
        switch ($key) {
238 7
            case 'content':
239 7
            case 'description':
240 7
                $height = 30;
241
242
                // Check the user preference and configuration
243 7
                if (   midgard_admin_asgard_plugin::get_preference('tinymce_enabled')
244 7
                    || (   midgard_admin_asgard_plugin::get_preference('tinymce_enabled') !== '0'
245 7
                        && $this->_config->get('tinymce_enabled'))) {
246 7
                    $widget = 'tinymce';
247
                }
248 7
                $output_mode = 'html';
249
250 7
                break;
251 7
            case 'value':
252 7
            case 'code':
253
                // These are typical "large" fields
254 5
                $height = 30;
255
256
                // Check the user preference and configuration
257 5
                if (   midgard_admin_asgard_plugin::get_preference('codemirror_enabled')
258 5
                    || (   midgard_admin_asgard_plugin::get_preference('codemirror_enabled') !== '0'
259 5
                        && $this->_config->get('codemirror_enabled'))) {
260 5
                    $widget = 'codemirror';
261
                }
262
263 5
                $dm_type = 'php';
264 5
                $output_mode = 'code';
265
266 5
                break;
267
268
            default:
269 7
                $height = 6;
270 7
                break;
271
        }
272
273 7
        $this->schema['fields'][$key] = [
274 7
            'title'       => $key,
275 7
            'storage'     => $key,
276 7
            'type'        => $dm_type,
277
            'type_config' => [
278 7
                'output_mode' => $output_mode,
279
            ],
280 7
            'widget'      => $widget,
281
            'widget_config' => [
282 7
                'height' => $height,
283 7
                'width' => '100%',
284
            ],
285
        ];
286 7
    }
287
288 7
    private function _add_name_field(string $key, midcom_core_dbaobject $name_obj)
289
    {
290 7
        $type_urlname_config = [];
291 7
        $allow_unclean_name_types = $this->_config->get('allow_unclean_names_for');
292 7
        foreach ($allow_unclean_name_types as $allow_unclean_name_types_type) {
293 7
            if ($name_obj->__object instanceof $allow_unclean_name_types_type) {
294
                $type_urlname_config['allow_unclean'] = true;
295
                break;
296
            }
297
        }
298
299
        // Enable generating the name from the title property
300 7
        $type_urlname_config['title_field'] = midcom_helper_reflector::get_title_property($name_obj);
301
302 7
        $this->schema['fields'][$key] = [
303 7
            'title'       => $key,
304 7
            'storage'     => $key,
305 7
            'type'        => 'urlname',
306 7
            'type_config' => $type_urlname_config,
307 7
            'widget'      => 'text',
308
        ];
309 7
    }
310
311 5
    private function _add_component_dropdown(string $key)
312
    {
313 5
        $components = ['' => ''];
314 5
        foreach (midcom::get()->componentloader->get_manifests() as $manifest) {
315
            // Skip purecode components
316 5
            if ($manifest->purecode) {
317 5
                continue;
318
            }
319
320 5
            $components[$manifest->name] = midcom::get()->i18n->get_string($manifest->name, $manifest->name) . " ({$manifest->name})";
321
        }
322 5
        asort($components);
323
324 5
        $this->schema['fields'][$key] = [
325 5
            'title'       => $key,
326 5
            'storage'     => $key,
327 5
            'type'        => 'select',
328
            'type_config' => [
329 5
                'options' => $components,
330
            ],
331 5
            'widget'      => 'select',
332
        ];
333 5
    }
334
335 8
    private function _add_linked_field(string $key)
336
    {
337 8
        $linked_type = $this->_reflector->get_link_name($key);
338 8
        $field_type = $this->_reflector->get_midgard_type($key);
339
340 8
        if ($key == 'up') {
341 8
            $field_label = sprintf($this->l10n->get('under %s'), midgard_admin_asgard_plugin::get_type_label($linked_type));
0 ignored issues
show
Bug introduced by
It seems like $linked_type can also be of type null; however, parameter $type of midgard_admin_asgard_plugin::get_type_label() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

341
            $field_label = sprintf($this->l10n->get('under %s'), midgard_admin_asgard_plugin::get_type_label(/** @scrutinizer ignore-type */ $linked_type));
Loading history...
342
        } else {
343 2
            $type_label = midgard_admin_asgard_plugin::get_type_label($linked_type);
344 2
            if (str_starts_with($type_label, $key)) {
345
                // Handle abbreviations like "lang" for "language"
346
                $field_label = $type_label;
347 2
            } elseif ($key == $type_label) {
348
                $field_label = $key;
349
            } else {
350 2
                $ref = midcom_helper_reflector::get($this->_object);
351 2
                $component_l10n = $ref->get_component_l10n();
352 2
                $field_label = sprintf($this->l10n->get('%s (%s)'), $component_l10n->get($key), $type_label);
353
            }
354
        }
355
356
        // Get the chooser widgets
357 8
        switch ($field_type) {
358
            case MGD_TYPE_UINT:
359
            case MGD_TYPE_STRING:
360
            case MGD_TYPE_GUID:
361 8
                $class = midcom::get()->dbclassloader->get_midcom_class_name_for_mgdschema_object($linked_type);
362 8
                if (!$class) {
363
                    break;
364
                }
365 8
                $this->schema['fields'][$key] = [
366 8
                    'title'       => $field_label,
367 8
                    'storage'     => $key,
368 8
                    'type'        => 'select',
369
                    'type_config' => [
370
                        'require_corresponding_option' => false,
371
                        'options' => [],
372
                        'allow_other' => true,
373
                        'allow_multiple' => false,
374
                    ],
375 8
                    'widget' => 'autocomplete',
376 8
                    'widget_config' => $this->build_autocomplete_config($key, $class, $linked_type),
0 ignored issues
show
Bug introduced by
It seems like $linked_type can also be of type null; however, parameter $linked_type of midgard_admin_asgard_sch...d_autocomplete_config() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

376
                    'widget_config' => $this->build_autocomplete_config($key, $class, /** @scrutinizer ignore-type */ $linked_type),
Loading history...
377 8
                    'required' => (midgard_object_class::get_property_parent($this->_object->__mgdschema_class_name__) == $key)
378
                ];
379 8
                break;
380
        }
381 8
    }
382
383 8
    private function build_autocomplete_config(string $key, string $class, string $linked_type) : array
384
    {
385 8
        $reflector = midcom_helper_reflector::get($linked_type);
386 8
        $component = midcom::get()->dbclassloader->get_component_for_class($linked_type);
387 8
        $searchfields = $reflector->get_search_properties();
388 8
        $label_property = $reflector->get_label_property();
389 8
        $has_parent = !empty(midgard_object_class::get_property_parent($linked_type)) || !empty(midgard_object_class::get_property_up($linked_type));
390 8
        $result_headers = [];
391
392 8
        foreach ($searchfields as $field) {
393 8
            if ($field !== $label_property) {
394 8
                $result_headers[] = [
395 8
                    'name' => $field,
396 8
                    'title' => ucfirst($field),
397
                ];
398
            }
399
        }
400 8
        $searchfields[] = 'guid';
401
402
        return [
403 8
            'class' => $class,
404 8
            'component' => $component,
405 8
            'titlefield' => method_exists($class, 'get_label') ? null : $label_property,
406 8
            'id_field' => $this->_reflector->get_link_target($key),
407 8
            'searchfields' => $searchfields,
408 8
            'result_headers' => $result_headers,
409
            'orders' => [],
410
            'creation_mode_enabled' => true,
411 8
            'creation_handler' => midcom_connection::get_url('self') . "__mfa/asgard/object/create/chooser/{$linked_type}/",
412 8
            'creation_default_key' => $reflector->get_title_property(new $linked_type),
413 8
            'categorize_by_parent_label' => $has_parent
414
        ];
415
    }
416
417 2
    private function _add_copy_fields()
418
    {
419
        // Add switch for copying parameters
420 2
        $this->schema['fields']['parameters'] = [
421 2
            'title'       => $this->l10n->get('copy parameters'),
422
            'storage'     => null,
423 2
            'type'        => 'boolean',
424 2
            'widget'      => 'checkbox',
425
            'default'     => true,
426
        ];
427
428
        // Add switch for copying metadata
429 2
        $this->schema['fields']['metadata'] = [
430 2
            'title'       => $this->l10n->get('copy metadata'),
431
            'storage'     => null,
432 2
            'type'        => 'boolean',
433 2
            'widget'      => 'checkbox',
434
            'default'     => true,
435
        ];
436
437
        // Add switch for copying attachments
438 2
        $this->schema['fields']['attachments'] = [
439 2
            'title'       => $this->l10n->get('copy attachments'),
440
            'storage'     => null,
441 2
            'type'        => 'boolean',
442 2
            'widget'      => 'checkbox',
443
            'default'     => true,
444
        ];
445
446
        // Add switch for copying privileges
447 2
        $this->schema['fields']['privileges'] = [
448 2
            'title'       => $this->l10n->get('copy privileges'),
449
            'storage'     => null,
450 2
            'type'        => 'boolean',
451 2
            'widget'      => 'checkbox',
452
            'default'     => true,
453
        ];
454 2
    }
455
456 29
    private function _get_score(string $field) : int
457
    {
458 29
        $preferred_fields = $this->_config->get('object_preferred_fields');
459 29
        $timerange_fields = $this->_config->get('object_timerange_fields');
460 29
        $phone_fields = $this->_config->get('object_phone_fields');
461 29
        $address_fields = $this->_config->get('object_address_fields');
462 29
        $location_fields = $this->_config->get('object_location_fields');
463
464 29
        $score = 7;
465
466 29
        if ($this->_reflector->get_midgard_type($field) == MGD_TYPE_LONGTEXT) {
467 14
            $score = 1;
468 28
        } elseif (in_array($field, $preferred_fields)) {
0 ignored issues
show
Bug introduced by
It seems like $preferred_fields can also be of type false; however, parameter $haystack of in_array() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

468
        } elseif (in_array($field, /** @scrutinizer ignore-type */ $preferred_fields)) {
Loading history...
469 14
            $score = 0;
470 25
        } elseif ($this->_reflector->is_link($field)) {
471 11
            $score = 2;
472 23
        } elseif (in_array($field, $timerange_fields)) {
473 5
            $score = 3;
474 19
        } elseif (in_array($field, $phone_fields)) {
475 5
            $score = 4;
476 16
        } elseif (in_array($field, $address_fields)) {
477 5
            $score = 5;
478 12
        } elseif (in_array($field, $location_fields)) {
479
            $score = 6;
480
        }
481
482 29
        return $score;
483
    }
484
485 29
    public function sort_schema_fields(string $first, string $second)
486
    {
487 29
        $score1 = $this->_get_score($first);
488 29
        $score2 = $this->_get_score($second);
489 29
        if ($score1 < $score2) {
490 13
            return -1;
491
        }
492 23
        if ($score1 > $score2) {
493 17
            return 1;
494
        }
495 13
        if (   $score1 < 3
496 13
            || $score1 > 6) {
497 10
            return strnatcmp($first, $second);
498
        }
499
        switch ($score1) {
500 3
            case 3:
501 1
                $type = 'timerange';
502 1
                break;
503 2
            case 4:
504 1
                $type = 'phone';
505 1
                break;
506 1
            case 5:
507 1
                $type = 'address';
508 1
                break;
509
            case 6:
510
                $type = 'location';
511
                break;
512
        }
513 3
        $fields = $this->_config->get('object_' . $type . '_fields');
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $type does not seem to be defined for all execution paths leading up to this point.
Loading history...
514 3
        return array_search($first, $fields) <=> array_search($second, $fields);
0 ignored issues
show
Bug introduced by
It seems like $fields can also be of type false; however, parameter $haystack of array_search() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

514
        return array_search($first, /** @scrutinizer ignore-type */ $fields) <=> array_search($second, $fields);
Loading history...
515
    }
516
}
517