1 | <?php |
||
2 | namespace Elgg\Database; |
||
3 | |||
4 | |||
5 | use Elgg\Database; |
||
6 | use Elgg\Database\EntityTable; |
||
7 | use Elgg\EventsService as Events; |
||
8 | use ElggSession as Session; |
||
9 | use Elgg\Cache\MetadataCache as Cache; |
||
10 | |||
11 | /** |
||
12 | * WARNING: API IN FLUX. DO NOT USE DIRECTLY. |
||
13 | * |
||
14 | * @access private |
||
15 | * |
||
16 | * @package Elgg.Core |
||
17 | * @subpackage Database |
||
18 | * @since 1.10.0 |
||
19 | */ |
||
20 | class MetadataTable { |
||
21 | |||
22 | use \Elgg\TimeUsing; |
||
23 | |||
24 | /** @var Cache */ |
||
25 | protected $cache; |
||
26 | |||
27 | /** @var Database */ |
||
28 | protected $db; |
||
29 | |||
30 | /** @var EntityTable */ |
||
31 | protected $entityTable; |
||
32 | |||
33 | /** @var Events */ |
||
34 | protected $events; |
||
35 | |||
36 | /** @var Session */ |
||
37 | protected $session; |
||
38 | |||
39 | /** @var string */ |
||
40 | protected $table; |
||
41 | |||
42 | /** |
||
43 | * @var string[] |
||
44 | */ |
||
45 | protected $tag_names = []; |
||
46 | |||
47 | const MYSQL_TEXT_BYTE_LIMIT = 65535; |
||
48 | |||
49 | /** |
||
50 | * Constructor |
||
51 | * |
||
52 | * @param Cache $cache A cache for this table |
||
53 | * @param Database $db The Elgg database |
||
54 | * @param EntityTable $entityTable The entities table |
||
55 | * @param Events $events The events registry |
||
56 | * @param Session $session The session |
||
57 | */ |
||
58 | 3711 | public function __construct( |
|
59 | Cache $cache, |
||
60 | Database $db, |
||
61 | EntityTable $entityTable, |
||
62 | Events $events, |
||
63 | Session $session) { |
||
64 | 3711 | $this->cache = $cache; |
|
65 | 3711 | $this->db = $db; |
|
66 | 3711 | $this->entityTable = $entityTable; |
|
67 | 3711 | $this->events = $events; |
|
68 | 3711 | $this->session = $session; |
|
69 | 3711 | $this->table = $this->db->prefix . "metadata"; |
|
70 | 3711 | } |
|
71 | |||
72 | /** |
||
73 | * Registers a metadata name as containing tags for an entity. |
||
74 | * |
||
75 | * @param string $name Tag name |
||
76 | * |
||
77 | * @return bool |
||
78 | */ |
||
79 | 293 | function registerTagName($name) { |
|
80 | 293 | if (!in_array($name, $this->tag_names)) { |
|
81 | 293 | $this->tag_names[] = $name; |
|
82 | } |
||
83 | |||
84 | 293 | return true; |
|
85 | } |
||
86 | |||
87 | /** |
||
88 | * Returns an array of valid metadata names for tags. |
||
89 | * |
||
90 | * @return string[] |
||
91 | */ |
||
92 | function getTagNames() { |
||
93 | return $this->tag_names; |
||
94 | } |
||
95 | |||
96 | /** |
||
97 | * Get a specific metadata object by its id. |
||
98 | * If you want multiple metadata objects, use |
||
99 | * {@link elgg_get_metadata()}. |
||
100 | * |
||
101 | * @param int $id The id of the metadata object being retrieved. |
||
102 | * |
||
103 | * @return \ElggMetadata|false false if not found |
||
104 | */ |
||
105 | 3582 | function get($id) { |
|
106 | 3582 | return _elgg_get_metastring_based_object_from_id($id, 'metadata'); |
|
107 | } |
||
108 | |||
109 | /** |
||
110 | * Deletes metadata using its ID. |
||
111 | * |
||
112 | * @param int $id The metadata ID to delete. |
||
113 | * @return bool |
||
114 | */ |
||
115 | function delete($id) { |
||
116 | $metadata = $this->get($id); |
||
117 | |||
118 | return $metadata ? $metadata->delete() : false; |
||
119 | } |
||
120 | |||
121 | /** |
||
122 | * Create a new metadata object, or update an existing one. |
||
123 | * |
||
124 | * Metadata can be an array by setting allow_multiple to true, but it is an |
||
125 | * indexed array with no control over the indexing. |
||
126 | * |
||
127 | * @param int $entity_guid The entity to attach the metadata to |
||
128 | * @param string $name Name of the metadata |
||
129 | * @param string $value Value of the metadata |
||
130 | * @param string $value_type 'text', 'integer', or '' for automatic detection |
||
131 | * @param bool $allow_multiple Allow multiple values for one key. Default is false |
||
132 | * |
||
133 | * @return int|false id of metadata or false if failure |
||
134 | */ |
||
135 | 3582 | function create($entity_guid, $name, $value, $value_type = '', $allow_multiple = false) { |
|
136 | |||
137 | 3582 | $entity_guid = (int) $entity_guid; |
|
138 | 3582 | $value_type = \ElggExtender::detectValueType($value, trim($value_type)); |
|
139 | 3582 | $allow_multiple = (boolean) $allow_multiple; |
|
140 | |||
141 | 3582 | if (!isset($value)) { |
|
142 | return false; |
||
143 | } |
||
144 | |||
145 | 3582 | if (strlen($value) > self::MYSQL_TEXT_BYTE_LIMIT) { |
|
146 | elgg_log("Metadata '$name' is above the MySQL TEXT size limit and may be truncated.", 'WARNING'); |
||
147 | } |
||
148 | |||
149 | 3582 | $query = "SELECT * FROM {$this->table} |
|
150 | WHERE entity_guid = :entity_guid and name = :name LIMIT 1"; |
||
151 | |||
152 | 3582 | $existing = $this->db->getDataRow($query, null, [ |
|
153 | 3582 | ':entity_guid' => $entity_guid, |
|
154 | 3582 | ':name' => $name, |
|
155 | ]); |
||
156 | 3582 | if ($existing && !$allow_multiple) { |
|
157 | 1 | $id = (int) $existing->id; |
|
158 | 1 | $result = $this->update($id, $name, $value, $value_type); |
|
159 | |||
160 | 1 | if (!$result) { |
|
161 | 1 | return false; |
|
162 | } |
||
163 | } else { |
||
164 | // Support boolean types |
||
165 | 3582 | if (is_bool($value)) { |
|
166 | 123 | $value = (int) $value; |
|
167 | } |
||
168 | |||
169 | // If ok then add it |
||
170 | 3582 | $query = "INSERT INTO {$this->table} |
|
171 | (entity_guid, name, value, value_type, time_created) |
||
172 | VALUES (:entity_guid, :name, :value, :value_type, :time_created)"; |
||
173 | |||
174 | 3582 | $id = $this->db->insertData($query, [ |
|
175 | 3582 | ':entity_guid' => $entity_guid, |
|
176 | 3582 | ':name' => $name, |
|
177 | 3582 | ':value' => $value, |
|
178 | 3582 | ':value_type' => $value_type, |
|
179 | 3582 | ':time_created' => $this->getCurrentTime()->getTimestamp(), |
|
180 | ]); |
||
181 | |||
182 | 3582 | if ($id !== false) { |
|
183 | 3582 | $obj = $this->get($id); |
|
184 | 3582 | if ($this->events->trigger('create', 'metadata', $obj)) { |
|
185 | 3582 | $this->cache->clear($entity_guid); |
|
186 | |||
187 | 3582 | return $id; |
|
188 | } else { |
||
189 | $this->delete($id); |
||
190 | } |
||
191 | } |
||
192 | } |
||
193 | |||
194 | return $id; |
||
195 | } |
||
196 | |||
197 | /** |
||
198 | * Update a specific piece of metadata. |
||
199 | * |
||
200 | * @param int $id ID of the metadata to update |
||
201 | * @param string $name Metadata name |
||
202 | * @param string $value Metadata value |
||
203 | * @param string $value_type Value type |
||
204 | * |
||
205 | * @return bool |
||
206 | */ |
||
207 | 1 | function update($id, $name, $value, $value_type) { |
|
208 | 1 | $id = (int) $id; |
|
209 | |||
210 | 1 | if (!$md = $this->get($id)) { |
|
211 | 1 | return false; |
|
212 | } |
||
213 | if (!$md->canEdit()) { |
||
214 | return false; |
||
215 | } |
||
216 | |||
217 | $value_type = \ElggExtender::detectValueType($value, trim($value_type)); |
||
218 | |||
219 | // Support boolean types (as integers) |
||
220 | if (is_bool($value)) { |
||
221 | $value = (int) $value; |
||
222 | } |
||
223 | if (strlen($value) > self::MYSQL_TEXT_BYTE_LIMIT) { |
||
224 | elgg_log("Metadata '$name' is above the MySQL TEXT size limit and may be truncated.", 'WARNING'); |
||
225 | } |
||
226 | // If ok then add it |
||
227 | $query = "UPDATE {$this->table} |
||
228 | SET name = :name, |
||
229 | value = :value, |
||
230 | value_type = :value_type |
||
231 | WHERE id = :id"; |
||
232 | |||
233 | $result = $this->db->updateData($query, false, [ |
||
234 | ':name' => $name, |
||
235 | ':value' => $value, |
||
236 | ':value_type' => $value_type, |
||
237 | ':id' => $id, |
||
238 | ]); |
||
239 | |||
240 | if ($result !== false) { |
||
241 | $this->cache->clear($md->entity_guid); |
||
242 | |||
243 | // @todo this event tells you the metadata has been updated, but does not |
||
244 | // let you do anything about it. What is needed is a plugin hook before |
||
245 | // the update that passes old and new values. |
||
246 | $obj = $this->get($id); |
||
247 | $this->events->trigger('update', 'metadata', $obj); |
||
248 | } |
||
249 | |||
250 | return $result; |
||
251 | } |
||
252 | |||
253 | /** |
||
254 | * Returns metadata. Accepts all elgg_get_entities() options for entity |
||
255 | * restraints. |
||
256 | * |
||
257 | * @see elgg_get_entities |
||
258 | * |
||
259 | * @warning 1.7's find_metadata() didn't support limits and returned all metadata. |
||
260 | * This function defaults to a limit of 25. There is probably not a reason |
||
261 | * for you to return all metadata unless you're exporting an entity, |
||
262 | * have other restraints in place, or are doing something horribly |
||
263 | * wrong in your code. |
||
264 | * |
||
265 | * @param array $options Array in format: |
||
266 | * |
||
267 | * metadata_names => null|ARR metadata names |
||
268 | * metadata_values => null|ARR metadata values |
||
269 | * metadata_ids => null|ARR metadata ids |
||
270 | * metadata_case_sensitive => BOOL Overall Case sensitive |
||
271 | * metadata_created_time_lower => INT Lower limit for created time. |
||
272 | * metadata_created_time_upper => INT Upper limit for created time. |
||
273 | * metadata_calculation => STR Perform the MySQL function on the metadata values returned. |
||
274 | * The "metadata_calculation" option causes this function to |
||
275 | * return the result of performing a mathematical calculation on |
||
276 | * all metadata that match the query instead of returning |
||
277 | * \ElggMetadata objects. |
||
278 | * |
||
279 | * @return \ElggMetadata[]|mixed |
||
280 | */ |
||
281 | 181 | View Code Duplication | function getAll(array $options = []) { |
0 ignored issues
–
show
|
|||
282 | |||
283 | // @todo remove support for count shortcut - see #4393 |
||
284 | // support shortcut of 'count' => true for 'metadata_calculation' => 'count' |
||
285 | 181 | if (isset($options['count']) && $options['count']) { |
|
286 | 1 | $options['metadata_calculation'] = 'count'; |
|
287 | 1 | unset($options['count']); |
|
288 | } |
||
289 | |||
290 | 181 | $options['metastring_type'] = 'metadata'; |
|
291 | 181 | return _elgg_get_metastring_based_objects($options); |
|
292 | } |
||
293 | |||
294 | /** |
||
295 | * Deletes metadata based on $options. |
||
296 | * |
||
297 | * @warning Unlike elgg_get_metadata() this will not accept an empty options array! |
||
298 | * This requires at least one constraint: |
||
299 | * metadata_name(s), metadata_value(s), or guid(s) must be set. |
||
300 | * |
||
301 | * @param array $options An options array. {@link elgg_get_metadata()} |
||
302 | * @return bool|null true on success, false on failure, null if no metadata to delete. |
||
303 | */ |
||
304 | 3614 | function deleteAll(array $options) { |
|
305 | 3614 | if (!_elgg_is_valid_options_for_batch_operation($options, 'metadata')) { |
|
306 | return false; |
||
307 | } |
||
308 | 3614 | $options['metastring_type'] = 'metadata'; |
|
309 | 3614 | $result = _elgg_batch_metastring_based_objects($options, 'elgg_batch_delete_callback', false); |
|
310 | |||
311 | // This moved last in case an object's constructor sets metadata. Currently the batch |
||
312 | // delete process has to create the entity to delete its metadata. See #5214 |
||
313 | 3614 | $this->cache->invalidateByOptions($options); |
|
314 | |||
315 | 3614 | return $result; |
|
316 | } |
||
317 | |||
318 | /** |
||
319 | * Returns entities based upon metadata. Also accepts all |
||
320 | * options available to elgg_get_entities(). Supports |
||
321 | * the singular option shortcut. |
||
322 | * |
||
323 | * @note Using metadata_names and metadata_values results in a |
||
324 | * "names IN (...) AND values IN (...)" clause. This is subtly |
||
325 | * differently than default multiple metadata_name_value_pairs, which use |
||
326 | * "(name = value) AND (name = value)" clauses. |
||
327 | * |
||
328 | * When in doubt, use name_value_pairs. |
||
329 | * |
||
330 | * To ask for entities that do not have a metadata value, use a custom |
||
331 | * where clause like this: |
||
332 | * |
||
333 | * $options['wheres'][] = "NOT EXISTS ( |
||
334 | * SELECT 1 FROM {$dbprefix}metadata md |
||
335 | * WHERE md.entity_guid = e.guid |
||
336 | * AND md.name = $name |
||
337 | * AND md.value = $value)"; |
||
338 | * |
||
339 | * Note the metadata name and value has been denormalized in the above example. |
||
340 | * |
||
341 | * @see elgg_get_entities |
||
342 | * |
||
343 | * @param array $options Array in format: |
||
344 | * |
||
345 | * metadata_names => null|ARR metadata names |
||
346 | * |
||
347 | * metadata_values => null|ARR metadata values |
||
348 | * |
||
349 | * metadata_name_value_pairs => null|ARR ( |
||
350 | * name => 'name', |
||
351 | * value => 'value', |
||
352 | * 'operand' => '=', |
||
353 | * 'case_sensitive' => true |
||
354 | * ) |
||
355 | * Currently if multiple values are sent via |
||
356 | * an array (value => array('value1', 'value2') |
||
357 | * the pair's operand will be forced to "IN". |
||
358 | * If passing "IN" as the operand and a string as the value, |
||
359 | * the value must be a properly quoted and escaped string. |
||
360 | * |
||
361 | * metadata_name_value_pairs_operator => null|STR The operator to use for combining |
||
362 | * (name = value) OPERATOR (name = value); default AND |
||
363 | * |
||
364 | * metadata_case_sensitive => BOOL Overall Case sensitive |
||
365 | * |
||
366 | * order_by_metadata => null|ARR array( |
||
367 | * 'name' => 'metadata_text1', |
||
368 | * 'direction' => ASC|DESC, |
||
369 | * 'as' => text|integer |
||
370 | * ) |
||
371 | * Also supports array('name' => 'metadata_text1') |
||
372 | * |
||
373 | * @return \ElggEntity[]|mixed If count, int. If not count, array. false on errors. |
||
374 | */ |
||
375 | 504 | function getEntities(array $options = []) { |
|
376 | $defaults = [ |
||
377 | 504 | 'metadata_names' => ELGG_ENTITIES_ANY_VALUE, |
|
378 | 504 | 'metadata_values' => ELGG_ENTITIES_ANY_VALUE, |
|
379 | 504 | 'metadata_name_value_pairs' => ELGG_ENTITIES_ANY_VALUE, |
|
380 | |||
381 | 504 | 'metadata_name_value_pairs_operator' => 'AND', |
|
382 | 'metadata_case_sensitive' => true, |
||
383 | 'order_by_metadata' => [], |
||
384 | ]; |
||
385 | |||
386 | 504 | $options = array_merge($defaults, $options); |
|
387 | |||
388 | 504 | $singulars = ['metadata_name', 'metadata_value', 'metadata_name_value_pair']; |
|
389 | |||
390 | 504 | $options = _elgg_normalize_plural_options_array($options, $singulars); |
|
391 | |||
392 | 504 | if (!$options = _elgg_entities_get_metastrings_options('metadata', $options)) { |
|
393 | return false; |
||
394 | } |
||
395 | |||
396 | 504 | return $this->entityTable->getEntities($options); |
|
397 | } |
||
398 | |||
399 | /** |
||
400 | * Returns metadata name and value SQL where for entities. |
||
401 | * NB: $names and $values are not paired. Use $pairs for this. |
||
402 | * Pairs default to '=' operand. |
||
403 | * |
||
404 | * This function is reused for annotations because the tables are |
||
405 | * exactly the same. |
||
406 | * |
||
407 | * @param string $e_table Entities table name |
||
408 | * @param string $n_table Normalized metastrings table name (Where entities, |
||
409 | * values, and names are joined. annotations / metadata) |
||
410 | * @param array|null $names Array of names |
||
411 | * @param array|null $values Array of values |
||
412 | * @param array|null $pairs Array of names / values / operands |
||
413 | * @param string $pair_operator ("AND" or "OR") Operator to use to join the where clauses for pairs |
||
414 | * @param bool $case_sensitive Case sensitive metadata names? |
||
415 | * @param array|null $order_by_metadata Array of names / direction |
||
416 | * |
||
417 | * @return false|array False on fail, array('joins', 'wheres') |
||
418 | * @access private |
||
419 | */ |
||
420 | 504 | function getEntityMetadataWhereSql($e_table, $n_table, $names = null, $values = null, |
|
421 | $pairs = null, $pair_operator = 'AND', $case_sensitive = true, $order_by_metadata = null) { |
||
422 | // short circuit if nothing requested |
||
423 | // 0 is a valid (if not ill-conceived) metadata name. |
||
424 | // 0 is also a valid metadata value for false, null, or 0 |
||
425 | 504 | View Code Duplication | if ((!$names && $names !== 0) |
426 | 504 | && (!$values && $values !== 0) |
|
427 | 504 | && (!$pairs && $pairs !== 0) |
|
428 | 504 | && !$order_by_metadata) { |
|
429 | 293 | return ''; |
|
430 | } |
||
431 | |||
432 | // join counter for incremental joins. |
||
433 | 504 | $i = 1; |
|
434 | |||
435 | // binary forces byte-to-byte comparision of strings, making |
||
436 | // it case- and diacritical-mark- sensitive. |
||
437 | // only supported on values. |
||
438 | 504 | $binary = ($case_sensitive) ? ' BINARY ' : ''; |
|
439 | |||
440 | $return = [ |
||
441 | 504 | 'joins' => [], |
|
442 | 'wheres' => [], |
||
443 | 'orders' => [], |
||
444 | ]; |
||
445 | |||
446 | 504 | $return['joins'][] = "JOIN {$this->db->prefix}{$n_table} n_table on |
|
447 | 504 | {$e_table}.guid = n_table.entity_guid"; |
|
448 | |||
449 | 504 | $wheres = []; |
|
450 | |||
451 | // get names wheres and joins |
||
452 | 504 | $names_where = ''; |
|
453 | 504 | View Code Duplication | if ($names !== null) { |
454 | 27 | if (!is_array($names)) { |
|
455 | 1 | $names = [$names]; |
|
456 | } |
||
457 | |||
458 | 27 | $sanitised_names = []; |
|
459 | 27 | foreach ($names as $name) { |
|
460 | // normalise to 0. |
||
461 | 27 | if (!$name) { |
|
462 | $name = '0'; |
||
463 | } |
||
464 | 27 | $sanitised_names[] = '\'' . $this->db->sanitizeString($name) . '\''; |
|
465 | } |
||
466 | |||
467 | 27 | if ($names_str = implode(',', $sanitised_names)) { |
|
468 | 27 | $names_where = "(n_table.name IN ($names_str))"; |
|
469 | } |
||
470 | } |
||
471 | |||
472 | // get values wheres and joins |
||
473 | 504 | $values_where = ''; |
|
474 | 504 | View Code Duplication | if ($values !== null) { |
475 | 13 | if (!is_array($values)) { |
|
476 | 1 | $values = [$values]; |
|
477 | } |
||
478 | |||
479 | 13 | $sanitised_values = []; |
|
480 | 13 | foreach ($values as $value) { |
|
481 | // normalize to 0 |
||
482 | 13 | if (!$value) { |
|
483 | 2 | $value = 0; |
|
484 | } |
||
485 | 13 | $sanitised_values[] = '\'' . $this->db->sanitizeString($value) . '\''; |
|
486 | } |
||
487 | |||
488 | 13 | if ($values_str = implode(',', $sanitised_values)) { |
|
489 | 13 | $values_where = "({$binary}n_table.value IN ($values_str))"; |
|
490 | } |
||
491 | } |
||
492 | |||
493 | 504 | View Code Duplication | if ($names_where && $values_where) { |
494 | 7 | $wheres[] = "($names_where AND $values_where)"; |
|
495 | 504 | } elseif ($names_where) { |
|
496 | 20 | $wheres[] = "($names_where)"; |
|
497 | 502 | } elseif ($values_where) { |
|
498 | 6 | $wheres[] = "($values_where)"; |
|
499 | } |
||
500 | |||
501 | // add pairs |
||
502 | // pairs must be in arrays. |
||
503 | 504 | if (is_array($pairs)) { |
|
504 | // check if this is an array of pairs or just a single pair. |
||
505 | 502 | if (isset($pairs['name']) || isset($pairs['value'])) { |
|
506 | 7 | $pairs = [$pairs]; |
|
507 | } |
||
508 | |||
509 | 502 | $pair_wheres = []; |
|
510 | |||
511 | // @todo when the pairs are > 3 should probably split the query up to |
||
512 | // denormalize the strings table. |
||
513 | |||
514 | 502 | foreach ($pairs as $index => $pair) { |
|
515 | // @todo move this elsewhere? |
||
516 | // support shortcut 'n' => 'v' method. |
||
517 | 502 | if (!is_array($pair)) { |
|
518 | $pair = [ |
||
519 | 'name' => $index, |
||
520 | 'value' => $pair |
||
521 | ]; |
||
522 | } |
||
523 | |||
524 | // must have at least a name and value |
||
525 | 502 | if (!isset($pair['name']) || !isset($pair['value'])) { |
|
526 | // @todo should probably return false. |
||
527 | 289 | continue; |
|
528 | } |
||
529 | |||
530 | // case sensitivity can be specified per pair. |
||
531 | // default to higher level setting. |
||
532 | 300 | if (isset($pair['case_sensitive'])) { |
|
533 | $pair_binary = ($pair['case_sensitive']) ? ' BINARY ' : ''; |
||
534 | } else { |
||
535 | 300 | $pair_binary = $binary; |
|
536 | } |
||
537 | |||
538 | 300 | View Code Duplication | if (isset($pair['operand'])) { |
539 | 3 | $operand = $this->db->sanitizeString($pair['operand']); |
|
540 | } else { |
||
541 | 297 | $operand = ' = '; |
|
542 | } |
||
543 | |||
544 | // for comparing |
||
545 | 300 | $trimmed_operand = trim(strtolower($operand)); |
|
546 | |||
547 | // certain operands can't work well with strings that can be interpreted as numbers |
||
548 | // for direct comparisons like IN, =, != we treat them as strings |
||
549 | // gt/lt comparisons need to stay unencapsulated because strings '5' > '15' |
||
550 | // see https://github.com/Elgg/Elgg/issues/7009 |
||
551 | 300 | $num_safe_operands = ['>', '<', '>=', '<=']; |
|
552 | 300 | $num_test_operand = trim(strtoupper($operand)); |
|
553 | |||
554 | 300 | $value = ''; |
|
555 | 300 | if (is_numeric($pair['value']) && in_array($num_test_operand, $num_safe_operands)) { |
|
556 | 1 | $value = $this->db->sanitizeString($pair['value']); |
|
557 | 299 | } else if (is_bool($pair['value'])) { |
|
558 | 1 | $value = (int) $pair['value']; |
|
559 | 298 | View Code Duplication | } else if (is_array($pair['value'])) { |
560 | 2 | $values_array = []; |
|
561 | |||
562 | 2 | foreach ($pair['value'] as $pair_value) { |
|
563 | 2 | if (is_numeric($pair_value) && !in_array($num_test_operand, $num_safe_operands)) { |
|
564 | 1 | $values_array[] = $this->db->sanitizeString($pair_value); |
|
565 | } else { |
||
566 | 2 | $values_array[] = "'" . $this->db->sanitizeString($pair_value) . "'"; |
|
567 | } |
||
568 | } |
||
569 | |||
570 | 2 | if ($values_array) { |
|
571 | 2 | $value = '(' . implode(', ', $values_array) . ')'; |
|
572 | } |
||
573 | |||
574 | // @todo allow support for non IN operands with array of values. |
||
575 | // will have to do more silly joins. |
||
576 | 2 | $operand = 'IN'; |
|
577 | 296 | } else if ($trimmed_operand == 'in') { |
|
578 | 1 | $value = "({$pair['value']})"; |
|
579 | } else { |
||
580 | 295 | $value = "'" . $this->db->sanitizeString($pair['value']) . "'"; |
|
581 | } |
||
582 | |||
583 | 300 | $name = $this->db->sanitizeString($pair['name']); |
|
584 | |||
585 | 300 | $return['joins'][] = "JOIN {$this->db->prefix}{$n_table} n_table{$i} |
|
586 | 300 | on {$e_table}.guid = n_table{$i}.entity_guid"; |
|
587 | |||
588 | 300 | $pair_wheres[] = "(n_table{$i}.name = '$name' AND {$pair_binary}n_table{$i}.value |
|
589 | 300 | $operand $value)"; |
|
590 | |||
591 | 300 | $i++; |
|
592 | } |
||
593 | |||
594 | 502 | if ($where = implode(" $pair_operator ", $pair_wheres)) { |
|
595 | 300 | $wheres[] = "($where)"; |
|
596 | } |
||
597 | } |
||
598 | |||
599 | 504 | if ($where = implode(' AND ', $wheres)) { |
|
600 | 326 | $return['wheres'][] = "($where)"; |
|
601 | } |
||
602 | |||
603 | 504 | if (is_array($order_by_metadata)) { |
|
604 | 504 | if ((count($order_by_metadata) > 0) && !isset($order_by_metadata[0])) { |
|
605 | // singleton, so fix |
||
606 | 6 | $order_by_metadata = [$order_by_metadata]; |
|
607 | } |
||
608 | 504 | foreach ($order_by_metadata as $order_by) { |
|
609 | 6 | if (is_array($order_by) && isset($order_by['name'])) { |
|
610 | 6 | $name = $this->db->sanitizeString($order_by['name']); |
|
611 | 6 | if (isset($order_by['direction'])) { |
|
612 | $direction = $this->db->sanitizeString($order_by['direction']); |
||
613 | } else { |
||
614 | 6 | $direction = 'ASC'; |
|
615 | } |
||
616 | 6 | $return['joins'][] = "JOIN {$this->db->prefix}{$n_table} n_table{$i} |
|
617 | 6 | on {$e_table}.guid = n_table{$i}.entity_guid"; |
|
618 | |||
619 | 6 | $return['wheres'][] = "(n_table{$i}.name = '$name')"; |
|
620 | |||
621 | 6 | if (isset($order_by['as']) && $order_by['as'] == 'integer') { |
|
622 | 5 | $return['orders'][] = "CAST(n_table{$i}.value AS SIGNED) $direction"; |
|
623 | } else { |
||
624 | 1 | $return['orders'][] = "n_table{$i}.value $direction"; |
|
625 | } |
||
626 | 6 | $i++; |
|
627 | } |
||
628 | } |
||
629 | } |
||
630 | |||
631 | 504 | return $return; |
|
632 | } |
||
633 | |||
634 | /** |
||
635 | * Get the URL for this metadata |
||
636 | * |
||
637 | * By default this links to the export handler in the current view. |
||
638 | * |
||
639 | * @param int $id Metadata ID |
||
640 | * |
||
641 | * @return mixed |
||
642 | */ |
||
643 | function getUrl($id) { |
||
644 | $extender = $this->get($id); |
||
645 | |||
646 | return $extender ? $extender->getURL() : false; |
||
647 | } |
||
648 | } |
||
649 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.