Elgg  Version 1.12
MetadataTable.php
Go to the documentation of this file.
1 <?php
2 namespace Elgg\Database;
3 
4 
5 use Elgg\Database;
8 use Elgg\EventsService as Events;
9 use ElggSession as Session;
10 use Elgg\Cache\MetadataCache as Cache;
11 
23  private $independents = array();
24 
26  private $cache;
27 
29  private $db;
30 
32  private $entityTable;
33 
35  private $metastringsTable;
36 
38  private $events;
39 
41  private $session;
42 
44  private $table;
45 
56  public function __construct(
57  Cache $cache,
58  Database $db,
59  EntityTable $entityTable,
60  Events $events,
61  MetastringsTable $metastringsTable,
62  Session $session) {
63  $this->cache = $cache;
64  $this->db = $db;
65  $this->entityTable = $entityTable;
66  $this->events = $events;
67  $this->metastringsTable = $metastringsTable;
68  $this->session = $session;
69  $this->table = $this->db->getTablePrefix() . "metadata";
70  }
71 
81  function get($id) {
83  }
84 
91  function delete($id) {
92  $metadata = $this->get($id);
93 
94  return $metadata ? $metadata->delete() : false;
95  }
96 
113  function create($entity_guid, $name, $value, $value_type = '', $owner_guid = 0,
114  $access_id = ACCESS_PRIVATE, $allow_multiple = false) {
115 
116  $entity_guid = (int)$entity_guid;
117  // name and value are encoded in add_metastring()
118  $value_type = detect_extender_valuetype($value, $this->db->sanitizeString(trim($value_type)));
119  $time = time();
120  $owner_guid = (int)$owner_guid;
121  $allow_multiple = (boolean)$allow_multiple;
122 
123  if (!isset($value)) {
124  return false;
125  }
126 
127  if ($owner_guid == 0) {
128  $owner_guid = $this->session->getLoggedInUserGuid();
129  }
130 
131  $access_id = (int)$access_id;
132 
133  $query = "SELECT * from {$this->table}"
134  . " WHERE entity_guid = $entity_guid and name_id=" . $this->metastringsTable->getId($name) . " limit 1";
135 
136  $existing = $this->db->getDataRow($query);
137  if ($existing && !$allow_multiple) {
138  $id = (int)$existing->id;
139  $result = $this->update($id, $name, $value, $value_type, $owner_guid, $access_id);
140 
141  if (!$result) {
142  return false;
143  }
144  } else {
145  // Support boolean types
146  if (is_bool($value)) {
147  $value = (int)$value;
148  }
149 
150  // Add the metastrings
151  $value_id = $this->metastringsTable->getId($value);
152  if (!$value_id) {
153  return false;
154  }
155 
156  $name_id = $this->metastringsTable->getId($name);
157  if (!$name_id) {
158  return false;
159  }
160 
161  // If ok then add it
162  $query = "INSERT into {$this->table}"
163  . " (entity_guid, name_id, value_id, value_type, owner_guid, time_created, access_id)"
164  . " VALUES ($entity_guid, '$name_id','$value_id','$value_type', $owner_guid, $time, $access_id)";
165 
166  $id = $this->db->insertData($query);
167 
168  if ($id !== false) {
169  $obj = $this->get($id);
170  if ($this->events->trigger('create', 'metadata', $obj)) {
171 
172  $this->cache->clear($entity_guid);
173 
174  return $id;
175  } else {
176  $this->delete($id);
177  }
178  }
179  }
180 
181  return $id;
182  }
183 
196  function update($id, $name, $value, $value_type, $owner_guid, $access_id) {
197  $id = (int)$id;
198 
199  if (!$md = $this->get($id)) {
200  return false;
201  }
202  if (!$md->canEdit()) {
203  return false;
204  }
205 
206  // If memcached then we invalidate the cache for this entry
207  static $metabyname_memcache;
208  if ((!$metabyname_memcache) && (is_memcache_available())) {
209  $metabyname_memcache = new \ElggMemcache('metabyname_memcache');
210  }
211 
212  if ($metabyname_memcache) {
213  // @todo fix memcache (name_id is not a property of \ElggMetadata)
214  $metabyname_memcache->delete("{$md->entity_guid}:{$md->name_id}");
215  }
216 
217  $value_type = detect_extender_valuetype($value, $this->db->sanitizeString(trim($value_type)));
218 
219  $owner_guid = (int)$owner_guid;
220  if ($owner_guid == 0) {
221  $owner_guid = $this->session->getLoggedInUserGuid();
222  }
223 
224  $access_id = (int)$access_id;
225 
226  // Support boolean types (as integers)
227  if (is_bool($value)) {
228  $value = (int)$value;
229  }
230 
231  $value_id = $this->metastringsTable->getId($value);
232  if (!$value_id) {
233  return false;
234  }
235 
236  $name_id = $this->metastringsTable->getId($name);
237  if (!$name_id) {
238  return false;
239  }
240 
241  // If ok then add it
242  $query = "UPDATE {$this->table}"
243  . " set name_id='$name_id', value_id='$value_id', value_type='$value_type', access_id=$access_id,"
244  . " owner_guid=$owner_guid where id=$id";
245 
246  $result = $this->db->updateData($query);
247  if ($result !== false) {
248 
249  $this->cache->clear($md->entity_guid);
250 
251  // @todo this event tells you the metadata has been updated, but does not
252  // let you do anything about it. What is needed is a plugin hook before
253  // the update that passes old and new values.
254  $obj = $this->get($id);
255  $this->events->trigger('update', 'metadata', $obj);
256  }
257 
258  return $result;
259  }
260 
277  function createFromArray($entity_guid, array $name_and_values, $value_type, $owner_guid,
278  $access_id = ACCESS_PRIVATE, $allow_multiple = false) {
279 
280  foreach ($name_and_values as $k => $v) {
281  $result = $this->create($entity_guid, $k, $v, $value_type, $owner_guid,
282  $access_id, $allow_multiple);
283  if (!$result) {
284  return false;
285  }
286  }
287  return true;
288  }
289 
319  function getAll(array $options = array()) {
320 
321  // @todo remove support for count shortcut - see #4393
322  // support shortcut of 'count' => true for 'metadata_calculation' => 'count'
323  if (isset($options['count']) && $options['count']) {
324  $options['metadata_calculation'] = 'count';
325  unset($options['count']);
326  }
327 
328  $options['metastring_type'] = 'metadata';
330  }
331 
342  function deleteAll(array $options) {
343  if (!_elgg_is_valid_options_for_batch_operation($options, 'metadata')) {
344  return false;
345  }
346  $options['metastring_type'] = 'metadata';
347  $result = _elgg_batch_metastring_based_objects($options, 'elgg_batch_delete_callback', false);
348 
349  // This moved last in case an object's constructor sets metadata. Currently the batch
350  // delete process has to create the entity to delete its metadata. See #5214
351  $this->cache->invalidateByOptions($options);
352 
353  return $result;
354  }
355 
364  function disableAll(array $options) {
365  if (!_elgg_is_valid_options_for_batch_operation($options, 'metadata')) {
366  return false;
367  }
368 
369  $this->cache->invalidateByOptions($options);
370 
371  // if we can see hidden (disabled) we need to use the offset
372  // otherwise we risk an infinite loop if there are more than 50
373  $inc_offset = access_get_show_hidden_status();
374 
375  $options['metastring_type'] = 'metadata';
376  return _elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', $inc_offset);
377  }
378 
390  function enableAll(array $options) {
391  if (!$options || !is_array($options)) {
392  return false;
393  }
394 
395  $this->cache->invalidateByOptions($options);
396 
397  $options['metastring_type'] = 'metadata';
398  return _elgg_batch_metastring_based_objects($options, 'elgg_batch_enable_callback');
399  }
400 
460  function getEntities(array $options = array()) {
461  $defaults = array(
462  'metadata_names' => ELGG_ENTITIES_ANY_VALUE,
463  'metadata_values' => ELGG_ENTITIES_ANY_VALUE,
464  'metadata_name_value_pairs' => ELGG_ENTITIES_ANY_VALUE,
465 
466  'metadata_name_value_pairs_operator' => 'AND',
467  'metadata_case_sensitive' => true,
468  'order_by_metadata' => array(),
469 
470  'metadata_owner_guids' => ELGG_ENTITIES_ANY_VALUE,
471  );
472 
473  $options = array_merge($defaults, $options);
474 
475  $singulars = array('metadata_name', 'metadata_value',
476  'metadata_name_value_pair', 'metadata_owner_guid');
477 
479 
481  return false;
482  }
483 
484  return $this->entityTable->getEntities($options);
485  }
486 
509  function getEntityMetadataWhereSql($e_table, $n_table, $names = null, $values = null,
510  $pairs = null, $pair_operator = 'AND', $case_sensitive = true, $order_by_metadata = null,
511  $owner_guids = null) {
512  // short circuit if nothing requested
513  // 0 is a valid (if not ill-conceived) metadata name.
514  // 0 is also a valid metadata value for false, null, or 0
515  // 0 is also a valid(ish) owner_guid
516  if ((!$names && $names !== 0)
517  && (!$values && $values !== 0)
518  && (!$pairs && $pairs !== 0)
519  && (!$owner_guids && $owner_guids !== 0)
520  && !$order_by_metadata) {
521  return '';
522  }
523 
524  // join counter for incremental joins.
525  $i = 1;
526 
527  // binary forces byte-to-byte comparision of strings, making
528  // it case- and diacritical-mark- sensitive.
529  // only supported on values.
530  $binary = ($case_sensitive) ? ' BINARY ' : '';
531 
533  'table_alias' => 'n_table',
534  'guid_column' => 'entity_guid',
535  ));
536 
537  $return = array (
538  'joins' => array (),
539  'wheres' => array(),
540  'orders' => array()
541  );
542 
543  // will always want to join these tables if pulling metastrings.
544  $return['joins'][] = "JOIN {$this->db->getTablePrefix()}{$n_table} n_table on
545  {$e_table}.guid = n_table.entity_guid";
546 
547  $wheres = array();
548 
549  // get names wheres and joins
550  $names_where = '';
551  if ($names !== null) {
552  if (!is_array($names)) {
553  $names = array($names);
554  }
555 
556  $sanitised_names = array();
557  foreach ($names as $name) {
558  // normalise to 0.
559  if (!$name) {
560  $name = '0';
561  }
562  $sanitised_names[] = '\'' . $this->db->sanitizeString($name) . '\'';
563  }
564 
565  if ($names_str = implode(',', $sanitised_names)) {
566  $return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msn on n_table.name_id = msn.id";
567  $names_where = "(msn.string IN ($names_str))";
568  }
569  }
570 
571  // get values wheres and joins
572  $values_where = '';
573  if ($values !== null) {
574  if (!is_array($values)) {
575  $values = array($values);
576  }
577 
578  $sanitised_values = array();
579  foreach ($values as $value) {
580  // normalize to 0
581  if (!$value) {
582  $value = 0;
583  }
584  $sanitised_values[] = '\'' . $this->db->sanitizeString($value) . '\'';
585  }
586 
587  if ($values_str = implode(',', $sanitised_values)) {
588  $return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msv on n_table.value_id = msv.id";
589  $values_where = "({$binary}msv.string IN ($values_str))";
590  }
591  }
592 
593  if ($names_where && $values_where) {
594  $wheres[] = "($names_where AND $values_where AND $access)";
595  } elseif ($names_where) {
596  $wheres[] = "($names_where AND $access)";
597  } elseif ($values_where) {
598  $wheres[] = "($values_where AND $access)";
599  }
600 
601  // add pairs
602  // pairs must be in arrays.
603  if (is_array($pairs)) {
604  // check if this is an array of pairs or just a single pair.
605  if (isset($pairs['name']) || isset($pairs['value'])) {
606  $pairs = array($pairs);
607  }
608 
609  $pair_wheres = array();
610 
611  // @todo when the pairs are > 3 should probably split the query up to
612  // denormalize the strings table.
613 
614  foreach ($pairs as $index => $pair) {
615  // @todo move this elsewhere?
616  // support shortcut 'n' => 'v' method.
617  if (!is_array($pair)) {
618  $pair = array(
619  'name' => $index,
620  'value' => $pair
621  );
622  }
623 
624  // must have at least a name and value
625  if (!isset($pair['name']) || !isset($pair['value'])) {
626  // @todo should probably return false.
627  continue;
628  }
629 
630  // case sensitivity can be specified per pair.
631  // default to higher level setting.
632  if (isset($pair['case_sensitive'])) {
633  $pair_binary = ($pair['case_sensitive']) ? ' BINARY ' : '';
634  } else {
635  $pair_binary = $binary;
636  }
637 
638  if (isset($pair['operand'])) {
639  $operand = $this->db->sanitizeString($pair['operand']);
640  } else {
641  $operand = ' = ';
642  }
643 
644  // for comparing
645  $trimmed_operand = trim(strtolower($operand));
646 
648  'table_alias' => "n_table{$i}",
649  'guid_column' => 'entity_guid',
650  ));
651 
652  // certain operands can't work well with strings that can be interpreted as numbers
653  // for direct comparisons like IN, =, != we treat them as strings
654  // gt/lt comparisons need to stay unencapsulated because strings '5' > '15'
655  // see https://github.com/Elgg/Elgg/issues/7009
656  $num_safe_operands = array('>', '<', '>=', '<=');
657  $num_test_operand = trim(strtoupper($operand));
658 
659  if (is_numeric($pair['value']) && in_array($num_test_operand, $num_safe_operands)) {
660  $value = $this->db->sanitizeString($pair['value']);
661  } else if (is_bool($pair['value'])) {
662  $value = (int)$pair['value'];
663  } else if (is_array($pair['value'])) {
664  $values_array = array();
665 
666  foreach ($pair['value'] as $pair_value) {
667  if (is_numeric($pair_value) && !in_array($num_test_operand, $num_safe_operands)) {
668  $values_array[] = $this->db->sanitizeString($pair_value);
669  } else {
670  $values_array[] = "'" . $this->db->sanitizeString($pair_value) . "'";
671  }
672  }
673 
674  if ($values_array) {
675  $value = '(' . implode(', ', $values_array) . ')';
676  }
677 
678  // @todo allow support for non IN operands with array of values.
679  // will have to do more silly joins.
680  $operand = 'IN';
681  } else if ($trimmed_operand == 'in') {
682  $value = "({$pair['value']})";
683  } else {
684  $value = "'" . $this->db->sanitizeString($pair['value']) . "'";
685  }
686 
687  $name = $this->db->sanitizeString($pair['name']);
688 
689  // @todo The multiple joins are only needed when the operator is AND
690  $return['joins'][] = "JOIN {$this->db->getTablePrefix()}{$n_table} n_table{$i}
691  on {$e_table}.guid = n_table{$i}.entity_guid";
692  $return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msn{$i}
693  on n_table{$i}.name_id = msn{$i}.id";
694  $return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msv{$i}
695  on n_table{$i}.value_id = msv{$i}.id";
696 
697  $pair_wheres[] = "(msn{$i}.string = '$name' AND {$pair_binary}msv{$i}.string
698  $operand $value AND $access)";
699 
700  $i++;
701  }
702 
703  if ($where = implode(" $pair_operator ", $pair_wheres)) {
704  $wheres[] = "($where)";
705  }
706  }
707 
708  // add owner_guids
709  if ($owner_guids) {
710  if (is_array($owner_guids)) {
711  $sanitised = array_map('sanitise_int', $owner_guids);
712  $owner_str = implode(',', $sanitised);
713  } else {
714  $owner_str = (int)$owner_guids;
715  }
716 
717  $wheres[] = "(n_table.owner_guid IN ($owner_str))";
718  }
719 
720  if ($where = implode(' AND ', $wheres)) {
721  $return['wheres'][] = "($where)";
722  }
723 
724  if (is_array($order_by_metadata)) {
725  if ((count($order_by_metadata) > 0) && !isset($order_by_metadata[0])) {
726  // singleton, so fix
727  $order_by_metadata = array($order_by_metadata);
728  }
729  foreach ($order_by_metadata as $order_by) {
730  if (is_array($order_by) && isset($order_by['name'])) {
731  $name = $this->db->sanitizeString($order_by['name']);
732  if (isset($order_by['direction'])) {
733  $direction = $this->db->sanitizeString($order_by['direction']);
734  } else {
735  $direction = 'ASC';
736  }
737  $return['joins'][] = "JOIN {$this->db->getTablePrefix()}{$n_table} n_table{$i}
738  on {$e_table}.guid = n_table{$i}.entity_guid";
739  $return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msn{$i}
740  on n_table{$i}.name_id = msn{$i}.id";
741  $return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msv{$i}
742  on n_table{$i}.value_id = msv{$i}.id";
743 
745  'table_alias' => "n_table{$i}",
746  'guid_column' => 'entity_guid',
747  ));
748 
749  $return['wheres'][] = "(msn{$i}.string = '$name' AND $access)";
750  if (isset($order_by['as']) && $order_by['as'] == 'integer') {
751  $return['orders'][] = "CAST(msv{$i}.string AS SIGNED) $direction";
752  } else {
753  $return['orders'][] = "msv{$i}.string $direction";
754  }
755  $i++;
756  }
757  }
758  }
759 
760  return $return;
761  }
762 
772  function getUrl($id) {
773  $extender = $this->get($id);
774 
775  return $extender ? $extender->getURL() : false;
776  }
777 
788  if (!isset($this->independents[$type])) {
789  $this->independents[$type] = array();
790  }
791 
792  $this->independents[$type][$subtype] = true;
793  }
794 
805  if (empty($this->independents[$type])) {
806  return false;
807  }
808 
809  return !empty($this->independents[$type][$subtype])
810  || !empty($this->independents[$type]['*']);
811  }
812 
823  function handleUpdate($event, $object_type, $object) {
824  if ($object instanceof \ElggEntity) {
825  if (!$this->isMetadataIndependent($object->getType(), $object->getSubtype())) {
826  $access_id = (int)$object->access_id;
827  $guid = (int)$object->getGUID();
828  $query = "update {$this->table} set access_id = {$access_id} where entity_guid = {$guid}";
829  $this->db->updateData($query);
830  }
831  }
832  return true;
833  }
834 
835 }
_elgg_entities_get_metastrings_options($type, $options)
Returns options to pass to elgg_get_entities() for metastrings operations.
getEntities(array $options=array())
Returns entities based upon metadata.
_elgg_batch_metastring_based_objects(array $options, $callback, $inc_offset=true)
Runs metastrings-based objects found using $options through $callback.
$CONFIG independents
A list of entity types and subtypes that have metadata whose access permission can be changed indepen...
Definition: config.php:291
_elgg_is_valid_options_for_batch_operation($options, $type)
Checks if there are some constraints on the options array for potentially dangerous operations...
Definition: elgglib.php:1726
if($guid==elgg_get_logged_in_user_guid()) $name
Definition: delete.php:21
$object
Definition: upgrade.php:12
disableAll(array $options)
Disables metadata based on $options.
$defaults
$metadata
Definition: entity.php:19
isMetadataIndependent($type, $subtype)
Determines whether entities of a given type and subtype should not change their metadata in line with...
$value
Definition: longtext.php:26
$return
Definition: opendd.php:15
getAll(array $options=array())
Returns metadata.
$guid
Removes an admin notice.
getUrl($id)
Get the URL for this metadata.
events($event="", $object_type="", $function="", $priority=500, $call=false, $object=null)
Deprecated events core function.
$entity_guid
Definition: save.php:9
detect_extender_valuetype($value, $value_type="")
Detect the value_type for a given value.
Definition: extender.php:21
$options
Definition: index.php:14
registerMetadataAsIndependent($type, $subtype= '*')
Mark entities with a particular type and subtype as having access permissions that can be changed ind...
enableAll(array $options)
Enables metadata based on $options.
update($id, $name, $value, $value_type, $owner_guid, $access_id)
Update a specific piece of metadata.
$owner_guid
deleteAll(array $options)
Deletes metadata based on $options.
getEntityMetadataWhereSql($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.
_elgg_get_metastring_based_object_from_id($id, $type)
Returns a singular metastring-based object by its ID.
_elgg_get_metastring_based_objects($options)
Returns an array of either or objects.
Definition: metastrings.php:76
const ACCESS_PRIVATE
Definition: elgglib.php:1993
const ELGG_ENTITIES_ANY_VALUE
Definition: elgglib.php:2006
$type
Definition: add.php:8
access_get_show_hidden_status()
Return current status of showing disabled entities.
Definition: access.php:172
createFromArray($entity_guid, array $name_and_values, $value_type, $owner_guid, $access_id=ACCESS_PRIVATE, $allow_multiple=false)
This function creates metadata from an associative array of "key => value" pairs. ...
In memory cache of known metadata values stored by entity.
handleUpdate($event, $object_type, $object)
When an entity is updated, resets the access ID on all of its child metadata.
create($entity_guid, $name, $value, $value_type= '', $owner_guid=0, $access_id=ACCESS_PRIVATE, $allow_multiple=false)
Create a new metadata object, or update an existing one.
is_memcache_available()
Return true if memcache is available and configured.
Definition: memcache.php:16
_elgg_normalize_plural_options_array($options, $singulars)
Normalise the singular keys in an options array to plural keys.
Definition: elgglib.php:1401
__construct(Cache $cache, Database $db, EntityTable $entityTable, Events $events, MetastringsTable $metastringsTable, Session $session)
Constructor.
_elgg_get_access_where_sql(array $options=array())
Returns the SQL where clause for enforcing read access to data.
Definition: access.php:216
$subtype
Definition: river.php:12
if(!$collection_name) $id
Definition: add.php:17
table
Definition: admin.php:59
$access
Definition: save.php:15