Elgg  Version 3.0
EntityTable.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Database;
4 
9 use Elgg\Config;
10 use Elgg\Database;
16 use Elgg\Logger;
17 use ElggBatch;
18 use ElggEntity;
19 use ElggGroup;
20 use ElggObject;
21 use ElggSession;
22 use ElggSite;
23 use ElggUser;
26 use stdClass;
28 
38 class EntityTable {
39 
41 
45  protected $config;
46 
50  protected $db;
51 
55  protected $table;
56 
60  protected $entity_classes;
61 
65  protected $entity_cache;
66 
70  protected $entity_preloader;
71 
75  protected $metadata_cache;
76 
81 
85  protected $events;
86 
90  protected $session;
91 
95  protected $translator;
96 
100  protected $logger;
101 
115  public function __construct(
116  Config $config,
117  Database $db,
124  LoggerInterface $logger
125  ) {
126  $this->config = $config;
127  $this->db = $db;
128  $this->table = $this->db->prefix . 'entities';
129  $this->entity_cache = $entity_cache;
130  $this->metadata_cache = $metadata_cache;
131  $this->private_settings_cache = $private_settings_cache;
132  $this->events = $events;
133  $this->session = $session;
134  $this->translator = $translator;
135  $this->logger = $logger;
136  }
137 
148  public function setEntityClass($type, $subtype, $class = '') {
149  if (!in_array($type, Config::getEntityTypes())) {
150  throw new InvalidParameterException("$type is not a valid entity type");
151  }
152 
153  $this->entity_classes[$type][$subtype] = $class;
154  }
155 
164  public function getEntityClass($type, $subtype) {
165  if (isset($this->entity_classes[$type][$subtype])) {
166  return $this->entity_classes[$type][$subtype];
167  }
168 
169  return '';
170  }
171 
189  public function getRow($guid, $user_guid = null) {
190 
191  if (!$guid) {
192  return false;
193  }
194 
195  $where = new EntityWhereClause();
196  $where->guids = (int) $guid;
197  $where->viewer_guid = $user_guid;
198 
199  $select = Select::fromTable('entities', 'e');
200  $select->select('e.*');
201  $select->addClause($where);
202 
203  return $this->db->getDataRow($select);
204  }
205 
217  public function insertRow(stdClass $row, array $attributes = []) {
218 
219  $sql = "
220  INSERT INTO {$this->db->prefix}entities
221  (type, subtype, owner_guid, container_guid, access_id, time_created, time_updated, last_action)
222  VALUES
223  (:type, :subtype, :owner_guid, :container_guid, :access_id, :time_created, :time_updated, :last_action)
224  ";
225 
226  return $this->db->insertData($sql, [
227  ':type' => $row->type,
228  ':subtype' => $row->subtype,
229  ':owner_guid' => $row->owner_guid,
230  ':container_guid' => $row->container_guid,
231  ':access_id' => $row->access_id,
232  ':time_created' => $row->time_created,
233  ':time_updated' => $row->time_updated,
234  ':last_action' => $row->last_action,
235  ]);
236  }
237 
246  public function updateRow($guid, stdClass $row) {
247  $sql = "
248  UPDATE {$this->db->prefix}entities
249  SET owner_guid = :owner_guid,
250  access_id = :access_id,
251  container_guid = :container_guid,
252  time_created = :time_created,
253  time_updated = :time_updated
254  WHERE guid = :guid
255  ";
256 
257  $params = [
258  ':owner_guid' => $row->owner_guid,
259  ':access_id' => $row->access_id,
260  ':container_guid' => $row->container_guid,
261  ':time_created' => $row->time_created,
262  ':time_updated' => $row->time_updated,
263  ':guid' => $guid,
264  ];
265 
266  return $this->db->updateData($sql, false, $params);
267  }
268 
283  public function rowToElggStar(stdClass $row) {
284  if (!isset($row->guid) || !isset($row->subtype)) {
285  return false;
286  }
287 
288  $class_name = $this->getEntityClass($row->type, $row->subtype);
289  if ($class_name && !class_exists($class_name)) {
290  $this->logger->error("Class '$class_name' was not found, missing plugin?");
291  $class_name = '';
292  }
293 
294  if (!$class_name) {
295  $map = [
296  'object' => ElggObject::class,
297  'user' => ElggUser::class,
298  'group' => ElggGroup::class,
299  'site' => ElggSite::class,
300  ];
301 
302  if (isset($map[$row->type])) {
303  $class_name = $map[$row->type];
304  } else {
305  throw new InvalidParameterException("Entity type {$row->type} is not supported.");
306  }
307  }
308 
309  $entity = new $class_name($row);
310  if (!$entity instanceof ElggEntity) {
311  throw new ClassException("$class_name must extend " . ElggEntity::class);
312  }
313 
314  return $entity;
315  }
316 
324  public function getFromCache($guid) {
325  $entity = $this->entity_cache->load($guid);
326  if ($entity) {
327  return $entity;
328  }
329 
330  $cache = _elgg_services()->dataCache->entities;
331  $entity = $cache->load($guid);
332  if (!$entity instanceof ElggEntity) {
333  return false;
334  }
335 
336  // Validate accessibility if from cache
338  return false;
339  }
340 
341  $entity->cache(false);
342 
343  return $entity;
344  }
345 
352  public function invalidateCache($guid) {
353  $ia = $this->session->setIgnoreAccess(true);
354  $ha = $this->session->getDisabledEntityVisibility();
355  $this->session->setDisabledEntityVisibility(true);
356  $entity = $this->get($guid);
357  if ($entity) {
358  $entity->invalidateCache();
359  }
360  $this->session->setDisabledEntityVisibility($ha);
361  $this->session->setIgnoreAccess($ia);
362  }
363 
379  public function get($guid, $type = null, $subtype = null) {
380  // We could also use: if (!(int) $guid) { return false },
381  // but that evaluates to a false positive for $guid = true.
382  // This is a bit slower, but more thorough.
383  if (!is_numeric($guid) || $guid === 0 || $guid === '0') {
384  return false;
385  }
386 
387  $guid = (int) $guid;
388 
389  $entity = $this->getFromCache($guid);
390  if ($entity && elgg_instanceof($entity, $type, $subtype)) {
391  return $entity;
392  }
393 
394  $row = $this->getRow($guid);
395  if (!$row) {
396  return false;
397  }
398 
399  if ($type && $row->type != $type) {
400  return false;
401  }
402 
403  if ($subtype && $row->subtype !== $subtype) {
404  return false;
405  }
406 
407  $entity = $row;
408 
409  if ($entity instanceof \stdClass) {
410  $entity = $this->rowToElggStar($entity);
411  }
412 
413  $entity->cache();
414 
415  return $entity;
416  }
417 
430  public function exists($guid) {
431 
432  // need to ignore access and show hidden entities to check existence
433  $ia = $this->session->setIgnoreAccess(true);
434  $show_hidden = $this->session->setDisabledEntityVisibility(true);
435 
436  $result = $this->getRow($guid);
437 
438  $this->session->setIgnoreAccess($ia);
439  $this->session->setDisabledEntityVisibility($show_hidden);
440 
441  return !empty($result);
442  }
443 
452  public function enable($guid, $recursive = true) {
453 
454  // Override access only visible entities
455  $old_access_status = $this->session->getDisabledEntityVisibility();
456  $this->session->setDisabledEntityVisibility(true);
457 
458  $result = false;
460  if ($entity) {
461  $result = $entity->enable($recursive);
462  }
463 
464  $this->session->setDisabledEntityVisibility($old_access_status);
465 
466  return $result;
467  }
468 
483  public function fetch(QueryBuilder $query, array $options = []) {
484  $results = $this->db->getData($query, $options['callback']);
485 
486  if (empty($results)) {
487  return [];
488  }
489 
490  $preload = array_filter($results, function ($e) {
491  return $e instanceof ElggEntity;
492  });
493  /* @var $preload ElggEntity[] */
494 
495  $this->metadata_cache->populateFromEntities($preload);
496 
497  if (elgg_extract('preload_private_settings', $options, false)) {
498  $this->private_settings_cache->populateFromEntities($preload);
499  }
500 
501  $props_to_preload = [];
502  if (elgg_extract('preload_owners', $options, false)) {
503  $props_to_preload[] = 'owner_guid';
504  }
505  if (elgg_extract('preload_containers', $options, false)) {
506  $props_to_preload[] = 'container_guid';
507  }
508 
509  if ($props_to_preload) {
510  _elgg_services()->entityPreloader->preload($preload, $props_to_preload);
511  }
512 
513  return $results;
514  }
515 
527  public function updateLastAction(ElggEntity $entity, $posted = null) {
528 
529  if ($posted === null) {
530  $posted = $this->getCurrentTime()->getTimestamp();
531  }
532 
533  $query = "
534  UPDATE {$this->db->prefix}entities
535  SET last_action = :last_action
536  WHERE guid = :guid
537  ";
538 
539  $params = [
540  ':last_action' => (int) $posted,
541  ':guid' => (int) $entity->guid,
542  ];
543 
544  $this->db->updateData($query, true, $params);
545 
546  return (int) $posted;
547  }
548 
558  public function getUserForPermissionsCheck($guid = 0) {
559  if (!$guid) {
560  return $this->session->getLoggedInUser();
561  }
562 
563  // need to ignore access and show hidden entities for potential hidden/disabled users
564  $ia = $this->session->setIgnoreAccess(true);
565  $show_hidden = $this->session->setDisabledEntityVisibility(true);
566 
567  $user = $this->get($guid, 'user');
568  if ($user) {
569  $this->metadata_cache->populateFromEntities([$user->guid]);
570  }
571 
572  $this->session->setIgnoreAccess($ia);
573  $this->session->setDisabledEntityVisibility($show_hidden);
574 
575  if (!$user) {
576  // requested to check access for a specific user_guid, but there is no user entity, so the caller
577  // should cancel the check and return false
578  $message = $this->translator->translate('UserFetchFailureException', [$guid]);
579  // $this->logger->warn($message);
580 
582  }
583 
584  return $user;
585  }
586 
595  public function disableEntities(ElggEntity $entity) {
596  if (!$entity->canEdit()) {
597  return false;
598  }
599 
600  if (!$this->events->trigger('disable', $entity->type, $entity)) {
601  return false;
602  }
603 
604  $qb = Update::table('entities');
605  $qb->set('enabled', $qb->param('no', ELGG_VALUE_STRING))
606  ->where($qb->compare('owner_guid', '=', $entity->guid, ELGG_VALUE_INTEGER))
607  ->orWhere($qb->compare('container_guid', '=', $entity->guid, ELGG_VALUE_INTEGER));
608 
609  $this->db->updateData($qb, true);
610 
611  $entity->invalidateCache();
612 
613  return true;
614  }
615 
625  public function delete(\ElggEntity $entity, $recursive = true) {
626  $guid = $entity->guid;
627  if (!$guid) {
628  return false;
629  }
630 
631  if (!_elgg_services()->events->triggerBefore('delete', $entity->type, $entity)) {
632  return false;
633  }
634 
635  // now trigger an event to let others know this entity is about to be deleted
636  // so they can prevent it or take their own actions
637  if (!_elgg_services()->events->triggerDeprecated('delete', $entity->type, $entity)) {
638  return false;
639  }
640 
641  if ($entity instanceof ElggUser) {
642  // ban to prevent using the site during delete
643  $entity->ban();
644  }
645 
646  if ($recursive) {
647  $this->deleteRelatedEntities($entity);
648  }
649 
650  $this->deleteEntityProperties($entity);
651 
652  $qb = Delete::fromTable('entities');
653  $qb->where($qb->compare('guid', '=', $guid, ELGG_VALUE_INTEGER));
654 
655  $this->db->deleteData($qb);
656 
657  _elgg_services()->events->triggerAfter('delete', $entity->type, $entity);
658 
659  return true;
660  }
661 
670  protected function deleteRelatedEntities(ElggEntity $entity) {
671  // Temporarily overriding access controls
672  $entity_disable_override = $this->session->getDisabledEntityVisibility();
673  $this->session->setDisabledEntityVisibility(true);
674  $ia = $this->session->setIgnoreAccess(true);
675 
676  $options = [
677  'wheres' => function (QueryBuilder $qb, $main_alias) use ($entity) {
678  $ors = $qb->merge([
679  $qb->compare("{$main_alias}.owner_guid", '=', $entity->guid, ELGG_VALUE_GUID),
680  $qb->compare("{$main_alias}.container_guid", '=', $entity->guid, ELGG_VALUE_GUID),
681  ], 'OR');
682 
683  return $qb->merge([
684  $ors,
685  $qb->compare("{$main_alias}.guid", 'neq', $entity->guid, ELGG_VALUE_GUID),
686  ]);
687  },
688  'limit' => false,
689  ];
690 
691  $batch = new ElggBatch('elgg_get_entities', $options);
692  $batch->setIncrementOffset(false);
693 
694  /* @var $e \ElggEntity */
695  foreach ($batch as $e) {
696  $this->delete($e, true);
697  }
698 
699  $this->session->setDisabledEntityVisibility($entity_disable_override);
700  $this->session->setIgnoreAccess($ia);
701  }
702 
710  protected function deleteEntityProperties(ElggEntity $entity) {
711  elgg_call(ELGG_IGNORE_ACCESS | ELGG_SHOW_DISABLED_ENTITIES, function() use ($entity) {
712  $entity->removeAllRelatedRiverItems();
713  $entity->removeAllPrivateSettings();
714  $entity->deleteOwnedAccessCollections();
716  $entity->deleteRelationships();
717  $entity->deleteOwnedAnnotations();
718  $entity->deleteAnnotations();
719  $entity->deleteMetadata();
720  });
721 
722  $dir = new \Elgg\EntityDirLocator($entity->guid);
723  $file_path = _elgg_config()->dataroot . $dir;
724  delete_directory($file_path);
725 
726  }
727 }
elgg_call(int $flags, Closure $closure)
Calls a callable autowiring the arguments using public DI services and applying logic based on flags...
Definition: elgglib.php:1176
deleteOwnedAccessCollections()
Remove all access collections owned by this entity.
disableEntities(ElggEntity $entity)
Disables all entities owned and contained by a user (or another entity)
removeAllPrivateSettings()
Removes all private settings.
Definition: ElggEntity.php:670
$query
Definition: groups.php:8
enable($guid, $recursive=true)
Enable an entity.
$params
Saves global plugin settings.
Definition: save.php:13
rowToElggStar(stdClass $row)
Create an Elgg* object from a given entity row.
__construct(Config $config, Database $db, EntityCache $entity_cache, MetadataCache $metadata_cache, PrivateSettingsCache $private_settings_cache, EventsService $events, ElggSession $session, Translator $translator, LoggerInterface $logger)
Constructor.
getRow($guid, $user_guid=null)
Returns a database row from the entities table.
static getEntityTypes()
Get the core entity types.
Definition: Config.php:562
getEntityClass($type, $subtype)
Returns class name registered as a constructor for a given type and subtype.
deleteMetadata($name=null)
Deletes all metadata on this object (metadata.entity_guid = $this->guid).
Definition: ElggEntity.php:475
static table($table, $alias=null)
{}
Definition: Update.php:13
deleteAnnotations($name=null)
Deletes all annotations on this object (annotations.entity_guid = $this->guid).
Definition: ElggEntity.php:700
invalidateCache()
Invalidate cache for entity.
const ELGG_VALUE_INTEGER
Value types.
Definition: constants.php:138
deleteOwnedAnnotations($name=null)
Deletes all annotations owned by this object (annotations.owner_guid = $this->guid).
Definition: ElggEntity.php:730
const ELGG_VALUE_GUID
Definition: constants.php:140
Events service.
$subtype
Definition: delete.php:22
Database abstraction query builder.
getCurrentTime($modifier= '')
Get the (cloned) time.
Definition: TimeUsing.php:27
fetch(QueryBuilder $query, array $options=[])
Returns an array of entities with optional filtering.
$guid
Removes an admin notice.
insertRow(stdClass $row, array $attributes=[])
Adds a new row to the entity table.
$type
Definition: delete.php:21
updateLastAction(ElggEntity $entity, $posted=null)
Update the last_action column in the entities table for $guid.
canEdit($user_guid=0)
Can a user edit this entity?
elgg_instanceof($entity, $type=null, $subtype=null)
Checks if $entity is an and optionally for type and subtype.
Definition: entities.php:798
$options
Elgg admin footer.
Definition: footer.php:6
elgg_get_ignore_access()
Get current ignore access setting.
Definition: access.php:63
$user_guid
Validate a user.
Definition: validate.php:6
const ELGG_IGNORE_ACCESS
elgg_call() flags
Definition: constants.php:156
deleteRelatedEntities(ElggEntity $entity)
Deletes entities owned or contained by the entity being deletes.
deleteRelationships($relationship=null)
Remove all relationships to and from this entity.
Definition: ElggEntity.php:539
$class
Definition: field.php:29
$entity
Definition: reset.php:8
const ELGG_SHOW_DISABLED_ENTITIES
Definition: constants.php:158
removeAllRelatedRiverItems()
Removes all river items related to this entity.
Definition: ElggEntity.php:684
deleteEntityProperties(ElggEntity $entity)
Clear data from secondary tables.
invalidateCache($guid)
Invalidate cache for entity.
Builds queries for filtering entities by their properties in the entities table.
In memory cache of known private settings values stored by entity.
Volatile cache for entities.
Definition: EntityCache.php:14
Exception indicating a user could not be looked up for a permissions check.
$user
Definition: ban.php:7
compare($x, $comparison, $y=null, $type=null, $case_sensitive=null)
Build value comparison clause.
elgg ElggUser
Definition: ElggUser.js:12
updateRow($guid, stdClass $row)
Update entity table row.
deleteAccessCollectionMemberships()
Remove the membership of all access collections for this entity (if the entity is a user) ...
exists($guid)
Does an entity exist?
elgg_extract($key, $array, $default=null, $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:1131
static fromTable($table, $alias=null)
{}
Definition: Select.php:13
$posted
Definition: comment.php:64
_elgg_config()
Get the Elgg config service.
merge($parts=null, $boolean= 'AND')
Merges multiple composite expressions with a boolean.
const ELGG_VALUE_STRING
Definition: constants.php:139
In memory cache of known metadata values stored by entity.
setEntityClass($type, $subtype, $class= '')
Sets class constructor name for entities with given type and subtype.
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:188
_elgg_services()
Get the global service provider.
Definition: elgglib.php:1292
trait TimeUsing
Adds methods for setting the current time (for testing)
Definition: TimeUsing.php:12
delete_directory($directory)
Delete a directory and all its contents.
Definition: filestore.php:77
$attributes
Definition: ajax_loader.php:13
elgg ElggEntity
Definition: ElggEntity.js:15
static fromTable($table, $alias=null)
{}
Definition: Delete.php:13
WARNING: API IN FLUX.
Definition: EntityTable.php:38
get_entity($guid)
Loads and returns an entity object from a guid.
Definition: entities.php:87
getUserForPermissionsCheck($guid=0)
Get a user by GUID even if the entity is hidden or disabled.