Passed
Push — master ( e819cb...ec2698 )
by Andreas
24:03
created

autocomplete::get_querystring()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 23
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 7.7999

Importance

Changes 0
Metric Value
cc 6
eloc 19
nc 5
nop 0
dl 0
loc 23
ccs 12
cts 19
cp 0.6316
crap 7.7999
rs 9.0111
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 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 = $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
        return preg_replace('/%+/', '%', $wildcard_query);
168
    }
169
170 1
    public function get_objects() : array
171
    {
172 1
        return $this->prepare_qb()->execute();
173
    }
174
175 1
    public function get_results() : array
176
    {
177 1
        if (empty($this->request["id_field"])) {
178
            throw new midcom_error("Empty ID field.");
179
        }
180
181 1
        $results = $this->get_objects();
182 1
        $items = [];
183
184 1
        foreach ($results as $object) {
185
            $item = [
186 1
                'id' => $object->{$this->request['id_field']},
187 1
                'label' => self::create_item_label($object, $this->request['result_headers'], $this->request['titlefield']),
188
            ];
189 1
            if (!empty($this->request['result_headers'])) {
190 1
                $item['description'] = self::build_label($object, array_column($this->request['result_headers'], 'name'));
191
            }
192 1
            if (!empty($this->request['categorize_by_parent_label'])) {
193
                $item['category'] = '';
194
                if ($parent = $object->get_parent()) {
195
                    $item['category'] = midcom_helper_reflector::get($parent)->get_object_label($parent);
196
                }
197
            }
198 1
            $item['value'] = $item['label'];
199
200 1
            $items[] = $item;
201
        }
202 1
        usort($items, [$this, 'sort_items']);
203
204 1
        return $items;
205
    }
206
207
    public static function sort_items($a, $b)
208
    {
209
        if (isset($a['category'])) {
210
            $cmp = strnatcasecmp($a['category'], $b['category']);
211
            if ($cmp != 0) {
212
                return $cmp;
213
            }
214
        }
215
        return strnatcasecmp($a['label'], $b['label']);
216
    }
217
218 70
    public static function add_head_elements(bool $creation_mode_enabled = false, bool $sortable = false)
219
    {
220 70
        $head = midcom::get()->head;
221 70
        $head->add_stylesheet(MIDCOM_STATIC_URL . "/stock-icons/font-awesome-4.7.0/css/font-awesome.min.css");
222 70
        $head->add_stylesheet(MIDCOM_STATIC_URL . '/midcom.datamanager/autocomplete.css');
223
224 70
        $components = ['menu', 'autocomplete'];
225 70
        if ($sortable) {
226
            $components[] = 'mouse';
227
            $components[] = 'sortable';
228
        }
229 70
        if ($creation_mode_enabled) {
230 23
            $components = array_merge($components, ['mouse', 'draggable', 'resizable', 'button', 'dialog']);
231 23
            $head->add_jsfile(MIDCOM_STATIC_URL . '/midcom.workflow/workflow.js');
232
        }
233 70
        $head->enable_jquery_ui($components);
234 70
        $head->add_jsfile(MIDCOM_STATIC_URL . '/midcom.datamanager/autocomplete.js');
235 70
    }
236
237 7
    public static function get_widget_config(string $type) : array
238
    {
239 7
        $handler_url = midcom_connection::get_url('self') . 'midcom-exec-midcom.datamanager/autocomplete.php';
240
241 7
        $widget_config = midcom_baseclasses_components_configuration::get('midcom.datamanager', 'config')->get('clever_classes');
242 7
        $config = $widget_config[$type];
243 7
        $config['handler_url'] = $handler_url;
244 7
        return $config;
245
    }
246
247 37
    public static function create_item_label($object, array $result_headers, ?string $titlefield) : string
248
    {
249 37
        if (!empty($titlefield)) {
250 30
            if ($label = self::build_label($object, (array) $titlefield)) {
251 24
                return $label;
252
            }
253
        }
254 17
        return midcom_helper_reflector::get($object)->get_object_label($object) ?:
255 9
            self::build_label($object, array_column($result_headers, 'name')) ?:
256 17
            get_class($object) . ' #' . $object->id;
257
    }
258
259 34
    private static function build_label($object, array $fields) : string
260
    {
261 34
        $label = [];
262 34
        foreach ($fields as $field) {
263 34
            if ($value = self::get_property_string($object, $field)) {
264 32
                $label[] = $value;
265
            }
266
        }
267 34
        return implode(', ', $label);
268
    }
269
270 34
    private static function get_property_string($object, string $field) : string
271
    {
272 34
        if (preg_match('/^metadata\.(.+)$/', $field, $regs)) {
273
            $date_fields = ['created', 'revised', 'published', 'schedulestart', 'scheduleend', 'imported', 'exported', 'approved'];
274
            $person_fields = ['creator', 'revisor', 'approver', 'locker'];
275
            $metadata_property = $regs[1];
276
            $value = $object->metadata->$metadata_property;
277
278
            if (in_array($metadata_property, $date_fields)) {
279
                return $value ? strftime('%x %X', $value) : '';
280
            }
281
            if (in_array($metadata_property, $person_fields)) {
282
                if ($value) {
283
                    $person = new midcom_db_person($value);
284
                    return self::sanitize_label($person->name);
285
                }
286
                return '';
287
            }
288
            return self::sanitize_label($value);
289
        }
290 34
        if (   $field == 'username'
291 34
            && $object instanceof midcom_db_person) {
292
            $account = new midcom_core_account($object);
293
            return self::sanitize_label($account->get_username());
294
        }
295 34
        return self::sanitize_label($object->$field);
296
    }
297
298 34
    private static function sanitize_label($input) : string
299
    {
300 34
        return trim(strip_tags((string) $input));
301
    }
302
}