Elgg  Version 2.3
MetadataTable.php
Go to the documentation of this file.
1 <?php
2 namespace Elgg\Database;
3 
4 
11 
22 
24 
26  protected $independents = array();
27 
29  protected $cache;
30 
32  protected $db;
33 
35  protected $entityTable;
36 
38  protected $metastringsTable;
39 
41  protected $events;
42 
44  protected $session;
45 
47  protected $table;
48 
59  public function __construct(
60  Cache $cache,
61  Database $db,
65  Session $session) {
66  $this->cache = $cache;
67  $this->db = $db;
68  $this->entityTable = $entityTable;
69  $this->events = $events;
70  $this->metastringsTable = $metastringsTable;
71  $this->session = $session;
72  $this->table = $this->db->prefix . "metadata";
73  }
74 
84  function get($id) {
86  }
87 
94  function delete($id) {
95  $metadata = $this->get($id);
96 
97  return $metadata ? $metadata->delete() : false;
98  }
99 
116  function create($entity_guid, $name, $value, $value_type = '', $owner_guid = 0,
117  $access_id = ACCESS_PRIVATE, $allow_multiple = false) {
118 
119  $entity_guid = (int)$entity_guid;
120  // name and value are encoded in add_metastring()
121  $value_type = \ElggExtender::detectValueType($value, trim($value_type));
122  $time = $this->getCurrentTime()->getTimestamp();
123  $owner_guid = (int)$owner_guid;
124  $allow_multiple = (boolean)$allow_multiple;
125 
126  if (!isset($value)) {
127  return false;
128  }
129 
130  if ($owner_guid == 0) {
131  $owner_guid = $this->session->getLoggedInUserGuid();
132  }
133 
134  $access_id = (int) $access_id;
135 
136  $query = "SELECT * FROM {$this->table}
137  WHERE entity_guid = :entity_guid and name_id = :name_id LIMIT 1";
138 
139  $params = [
140  ':entity_guid' => $entity_guid,
141  ':name_id' => $this->metastringsTable->getId($name)
142  ];
143 
144  $existing = $this->db->getDataRow($query, null, $params);
145  if ($existing && !$allow_multiple) {
146  $id = (int)$existing->id;
147  $result = $this->update($id, $name, $value, $value_type, $owner_guid, $access_id);
148 
149  if (!$result) {
150  return false;
151  }
152  } else {
153  // Support boolean types
154  if (is_bool($value)) {
155  $value = (int)$value;
156  }
157 
158  // Add the metastrings
159  $value_id = $this->metastringsTable->getId($value);
160  if (!$value_id) {
161  return false;
162  }
163 
164  $name_id = $this->metastringsTable->getId($name);
165  if (!$name_id) {
166  return false;
167  }
168 
169  // If ok then add it
170  $query = "INSERT INTO {$this->table}
171  (entity_guid, name_id, value_id, value_type, owner_guid, time_created, access_id)
172  VALUES (:entity_guid, :name_id, :value_id, :value_type, :owner_guid, :time_created, :access_id)";
173 
174  $params = [
175  ':entity_guid' => $entity_guid,
176  ':name_id' => $name_id,
177  ':value_id' => $value_id,
178  ':value_type' => $value_type,
179  ':owner_guid' => $owner_guid,
180  ':time_created' => $time,
181  ':access_id' => $access_id,
182  ];
183 
184  $id = $this->db->insertData($query, $params);
185 
186  if ($id !== false) {
187  $obj = $this->get($id);
188  if ($this->events->trigger('create', 'metadata', $obj)) {
189 
190  $this->cache->clear($entity_guid);
191 
192  return $id;
193  } else {
194  $this->delete($id);
195  }
196  }
197  }
198 
199  return $id;
200  }
201 
214  function update($id, $name, $value, $value_type, $owner_guid, $access_id) {
215  $id = (int)$id;
216 
217  if (!$md = $this->get($id)) {
218  return false;
219  }
220  if (!$md->canEdit()) {
221  return false;
222  }
223 
224  $value_type = \ElggExtender::detectValueType($value, trim($value_type));
225 
226  $owner_guid = (int)$owner_guid;
227  if ($owner_guid == 0) {
228  $owner_guid = $this->session->getLoggedInUserGuid();
229  }
230 
231  $access_id = (int)$access_id;
232 
233  // Support boolean types (as integers)
234  if (is_bool($value)) {
235  $value = (int)$value;
236  }
237 
238  $value_id = $this->metastringsTable->getId($value);
239  if (!$value_id) {
240  return false;
241  }
242 
243  $name_id = $this->metastringsTable->getId($name);
244  if (!$name_id) {
245  return false;
246  }
247 
248  // If ok then add it
249  $query = "UPDATE {$this->table}
250  SET name_id = :name_id,
251  value_id = :value_id,
252  value_type = :value_type,
253  access_id = :access_id,
254  owner_guid = :owner_guid
255  WHERE id = :id";
256 
257  $params = [
258  ':name_id' => $name_id,
259  ':value_id' => $value_id,
260  ':value_type' => $value_type,
261  ':access_id' => $access_id,
262  ':owner_guid' => $owner_guid,
263  ':id' => $id,
264  ];
265 
266  $result = $this->db->updateData($query, false, $params);
267 
268  if ($result !== false) {
269 
270  $this->cache->clear($md->entity_guid);
271 
272  // @todo this event tells you the metadata has been updated, but does not
273  // let you do anything about it. What is needed is a plugin hook before
274  // the update that passes old and new values.
275  $obj = $this->get($id);
276  $this->events->trigger('update', 'metadata', $obj);
277  }
278 
279  return $result;
280  }
281 
298  function createFromArray($entity_guid, array $name_and_values, $value_type, $owner_guid,
299  $access_id = ACCESS_PRIVATE, $allow_multiple = false) {
300 
301  foreach ($name_and_values as $k => $v) {
302  $result = $this->create($entity_guid, $k, $v, $value_type, $owner_guid,
303  $access_id, $allow_multiple);
304  if (!$result) {
305  return false;
306  }
307  }
308  return true;
309  }
310 
340  function getAll(array $options = array()) {
341 
342  // @todo remove support for count shortcut - see #4393
343  // support shortcut of 'count' => true for 'metadata_calculation' => 'count'
344  if (isset($options['count']) && $options['count']) {
345  $options['metadata_calculation'] = 'count';
346  unset($options['count']);
347  }
348 
349  $options['metastring_type'] = 'metadata';
351  }
352 
363  function deleteAll(array $options) {
364  if (!_elgg_is_valid_options_for_batch_operation($options, 'metadata')) {
365  return false;
366  }
367  $options['metastring_type'] = 'metadata';
368  $result = _elgg_batch_metastring_based_objects($options, 'elgg_batch_delete_callback', false);
369 
370  // This moved last in case an object's constructor sets metadata. Currently the batch
371  // delete process has to create the entity to delete its metadata. See #5214
372  $this->cache->invalidateByOptions($options);
373 
374  return $result;
375  }
376 
385  function disableAll(array $options) {
386  if (!_elgg_is_valid_options_for_batch_operation($options, 'metadata')) {
387  return false;
388  }
389 
390  $this->cache->invalidateByOptions($options);
391 
392  // if we can see hidden (disabled) we need to use the offset
393  // otherwise we risk an infinite loop if there are more than 50
394  $inc_offset = access_get_show_hidden_status();
395 
396  $options['metastring_type'] = 'metadata';
397  return _elgg_batch_metastring_based_objects($options, 'elgg_batch_disable_callback', $inc_offset);
398  }
399 
411  function enableAll(array $options) {
412  if (!$options || !is_array($options)) {
413  return false;
414  }
415 
416  $this->cache->invalidateByOptions($options);
417 
418  $options['metastring_type'] = 'metadata';
419  return _elgg_batch_metastring_based_objects($options, 'elgg_batch_enable_callback');
420  }
421 
481  function getEntities(array $options = array()) {
482  $defaults = array(
483  'metadata_names' => ELGG_ENTITIES_ANY_VALUE,
484  'metadata_values' => ELGG_ENTITIES_ANY_VALUE,
485  'metadata_name_value_pairs' => ELGG_ENTITIES_ANY_VALUE,
486 
487  'metadata_name_value_pairs_operator' => 'AND',
488  'metadata_case_sensitive' => true,
489  'order_by_metadata' => array(),
490 
491  'metadata_owner_guids' => ELGG_ENTITIES_ANY_VALUE,
492  );
493 
494  $options = array_merge($defaults, $options);
495 
496  $singulars = array('metadata_name', 'metadata_value',
497  'metadata_name_value_pair', 'metadata_owner_guid');
498 
500 
502  return false;
503  }
504 
505  return $this->entityTable->getEntities($options);
506  }
507 
530  function getEntityMetadataWhereSql($e_table, $n_table, $names = null, $values = null,
531  $pairs = null, $pair_operator = 'AND', $case_sensitive = true, $order_by_metadata = null,
532  $owner_guids = null) {
533  // short circuit if nothing requested
534  // 0 is a valid (if not ill-conceived) metadata name.
535  // 0 is also a valid metadata value for false, null, or 0
536  // 0 is also a valid(ish) owner_guid
537  if ((!$names && $names !== 0)
538  && (!$values && $values !== 0)
539  && (!$pairs && $pairs !== 0)
540  && (!$owner_guids && $owner_guids !== 0)
541  && !$order_by_metadata) {
542  return '';
543  }
544 
545  // join counter for incremental joins.
546  $i = 1;
547 
548  // binary forces byte-to-byte comparision of strings, making
549  // it case- and diacritical-mark- sensitive.
550  // only supported on values.
551  $binary = ($case_sensitive) ? ' BINARY ' : '';
552 
554  'table_alias' => 'n_table',
555  'guid_column' => 'entity_guid',
556  ));
557 
558  $return = array (
559  'joins' => array (),
560  'wheres' => array(),
561  'orders' => array()
562  );
563 
564  // will always want to join these tables if pulling metastrings.
565  $return['joins'][] = "JOIN {$this->db->prefix}{$n_table} n_table on
566  {$e_table}.guid = n_table.entity_guid";
567 
568  $wheres = array();
569 
570  // get names wheres and joins
571  $names_where = '';
572  if ($names !== null) {
573  if (!is_array($names)) {
574  $names = array($names);
575  }
576 
577  $sanitised_names = array();
578  foreach ($names as $name) {
579  // normalise to 0.
580  if (!$name) {
581  $name = '0';
582  }
583  $sanitised_names[] = '\'' . $this->db->sanitizeString($name) . '\'';
584  }
585 
586  if ($names_str = implode(',', $sanitised_names)) {
587  $return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msn on n_table.name_id = msn.id";
588  $names_where = "(msn.string IN ($names_str))";
589  }
590  }
591 
592  // get values wheres and joins
593  $values_where = '';
594  if ($values !== null) {
595  if (!is_array($values)) {
596  $values = array($values);
597  }
598 
599  $sanitised_values = array();
600  foreach ($values as $value) {
601  // normalize to 0
602  if (!$value) {
603  $value = 0;
604  }
605  $sanitised_values[] = '\'' . $this->db->sanitizeString($value) . '\'';
606  }
607 
608  if ($values_str = implode(',', $sanitised_values)) {
609  $return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msv on n_table.value_id = msv.id";
610  $values_where = "({$binary}msv.string IN ($values_str))";
611  }
612  }
613 
614  if ($names_where && $values_where) {
615  $wheres[] = "($names_where AND $values_where AND $access)";
616  } elseif ($names_where) {
617  $wheres[] = "($names_where AND $access)";
618  } elseif ($values_where) {
619  $wheres[] = "($values_where AND $access)";
620  }
621 
622  // add pairs
623  // pairs must be in arrays.
624  if (is_array($pairs)) {
625  // check if this is an array of pairs or just a single pair.
626  if (isset($pairs['name']) || isset($pairs['value'])) {
627  $pairs = array($pairs);
628  }
629 
630  $pair_wheres = array();
631 
632  // @todo when the pairs are > 3 should probably split the query up to
633  // denormalize the strings table.
634 
635  foreach ($pairs as $index => $pair) {
636  // @todo move this elsewhere?
637  // support shortcut 'n' => 'v' method.
638  if (!is_array($pair)) {
639  $pair = array(
640  'name' => $index,
641  'value' => $pair
642  );
643  }
644 
645  // must have at least a name and value
646  if (!isset($pair['name']) || !isset($pair['value'])) {
647  // @todo should probably return false.
648  continue;
649  }
650 
651  // case sensitivity can be specified per pair.
652  // default to higher level setting.
653  if (isset($pair['case_sensitive'])) {
654  $pair_binary = ($pair['case_sensitive']) ? ' BINARY ' : '';
655  } else {
656  $pair_binary = $binary;
657  }
658 
659  if (isset($pair['operand'])) {
660  $operand = $this->db->sanitizeString($pair['operand']);
661  } else {
662  $operand = ' = ';
663  }
664 
665  // for comparing
666  $trimmed_operand = trim(strtolower($operand));
667 
669  'table_alias' => "n_table{$i}",
670  'guid_column' => 'entity_guid',
671  ));
672 
673  // certain operands can't work well with strings that can be interpreted as numbers
674  // for direct comparisons like IN, =, != we treat them as strings
675  // gt/lt comparisons need to stay unencapsulated because strings '5' > '15'
676  // see https://github.com/Elgg/Elgg/issues/7009
677  $num_safe_operands = array('>', '<', '>=', '<=');
678  $num_test_operand = trim(strtoupper($operand));
679 
680  if (is_numeric($pair['value']) && in_array($num_test_operand, $num_safe_operands)) {
681  $value = $this->db->sanitizeString($pair['value']);
682  } else if (is_bool($pair['value'])) {
683  $value = (int)$pair['value'];
684  } else if (is_array($pair['value'])) {
685  $values_array = array();
686 
687  foreach ($pair['value'] as $pair_value) {
688  if (is_numeric($pair_value) && !in_array($num_test_operand, $num_safe_operands)) {
689  $values_array[] = $this->db->sanitizeString($pair_value);
690  } else {
691  $values_array[] = "'" . $this->db->sanitizeString($pair_value) . "'";
692  }
693  }
694 
695  if ($values_array) {
696  $value = '(' . implode(', ', $values_array) . ')';
697  }
698 
699  // @todo allow support for non IN operands with array of values.
700  // will have to do more silly joins.
701  $operand = 'IN';
702  } else if ($trimmed_operand == 'in') {
703  $value = "({$pair['value']})";
704  } else {
705  $value = "'" . $this->db->sanitizeString($pair['value']) . "'";
706  }
707 
708  $name = $this->db->sanitizeString($pair['name']);
709 
710  // @todo The multiple joins are only needed when the operator is AND
711  $return['joins'][] = "JOIN {$this->db->prefix}{$n_table} n_table{$i}
712  on {$e_table}.guid = n_table{$i}.entity_guid";
713  $return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msn{$i}
714  on n_table{$i}.name_id = msn{$i}.id";
715  $return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msv{$i}
716  on n_table{$i}.value_id = msv{$i}.id";
717 
718  $pair_wheres[] = "(msn{$i}.string = '$name' AND {$pair_binary}msv{$i}.string
719  $operand $value AND $access)";
720 
721  $i++;
722  }
723 
724  if ($where = implode(" $pair_operator ", $pair_wheres)) {
725  $wheres[] = "($where)";
726  }
727  }
728 
729  // add owner_guids
730  if ($owner_guids) {
731  if (is_array($owner_guids)) {
732  $sanitised = array_map('sanitise_int', $owner_guids);
733  $owner_str = implode(',', $sanitised);
734  } else {
735  $owner_str = (int)$owner_guids;
736  }
737 
738  $wheres[] = "(n_table.owner_guid IN ($owner_str))";
739  }
740 
741  if ($where = implode(' AND ', $wheres)) {
742  $return['wheres'][] = "($where)";
743  }
744 
745  if (is_array($order_by_metadata)) {
746  if ((count($order_by_metadata) > 0) && !isset($order_by_metadata[0])) {
747  // singleton, so fix
748  $order_by_metadata = array($order_by_metadata);
749  }
750  foreach ($order_by_metadata as $order_by) {
751  if (is_array($order_by) && isset($order_by['name'])) {
752  $name = $this->db->sanitizeString($order_by['name']);
753  if (isset($order_by['direction'])) {
754  $direction = $this->db->sanitizeString($order_by['direction']);
755  } else {
756  $direction = 'ASC';
757  }
758  $return['joins'][] = "JOIN {$this->db->prefix}{$n_table} n_table{$i}
759  on {$e_table}.guid = n_table{$i}.entity_guid";
760  $return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msn{$i}
761  on n_table{$i}.name_id = msn{$i}.id";
762  $return['joins'][] = "JOIN {$this->metastringsTable->getTableName()} msv{$i}
763  on n_table{$i}.value_id = msv{$i}.id";
764 
766  'table_alias' => "n_table{$i}",
767  'guid_column' => 'entity_guid',
768  ));
769 
770  $return['wheres'][] = "(msn{$i}.string = '$name' AND $access)";
771  if (isset($order_by['as']) && $order_by['as'] == 'integer') {
772  $return['orders'][] = "CAST(msv{$i}.string AS SIGNED) $direction";
773  } else {
774  $return['orders'][] = "msv{$i}.string $direction";
775  }
776  $i++;
777  }
778  }
779  }
780 
781  return $return;
782  }
783 
793  function getUrl($id) {
794  $extender = $this->get($id);
795 
796  return $extender ? $extender->getURL() : false;
797  }
798 
809  if (!isset($this->independents[$type])) {
810  $this->independents[$type] = array();
811  }
812 
813  $this->independents[$type][$subtype] = true;
814  }
815 
826  if (empty($this->independents[$type])) {
827  return false;
828  }
829 
830  return !empty($this->independents[$type][$subtype])
831  || !empty($this->independents[$type]['*']);
832  }
833 
844  function handleUpdate($event, $object_type, $object) {
845  if ($object instanceof \ElggEntity) {
846  if (!$this->isMetadataIndependent($object->getType(), $object->getSubtype())) {
847  $access_id = (int)$object->access_id;
848  $guid = (int)$object->getGUID();
849  $query = "update {$this->table} set access_id = {$access_id} where entity_guid = {$guid}";
850  $this->db->updateData($query);
851  }
852  }
853  return true;
854  }
855 
856 }
_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
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:215
_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:1870
if($guid==elgg_get_logged_in_user_guid()) $name
Definition: delete.php:21
disableAll(array $options)
Disables metadata based on $options.
$defaults
The Elgg database.
Definition: Database.php:17
$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:42
$subtype
Definition: delete.php:28
$return
Definition: opendd.php:15
getAll(array $options=array())
Returns metadata.
static detectValueType($value, $value_type="")
Detect the value_type for a value to be stored as metadata or an annotation.
getCurrentTime($modifier= '')
Get the (cloned) time.
Definition: TimeUsing.php:26
$guid
Removes an admin notice.
getUrl($id)
Get the URL for this metadata.
$options
Elgg admin footer.
Definition: footer.php:6
$params
Definition: login.php:72
$entity_guid
Definition: save.php:9
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:79
const ACCESS_PRIVATE
Definition: elgglib.php:2082
table
Definition: admin.css.php:59
const ELGG_ENTITIES_ANY_VALUE
Definition: elgglib.php:2095
access_get_show_hidden_status()
Return current status of showing disabled entities.
Definition: access.php:170
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. ...
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.
$index
Definition: gallery.php:49
trait TimeUsing
Adds methods for setting the current time (for testing)
Definition: TimeUsing.php:11
_elgg_normalize_plural_options_array($options, $singulars)
Normalise the singular keys in an options array to plural keys.
Definition: elgglib.php:1528
__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:214
if(!$collection_name) $id
Definition: add.php:17
http free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:5
$access
Definition: save.php:15
if(!$display_name) $type
Definition: delete.php:27