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