Elgg  Version 2.3
metastrings.php
Go to the documentation of this file.
1 <?php
25 function elgg_get_metastring_id($string, $case_sensitive = true) {
26  return _elgg_services()->metastringsTable->getId($string, $case_sensitive);
27 }
28 
40 function elgg_get_metastring_map(array $strings) {
41  return _elgg_services()->metastringsTable->getMap($strings);
42 }
43 
81 
82  switch ($options['metastring_type']) {
83  case 'metadata':
84  $type = 'metadata';
85  $callback = 'row_to_elggmetadata';
86  break;
87 
88  case 'annotations':
89  case 'annotation':
90  $type = 'annotations';
91  $callback = 'row_to_elggannotation';
92  break;
93 
94  default:
95  return false;
96  }
97 
98  $defaults = array(
99  // entities
100  'types' => ELGG_ENTITIES_ANY_VALUE,
101  'subtypes' => ELGG_ENTITIES_ANY_VALUE,
102  'type_subtype_pairs' => ELGG_ENTITIES_ANY_VALUE,
103 
104  'guids' => ELGG_ENTITIES_ANY_VALUE,
105  'owner_guids' => ELGG_ENTITIES_ANY_VALUE,
106  'container_guids' => ELGG_ENTITIES_ANY_VALUE,
107  'site_guids' => get_config('site_guid'),
108 
109  'modified_time_lower' => ELGG_ENTITIES_ANY_VALUE,
110  'modified_time_upper' => ELGG_ENTITIES_ANY_VALUE,
111  'created_time_lower' => ELGG_ENTITIES_ANY_VALUE,
112  'created_time_upper' => ELGG_ENTITIES_ANY_VALUE,
113 
114  // options are normalized to the plural in case we ever add support for them.
115  'metastring_names' => ELGG_ENTITIES_ANY_VALUE,
116  'metastring_values' => ELGG_ENTITIES_ANY_VALUE,
117  //'metastring_name_value_pairs' => ELGG_ENTITIES_ANY_VALUE,
118  //'metastring_name_value_pairs_operator' => 'AND',
119 
120  'metastring_case_sensitive' => true,
121  //'order_by_metastring' => array(),
122  'metastring_calculation' => ELGG_ENTITIES_NO_VALUE,
123 
124  'metastring_created_time_lower' => ELGG_ENTITIES_ANY_VALUE,
125  'metastring_created_time_upper' => ELGG_ENTITIES_ANY_VALUE,
126 
127  'metastring_owner_guids' => ELGG_ENTITIES_ANY_VALUE,
128 
129  'metastring_ids' => ELGG_ENTITIES_ANY_VALUE,
130 
131  // sql
132  'order_by' => 'n_table.time_created ASC, n_table.id ASC',
133  'limit' => elgg_get_config('default_limit'),
134  'offset' => 0,
135  'count' => false,
136  'selects' => array(),
137  'wheres' => array(),
138  'joins' => array(),
139 
140  'distinct' => true,
141  'preload_owners' => false,
142  'callback' => $callback,
143 
144  'batch' => false,
145  'batch_inc_offset' => true,
146  'batch_size' => 25,
147  );
148 
149  // @todo Ignore site_guid right now because of #2910
150  $options['site_guid'] = ELGG_ENTITIES_ANY_VALUE;
151 
152  $options = array_merge($defaults, $options);
153 
154  if ($options['batch'] && !$options['count']) {
155  $batch_size = $options['batch_size'];
156  $batch_inc_offset = $options['batch_inc_offset'];
157 
158  // clean batch keys from $options.
159  unset($options['batch'], $options['batch_size'], $options['batch_inc_offset']);
160 
161  return new \ElggBatch('_elgg_get_metastring_based_objects', $options, null, $batch_size, $batch_inc_offset);
162  }
163 
164  // can't use helper function with type_subtype_pair because
165  // it's already an array...just need to merge it
166  if (isset($options['type_subtype_pair'])) {
167  if (isset($options['type_subtype_pairs'])) {
168  $options['type_subtype_pairs'] = array_merge($options['type_subtype_pairs'],
169  $options['type_subtype_pair']);
170  } else {
171  $options['type_subtype_pairs'] = $options['type_subtype_pair'];
172  }
173  }
174 
175  $singulars = array(
176  'type', 'subtype', 'type_subtype_pair',
177  'guid', 'owner_guid', 'container_guid', 'site_guid',
178  'metastring_name', 'metastring_value',
179  'metastring_owner_guid', 'metastring_id',
180  'select', 'where', 'join'
181  );
182 
184 
185  if (!$options) {
186  return false;
187  }
188 
189  $db_prefix = elgg_get_config('dbprefix');
190 
191  // evaluate where clauses
192  if (!is_array($options['wheres'])) {
193  $options['wheres'] = array($options['wheres']);
194  }
195 
196  $wheres = $options['wheres'];
197 
198  // entities
199  $wheres[] = _elgg_services()->entityTable->getEntityTypeSubtypeWhereSql('e', $options['types'],
200  $options['subtypes'], $options['type_subtype_pairs']);
201 
202  $wheres[] = _elgg_get_guid_based_where_sql('e.guid', $options['guids']);
203  $wheres[] = _elgg_get_guid_based_where_sql('e.owner_guid', $options['owner_guids']);
204  $wheres[] = _elgg_get_guid_based_where_sql('e.container_guid', $options['container_guids']);
205  $wheres[] = _elgg_get_guid_based_where_sql('e.site_guid', $options['site_guids']);
206 
207  $wheres[] = _elgg_get_entity_time_where_sql('e', $options['created_time_upper'],
208  $options['created_time_lower'], $options['modified_time_upper'], $options['modified_time_lower']);
209 
210 
211  $wheres[] = _elgg_get_entity_time_where_sql('n_table', $options['metastring_created_time_upper'],
212  $options['metastring_created_time_lower'], null, null);
213 
214  $wheres[] = _elgg_get_guid_based_where_sql('n_table.owner_guid',
215  $options['metastring_owner_guids']);
216 
217  // see if any functions failed
218  // remove empty strings on successful functions
219  foreach ($wheres as $i => $where) {
220  if ($where === false) {
221  return false;
222  } elseif (empty($where)) {
223  unset($wheres[$i]);
224  }
225  }
226 
227  // remove identical where clauses
228  $wheres = array_unique($wheres);
229 
230  // evaluate join clauses
231  if (!is_array($options['joins'])) {
232  $options['joins'] = array($options['joins']);
233  }
234 
235  $joins = array();
236  $joins[] = "JOIN {$db_prefix}entities e ON n_table.entity_guid = e.guid";
237 
238  // evaluate selects
239  if (!is_array($options['selects'])) {
240  $options['selects'] = array($options['selects']);
241  }
242 
243  $selects = $options['selects'];
244 
245  // For performance reasons we don't want the joins required for metadata / annotations
246  // unless we're going through one of their callbacks.
247  // this means we expect the functions passing different callbacks to pass their required joins.
248  // If we're doing a calculation
249  $custom_callback = ($options['callback'] == 'row_to_elggmetadata'
250  || $options['callback'] == 'row_to_elggannotation');
251  $is_calculation = $options['metastring_calculation'] ? true : false;
252 
253  if ($custom_callback || $is_calculation) {
254  $joins[] = "JOIN {$db_prefix}metastrings n on n_table.name_id = n.id";
255  $joins[] = "JOIN {$db_prefix}metastrings v on n_table.value_id = v.id";
256 
257  $selects[] = 'n.string as name';
258  $selects[] = 'v.string as value';
259  }
260 
261  // add optional joins
262  $joins = array_merge($joins, $options['joins']);
263 
264  // metastrings
265  $metastring_clauses = _elgg_get_metastring_sql('n_table', $options['metastring_names'],
266  $options['metastring_values'], null, $options['metastring_ids'],
267  $options['metastring_case_sensitive']);
268 
269  if ($metastring_clauses) {
270  $wheres = array_merge($wheres, $metastring_clauses['wheres']);
271  $joins = array_merge($joins, $metastring_clauses['joins']);
272  } else {
273  $wheres[] = _elgg_get_access_where_sql(array(
274  'table_alias' => 'n_table',
275  'guid_column' => 'entity_guid',
276  ));
277  }
278 
279  $distinct = $options['distinct'] ? "DISTINCT " : "";
280 
281  if ($options['metastring_calculation'] === ELGG_ENTITIES_NO_VALUE && !$options['count']) {
282  $selects = array_unique($selects);
283  // evalutate selects
284  $select_str = '';
285  if ($selects) {
286  foreach ($selects as $select) {
287  $select_str .= ", $select";
288  }
289  }
290 
291  $query = "SELECT $distinct n_table.*{$select_str} FROM {$db_prefix}$type n_table";
292  } elseif ($options['count']) {
293  // count is over the entities
294  $query = "SELECT count($distinct e.guid) as calculation FROM {$db_prefix}$type n_table";
295  } else {
296  $query = "SELECT {$options['metastring_calculation']}(v.string) as calculation FROM {$db_prefix}$type n_table";
297  }
298 
299  foreach ($joins as $i => $join) {
300  if ($join === false) {
301  return false;
302  } elseif (empty($join)) {
303  unset($joins[$i]);
304  }
305  }
306 
307  // remove identical join clauses
308  $joins = array_unique($joins);
309 
310  // add joins
311  foreach ($joins as $j) {
312  $query .= " $j ";
313  }
314 
315  // add wheres
316  $query .= ' WHERE ';
317 
318  foreach ($wheres as $w) {
319  $query .= " $w AND ";
320  }
321 
322  // Add access controls
323  $query .= _elgg_get_access_where_sql(array('table_alias' => 'e'));
324 
325  // reverse order by
326  if (isset($options['reverse_order_by']) && $options['reverse_order_by']) {
327  $options['order_by'] = _elgg_sql_reverse_order_by_clause($options['order_by']);
328  }
329 
330  if ($options['metastring_calculation'] === ELGG_ENTITIES_NO_VALUE && !$options['count']) {
331  if (isset($options['group_by'])) {
332  $options['group_by'] = sanitise_string($options['group_by']);
333  $query .= " GROUP BY {$options['group_by']}";
334  }
335 
336  if (isset($options['order_by']) && $options['order_by']) {
337  $options['order_by'] = sanitise_string($options['order_by']);
338  $query .= " ORDER BY {$options['order_by']}, n_table.id";
339  }
340 
341  if ($options['limit']) {
342  $limit = sanitise_int($options['limit']);
343  $offset = sanitise_int($options['offset'], false);
344  $query .= " LIMIT $offset, $limit";
345  }
346 
347  $dt = get_data($query, $options['callback']);
348 
349  if ($options['preload_owners'] && is_array($dt) && count($dt) > 1) {
350  _elgg_services()->entityPreloader->preload($dt, ['owner_guid']);
351  }
352 
353  return $dt;
354  } else {
355  $result = get_data_row($query);
356  return $result->calculation;
357  }
358 }
359 
375 function _elgg_get_metastring_sql($table, $names = null, $values = null,
376  $pairs = null, $ids = null, $case_sensitive = false) {
377 
378  if ((!$names && $names !== 0)
379  && (!$values && $values !== 0)
380  && !$ids
381  && (!$pairs && $pairs !== 0)) {
382 
383  return array();
384  }
385 
386  $db_prefix = elgg_get_config('dbprefix');
387 
388  // binary forces byte-to-byte comparision of strings, making
389  // it case- and diacritical-mark- sensitive.
390  // only supported on values.
391  $binary = ($case_sensitive) ? ' BINARY ' : '';
392 
393  $return = array (
394  'joins' => array (),
395  'wheres' => array()
396  );
397 
398  $wheres = array();
399 
400  // get names wheres and joins
401  $names_where = '';
402  if ($names !== null) {
403  if (!is_array($names)) {
404  $names = array($names);
405  }
406 
407  $sanitised_names = array();
408  foreach ($names as $name) {
409  // normalise to 0.
410  if (!$name) {
411  $name = '0';
412  }
413  $sanitised_names[] = '\'' . sanitise_string($name) . '\'';
414  }
415 
416  if ($names_str = implode(',', $sanitised_names)) {
417  $return['joins'][] = "JOIN {$db_prefix}metastrings msn on $table.name_id = msn.id";
418  $names_where = "(msn.string IN ($names_str))";
419  }
420  }
421 
422  // get values wheres and joins
423  $values_where = '';
424  if ($values !== null) {
425  if (!is_array($values)) {
426  $values = array($values);
427  }
428 
429  $sanitised_values = array();
430  foreach ($values as $value) {
431  // normalize to 0
432  if (!$value) {
433  $value = 0;
434  }
435  $sanitised_values[] = '\'' . sanitise_string($value) . '\'';
436  }
437 
438  if ($values_str = implode(',', $sanitised_values)) {
439  $return['joins'][] = "JOIN {$db_prefix}metastrings msv on $table.value_id = msv.id";
440  $values_where = "({$binary}msv.string IN ($values_str))";
441  }
442  }
443 
444  if ($ids !== null) {
445  if (!is_array($ids)) {
446  $ids = array($ids);
447  }
448 
449  $ids_str = implode(',', $ids);
450 
451  if ($ids_str) {
452  $wheres[] = "n_table.id IN ($ids_str)";
453  }
454  }
455 
456  if ($names_where && $values_where) {
457  $wheres[] = "($names_where AND $values_where)";
458  } elseif ($names_where) {
459  $wheres[] = $names_where;
460  } elseif ($values_where) {
461  $wheres[] = $values_where;
462  }
463 
464  $wheres[] = _elgg_get_access_where_sql(array(
465  'table_alias' => $table,
466  'guid_column' => 'entity_guid',
467  ));
468 
469  if ($where = implode(' AND ', $wheres)) {
470  $return['wheres'][] = "($where)";
471  }
472 
473  return $return;
474 }
475 
484 
485  // support either metastrings_type or metastring_type
486  // because I've made this mistake many times and hunting it down is a pain...
487  $type = elgg_extract('metastring_type', $options, null);
488  $type = elgg_extract('metastrings_type', $options, $type);
489 
490  $options['metastring_type'] = $type;
491 
492  // support annotation_ and annotations_ because they're way too easy to confuse
493  $prefixes = array('metadata_', 'annotation_', 'annotations_');
494 
495  // map the metadata_* options to metastring_* options
496  $map = array(
497  'names' => 'metastring_names',
498  'values' => 'metastring_values',
499  'case_sensitive' => 'metastring_case_sensitive',
500  'owner_guids' => 'metastring_owner_guids',
501  'created_time_lower' => 'metastring_created_time_lower',
502  'created_time_upper' => 'metastring_created_time_upper',
503  'calculation' => 'metastring_calculation',
504  'ids' => 'metastring_ids',
505  );
506 
507  foreach ($prefixes as $prefix) {
508  $singulars = array("{$prefix}name", "{$prefix}value", "{$prefix}owner_guid", "{$prefix}id");
510 
511  foreach ($map as $specific => $normalized) {
512  $key = $prefix . $specific;
513  if (isset($options[$key])) {
514  $options[$normalized] = $options[$key];
515  }
516  }
517  }
518 
519  return $options;
520 }
521 
537  $id = (int)$id;
538  $db_prefix = elgg_get_config('dbprefix');
539 
541 
542  switch ($type) {
543  case 'annotation':
544  case 'annotations':
545  $type = 'annotation';
546  $table = "{$db_prefix}annotations";
547  break;
548 
549  case 'metadata':
550  $table = "{$db_prefix}metadata";
551  break;
552  }
553 
554  if ($enabled === 'yes' || $enabled === 1 || $enabled === true) {
555  $enabled = 'yes';
556  $event = 'enable';
557  } elseif ($enabled === 'no' || $enabled === 0 || $enabled === false) {
558  $enabled = 'no';
559  $event = 'disable';
560  } else {
561  return false;
562  }
563 
564  $return = false;
565 
566  if ($object) {
567  // don't set it if it's already set.
568  if ($object->enabled == $enabled) {
569  $return = false;
570  } elseif ($object->canEdit() && (elgg_trigger_event($event, $type, $object))) {
571  $return = update_data("UPDATE $table SET enabled = :enabled where id = :id", [
572  ':enabled' => $enabled,
573  ':id' => $id,
574  ]);
575  }
576  }
577 
578  return $return;
579 }
580 
596 function _elgg_batch_metastring_based_objects(array $options, $callback, $inc_offset = true) {
597  if (!$options || !is_array($options)) {
598  return false;
599  }
600 
601  $batch = new \ElggBatch('_elgg_get_metastring_based_objects', $options, $callback, 50, $inc_offset);
602  return $batch->callbackResult;
603 }
604 
614  $id = (int)$id;
615  if (!$id) {
616  return false;
617  }
618 
619  $options = array(
620  'metastring_type' => $type,
621  'metastring_id' => $id,
622  );
623 
625 
626  if ($obj && count($obj) == 1) {
627  return $obj[0];
628  }
629 
630  return false;
631 }
632 
642  $id = (int)$id;
643  $db_prefix = elgg_get_config('dbprefix');
644 
645  switch ($type) {
646  case 'annotations':
647  case 'annotation':
648  $table = $db_prefix . 'annotations';
649  $type = 'annotation';
650  break;
651 
652  case 'metadata':
653  $table = $db_prefix . 'metadata';
654  $type = 'metadata';
655  break;
656 
657  default:
658  return false;
659  }
660 
662 
663  if ($obj) {
664  if ($obj->canEdit()) {
665  // bc code for when we triggered 'delete', 'annotations' #4770
666  $result = true;
667  if ($type == "annotation") {
668  $result = elgg_trigger_event('delete', 'annotations', $obj);
669  if ($result === false) {
670  elgg_deprecated_notice("Use the event 'delete', 'annotation'", 1.9);
671  }
672  }
673 
674  if (elgg_trigger_event('delete', $type, $obj) && $result) {
675  return (bool)delete_data("DELETE FROM $table WHERE id = :id", [
676  ':id' => $id,
677  ]);
678  }
679  }
680  }
681 
682  return false;
683 }
684 
695  $valid_types = array('metadata', 'annotation');
696  if (!in_array($type, $valid_types)) {
697  return false;
698  }
699 
700  // the options for annotations are singular (annotation_name) but the table
701  // is plural (elgg_annotations) so rewrite for the table name.
702  $n_table = ($type == 'annotation') ? 'annotations' : $type;
703 
704  $singulars = array("{$type}_name", "{$type}_value",
705  "{$type}_name_value_pair", "{$type}_owner_guid");
707 
708  $clauses = _elgg_get_entity_metadata_where_sql('e', $n_table, $options["{$type}_names"],
709  $options["{$type}_values"], $options["{$type}_name_value_pairs"],
710  $options["{$type}_name_value_pairs_operator"], $options["{$type}_case_sensitive"],
711  $options["order_by_{$type}"], $options["{$type}_owner_guids"]);
712 
713  if ($clauses) {
714  // merge wheres to pass to elgg_get_entities()
715  if (isset($options['wheres']) && !is_array($options['wheres'])) {
716  $options['wheres'] = array($options['wheres']);
717  } elseif (!isset($options['wheres'])) {
718  $options['wheres'] = array();
719  }
720 
721  $options['wheres'] = array_merge($options['wheres'], $clauses['wheres']);
722 
723  // merge joins to pass to elgg_get_entities()
724  if (isset($options['joins']) && !is_array($options['joins'])) {
725  $options['joins'] = array($options['joins']);
726  } elseif (!isset($options['joins'])) {
727  $options['joins'] = array();
728  }
729 
730  $options['joins'] = array_merge($options['joins'], $clauses['joins']);
731 
732  if ($clauses['orders']) {
733  $order_by_metadata = implode(", ", $clauses['orders']);
734  if (isset($options['order_by']) && $options['order_by']) {
735  $options['order_by'] = "$order_by_metadata, {$options['order_by']}";
736  } else {
737  $options['order_by'] = "$order_by_metadata, e.time_created DESC";
738  }
739  }
740  }
741 
742  return $options;
743 }
744 
756  global $CONFIG;
757  $value[] = $CONFIG->path . 'engine/tests/ElggCoreMetastringsTest.php';
758  return $value;
759 }
760 
761 return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
762  $hooks->registerHandler('unit_test', 'system', '_elgg_metastrings_test');
763 };
_elgg_entities_get_metastrings_options($type, $options)
Returns options to pass to elgg_get_entities() for metastrings operations.
$object
These two snippets demonstrates triggering an event and how to register for that event.
Definition: trigger.php:7
elgg_get_config($name, $site_guid=0)
Get an Elgg configuration value.
_elgg_batch_metastring_based_objects(array $options, $callback, $inc_offset=true)
Runs metastrings-based objects found using $options through $callback.
elgg_get_metastring_id($string, $case_sensitive=true)
Gets the metastring identifier for a value.
Definition: metastrings.php:25
$table
Definition: cron.php:34
if($guid==elgg_get_logged_in_user_guid()) $name
Definition: delete.php:21
delete_data($query, array $params=[])
Remove a row from the database.
Definition: database.php:116
$defaults
const ELGG_ENTITIES_NO_VALUE
Definition: elgglib.php:2104
_elgg_get_guid_based_where_sql($column, $guids)
Returns SQL where clause for owner and containers.
Definition: entities.php:341
get_config($name, $site_guid=0)
Gets a configuration value.
_elgg_get_entity_time_where_sql($table, $time_created_upper=null, $time_created_lower=null, $time_updated_upper=null, $time_updated_lower=null)
Returns SQL where clause for entity time limits.
Definition: entities.php:359
if($selector) $select
Definition: filter.php:36
$value
Definition: longtext.php:42
$return
Definition: opendd.php:15
if(!$count) $offset
Definition: pagination.php:26
_elgg_get_metastring_sql($table, $names=null, $values=null, $pairs=null, $ids=null, $case_sensitive=false)
Returns an array of joins and wheres for use in metastrings.
_elgg_sql_reverse_order_by_clause($order_by)
Reverses the ordering in an ORDER BY clause.
Definition: elgglib.php:1802
$options
Elgg admin footer.
Definition: footer.php:6
$string
_elgg_get_entity_metadata_where_sql($e_table, $n_table, $names=null, $values=null, $pairs=null, $pair_operator= 'AND', $case_sensitive=true, $order_by_metadata=null, $owner_guids=null)
Returns metadata name and value SQL where for entities.
Definition: metadata.php:317
$limit
Definition: userpicker.php:38
get_data_row($query, $callback=null, array $params=[])
Retrieve a single row from the database.
Definition: database.php:72
$key
Definition: summary.php:34
update_data($query, array $params=[], $get_num_rows=false)
Update a row in the database.
Definition: database.php:102
_elgg_delete_metastring_based_object_by_id($id, $type)
Deletes a metastring-based object by its id.
_elgg_get_metastring_based_object_from_id($id, $type)
Returns a singular metastring-based object by its ID.
global $CONFIG
sanitise_string($string)
Alias of sanitize_string.
Definition: database.php:166
_elgg_get_metastring_based_objects($options)
Returns an array of either or objects.
Definition: metastrings.php:79
const ELGG_ENTITIES_ANY_VALUE
Definition: elgglib.php:2095
elgg_deprecated_notice($msg, $dep_version, $backtrace_level=1)
Log a notice about deprecated use of a function, view, etc.
Definition: elgglib.php:1098
elgg global
Pointer to the global context.
Definition: elgglib.js:12
get_data($query, $callback=null, array $params=[])
Retrieve rows from the database.
Definition: database.php:55
_elgg_services(\Elgg\Di\ServiceProvider $services=null)
Get the global service provider.
Definition: autoloader.php:17
elgg_extract($key, $array, $default=null, $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:1375
_elgg_normalize_metastrings_options(array $options=array())
Normalizes metadata / annotation option names to their corresponding metastrings name.
sanitise_int($int, $signed=true)
Alias of sanitize_int.
Definition: database.php:194
_elgg_metastrings_test($hook, $type, $value)
Metastring unit tests.
_elgg_normalize_plural_options_array($options, $singulars)
Normalise the singular keys in an options array to plural keys.
Definition: elgglib.php:1528
_elgg_get_access_where_sql(array $options=array())
Returns the SQL where clause for enforcing read access to data.
Definition: access.php:214
elgg_trigger_event($event, $object_type, $object=null)
Definition: elgglib.php:614
_elgg_set_metastring_based_object_enabled_by_id($id, $enabled, $type)
Enables or disables a metastrings-based object by its id.
if(!$collection_name) $id
Definition: add.php:17
$enabled
CI CLI installer script.
Definition: ci_installer.php:8
elgg_get_metastring_map(array $strings)
Get a map of strings to their metastring identifiers (case sensitive matches)
Definition: metastrings.php:40
if(!$num_display) $db_prefix
Definition: content.php:13
if(!$display_name) $type
Definition: delete.php:27