Passed
Push — master ( a7c813...0cded3 )
by Andreas
37:11 queued 18:46
created

midgard_admin_asgard_schemadb::_add_linked_field()   B

Complexity

Conditions 8
Paths 28

Size

Total Lines 45
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 8.1105

Importance

Changes 0
Metric Value
cc 8
eloc 34
nc 28
nop 1
dl 0
loc 45
ccs 22
cts 25
cp 0.88
crap 8.1105
rs 8.1315
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
        return !in_array($key, ['id', 'guid', 'metadata']);
156
    }
157
158 7
    private function _add_string_field(string $key, string $type)
159
    {
160 7
        if (   $key == 'component'
161 7
            && $type == midcom_db_topic::class) {
162 5
            $this->_add_component_dropdown($key);
163 5
            return;
164
        }
165
166
        // 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)
167 7
        if ($this->_object instanceof $type) {
168 7
            $name_obj = $this->_object;
169
        } else {
170
            $name_obj = new $type();
171
        }
172
173 7
        if ($key === midcom_helper_reflector::get_name_property($name_obj)) {
174 7
            $this->_add_name_field($key, $name_obj);
175 7
            return;
176
        }
177
178 7
        $this->schema['fields'][$key] = [
179 7
            'title'       => $key,
180 7
            'storage'     => $key,
181 7
            'type'        => 'text',
182 7
            'widget'      => 'text',
183
        ];
184 7
    }
185
186 9
    private function _add_rcs_field()
187
    {
188 9
        $this->schema['fields']['_rcs_message'] = [
189 9
            'title'       => $this->l10n->get('revision comment'),
190
            'storage'     => null,
191 9
            'type'        => 'rcsmessage',
192 9
            'widget'      => 'text',
193
            'start_fieldset' => [
194 9
                'title' => $this->l10n->get('revision'),
195 9
                'css_group' => 'rcs',
196
            ],
197 9
            'end_fieldset' => '',
198
        ];
199 9
    }
200
201 2
    private function _add_int_field(string $key)
202
    {
203 2
        if (in_array($key, ['start', 'end', 'added', 'date'])) {
204
            // We can safely assume that INT fields called start and end store unixtimes
205
            $this->schema['fields'][$key] = [
206
                'title'       => $key,
207
                'storage'     => $key,
208
                'type' => 'date',
209
                'type_config' => [
210
                    'storage_type' => 'UNIXTIME'
211
                    ],
212
                'widget' => 'jsdate',
213
            ];
214
        } else {
215 2
            $this->schema['fields'][$key] = [
216 2
                'title'       => $key,
217 2
                'storage'     => $key,
218 2
                'type'        => 'number',
219 2
                'widget'      => 'text',
220
            ];
221
        }
222 2
    }
223
224 7
    private function _add_longtext_field(string $key)
225
    {
226
        // Figure out nice size for the editing field
227
228 7
        $output_mode = '';
229 7
        $widget = 'textarea';
230 7
        $dm_type = 'text';
231
232 7
        switch ($key) {
233 7
            case 'content':
234 7
            case 'description':
235 7
                $height = 30;
236
237
                // Check the user preference and configuration
238 7
                if (   midgard_admin_asgard_plugin::get_preference('tinymce_enabled')
239 7
                    || (   midgard_admin_asgard_plugin::get_preference('tinymce_enabled') !== '0'
240 7
                        && $this->_config->get('tinymce_enabled'))) {
241 7
                    $widget = 'tinymce';
242
                }
243 7
                $output_mode = 'html';
244
245 7
                break;
246 7
            case 'value':
247 7
            case 'code':
248
                // These are typical "large" fields
249 5
                $height = 30;
250
251
                // Check the user preference and configuration
252 5
                if (   midgard_admin_asgard_plugin::get_preference('codemirror_enabled')
253 5
                    || (   midgard_admin_asgard_plugin::get_preference('codemirror_enabled') !== '0'
254 5
                        && $this->_config->get('codemirror_enabled'))) {
255 5
                    $widget = 'codemirror';
256
                }
257
258 5
                $dm_type = 'php';
259 5
                $output_mode = 'code';
260
261 5
                break;
262
263
            default:
264 7
                $height = 6;
265 7
                break;
266
        }
267
268 7
        $this->schema['fields'][$key] = [
269 7
            'title'       => $key,
270 7
            'storage'     => $key,
271 7
            'type'        => $dm_type,
272
            'type_config' => [
273 7
                'output_mode' => $output_mode,
274
            ],
275 7
            'widget'      => $widget,
276
            'widget_config' => [
277 7
                'height' => $height,
278 7
                'width' => '100%',
279
            ],
280
        ];
281 7
    }
282
283 7
    private function _add_name_field(string $key, midcom_core_dbaobject $name_obj)
284
    {
285 7
        $type_urlname_config = [];
286 7
        $allow_unclean_name_types = $this->_config->get('allow_unclean_names_for');
287 7
        foreach ($allow_unclean_name_types as $allow_unclean_name_types_type) {
288 7
            if ($name_obj->__object instanceof $allow_unclean_name_types_type) {
289
                $type_urlname_config['allow_unclean'] = true;
290
                break;
291
            }
292
        }
293
294
        // Enable generating the name from the title property
295 7
        $type_urlname_config['title_field'] = midcom_helper_reflector::get_title_property($name_obj);
296
297 7
        $this->schema['fields'][$key] = [
298 7
            'title'       => $key,
299 7
            'storage'     => $key,
300 7
            'type'        => 'urlname',
301 7
            'type_config' => $type_urlname_config,
302 7
            'widget'      => 'text',
303
        ];
304 7
    }
305
306 5
    private function _add_component_dropdown(string $key)
307
    {
308 5
        $components = ['' => ''];
309 5
        foreach (midcom::get()->componentloader->get_manifests() as $manifest) {
310
            // Skip purecode components
311 5
            if ($manifest->purecode) {
312 5
                continue;
313
            }
314
315 5
            $components[$manifest->name] = midcom::get()->i18n->get_string($manifest->name, $manifest->name) . " ({$manifest->name})";
316
        }
317 5
        asort($components);
318
319 5
        $this->schema['fields'][$key] = [
320 5
            'title'       => $key,
321 5
            'storage'     => $key,
322 5
            'type'        => 'select',
323
            'type_config' => [
324 5
                'options' => $components,
325
            ],
326 5
            'widget'      => 'select',
327
        ];
328 5
    }
329
330 8
    private function _add_linked_field(string $key)
331
    {
332 8
        $linked_type = $this->_reflector->get_link_name($key);
333 8
        $field_type = $this->_reflector->get_midgard_type($key);
334
335 8
        if ($key == 'up') {
336 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

336
            $field_label = sprintf($this->l10n->get('under %s'), midgard_admin_asgard_plugin::get_type_label(/** @scrutinizer ignore-type */ $linked_type));
Loading history...
337
        } else {
338 2
            $type_label = midgard_admin_asgard_plugin::get_type_label($linked_type);
339 2
            if (str_starts_with($type_label, $key)) {
340
                // Handle abbreviations like "lang" for "language"
341
                $field_label = $type_label;
342 2
            } elseif ($key == $type_label) {
343
                $field_label = $key;
344
            } else {
345 2
                $ref = midcom_helper_reflector::get($this->_object);
346 2
                $component_l10n = $ref->get_component_l10n();
347 2
                $field_label = sprintf($this->l10n->get('%s (%s)'), $component_l10n->get($key), $type_label);
348
            }
349
        }
350
351
        // Get the chooser widgets
352 8
        switch ($field_type) {
353
            case MGD_TYPE_UINT:
354
            case MGD_TYPE_STRING:
355
            case MGD_TYPE_GUID:
356 8
                $class = midcom::get()->dbclassloader->get_midcom_class_name_for_mgdschema_object($linked_type);
357 8
                if (!$class) {
358
                    break;
359
                }
360 8
                $this->schema['fields'][$key] = [
361 8
                    'title'       => $field_label,
362 8
                    'storage'     => $key,
363 8
                    'type'        => 'select',
364
                    'type_config' => [
365
                        'require_corresponding_option' => false,
366
                        'options' => [],
367
                        'allow_other' => true,
368
                        'allow_multiple' => false,
369
                    ],
370 8
                    'widget' => 'autocomplete',
371 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

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

463
        } elseif (in_array($field, /** @scrutinizer ignore-type */ $preferred_fields)) {
Loading history...
464 14
            $score = 0;
465 25
        } elseif ($this->_reflector->is_link($field)) {
466 11
            $score = 2;
467 23
        } elseif (in_array($field, $timerange_fields)) {
468 5
            $score = 3;
469 19
        } elseif (in_array($field, $phone_fields)) {
470 5
            $score = 4;
471 16
        } elseif (in_array($field, $address_fields)) {
472 5
            $score = 5;
473 12
        } elseif (in_array($field, $location_fields)) {
474
            $score = 6;
475
        }
476
477 29
        return $score;
478
    }
479
480 29
    public function sort_schema_fields(string $first, string $second)
481
    {
482 29
        $score1 = $this->_get_score($first);
483 29
        $score2 = $this->_get_score($second);
484 29
        if ($score1 < $score2) {
485 13
            return -1;
486
        }
487 23
        if ($score1 > $score2) {
488 17
            return 1;
489
        }
490 13
        if (   $score1 < 3
491 13
            || $score1 > 6) {
492 10
            return strnatcmp($first, $second);
493
        }
494
        switch ($score1) {
495 3
            case 3:
496 1
                $type = 'timerange';
497 1
                break;
498 2
            case 4:
499 1
                $type = 'phone';
500 1
                break;
501 1
            case 5:
502 1
                $type = 'address';
503 1
                break;
504
            case 6:
505
                $type = 'location';
506
                break;
507
        }
508 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...
509 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

509
        return array_search($first, /** @scrutinizer ignore-type */ $fields) <=> array_search($second, $fields);
Loading history...
510
    }
511
}
512