Completed
Push — master ( 2134f6...01c567 )
by Andreas
22:35
created

autocomplete::get_querystring()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 24
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 7.5435

Importance

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