Passed
Push — master ( fef947...832870 )
by Andreas
12:13
created

autocomplete::sanitize_label()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright CONTENT CONTROL GmbH, http://www.contentcontrol-berlin.de
4
 */
5
6
namespace midcom\datamanager\helper;
7
8
use midcom;
9
use midcom_connection;
10
use midcom_core_query;
11
use midcom_core_account;
12
use midcom_db_person;
13
use midcom_error;
14
use midcom_helper_reflector;
15
use midcom_baseclasses_components_configuration;
16
17
/**
18
 * Experimental autocomplete helper
19
 */
20
class autocomplete
21
{
22
    /**
23
     * The request data we're working on
24
     */
25
    private array $request;
26
27 2
    public function __construct(array $data)
28
    {
29 2
        $this->request = $data;
30 2
        $this->verify_request();
31
    }
32
33 2
    private function verify_request()
34
    {
35 2
        if (!class_exists($this->request['class'])) {
36
            throw new midcom_error("Class {$this->request['class']} could not be loaded");
37
        }
38
39 2
        if (empty($this->request['searchfields'])) {
40
            throw new midcom_error("No fields to search for defined");
41
        }
42
43 2
        if (empty($this->request["term"])) {
44
            throw new midcom_error("Empty query string.");
45
        }
46
47 2
        $this->request['titlefield'] ??= null;
48 2
        $this->request['result_headers'] ??= [];
49
    }
50
51 1
    private function prepare_qb()
52
    {
53 1
        $qb = $this->request['class']::new_query_builder();
54
55 1
        if (!empty($this->request['constraints'])) {
56
            $this->apply_constraints($qb, $this->request['constraints']);
57
        }
58
59 1
        if ($constraints = $this->get_search_constraints()) {
60 1
            $qb->begin_group('OR');
61 1
            $this->apply_constraints($qb, $constraints);
62 1
            $qb->end_group();
63
        }
64
65 1
        if (!empty($this->request['orders'])) {
66
            ksort($this->request['orders']);
67
            foreach ($this->request['orders'] as $data) {
68
                foreach ($data as $field => $order) {
69
                    $qb->add_order($field, $order);
70
                }
71
            }
72
        }
73 1
        return $qb;
74
    }
75
76 1
    private function apply_constraints(midcom_core_query $query, array $constraints)
77
    {
78 1
        foreach ($constraints as $key => $data) {
79 1
            if (   !array_key_exists('value', $data)
80 1
                || empty($data['field'])
81 1
                || empty($data['op'])) {
82
                debug_add("Constraint #{$key} is not correctly defined, skipping", MIDCOM_LOG_WARN);
83
                continue;
84
            }
85 1
            if ($data['field'] === 'username') {
86
                midcom_core_account::add_username_constraint($query, $data['op'], $data['value']);
87
            } else {
88 1
                $query->add_constraint($data['field'], $data['op'], $data['value']);
89
            }
90
        }
91
    }
92
93 1
    private function get_search_constraints() : array
94
    {
95 1
        $constraints = [];
96 1
        $query = $this->request["term"];
97 1
        if (preg_match('/^%+$/', $query)) {
98
            debug_add('query is all wildcards, don\'t waste time in adding LIKE constraints');
99
            return $constraints;
100
        }
101
102 1
        $reflector = new midcom_helper_reflector($this->request['class']);
103
104 1
        foreach ($this->request['searchfields'] as $field) {
105 1
            $field_type = $reflector->get_midgard_type($field);
106 1
            $operator = 'LIKE';
107 1
            if (str_contains($field, '.')) {
108
                //TODO: This should be resolved properly
109
                $field_type = MGD_TYPE_STRING;
110
            }
111
            switch ($field_type) {
112 1
                case MGD_TYPE_GUID:
113
                case MGD_TYPE_STRING:
114
                case MGD_TYPE_LONGTEXT:
115 1
                    $query = $this->get_querystring();
116 1
                    break;
117
                case MGD_TYPE_INT:
118
                case MGD_TYPE_UINT:
119
                case MGD_TYPE_FLOAT:
120
                    $operator = '=';
121
                    break;
122
                default:
123
                    debug_add("can't handle field type " . $field_type, MIDCOM_LOG_WARN);
124
                    continue 2;
125
            }
126 1
            debug_add("adding search (ORed) constraint: {$field} {$operator} '{$query}'");
127 1
            $constraints[] = [
128 1
                'field' => $field,
129 1
                'op' => $operator,
130 1
                'value' => $query
131 1
            ];
132
        }
133 1
        return $constraints;
134
    }
135
136 2
    public function get_querystring() : string
137
    {
138 2
        $query = $this->request["term"];
139 2
        $wildcard_query = $query;
140 2
        if (   isset($this->request['auto_wildcards'])
141 2
            && !str_contains($query, '%')) {
142 1
            switch ($this->request['auto_wildcards']) {
143 1
                case 'start':
144
                    $wildcard_query = '%' . $query;
145
                    break;
146 1
                case 'end':
147 1
                    $wildcard_query = $query . '%';
148 1
                    break;
149
                case 'both':
150
                    $wildcard_query = '%' . $query . '%';
151
                    break;
152
                default:
153
                    debug_add("Don't know how to handle auto_wildcards value '" . $this->request['auto_wildcards'] . "'", MIDCOM_LOG_WARN);
154
                    break;
155
            }
156
        }
157 2
        $wildcard_query = str_replace("*", "%", $wildcard_query);
158 2
        return preg_replace('/%+/', '%', $wildcard_query);
159
    }
160
161 1
    public function get_objects() : array
162
    {
163 1
        return $this->prepare_qb()->execute();
164
    }
165
166 1
    public function get_results() : array
167
    {
168 1
        if (empty($this->request["id_field"])) {
169
            throw new midcom_error("Empty ID field.");
170
        }
171
172 1
        $results = $this->get_objects();
173 1
        $items = [];
174
175 1
        foreach ($results as $object) {
176 1
            $item = [
177 1
                'id' => $object->{$this->request['id_field']},
178 1
                'label' => self::create_item_label($object, $this->request['result_headers'], $this->request['titlefield']),
179 1
            ];
180 1
            if (!empty($this->request['result_headers'])) {
181 1
                $item['description'] = self::build_label($object, array_column($this->request['result_headers'], 'name'));
182
            }
183 1
            if (!empty($this->request['categorize_by_parent_label'])) {
184
                $item['category'] = '';
185
                if ($parent = $object->get_parent()) {
186
                    $item['category'] = midcom_helper_reflector::get($parent)->get_object_label($parent);
187
                }
188
            }
189 1
            $item['value'] = $item['label'];
190
191 1
            $items[] = $item;
192
        }
193 1
        usort($items, $this->sort_items(...));
194
195 1
        return $items;
196
    }
197
198
    public static function sort_items(array $a, array $b) : int
199
    {
200
        if (isset($a['category'])) {
201
            $cmp = strnatcasecmp($a['category'], $b['category']);
202
            if ($cmp != 0) {
203
                return $cmp;
204
            }
205
        }
206
        return strnatcasecmp($a['label'], $b['label']);
207
    }
208
209 74
    public static function add_head_elements(bool $creation_mode_enabled = false, bool $sortable = false)
210
    {
211 74
        $head = midcom::get()->head;
212 74
        $head->add_stylesheet(MIDCOM_STATIC_URL . "/stock-icons/font-awesome-4.7.0/css/font-awesome.min.css");
213 74
        $head->add_stylesheet(MIDCOM_STATIC_URL . '/midcom.datamanager/autocomplete.css');
214
215 74
        $components = ['menu', 'autocomplete'];
216 74
        if ($sortable) {
217 2
            $components[] = 'mouse';
218 2
            $components[] = 'sortable';
219
        }
220 74
        if ($creation_mode_enabled) {
221 23
            $components = array_merge($components, ['mouse', 'draggable', 'resizable', 'button', 'dialog']);
222 23
            $head->add_jsfile(MIDCOM_STATIC_URL . '/midcom.workflow/workflow.js');
223
        }
224 74
        $head->enable_jquery_ui($components);
225 74
        $head->add_jsfile(MIDCOM_STATIC_URL . '/midcom.datamanager/autocomplete.js');
226
    }
227
228 7
    public static function get_widget_config(string $type) : array
229
    {
230 7
        $handler_url = midcom_connection::get_url('self') . 'midcom-exec-midcom.datamanager/autocomplete.php';
231
232 7
        $widget_config = midcom_baseclasses_components_configuration::get('midcom.datamanager', 'config')->get('clever_classes');
233 7
        $config = $widget_config[$type];
234 7
        $config['handler_url'] = $handler_url;
235 7
        return $config;
236
    }
237
238 39
    public static function create_item_label($object, array $result_headers, ?string $titlefield) : string
239
    {
240 39
        if (!empty($titlefield)) {
241 32
            if (is_callable($titlefield)) {
242
                return $titlefield($object);
243
            }
244 32
            if ($label = self::build_label($object, (array) $titlefield)) {
245 26
                return $label;
246
            }
247
        }
248 17
        return midcom_helper_reflector::get($object)->get_object_label($object) ?:
249 9
            self::build_label($object, array_column($result_headers, 'name')) ?:
250 17
            get_class($object) . ' #' . $object->id;
251
    }
252
253 36
    private static function build_label($object, array $fields) : string
254
    {
255 36
        $label = [];
256 36
        foreach ($fields as $field) {
257 36
            if ($value = self::get_property_string($object, $field)) {
258 34
                $label[] = $value;
259
            }
260
        }
261 36
        return implode(', ', $label);
262
    }
263
264 36
    private static function get_property_string($object, string $field) : string
265
    {
266 36
        if (preg_match('/^metadata\.(.+)$/', $field, $regs)) {
267
            $date_fields = ['created', 'revised', 'published', 'schedulestart', 'scheduleend', 'imported', 'exported', 'approved'];
268
            $person_fields = ['creator', 'revisor', 'approver', 'locker'];
269
            $metadata_property = $regs[1];
270
            $value = $object->metadata->$metadata_property;
271
272
            if (in_array($metadata_property, $date_fields)) {
273
                $formatter = midcom::get()->i18n->get_l10n()->get_formatter();
274
                return $value ? $formatter->datetime($value, \IntlDateFormatter::SHORT, \IntlDateFormatter::MEDIUM) : '';
275
            }
276
            if (in_array($metadata_property, $person_fields)) {
277
                if ($value) {
278
                    $person = new midcom_db_person($value);
279
                    return self::sanitize_label($person->name);
280
                }
281
                return '';
282
            }
283
            return self::sanitize_label($value);
284
        }
285 36
        if (   $field == 'username'
286 36
            && $object instanceof midcom_db_person) {
287
            $account = new midcom_core_account($object);
288
            return self::sanitize_label($account->get_username());
289
        }
290 36
        return self::sanitize_label($object->$field);
291
    }
292
293 36
    private static function sanitize_label($input) : string
294
    {
295 36
        return trim(strip_tags((string) $input));
296
    }
297
}