Completed
Push — master ( 02005f...26b877 )
by Andreas
27:20
created

autocomplete::get_search_constraints()   B

Complexity

Conditions 10
Paths 16

Size

Total Lines 41
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 16.4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 31
c 1
b 0
f 0
nc 16
nop 0
dl 0
loc 41
ccs 18
cts 30
cp 0.6
crap 16.4
rs 7.6666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
        // Load component if possible
39 2
        midcom::get()->componentloader->load_graceful($this->request['component']);
40
41 2
        if (!class_exists($this->request['class'])) {
42
            throw new midcom_error("Class {$this->request['class']} could not be loaded");
43
        }
44
45 2
        if (empty($this->request['searchfields'])) {
46
            throw new midcom_error("No fields to search for defined");
47
        }
48
49 2
        if (empty($this->request["term"])) {
50
            throw new midcom_error("Empty query string.");
51
        }
52
53 2
        if (!isset($this->request['titlefield'])) {
54 2
            $this->request['titlefield'] = null;
55
        }
56 2
        if (!isset($this->request['result_headers'])) {
57 1
            $this->request['result_headers'] = [];
58
        }
59 2
    }
60
61 1
    private function prepare_qb()
62
    {
63 1
        $qb = call_user_func([$this->request['class'], 'new_query_builder']);
64
65 1
        if (!empty($this->request['constraints'])) {
66
            $this->apply_constraints($qb, $this->request['constraints']);
67
        }
68
69 1
        $constraints = $this->get_search_constraints();
70 1
        if (!empty($constraints)) {
71 1
            $qb->begin_group('OR');
72 1
            $this->apply_constraints($qb, $constraints);
73 1
            $qb->end_group();
74
        }
75
76 1
        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 1
        return $qb;
85
    }
86
87 1
    private function apply_constraints(midcom_core_query $query, array $constraints)
88
    {
89 1
        ksort($constraints);
90 1
        foreach ($constraints as $key => $data) {
91 1
            if (   !array_key_exists('value', $data)
92 1
                || empty($data['field'])
93 1
                || empty($data['op'])) {
94
                debug_add("Constraint #{$key} is not correctly defined, skipping", MIDCOM_LOG_WARN);
95
                continue;
96
            }
97 1
            if ($data['field'] === 'username') {
98
                midcom_core_account::add_username_constraint($query, $data['op'], $data['value']);
99
            } else {
100 1
                $query->add_constraint($data['field'], $data['op'], $data['value']);
101
            }
102
        }
103 1
    }
104
105 1
    private function get_search_constraints() : array
106
    {
107 1
        $constraints = [];
108 1
        $query = $this->request["term"];
109 1
        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 1
        $reflector = new midgard_reflection_property(midcom_helper_reflector::resolve_baseclass($this->request['class']));
115
116 1
        foreach ($this->request['searchfields'] as $field) {
117 1
            $field_type = $reflector->get_midgard_type($field);
118 1
            $operator = 'LIKE';
119 1
            if (strpos($field, '.')) {
120
                //TODO: This should be resolved properly
121
                $field_type = MGD_TYPE_STRING;
122
            }
123
            switch ($field_type) {
124 1
                case MGD_TYPE_GUID:
125
                case MGD_TYPE_STRING:
126
                case MGD_TYPE_LONGTEXT:
127 1
                    $query = $this->get_querystring();
128 1
                    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 1
            debug_add("adding search (ORed) constraint: {$field} {$operator} '{$query}'");
139 1
            $constraints[] = [
140 1
                'field' => $field,
141 1
                'op' => $operator,
142 1
                'value' => $query
143
            ];
144
        }
145 1
        return $constraints;
146
    }
147
148 2
    public function get_querystring() : string
149
    {
150 2
        $query = $this->request["term"];
151 2
        $wildcard_query = $query;
152 2
        if (   isset($this->request['auto_wildcards'])
153 2
            && strpos($query, '%') === false) {
154 1
            switch ($this->request['auto_wildcards']) {
155 1
                case 'start':
156
                    $wildcard_query = '%' . $query;
157
                    break;
158 1
                case 'end':
159 1
                    $wildcard_query = $query . '%';
160 1
                    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 2
        $wildcard_query = str_replace("*", "%", $wildcard_query);
170 2
        $wildcard_query = preg_replace('/%+/', '%', $wildcard_query);
171 2
        return $wildcard_query;
172
    }
173
174 1
    public function get_objects() : array
175
    {
176 1
        return $this->prepare_qb()->execute();
177
    }
178
179 1
    public function get_results() : array
180
    {
181 1
        if (empty($this->request["id_field"])) {
182
            throw new midcom_error("Empty ID field.");
183
        }
184
185 1
        $results = $this->get_objects();
186 1
        $items = [];
187
188 1
        foreach ($results as $object) {
189
            $item = [
190 1
                'id' => $object->{$this->request['id_field']},
191 1
                'label' => self::create_item_label($object, $this->request['result_headers'], $this->request['titlefield']),
192
            ];
193 1
            if (!empty($this->request['result_headers'])) {
194 1
                $item['description'] = self::build_label($object, array_column($this->request['result_headers'], 'name'));
195
            }
196 1
            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 1
            $item['value'] = $item['label'];
203
204 1
            $items[] = $item;
205
        }
206 1
        usort($items, [$this, 'sort_items']);
207
208 1
        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 37
    public static function create_item_label($object, $result_headers, $titlefield) : string
252
    {
253 37
        $label = [];
254 37
        if (!empty($titlefield)) {
255 30
            if ($label = self::build_label($object, (array) $titlefield)) {
256 24
                return $label;
257
            }
258
        }
259 17
        if ($label = midcom_helper_reflector::get($object)->get_object_label($object)) {
260 8
            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 34
    private static function build_label($object, array $fields) : string
270
    {
271 34
        $label = [];
272 34
        foreach ((array) $fields as $field) {
273 34
            if ($value = self::get_property_string($object, $field)) {
274 32
                $label[] = $value;
275
            }
276
        }
277 34
        return implode(', ', $label);
278
    }
279
280 34
    private static function get_property_string($object, string $field) : string
281
    {
282 34
        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 34
        if (   $field == 'username'
301 34
            && $object instanceof midcom_db_person) {
302
            $account = new midcom_core_account($object);
303
            return self::sanitize_label($account->get_username());
304
        }
305 34
        return self::sanitize_label($object->$field);
306
    }
307
308 34
    private static function sanitize_label($input) : string
309
    {
310 34
        return trim(strip_tags((string) $input));
311
    }
312
}