Passed
Push — master ( 0c04d4...cce3fe )
by Andreas
21:48
created

autocomplete::build_label()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

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