Elgg  Version 1.11
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 
532  $access = _elgg_get_access_where_sql(array('table_alias' => 'n_table'));
533 
534  $return = array (
535  'joins' => array (),
536  'wheres' => array(),
537  'orders' => array()
538  );
539 
540  // will always want to join these tables if pulling metastrings.
541  $return['joins'][] = "JOIN {$this->db->getTablePrefix()}{$n_table} n_table on
542  {$e_table}.guid = n_table.entity_guid";
543 
544  $wheres = array();
545 
546  // get names wheres and joins
547  $names_where = '';
548  if ($names !== null) {
549  if (!is_array($names)) {
550  $names = array($names);
551  }
552 
553  $sanitised_names = array();
554  foreach ($names as $name) {
555  // normalise to 0.
556  if (!$name) {
557  $name = '0';
558  }
559  $sanitised_names[] = '\'' . $this->db->sanitizeString($name) . '\'';
560  }
561 
562  if ($names_str = implode(',', $sanitised_names)) {
563  $return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msn on n_table.name_id = msn.id";
564  $names_where = "(msn.string IN ($names_str))";
565  }
566  }
567 
568  // get values wheres and joins
569  $values_where = '';
570  if ($values !== null) {
571  if (!is_array($values)) {
572  $values = array($values);
573  }
574 
575  $sanitised_values = array();
576  foreach ($values as $value) {
577  // normalize to 0
578  if (!$value) {
579  $value = 0;
580  }
581  $sanitised_values[] = '\'' . $this->db->sanitizeString($value) . '\'';
582  }
583 
584  if ($values_str = implode(',', $sanitised_values)) {
585  $return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msv on n_table.value_id = msv.id";
586  $values_where = "({$binary}msv.string IN ($values_str))";
587  }
588  }
589 
590  if ($names_where && $values_where) {
591  $wheres[] = "($names_where AND $values_where AND $access)";
592  } elseif ($names_where) {
593  $wheres[] = "($names_where AND $access)";
594  } elseif ($values_where) {
595  $wheres[] = "($values_where AND $access)";
596  }
597 
598  // add pairs
599  // pairs must be in arrays.
600  if (is_array($pairs)) {
601  // check if this is an array of pairs or just a single pair.
602  if (isset($pairs['name']) || isset($pairs['value'])) {
603  $pairs = array($pairs);
604  }
605 
606  $pair_wheres = array();
607 
608  // @todo when the pairs are > 3 should probably split the query up to
609  // denormalize the strings table.
610 
611  foreach ($pairs as $index => $pair) {
612  // @todo move this elsewhere?
613  // support shortcut 'n' => 'v' method.
614  if (!is_array($pair)) {
615  $pair = array(
616  'name' => $index,
617  'value' => $pair
618  );
619  }
620 
621  // must have at least a name and value
622  if (!isset($pair['name']) || !isset($pair['value'])) {
623  // @todo should probably return false.
624  continue;
625  }
626 
627  // case sensitivity can be specified per pair.
628  // default to higher level setting.
629  if (isset($pair['case_sensitive'])) {
630  $pair_binary = ($pair['case_sensitive']) ? ' BINARY ' : '';
631  } else {
632  $pair_binary = $binary;
633  }
634 
635  if (isset($pair['operand'])) {
636  $operand = $this->db->sanitizeString($pair['operand']);
637  } else {
638  $operand = ' = ';
639  }
640 
641  // for comparing
642  $trimmed_operand = trim(strtolower($operand));
643 
644  $access = _elgg_get_access_where_sql(array('table_alias' => "n_table{$i}"));
645 
646  // certain operands can't work well with strings that can be interpreted as numbers
647  // for direct comparisons like IN, =, != we treat them as strings
648  // gt/lt comparisons need to stay unencapsulated because strings '5' > '15'
649  // see https://github.com/Elgg/Elgg/issues/7009
650  $num_safe_operands = array('>', '<', '>=', '<=');
651  $num_test_operand = trim(strtoupper($operand));
652 
653  if (is_numeric($pair['value']) && in_array($num_test_operand, $num_safe_operands)) {
654  $value = $this->db->sanitizeString($pair['value']);
655  } else if (is_bool($pair['value'])) {
656  $value = (int)$pair['value'];
657  } else if (is_array($pair['value'])) {
658  $values_array = array();
659 
660  foreach ($pair['value'] as $pair_value) {
661  if (is_numeric($pair_value) && !in_array($num_test_operand, $num_safe_operands)) {
662  $values_array[] = $this->db->sanitizeString($pair_value);
663  } else {
664  $values_array[] = "'" . $this->db->sanitizeString($pair_value) . "'";
665  }
666  }
667 
668  if ($values_array) {
669  $value = '(' . implode(', ', $values_array) . ')';
670  }
671 
672  // @todo allow support for non IN operands with array of values.
673  // will have to do more silly joins.
674  $operand = 'IN';
675  } else if ($trimmed_operand == 'in') {
676  $value = "({$pair['value']})";
677  } else {
678  $value = "'" . $this->db->sanitizeString($pair['value']) . "'";
679  }
680 
681  $name = $this->db->sanitizeString($pair['name']);
682 
683  // @todo The multiple joins are only needed when the operator is AND
684  $return['joins'][] = "JOIN {$this->db->getTablePrefix()}{$n_table} n_table{$i}
685  on {$e_table}.guid = n_table{$i}.entity_guid";
686  $return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msn{$i}
687  on n_table{$i}.name_id = msn{$i}.id";
688  $return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msv{$i}
689  on n_table{$i}.value_id = msv{$i}.id";
690 
691  $pair_wheres[] = "(msn{$i}.string = '$name' AND {$pair_binary}msv{$i}.string
692  $operand $value AND $access)";
693 
694  $i++;
695  }
696 
697  if ($where = implode(" $pair_operator ", $pair_wheres)) {
698  $wheres[] = "($where)";
699  }
700  }
701 
702  // add owner_guids
703  if ($owner_guids) {
704  if (is_array($owner_guids)) {
705  $sanitised = array_map('sanitise_int', $owner_guids);
706  $owner_str = implode(',', $sanitised);
707  } else {
708  $owner_str = (int)$owner_guids;
709  }
710 
711  $wheres[] = "(n_table.owner_guid IN ($owner_str))";
712  }
713 
714  if ($where = implode(' AND ', $wheres)) {
715  $return['wheres'][] = "($where)";
716  }
717 
718  if (is_array($order_by_metadata)) {
719  if ((count($order_by_metadata) > 0) && !isset($order_by_metadata[0])) {
720  // singleton, so fix
721  $order_by_metadata = array($order_by_metadata);
722  }
723  foreach ($order_by_metadata as $order_by) {
724  if (is_array($order_by) && isset($order_by['name'])) {
725  $name = $this->db->sanitizeString($order_by['name']);
726  if (isset($order_by['direction'])) {
727  $direction = $this->db->sanitizeString($order_by['direction']);
728  } else {
729  $direction = 'ASC';
730  }
731  $return['joins'][] = "JOIN {$this->db->getTablePrefix()}{$n_table} n_table{$i}
732  on {$e_table}.guid = n_table{$i}.entity_guid";
733  $return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msn{$i}
734  on n_table{$i}.name_id = msn{$i}.id";
735  $return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msv{$i}
736  on n_table{$i}.value_id = msv{$i}.id";
737 
738  $access = _elgg_get_access_where_sql(array('table_alias' => "n_table{$i}"));
739 
740  $return['wheres'][] = "(msn{$i}.string = '$name' AND $access)";
741  if (isset($order_by['as']) && $order_by['as'] == 'integer') {
742  $return['orders'][] = "CAST(msv{$i}.string AS SIGNED) $direction";
743  } else {
744  $return['orders'][] = "msv{$i}.string $direction";
745  }
746  $i++;
747  }
748  }
749  }
750 
751  return $return;
752  }
753 
763  function getUrl($id) {
764  $extender = $this->get($id);
765 
766  return $extender ? $extender->getURL() : false;
767  }
768 
779  if (!isset($this->independents[$type])) {
780  $this->independents[$type] = array();
781  }
782 
783  $this->independents[$type][$subtype] = true;
784  }
785 
796  if (empty($this->independents[$type])) {
797  return false;
798  }
799 
800  return !empty($this->independents[$type][$subtype])
801  || !empty($this->independents[$type]['*']);
802  }
803 
814  function handleUpdate($event, $object_type, $object) {
815  if ($object instanceof \ElggEntity) {
816  if (!$this->isMetadataIndependent($object->getType(), $object->getSubtype())) {
817  $access_id = (int)$object->access_id;
818  $guid = (int)$object->getGUID();
819  $query = "update {$this->table} set access_id = {$access_id} where entity_guid = {$guid}";
820  $this->db->updateData($query);
821  }
822  }
823  return true;
824  }
825 
826 }
_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:1687
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:1954
const ELGG_ENTITIES_ANY_VALUE
Definition: elgglib.php:1967
$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:1376
__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