Elgg  Version 2.3
EntityTable.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Database;
4 
29 
39 class EntityTable {
40 
42 
46  protected $config;
47 
51  protected $db;
52 
56  protected $table;
57 
61  protected $subtype_table;
62 
66  protected $entity_cache;
67 
71  protected $entity_preloader;
72 
76  protected $metadata_cache;
77 
81  protected $events;
82 
86  protected $session;
87 
91  protected $translator;
92 
96  protected $logger;
97 
111  public function __construct(
112  Conf $config,
113  Database $db,
121  ) {
122  $this->config = $config;
123  $this->db = $db;
124  $this->table = $this->db->prefix . 'entities';
125  $this->entity_cache = $entity_cache;
126  $this->metadata_cache = $metadata_cache;
127  $this->subtype_table = $subtype_table;
128  $this->events = $events;
129  $this->session = $session;
130  $this->translator = $translator;
131  $this->logger = $logger;
132  }
133 
151  public function getRow($guid, $user_guid = null) {
152 
153  if (!$guid) {
154  return false;
155  }
156 
158  'table_alias' => '',
159  'user_guid' => $user_guid,
160  ]);
161 
162  $sql = "SELECT * FROM {$this->db->prefix}entities
163  WHERE guid = :guid AND $access";
164 
165  $params = [
166  ':guid' => (int) $guid,
167  ];
168 
169  return $this->db->getDataRow($sql, null, $params);
170  }
171 
178  public function insertRow(stdClass $row) {
179 
180  $sql = "INSERT INTO {$this->db->prefix}entities
181  (type, subtype, owner_guid, site_guid, container_guid,
182  access_id, time_created, time_updated, last_action)
183  VALUES
184  (:type, :subtype_id, :owner_guid, :site_guid, :container_guid,
185  :access_id, :time_created, :time_updated, :last_action)";
186 
187  return $this->db->insertData($sql, [
188  ':type' => $row->type,
189  ':subtype_id' => $row->subtype_id,
190  ':owner_guid' => $row->owner_guid,
191  ':site_guid' => $row->site_guid,
192  ':container_guid' => $row->container_guid,
193  ':access_id' => $row->access_id,
194  ':time_created' => $row->time_created,
195  ':time_updated' => $row->time_updated,
196  ':last_action' => $row->last_action,
197  ]);
198  }
199 
207  public function updateRow($guid, stdClass $row) {
208  $sql = "
209  UPDATE {$this->db->prefix}entities
210  SET owner_guid = :owner_guid,
211  access_id = :access_id,
212  container_guid = :container_guid,
213  time_created = :time_created,
214  time_updated = :time_updated
215  WHERE guid = :guid
216  ";
217 
218  $params = [
219  ':owner_guid' => $row->owner_guid,
220  ':access_id' => $row->access_id,
221  ':container_guid' => $row->container_guid,
222  ':time_created' => $row->time_created,
223  ':time_updated' => $row->time_updated,
224  ':guid' => $guid,
225  ];
226 
227  return $this->db->updateData($sql, false, $params);
228  }
229 
246  public function rowToElggStar($row) {
247  if (!$row instanceof stdClass) {
248  return $row;
249  }
250 
251  if (!isset($row->guid) || !isset($row->subtype)) {
252  return $row;
253  }
254 
255  $class_name = $this->subtype_table->getClassFromId($row->subtype);
256  if ($class_name && !class_exists($class_name)) {
257  $this->logger->error("Class '$class_name' was not found, missing plugin?");
258  $class_name = '';
259  }
260 
261  if (!$class_name) {
262  $map = [
263  'object' => ElggObject::class,
264  'user' => ElggUser::class,
265  'group' => ElggGroup::class,
266  'site' => ElggSite::class,
267  ];
268 
269  if (isset($map[$row->type])) {
270  $class_name = $map[$row->type];
271  } else {
272  throw new InstallationException("Entity type {$row->type} is not supported.");
273  }
274  }
275 
276  $entity = new $class_name($row);
277  if (!$entity instanceof ElggEntity) {
278  throw new ClassException("$class_name must extend " . ElggEntity::class);
279  }
280 
281  return $entity;
282  }
283 
291  protected function getFromCache($guid) {
292  $entity = $this->entity_cache->get($guid);
293  if ($entity) {
294  return $entity;
295  }
296 
297  $memcache = _elgg_get_memcache('new_entity_cache');
298  $entity = $memcache->load($guid);
299  if (!$entity instanceof ElggEntity) {
300  return false;
301  }
302 
303  // Validate accessibility if from memcache
305  return false;
306  }
307 
308  $this->entity_cache->set($entity);
309  return $entity;
310  }
311 
323  public function get($guid, $type = '') {
324  // We could also use: if (!(int) $guid) { return false },
325  // but that evaluates to a false positive for $guid = true.
326  // This is a bit slower, but more thorough.
327  if (!is_numeric($guid) || $guid === 0 || $guid === '0') {
328  return false;
329  }
330 
331  $guid = (int) $guid;
332 
333  $entity = $this->getFromCache($guid);
334  if ($entity && (!$type || elgg_instanceof($entity, $type))) {
335  return $entity;
336  }
337 
338  $row = $this->getRow($guid);
339  if (!$row) {
340  return false;
341  }
342 
343  if ($type && $row->type != $type) {
344  return false;
345  }
346 
347  $entity = $this->rowToElggStar($row);
348 
349  if ($entity instanceof ElggEntity) {
350  $entity->storeInPersistedCache(_elgg_get_memcache('new_entity_cache'));
351  }
352 
353  return $entity;
354  }
355 
367  public function exists($guid) {
368 
369  // need to ignore access and show hidden entities to check existence
370  $ia = $this->session->setIgnoreAccess(true);
371  $show_hidden = access_show_hidden_entities(true);
372 
373  $result = $this->getRow($guid);
374 
375  $this->session->setIgnoreAccess($ia);
376  access_show_hidden_entities($show_hidden);
377 
378  return !empty($result);
379  }
380 
388  public function enable($guid, $recursive = true) {
389 
390  // Override access only visible entities
391  $old_access_status = access_get_show_hidden_status();
393 
394  $result = false;
396  if ($entity) {
397  $result = $entity->enable($recursive);
398  }
399 
400  access_show_hidden_entities($old_access_status);
401  return $result;
402  }
403 
502  public function getEntities(array $options = array()) {
503 
504 
505  $defaults = array(
506  'types' => ELGG_ENTITIES_ANY_VALUE,
507  'subtypes' => ELGG_ENTITIES_ANY_VALUE,
508  'type_subtype_pairs' => ELGG_ENTITIES_ANY_VALUE,
509 
510  'guids' => ELGG_ENTITIES_ANY_VALUE,
511  'owner_guids' => ELGG_ENTITIES_ANY_VALUE,
512  'container_guids' => ELGG_ENTITIES_ANY_VALUE,
513  'site_guids' => $this->config->get('site_guid'),
514 
515  'modified_time_lower' => ELGG_ENTITIES_ANY_VALUE,
516  'modified_time_upper' => ELGG_ENTITIES_ANY_VALUE,
517  'created_time_lower' => ELGG_ENTITIES_ANY_VALUE,
518  'created_time_upper' => ELGG_ENTITIES_ANY_VALUE,
519 
520  'reverse_order_by' => false,
521  'order_by' => 'e.time_created desc',
522  'group_by' => ELGG_ENTITIES_ANY_VALUE,
523  'limit' => $this->config->get('default_limit'),
524  'offset' => 0,
525  'count' => false,
526  'selects' => array(),
527  'wheres' => array(),
528  'joins' => array(),
529 
530  'preload_owners' => false,
531  'preload_containers' => false,
532  'callback' => 'entity_row_to_elggstar',
533  'distinct' => true,
534 
535  'batch' => false,
536  'batch_inc_offset' => true,
537  'batch_size' => 25,
538 
539  // private API
540  '__ElggBatch' => null,
541  );
542 
543  $options = array_merge($defaults, $options);
544 
545  if ($options['batch'] && !$options['count']) {
546  $batch_size = $options['batch_size'];
547  $batch_inc_offset = $options['batch_inc_offset'];
548 
549  // clean batch keys from $options.
550  unset($options['batch'], $options['batch_size'], $options['batch_inc_offset']);
551 
552  return new \ElggBatch([$this, 'getEntities'], $options, null, $batch_size, $batch_inc_offset);
553  }
554 
555  // can't use helper function with type_subtype_pair because
556  // it's already an array...just need to merge it
557  if (isset($options['type_subtype_pair'])) {
558  if (isset($options['type_subtype_pairs'])) {
559  $options['type_subtype_pairs'] = array_merge($options['type_subtype_pairs'],
560  $options['type_subtype_pair']);
561  } else {
562  $options['type_subtype_pairs'] = $options['type_subtype_pair'];
563  }
564  }
565 
566  $singulars = array('type', 'subtype', 'guid', 'owner_guid', 'container_guid', 'site_guid');
567  $options = _elgg_normalize_plural_options_array($options, $singulars);
568 
569  $options = $this->autoJoinTables($options);
570 
571  // evaluate where clauses
572  if (!is_array($options['wheres'])) {
573  $options['wheres'] = array($options['wheres']);
574  }
575 
576  $wheres = $options['wheres'];
577 
578  $wheres[] = $this->getEntityTypeSubtypeWhereSql('e', $options['types'],
579  $options['subtypes'], $options['type_subtype_pairs']);
580 
581  $wheres[] = $this->getGuidBasedWhereSql('e.guid', $options['guids']);
582  $wheres[] = $this->getGuidBasedWhereSql('e.owner_guid', $options['owner_guids']);
583  $wheres[] = $this->getGuidBasedWhereSql('e.container_guid', $options['container_guids']);
584  $wheres[] = $this->getGuidBasedWhereSql('e.site_guid', $options['site_guids']);
585 
586  $wheres[] = $this->getEntityTimeWhereSql('e', $options['created_time_upper'],
587  $options['created_time_lower'], $options['modified_time_upper'], $options['modified_time_lower']);
588 
589  // see if any functions failed
590  // remove empty strings on successful functions
591  foreach ($wheres as $i => $where) {
592  if ($where === false) {
593  return false;
594  } elseif (empty($where)) {
595  unset($wheres[$i]);
596  }
597  }
598 
599  // remove identical where clauses
600  $wheres = array_unique($wheres);
601 
602  // evaluate join clauses
603  if (!is_array($options['joins'])) {
604  $options['joins'] = array($options['joins']);
605  }
606 
607  // remove identical join clauses
608  $joins = array_unique($options['joins']);
609 
610  foreach ($joins as $i => $join) {
611  if ($join === false) {
612  return false;
613  } elseif (empty($join)) {
614  unset($joins[$i]);
615  }
616  }
617 
618  // evalutate selects
619  if ($options['selects']) {
620  $selects = '';
621  foreach ($options['selects'] as $select) {
622  $selects .= ", $select";
623  }
624  } else {
625  $selects = '';
626  }
627 
628  if (!$options['count']) {
629  $distinct = $options['distinct'] ? "DISTINCT" : "";
630  $query = "SELECT $distinct e.*{$selects} FROM {$this->db->prefix}entities e ";
631  } else {
632  // note: when DISTINCT unneeded, it's slightly faster to compute COUNT(*) than GUIDs
633  $count_expr = $options['distinct'] ? "DISTINCT e.guid" : "*";
634  $query = "SELECT COUNT($count_expr) as total FROM {$this->db->prefix}entities e ";
635  }
636 
637  // add joins
638  foreach ($joins as $j) {
639  $query .= " $j ";
640  }
641 
642  // add wheres
643  $query .= ' WHERE ';
644 
645  foreach ($wheres as $w) {
646  $query .= " $w AND ";
647  }
648 
649  // Add access controls
650  $query .= _elgg_get_access_where_sql();
651 
652  // reverse order by
653  if ($options['reverse_order_by']) {
654  $options['order_by'] = _elgg_sql_reverse_order_by_clause($options['order_by']);
655  }
656 
657  if ($options['count']) {
658  $total = $this->db->getDataRow($query);
659  return (int) $total->total;
660  }
661 
662  if ($options['group_by']) {
663  $query .= " GROUP BY {$options['group_by']}";
664  }
665 
666  if ($options['order_by']) {
667  $query .= " ORDER BY {$options['order_by']}";
668  }
669 
670  if ($options['limit']) {
671  $limit = sanitise_int($options['limit'], false);
672  $offset = sanitise_int($options['offset'], false);
673  $query .= " LIMIT $offset, $limit";
674  }
675 
676  if ($options['callback'] === 'entity_row_to_elggstar') {
677  $results = $this->fetchFromSql($query, $options['__ElggBatch']);
678  } else {
679  $results = $this->db->getData($query, $options['callback']);
680  }
681 
682  if (!$results) {
683  // no results, no preloading
684  return $results;
685  }
686 
687  // populate entity and metadata caches, and prepare $entities for preloader
688  $guids = array();
689  foreach ($results as $item) {
690  // A custom callback could result in items that aren't \ElggEntity's, so check for them
691  if ($item instanceof ElggEntity) {
692  $this->entity_cache->set($item);
693  // plugins usually have only settings
694  if (!$item instanceof ElggPlugin) {
695  $guids[] = $item->guid;
696  }
697  }
698  }
699  // @todo Without this, recursive delete fails. See #4568
700  reset($results);
701 
702  if ($guids) {
703  // there were entities in the result set, preload metadata for them
704  $this->metadata_cache->populateFromEntities($guids);
705  }
706 
707  if (count($results) > 1) {
708  $props_to_preload = [];
709  if ($options['preload_owners']) {
710  $props_to_preload[] = 'owner_guid';
711  }
712  if ($options['preload_containers']) {
713  $props_to_preload[] = 'container_guid';
714  }
715  if ($props_to_preload) {
716  // note, ElggEntityPreloaderIntegrationTest assumes it can swap out
717  // the preloader after boot. If you inject this component at construction
718  // time that unit test will break. :/
719  _elgg_services()->entityPreloader->preload($results, $props_to_preload);
720  }
721  }
722 
723  return $results;
724  }
725 
733  protected function autoJoinTables(array $options) {
734  // we must be careful that the query doesn't specify any options that may join
735  // tables or change the selected columns
736  if (!is_array($options['types'])
737  || count($options['types']) !== 1
738  || !empty($options['selects'])
739  || !empty($options['wheres'])
740  || !empty($options['joins'])
741  || $options['callback'] !== 'entity_row_to_elggstar'
742  || $options['count']) {
743  // Too dangerous to auto-join
744  return $options;
745  }
746 
747  $join_types = [
748  // Each class must have a static getExternalAttributes() : array
749  'object' => 'ElggObject',
750  'user' => 'ElggUser',
751  'group' => 'ElggGroup',
752  'site' => 'ElggSite',
753  ];
754 
755  // We use reset() because $options['types'] may not have a numeric key
756  $type = reset($options['types']);
757  if (empty($join_types[$type])) {
758  return $options;
759  }
760 
761  // Get the columns we'll need to select. We can't use st.* because the order_by
762  // clause may reference "guid", which MySQL will complain about being ambiguous
763  if (!is_callable([$join_types[$type], 'getExternalAttributes'])) {
764  // for some reason can't get external attributes.
765  return $options;
766  }
767 
768  $attributes = $join_types[$type]::getExternalAttributes();
769  foreach (array_keys($attributes) as $col) {
770  $options['selects'][] = "st.$col";
771  }
772 
773  // join the secondary table
774  $options['joins'][] = "JOIN {$this->db->prefix}{$type}s_entity st ON (e.guid = st.guid)";
775 
776  return $options;
777  }
778 
789  public function fetchFromSql($sql, \ElggBatch $batch = null) {
790  $plugin_subtype = $this->subtype_table->getId('object', 'plugin');
791 
792  // Keys are types, values are columns that, if present, suggest that the secondary
793  // table is already JOINed. Note it's OK if guess incorrectly because entity load()
794  // will fetch any missing attributes.
795  $types_to_optimize = array(
796  'object' => 'title',
797  'user' => 'password',
798  'group' => 'name',
799  'site' => 'url',
800  );
801 
802  $rows = $this->db->getData($sql);
803 
804  // guids to look up in each type
805  $lookup_types = array();
806  // maps GUIDs to the $rows key
807  $guid_to_key = array();
808 
809  if (isset($rows[0]->type, $rows[0]->subtype)
810  && $rows[0]->type === 'object'
811  && $rows[0]->subtype == $plugin_subtype) {
812  // Likely the entire resultset is plugins, which have already been optimized
813  // to JOIN the secondary table. In this case we allow retrieving from cache,
814  // but abandon the extra queries.
815  $types_to_optimize = array();
816  }
817 
818  // First pass: use cache where possible, gather GUIDs that we're optimizing
819  foreach ($rows as $i => $row) {
820  if (empty($row->guid) || empty($row->type)) {
821  throw new LogicException('Entity row missing guid or type');
822  }
823 
824  // We try ephemeral cache because it's blazingly fast and we ideally want to access
825  // the same PHP instance. We don't try memcache because it isn't worth the overhead.
826  $entity = $this->entity_cache->get($row->guid);
827  if ($entity) {
828  // from static var, must be refreshed in case row has extra columns
829  $entity->refresh($row);
830  $rows[$i] = $entity;
831  continue;
832  }
833 
834  if (isset($types_to_optimize[$row->type])) {
835  // check if row already looks JOINed.
836  if (isset($row->{$types_to_optimize[$row->type]})) {
837  // Row probably already contains JOINed secondary table. Don't make another query just
838  // to pull data that's already there
839  continue;
840  }
841  $lookup_types[$row->type][] = $row->guid;
842  $guid_to_key[$row->guid] = $i;
843  }
844  }
845  // Do secondary queries and merge rows
846  if ($lookup_types) {
847  foreach ($lookup_types as $type => $guids) {
848  $set = "(" . implode(',', $guids) . ")";
849  $sql = "SELECT * FROM {$this->db->prefix}{$type}s_entity WHERE guid IN $set";
850  $secondary_rows = $this->db->getData($sql);
851  if ($secondary_rows) {
852  foreach ($secondary_rows as $secondary_row) {
853  $key = $guid_to_key[$secondary_row->guid];
854  // cast to arrays to merge then cast back
855  $rows[$key] = (object) array_merge((array) $rows[$key], (array) $secondary_row);
856  }
857  }
858  }
859  }
860  // Second pass to finish conversion
861  foreach ($rows as $i => $row) {
862  if ($row instanceof ElggEntity) {
863  continue;
864  } else {
865  try {
866  $rows[$i] = $this->rowToElggStar($row);
867  } catch (IncompleteEntityException $e) {
868  // don't let incomplete entities throw fatal errors
869  unset($rows[$i]);
870 
871  // report incompletes to the batch process that spawned this query
872  if ($batch) {
873  $batch->reportIncompleteEntity($row);
874  }
875  }
876  }
877  }
878  return $rows;
879  }
880 
892  public function getEntityTypeSubtypeWhereSql($table, $types = [], $subtypes = [], $pairs = []) {
893  // subtype depends upon type.
894  if ($subtypes && !$types) {
895  $this->logger->warn("Cannot set subtypes without type.");
896  return false;
897  }
898 
899  // short circuit if nothing is requested
900  if (!$types && !$subtypes && !$pairs) {
901  return '';
902  }
903 
904  // these are the only valid types for entities in elgg
905  $valid_types = $this->config->get('entity_types');
906 
907  // pairs override
908  $wheres = array();
909  if (!is_array($pairs)) {
910  if (!is_array($types)) {
911  $types = array($types);
912  }
913 
914  if (!is_array($subtypes)) {
915  $subtypes = array($subtypes);
916  }
917 
918  // decrementer for valid types. Return false if no valid types
919  $valid_types_count = count($types);
920  $valid_subtypes_count = 0;
921  // remove invalid types to get an accurate count of
922  // valid types for the invalid subtype detection to use
923  // below.
924  // also grab the count of ALL subtypes on valid types to decrement later on
925  // and check against.
926  //
927  // yes this is duplicating a foreach on $types.
928  foreach ($types as $type) {
929  if (!in_array($type, $valid_types)) {
930  $valid_types_count--;
931  unset($types[array_search($type, $types)]);
932  } else {
933  // do the checking (and decrementing) in the subtype section.
934  $valid_subtypes_count += count($subtypes);
935  }
936  }
937 
938  // return false if nothing is valid.
939  if (!$valid_types_count) {
940  return false;
941  }
942 
943  // subtypes are based upon types, so we need to look at each
944  // type individually to get the right subtype id.
945  foreach ($types as $type) {
946  $subtype_ids = array();
947  if ($subtypes) {
948  foreach ($subtypes as $subtype) {
949  // check that the subtype is valid
950  if (!$subtype && ELGG_ENTITIES_NO_VALUE === $subtype) {
951  // subtype value is 0
952  $subtype_ids[] = ELGG_ENTITIES_NO_VALUE;
953  } elseif (!$subtype) {
954  // subtype is ignored.
955  // this handles ELGG_ENTITIES_ANY_VALUE, '', and anything falsy that isn't 0
956  continue;
957  } else {
958  $subtype_id = get_subtype_id($type, $subtype);
959 
960  if ($subtype_id) {
961  $subtype_ids[] = $subtype_id;
962  } else {
963  $valid_subtypes_count--;
964  $this->logger->notice("Type-subtype '$type:$subtype' does not exist!");
965  continue;
966  }
967  }
968  }
969 
970  // return false if we're all invalid subtypes in the only valid type
971  if ($valid_subtypes_count <= 0) {
972  return false;
973  }
974  }
975 
976  if (is_array($subtype_ids) && count($subtype_ids)) {
977  $subtype_ids_str = implode(',', $subtype_ids);
978  $wheres[] = "({$table}.type = '$type' AND {$table}.subtype IN ($subtype_ids_str))";
979  } else {
980  $wheres[] = "({$table}.type = '$type')";
981  }
982  }
983  } else {
984  // using type/subtype pairs
985  $valid_pairs_count = count($pairs);
986  $valid_pairs_subtypes_count = 0;
987 
988  // same deal as above--we need to know how many valid types
989  // and subtypes we have before hitting the subtype section.
990  // also normalize the subtypes into arrays here.
991  foreach ($pairs as $paired_type => $paired_subtypes) {
992  if (!in_array($paired_type, $valid_types)) {
993  $valid_pairs_count--;
994  unset($pairs[array_search($paired_type, $pairs)]);
995  } else {
996  if ($paired_subtypes && !is_array($paired_subtypes)) {
997  $pairs[$paired_type] = array($paired_subtypes);
998  }
999  $valid_pairs_subtypes_count += count($paired_subtypes);
1000  }
1001  }
1002 
1003  if ($valid_pairs_count <= 0) {
1004  return false;
1005  }
1006  foreach ($pairs as $paired_type => $paired_subtypes) {
1007  // this will always be an array because of line 2027, right?
1008  // no...some overly clever person can say pair => array('object' => null)
1009  if (is_array($paired_subtypes)) {
1010  $paired_subtype_ids = array();
1011  foreach ($paired_subtypes as $paired_subtype) {
1012  if (ELGG_ENTITIES_NO_VALUE === $paired_subtype || ($paired_subtype_id = get_subtype_id($paired_type, $paired_subtype))) {
1013 
1014  $paired_subtype_ids[] = (ELGG_ENTITIES_NO_VALUE === $paired_subtype) ?
1015  ELGG_ENTITIES_NO_VALUE : $paired_subtype_id;
1016  } else {
1017  $valid_pairs_subtypes_count--;
1018  $this->logger->notice("Type-subtype '$paired_type:$paired_subtype' does not exist!");
1019  // return false if we're all invalid subtypes in the only valid type
1020  continue;
1021  }
1022  }
1023 
1024  // return false if there are no valid subtypes.
1025  if ($valid_pairs_subtypes_count <= 0) {
1026  return false;
1027  }
1028 
1029 
1030  if ($paired_subtype_ids_str = implode(',', $paired_subtype_ids)) {
1031  $wheres[] = "({$table}.type = '$paired_type'"
1032  . " AND {$table}.subtype IN ($paired_subtype_ids_str))";
1033  }
1034  } else {
1035  $wheres[] = "({$table}.type = '$paired_type')";
1036  }
1037  }
1038  }
1039 
1040  // pairs override the above. return false if they don't exist.
1041  if (is_array($wheres) && count($wheres)) {
1042  $where = implode(' OR ', $wheres);
1043  return "($where)";
1044  }
1045 
1046  return '';
1047  }
1048 
1060  // short circuit if nothing requested
1061  // 0 is a valid guid
1062  if (!$guids && $guids !== 0) {
1063  return '';
1064  }
1065 
1066  // normalize and sanitise owners
1067  if (!is_array($guids)) {
1068  $guids = array($guids);
1069  }
1070 
1071  $guids_sanitized = array();
1072  foreach ($guids as $guid) {
1073  if ($guid !== ELGG_ENTITIES_NO_VALUE) {
1074  $guid = sanitise_int($guid);
1075 
1076  if (!$guid) {
1077  return false;
1078  }
1079  }
1080  $guids_sanitized[] = $guid;
1081  }
1082 
1083  $where = '';
1084  $guid_str = implode(',', $guids_sanitized);
1085 
1086  // implode(',', 0) returns 0.
1087  if ($guid_str !== false && $guid_str !== '') {
1088  $where = "($column IN ($guid_str))";
1089  }
1090 
1091  return $where;
1092  }
1093 
1107  public function getEntityTimeWhereSql($table, $time_created_upper = null,
1108  $time_created_lower = null, $time_updated_upper = null, $time_updated_lower = null) {
1109 
1110  $wheres = array();
1111 
1112  // exploit PHP's loose typing (quack) to check that they are INTs and not str cast to 0
1113  if ($time_created_upper && $time_created_upper == sanitise_int($time_created_upper)) {
1114  $wheres[] = "{$table}.time_created <= $time_created_upper";
1115  }
1116 
1117  if ($time_created_lower && $time_created_lower == sanitise_int($time_created_lower)) {
1118  $wheres[] = "{$table}.time_created >= $time_created_lower";
1119  }
1120 
1121  if ($time_updated_upper && $time_updated_upper == sanitise_int($time_updated_upper)) {
1122  $wheres[] = "{$table}.time_updated <= $time_updated_upper";
1123  }
1124 
1125  if ($time_updated_lower && $time_updated_lower == sanitise_int($time_updated_lower)) {
1126  $wheres[] = "{$table}.time_updated >= $time_updated_lower";
1127  }
1128 
1129  if (is_array($wheres) && count($wheres) > 0) {
1130  $where_str = implode(' AND ', $wheres);
1131  return "($where_str)";
1132  }
1133 
1134  return '';
1135  }
1136 
1168  public function getEntitiesFromAttributes(array $options = array()) {
1169  $defaults = array(
1170  'attribute_name_value_pairs' => ELGG_ENTITIES_ANY_VALUE,
1171  'attribute_name_value_pairs_operator' => 'AND',
1172  );
1173 
1174  $options = array_merge($defaults, $options);
1175 
1176  $singulars = array('type', 'attribute_name_value_pair');
1178 
1180 
1181  if ($clauses) {
1182  // merge wheres to pass to elgg_get_entities()
1183  if (isset($options['wheres']) && !is_array($options['wheres'])) {
1184  $options['wheres'] = array($options['wheres']);
1185  } elseif (!isset($options['wheres'])) {
1186  $options['wheres'] = array();
1187  }
1188 
1189  $options['wheres'] = array_merge($options['wheres'], $clauses['wheres']);
1190 
1191  // merge joins to pass to elgg_get_entities()
1192  if (isset($options['joins']) && !is_array($options['joins'])) {
1193  $options['joins'] = array($options['joins']);
1194  } elseif (!isset($options['joins'])) {
1195  $options['joins'] = array();
1196  }
1197 
1198  $options['joins'] = array_merge($options['joins'], $clauses['joins']);
1199  }
1200 
1202  }
1203 
1211  public function getEntityAttributeWhereSql(array $options = array()) {
1212 
1213  if (!isset($options['types'])) {
1214  throw new InvalidArgumentException("The entity type must be defined for elgg_get_entities_from_attributes()");
1215  }
1216 
1217  if (is_array($options['types']) && count($options['types']) !== 1) {
1218  throw new InvalidArgumentException("Only one type can be passed to elgg_get_entities_from_attributes()");
1219  }
1220 
1221  // type can be passed as string or array
1222  $type = $options['types'];
1223  if (is_array($type)) {
1224  $type = $type[0];
1225  }
1226 
1227  // @todo the types should be defined somewhere (as constant on \ElggEntity?)
1228  if (!in_array($type, array('group', 'object', 'site', 'user'))) {
1229  throw new InvalidArgumentException("Invalid type '$type' passed to elgg_get_entities_from_attributes()");
1230  }
1231 
1232 
1233  $type_table = "{$this->db->prefix}{$type}s_entity";
1234 
1235  $return = array(
1236  'joins' => array(),
1237  'wheres' => array(),
1238  );
1239 
1240  // short circuit if nothing requested
1241  if ($options['attribute_name_value_pairs'] == ELGG_ENTITIES_ANY_VALUE) {
1242  return $return;
1243  }
1244 
1245  if (!is_array($options['attribute_name_value_pairs'])) {
1246  throw new InvalidArgumentException("attribute_name_value_pairs must be an array for elgg_get_entities_from_attributes()");
1247  }
1248 
1249  $wheres = array();
1250 
1251  // check if this is an array of pairs or just a single pair.
1252  $pairs = $options['attribute_name_value_pairs'];
1253  if (isset($pairs['name']) || isset($pairs['value'])) {
1254  $pairs = array($pairs);
1255  }
1256 
1257  $pair_wheres = array();
1258  foreach ($pairs as $index => $pair) {
1259  // must have at least a name and value
1260  if (!isset($pair['name']) || !isset($pair['value'])) {
1261  continue;
1262  }
1263 
1264  if (isset($pair['operand'])) {
1265  $operand = sanitize_string($pair['operand']);
1266  } else {
1267  $operand = '=';
1268  }
1269 
1270  if (is_numeric($pair['value'])) {
1271  $value = sanitize_string($pair['value']);
1272  } else if (is_array($pair['value'])) {
1273  $values_array = array();
1274  foreach ($pair['value'] as $pair_value) {
1275  if (is_numeric($pair_value)) {
1276  $values_array[] = sanitize_string($pair_value);
1277  } else {
1278  $values_array[] = "'" . sanitize_string($pair_value) . "'";
1279  }
1280  }
1281 
1282  $operand = 'IN';
1283  if ($values_array) {
1284  $value = '(' . implode(', ', $values_array) . ')';
1285  }
1286  } else {
1287  $value = "'" . sanitize_string($pair['value']) . "'";
1288  }
1289 
1290  $name = sanitize_string($pair['name']);
1291 
1292  // case sensitivity can be specified per pair
1293  $pair_binary = '';
1294  if (isset($pair['case_sensitive'])) {
1295  $pair_binary = ($pair['case_sensitive']) ? 'BINARY ' : '';
1296  }
1297 
1298  $pair_wheres[] = "({$pair_binary}type_table.$name $operand $value)";
1299  }
1300 
1301  if ($where = implode(" {$options['attribute_name_value_pairs_operator']} ", $pair_wheres)) {
1302  $return['wheres'][] = "($where)";
1303 
1304  $return['joins'][] = "JOIN $type_table type_table ON e.guid = type_table.guid";
1305  }
1306 
1307  return $return;
1308  }
1309 
1327  public function getDates($type = '', $subtype = '', $container_guid = 0, $site_guid = 0, $order_by = 'time_created') {
1328 
1329  $site_guid = (int) $site_guid;
1330  if ($site_guid == 0) {
1331  $site_guid = $this->config->get('site_guid');
1332  }
1333  $where = array();
1334 
1335  if ($type != "") {
1336  $type = sanitise_string($type);
1337  $where[] = "type='$type'";
1338  }
1339 
1340  if (is_array($subtype)) {
1341  $tempwhere = "";
1342  if (sizeof($subtype)) {
1343  foreach ($subtype as $typekey => $subtypearray) {
1344  foreach ($subtypearray as $subtypeval) {
1345  $typekey = sanitise_string($typekey);
1346  if (!empty($subtypeval)) {
1347  if (!$subtypeval = (int) get_subtype_id($typekey, $subtypeval)) {
1348  return false;
1349  }
1350  } else {
1351  $subtypeval = 0;
1352  }
1353  if (!empty($tempwhere)) {
1354  $tempwhere .= " or ";
1355  }
1356  $tempwhere .= "(type = '{$typekey}' and subtype = {$subtypeval})";
1357  }
1358  }
1359  }
1360  if (!empty($tempwhere)) {
1361  $where[] = "({$tempwhere})";
1362  }
1363  } else {
1364  if ($subtype) {
1365  if (!$subtype_id = get_subtype_id($type, $subtype)) {
1366  return false;
1367  } else {
1368  $where[] = "subtype=$subtype_id";
1369  }
1370  }
1371  }
1372 
1373  if ($container_guid !== 0) {
1374  if (is_array($container_guid)) {
1375  foreach ($container_guid as $key => $val) {
1376  $container_guid[$key] = (int) $val;
1377  }
1378  $where[] = "container_guid in (" . implode(",", $container_guid) . ")";
1379  } else {
1381  $where[] = "container_guid = {$container_guid}";
1382  }
1383  }
1384 
1385  if ($site_guid > 0) {
1386  $where[] = "site_guid = {$site_guid}";
1387  }
1388 
1389  $where[] = _elgg_get_access_where_sql(array('table_alias' => ''));
1390 
1391  $sql = "SELECT DISTINCT EXTRACT(YEAR_MONTH FROM FROM_UNIXTIME(time_created)) AS yearmonth
1392  FROM {$this->db->prefix}entities where ";
1393 
1394  foreach ($where as $w) {
1395  $sql .= " $w and ";
1396  }
1397 
1398  $sql .= "1=1 ORDER BY $order_by";
1399  if ($result = $this->db->getData($sql)) {
1400  $endresult = array();
1401  foreach ($result as $res) {
1402  $endresult[] = $res->yearmonth;
1403  }
1404  return $endresult;
1405  }
1406  return false;
1407  }
1408 
1420  public function updateLastAction(ElggEntity $entity, $posted = null) {
1421 
1422  if (!$posted) {
1423  $posted = $this->getCurrentTime()->getTimestamp();
1424  }
1425 
1426  $query = "
1427  UPDATE {$this->db->prefix}entities
1428  SET last_action = :last_action
1429  WHERE guid = :guid
1430  ";
1431 
1432  $params = [
1433  ':last_action' => (int) $posted,
1434  ':guid' => (int) $entity->guid,
1435  ];
1436 
1437  $result = $this->db->updateData($query, true, $params);
1438  if ($result) {
1439  $entity->last_action = $posted;
1440  _elgg_services()->entityCache->set($entity);
1441  $entity->storeInPersistedCache(_elgg_get_memcache('new_entity_cache'));
1442  return (int) $posted;
1443  }
1444 
1445  return false;
1446  }
1447 
1457  public function getUserForPermissionsCheck($guid = 0) {
1458  if (!$guid) {
1459  return $this->session->getLoggedInUser();
1460  }
1461 
1462  // need to ignore access and show hidden entities for potential hidden/disabled users
1463  $ia = $this->session->setIgnoreAccess(true);
1464  $show_hidden = access_show_hidden_entities(true);
1465 
1466  $user = $this->get($guid, 'user');
1467 
1468  $this->session->setIgnoreAccess($ia);
1469  access_show_hidden_entities($show_hidden);
1470 
1471  if (!$user) {
1472  // requested to check access for a specific user_guid, but there is no user entity, so the caller
1473  // should cancel the check and return false
1474  $message = $this->translator->translate('UserFetchFailureException', array($guid));
1475  $this->logger->warn($message);
1476 
1477  throw new UserFetchFailureException();
1478  }
1479 
1480  return $user;
1481  }
1482 
1489  public function disableEntities($owner_guid) {
1491  if (!$entity || !$entity->canEdit()) {
1492  return false;
1493  }
1494 
1495  if (!$this->events->trigger('disable', $entity->type, $entity)) {
1496  return false;
1497  }
1498 
1499  $query = "
1500  UPDATE {$this->table}entities
1501  SET enabled='no'
1502  WHERE owner_guid = :owner_guid
1503  OR container_guid = :owner_guid";
1504 
1505  $params = [
1506  ':owner_guid' => (int) $owner_guid,
1507  ];
1508 
1511 
1512  if ($this->db->updateData($query, true, $params)) {
1513  return true;
1514  }
1515 
1516  return false;
1517  }
1518 
1519 }
insertRow(stdClass $row)
Adds a new row to the entity table.
enable($guid, $recursive=true)
Enable an entity.
_elgg_invalidate_cache_for_entity($entity_guid)
Invalidate entity cache.
Definition: cache.php:249
getRow($guid, $user_guid=null)
Returns a database row from the entities table.
if(!$items) $item
Definition: delete.php:17
if($guid==elgg_get_logged_in_user_guid()) $name
Definition: delete.php:21
$e
Definition: metadata.php:12
get_subtype_id($type, $subtype)
Return the id for a given subtype.
Definition: entities.php:27
$defaults
const ELGG_ENTITIES_NO_VALUE
Definition: elgglib.php:2104
The Elgg database.
Definition: Database.php:17
__construct(Conf $config, Database $db, EntityCache $entity_cache, MetadataCache $metadata_cache, SubtypeTable $subtype_table, EventsService $events, ElggSession $session, Translator $translator, Logger $logger)
Constructor.
if($selector) $select
Definition: filter.php:36
$value
Definition: longtext.php:42
$column
Definition: add.php:13
$subtype
Definition: delete.php:28
$return
Definition: opendd.php:15
if(!$count) $offset
Definition: pagination.php:26
getCurrentTime($modifier= '')
Get the (cloned) time.
Definition: TimeUsing.php:26
$guid
Removes an admin notice.
_elgg_get_memcache($namespace= 'default')
Get a namespaced ElggMemcache object (if memcache is available) or a null cache.
Definition: memcache.php:40
storeInPersistedCache(\ElggSharedMemoryCache $cache, $last_action=0)
Cache the entity in a persisted cache.
Definition: ElggEntity.php:602
autoJoinTables(array $options)
Decorate getEntities() options in order to auto-join secondary tables where it&#39;s safe to do so...
updateLastAction(ElggEntity $entity, $posted=null)
Update the last_action column in the entities table for $guid.
_elgg_sql_reverse_order_by_clause($order_by)
Reverses the ordering in an ORDER BY clause.
Definition: elgglib.php:1802
sanitize_string($string)
Sanitizes a string for use in a query.
Definition: database.php:153
$options
Elgg admin footer.
Definition: footer.php:6
getDates($type= '', $subtype= '', $container_guid=0, $site_guid=0, $order_by= 'time_created')
Returns a list of months in which entities were updated or created.
_elgg_invalidate_memcache_for_entity($entity_guid)
Invalidate an entity in memcache.
Definition: memcache.php:28
elgg_get_ignore_access()
Get current ignore access setting.
Definition: access.php:54
$params
Definition: login.php:72
list style type
Definition: admin.css.php:808
getEntityTimeWhereSql($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.
WARNING: API IN FLUX.
Definition: Translator.php:11
$owner_guid
rowToElggStar($row)
Create an Elgg* object from a given entity row.
elgg_instanceof($entity, $type=null, $subtype=null, $class=null)
Checks if $entity is an and optionally for type and subtype.
Definition: entities.php:736
$limit
Definition: userpicker.php:38
$key
Definition: summary.php:34
getGuidBasedWhereSql($column, $guids)
Returns SQL where clause for owner and containers.
Volatile cache for entities.
Definition: EntityCache.php:12
getEntityTypeSubtypeWhereSql($table, $types=[], $subtypes=[], $pairs=[])
Returns SQL where clause for type and subtype on main entity table.
Exception indicating a user could not be looked up for a permissions check.
sanitise_string($string)
Alias of sanitize_string.
Definition: database.php:166
$user
Definition: ban.php:13
table
Definition: admin.css.php:59
const ELGG_ENTITIES_ANY_VALUE
Definition: elgglib.php:2095
elgg ElggUser
Definition: ElggUser.js:12
updateRow($guid, stdClass $row)
Update entity table row.
disableEntities($owner_guid)
Disables all entities owned and contained by a user (or another entity)
getEntityAttributeWhereSql(array $options=array())
Get the join and where clauses for working with entity attributes.
_elgg_services(\Elgg\Di\ServiceProvider $services=null)
Get the global service provider.
Definition: autoloader.php:17
access_get_show_hidden_status()
Return current status of showing disabled entities.
Definition: access.php:170
exists($guid)
Does an entity exist?
getEntities(array $options=array())
Returns an array of entities with optional filtering.
fetchFromSql($sql,\ElggBatch $batch=null)
Return entities from an SQL query generated by elgg_get_entities.
$posted
Definition: comment.php:83
$guids
access_show_hidden_entities($show_hidden)
Show or hide disabled entities.
Definition: access.php:158
In memory cache of known metadata values stored by entity.
$subtypes
$entity
Definition: delete.php:7
getFromCache($guid)
Get an entity from the in-memory or memcache caches.
class
Definition: placeholder.php:21
has_access_to_entity($entity, $user=null)
Can a user access an entity.
Definition: access.php:237
$row
sanitise_int($int, $signed=true)
Alias of sanitize_int.
Definition: database.php:194
_elgg_get_entity_attribute_where_sql(array $options=array())
Get the join and where clauses for working with entity attributes.
Definition: entities.php:485
$rows
getEntitiesFromAttributes(array $options=array())
Gets entities based upon attributes in secondary tables.
$index
Definition: gallery.php:49
$container_guid
trait TimeUsing
Adds methods for setting the current time (for testing)
Definition: TimeUsing.php:11
elgg_get_entities_from_relationship($options)
Return entities matching a given query joining against a relationship.
_elgg_normalize_plural_options_array($options, $singulars)
Normalise the singular keys in an options array to plural keys.
Definition: elgglib.php:1528
$user_guid
Avatar remove action.
Definition: remove.php:6
_elgg_get_access_where_sql(array $options=array())
Returns the SQL where clause for enforcing read access to data.
Definition: access.php:214
$attributes
Definition: ajax_loader.php:13
elgg ElggEntity
Definition: ElggEntity.js:16
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
get_entity($guid)
Loads and returns an entity object from a guid.
Definition: entities.php:204
$access
Definition: save.php:15
if(!$display_name) $type
Definition: delete.php:27
getUserForPermissionsCheck($guid=0)
Get a user by GUID even if the entity is hidden or disabled.