Elgg  Version master
EntityTable.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Database;
4 
7 use Elgg\Config;
8 use Elgg\Database;
19 
26 class EntityTable {
27 
28  use Loggable;
29  use TimeUsing;
30 
34  public const TABLE_NAME = 'entities';
35 
36  public const DEFAULT_JOIN_ALIAS = 'e';
37 
38  protected Config $config;
39 
40  protected Database $db;
41 
43 
45 
47 
49 
51 
53 
54  protected array $deleted_guids = [];
55 
56  protected array $entity_classes = [];
57 
69  public function __construct(
70  Config $config,
71  Database $db,
72  EntityCache $entity_cache,
73  MetadataCache $metadata_cache,
74  EventsService $events,
75  SessionManagerService $session_manager,
76  Translator $translator
77  ) {
78  $this->config = $config;
79  $this->db = $db;
80  $this->entity_cache = $entity_cache;
81  $this->metadata_cache = $metadata_cache;
82  $this->events = $events;
83  $this->session_manager = $session_manager;
84  $this->translator = $translator;
85  }
86 
97  public function setEntityClass(string $type, string $subtype, string $class = ''): void {
98  if (!in_array($type, Config::ENTITY_TYPES)) {
99  throw new DomainException("{$type} is not a valid entity type");
100  }
101 
102  $this->entity_classes[$type][$subtype] = $class;
103  }
104 
113  public function getEntityClass(string $type, string $subtype): string {
114  return $this->entity_classes[$type][$subtype] ?? '';
115  }
116 
131  public function getRow(int $guid, int $user_guid = null): ?\stdClass {
132  if ($guid < 0) {
133  return null;
134  }
135 
136  $where = new EntityWhereClause();
137  $where->guids = $guid;
138  $where->viewer_guid = $user_guid;
139 
140  $select = Select::fromTable(self::TABLE_NAME, self::DEFAULT_JOIN_ALIAS);
141  $select->select("{$select->getTableAlias()}.*");
142  $select->addClause($where);
143 
144  return $this->db->getDataRow($select) ?: null;
145  }
146 
158  public function insertRow(\stdClass $row, array $attributes = []): int {
159  $insert = Insert::intoTable(self::TABLE_NAME);
160  $insert->values([
161  'type' => $insert->param($row->type, ELGG_VALUE_STRING),
162  'subtype' => $insert->param($row->subtype, ELGG_VALUE_STRING),
163  'owner_guid' => $insert->param($row->owner_guid, ELGG_VALUE_GUID),
164  'container_guid' => $insert->param($row->container_guid, ELGG_VALUE_GUID),
165  'access_id' => $insert->param($row->access_id, ELGG_VALUE_ID),
166  'time_created' => $insert->param($row->time_created, ELGG_VALUE_TIMESTAMP),
167  'time_updated' => $insert->param($row->time_updated, ELGG_VALUE_TIMESTAMP),
168  'last_action' => $insert->param($row->last_action, ELGG_VALUE_TIMESTAMP),
169  ]);
170 
171  return $this->db->insertData($insert);
172  }
173 
182  public function updateRow(int $guid, \stdClass $row): bool {
183  $update = Update::table(self::TABLE_NAME);
184  $update->set('owner_guid', $update->param($row->owner_guid, ELGG_VALUE_GUID))
185  ->set('container_guid', $update->param($row->container_guid, ELGG_VALUE_GUID))
186  ->set('access_id', $update->param($row->access_id, ELGG_VALUE_ID))
187  ->set('time_created', $update->param($row->time_created, ELGG_VALUE_TIMESTAMP))
188  ->set('time_updated', $update->param($row->time_updated, ELGG_VALUE_TIMESTAMP))
189  ->where($update->compare('guid', '=', $guid, ELGG_VALUE_GUID));
190 
191  return $this->db->updateData($update);
192  }
193 
205  public function rowToElggStar(\stdClass $row): ?\ElggEntity {
206  if (!isset($row->guid) || !isset($row->subtype)) {
207  return null;
208  }
209 
210  $class_name = $this->getEntityClass($row->type, $row->subtype);
211  if ($class_name && !class_exists($class_name)) {
212  $this->getLogger()->error("Class '{$class_name}' was not found, missing plugin?");
213  $class_name = '';
214  }
215 
216  if (!$class_name) {
217  $map = [
218  'object' => \ElggObject::class,
219  'user' => \ElggUser::class,
220  'group' => \ElggGroup::class,
221  'site' => \ElggSite::class,
222  ];
223 
224  if (isset($map[$row->type])) {
225  $class_name = $map[$row->type];
226  } else {
227  throw new DomainException("Entity type {$row->type} is not supported.");
228  }
229  }
230 
231  $entity = new $class_name($row);
232  if (!$entity instanceof \ElggEntity) {
233  throw new ClassException("{$class_name} must extend " . \ElggEntity::class);
234  }
235 
236  return $entity;
237  }
238 
246  public function getFromCache(int $guid): ?\ElggEntity {
247  $entity = $this->entity_cache->load($guid);
248  if ($entity) {
249  return $entity;
250  }
251 
252  $entity = _elgg_services()->sessionCache->entities->load($guid);
253  if (!$entity instanceof \ElggEntity) {
254  return null;
255  }
256 
257  // Validate accessibility if from cache
258  if (!elgg_get_ignore_access() && !$entity->hasAccess()) {
259  return null;
260  }
261 
262  $entity->cache(false);
263 
264  return $entity;
265  }
266 
274  public function invalidateCache(int $guid): void {
275  elgg_call(ELGG_IGNORE_ACCESS | ELGG_SHOW_DISABLED_ENTITIES, function() use ($guid) {
276  $entity = $this->get($guid);
277  if ($entity instanceof \ElggEntity) {
278  $entity->invalidateCache();
279  }
280  });
281  }
282 
296  public function get(int $guid, string $type = null, string $subtype = null): ?\ElggEntity {
297  $entity = $this->getFromCache($guid);
298  if ($entity instanceof \ElggEntity &&
299  (!isset($type) || $entity->type === $type) &&
300  (!isset($subtype) || $entity->subtype === $subtype)
301  ) {
302  return $entity;
303  }
304 
305  $row = $this->getRow($guid);
306  if (empty($row)) {
307  return null;
308  }
309 
310  if (isset($type) && $row->type !== $type) {
311  return null;
312  }
313 
314  if (isset($subtype) && $row->subtype !== $subtype) {
315  return null;
316  }
317 
318  $entity = $row;
319 
320  if ($entity instanceof \stdClass) {
321  // Need to check for \stdClass because the unit test mocker returns \ElggEntity classes
322  $entity = $this->rowToElggStar($entity);
323  }
324 
325  $entity->cache();
326 
327  return $entity;
328  }
329 
342  public function exists(int $guid): bool {
343  return elgg_call(ELGG_IGNORE_ACCESS | ELGG_SHOW_DISABLED_ENTITIES, function() use ($guid) {
344  // need to ignore access and show hidden entities to check existence
345  return !empty($this->getRow($guid));
346  });
347  }
348 
362  public function fetch(QueryBuilder $query, array $options = []): array {
363  $results = $this->db->getData($query, $options['callback']);
364  if (empty($results)) {
365  return [];
366  }
367 
368  /* @var $preload \ElggEntity[] */
369  $preload = array_filter($results, function ($e) {
370  return $e instanceof \ElggEntity;
371  });
372 
373  $this->metadata_cache->populateFromEntities($preload);
374 
375  $props_to_preload = [];
376  if (elgg_extract('preload_owners', $options, false)) {
377  $props_to_preload[] = 'owner_guid';
378  }
379 
380  if (elgg_extract('preload_containers', $options, false)) {
381  $props_to_preload[] = 'container_guid';
382  }
383 
384  if (!empty($props_to_preload)) {
385  _elgg_services()->entityPreloader->preload($preload, $props_to_preload);
386  }
387 
388  return $results;
389  }
390 
402  public function updateLastAction(\ElggEntity $entity, int $posted = null): int {
403  if ($posted === null) {
404  $posted = $this->getCurrentTime()->getTimestamp();
405  }
406 
407  $update = Update::table(self::TABLE_NAME);
408  $update->set('last_action', $update->param($posted, ELGG_VALUE_TIMESTAMP))
409  ->where($update->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
410 
411  $this->db->updateData($update);
412 
413  return (int) $posted;
414  }
415 
424  public function getUserForPermissionsCheck(int $guid = null): ?\ElggUser {
425  if (empty($guid) || $guid === $this->session_manager->getLoggedInUserGuid()) {
426  return $this->session_manager->getLoggedInUser();
427  }
428 
429  $user = elgg_call(ELGG_IGNORE_ACCESS | ELGG_SHOW_DISABLED_ENTITIES, function() use ($guid) {
430  // need to ignore access and show hidden entities for potential hidden/disabled users
431  return $this->get($guid, 'user');
432  });
433 
434  if (!$user instanceof \ElggUser) {
435  // requested to check access for a specific user_guid, but there is no user entity, so the caller
436  // should cancel the check and return false
437  $message = $this->translator->translate('UserFetchFailureException', [$guid]);
438 
440  }
441 
442  return $user;
443  }
444 
452  public function enable(\ElggEntity $entity): bool {
453  $qb = Update::table(self::TABLE_NAME);
454  $qb->set('enabled', $qb->param('yes', ELGG_VALUE_STRING))
455  ->where($qb->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
456 
457  return $this->db->updateData($qb);
458  }
459 
467  public function disable(\ElggEntity $entity): bool {
468  $qb = Update::table(self::TABLE_NAME);
469  $qb->set('enabled', $qb->param('no', ELGG_VALUE_STRING))
470  ->where($qb->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
471 
472  return $this->db->updateData($qb);
473  }
474 
483  public function delete(\ElggEntity $entity, bool $recursive = true): bool {
484  $guid = $entity->guid;
485  if (!$guid) {
486  return false;
487  }
488 
489  if (!$this->events->triggerBefore('delete', $entity->type, $entity)) {
490  return false;
491  }
492 
493  $this->events->trigger('delete', $entity->type, $entity);
494 
495  if ($entity instanceof \ElggUser) {
496  // ban to prevent using the site during delete
497  $entity->ban();
498  }
499 
500  // we're going to delete this entity, log the guid to prevent deadloops
501  $this->deleted_guids[] = $entity->guid;
502 
503  if ($recursive) {
504  $this->deleteRelatedEntities($entity);
505  }
506 
507  $this->deleteEntityProperties($entity);
508 
509  $qb = Delete::fromTable(self::TABLE_NAME);
510  $qb->where($qb->compare('guid', '=', $guid, ELGG_VALUE_GUID));
511 
512  $this->db->deleteData($qb);
513 
514  $this->events->triggerAfter('delete', $entity->type, $entity);
515 
516  return true;
517  }
518 
526  protected function deleteRelatedEntities(\ElggEntity $entity): void {
527  // Temporarily overriding access controls
528  elgg_call(ELGG_IGNORE_ACCESS | ELGG_SHOW_DISABLED_ENTITIES, function() use ($entity) {
529  /* @var $batch \ElggBatch */
531  'wheres' => function (QueryBuilder $qb, $main_alias) use ($entity) {
532  $ors = $qb->merge([
533  $qb->compare("{$main_alias}.owner_guid", '=', $entity->guid, ELGG_VALUE_GUID),
534  $qb->compare("{$main_alias}.container_guid", '=', $entity->guid, ELGG_VALUE_GUID),
535  ], 'OR');
536 
537  return $qb->merge([
538  $ors,
539  $qb->compare("{$main_alias}.guid", 'neq', $entity->guid, ELGG_VALUE_GUID),
540  ]);
541  },
542  'limit' => false,
543  'batch' => true,
544  'batch_inc_offset' => false,
545  ]);
546 
547  /* @var $e \ElggEntity */
548  foreach ($batch as $e) {
549  if (in_array($e->guid, $this->deleted_guids)) {
550  // prevent deadloops, doing this here in case of large deletes which could cause query length issues
551  $batch->reportFailure();
552  continue;
553  }
554 
555  if (!$this->delete($e, true)) {
556  $batch->reportFailure();
557  }
558  }
559  });
560  }
561 
569  protected function deleteEntityProperties(\ElggEntity $entity): void {
570  // Temporarily overriding access controls and disable system_log to save performance
572  $entity->removeAllRelatedRiverItems();
573  $entity->deleteOwnedAccessCollections();
575  // remove relationships without events
576  // can't use DI provided service because of circular reference
577  _elgg_services()->relationshipsTable->removeAll($entity->guid, '', false, '', false);
578  _elgg_services()->relationshipsTable->removeAll($entity->guid, '', true, '', false);
579  $entity->deleteOwnedAnnotations();
580  $entity->deleteAnnotations();
581  $entity->deleteMetadata();
582  _elgg_services()->delayedEmailQueueTable->deleteAllRecipientRows($entity->guid);
583  });
584 
585  $dir = new \Elgg\EntityDirLocator($entity->guid);
586  $file_path = _elgg_services()->config->dataroot . $dir;
587  elgg_delete_directory($file_path);
588  }
589 }
enable(\ElggEntity $entity)
Enables entity.
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:299
deleteOwnedAccessCollections()
Remove all access collections owned by this entity.
const ELGG_DISABLE_SYSTEM_LOG
Definition: constants.php:134
$user_guid
Definition: login_as.php:10
getFromCache(int $guid)
Get an entity from the in-memory or memcache caches.
static table($table, $alias=null)
{}
Definition: Update.php:13
const ENTITY_TYPES
Definition: Config.php:259
The Elgg database.
Definition: Database.php:25
SessionManagerService $session_manager
Definition: EntityTable.php:50
deleteOwnedAnnotations(string $name=null)
Deletes all annotations owned by this object (annotations.owner_guid = $this->guid).
Definition: ElggEntity.php:683
disable(\ElggEntity $entity)
Disables entity.
c Accompany it with the information you received as to the offer to distribute corresponding source complete source code means all the source code for all modules it plus any associated interface definition plus the scripts used to control compilation and installation of the executable as a special the source code distributed need not include anything that is normally and so on of the operating system on which the executable unless that component itself accompanies the executable If distribution of executable or object code is made by offering access to copy from a designated then offering equivalent access to copy the source code from the same place counts as distribution of the source even though third parties are not compelled to copy the source along with the object code You may not or distribute the Program except as expressly provided under this License Any attempt otherwise to sublicense or distribute the Program is void
Definition: LICENSE.txt:215
Exception thrown if a value does not adhere to a defined valid data domain.
const ELGG_VALUE_GUID
Definition: constants.php:113
Events service.
Database abstraction query builder.
fetch(QueryBuilder $query, array $options=[])
Returns an array of entities with optional filtering.
$type
Definition: delete.php:22
deleteAnnotations(string $name=null)
Deletes all annotations on this object (annotations.entity_guid = $this->guid).
Definition: ElggEntity.php:656
const ELGG_VALUE_ID
Definition: constants.php:114
trait TimeUsing
Adds methods for setting the current time (for testing)
Definition: TimeUsing.php:10
deleteRelatedEntities(\ElggEntity $entity)
Deletes entities owned or contained by the entity being deletes.
getUserForPermissionsCheck(int $guid=null)
Get a user by GUID even if the entity is hidden or disabled.
$options
Elgg admin footer.
Definition: footer.php:6
$class
Definition: summary.php:44
elgg_get_ignore_access()
Functions for Elgg&#39;s access system for entities, metadata, and annotations.
Definition: access.php:16
elgg_extract($key, $array, $default=null, bool $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:254
MetadataCache $metadata_cache
Definition: EntityTable.php:46
getCurrentTime($modifier= '')
Get the (cloned) time.
Definition: TimeUsing.php:25
trait Loggable
Enables adding a logger.
Definition: Loggable.php:14
const ELGG_IGNORE_ACCESS
elgg_call() flags
Definition: constants.php:130
invalidateCache(int $guid)
Invalidate cache for entity.
getEntityClass(string $type, string $subtype)
Returns class name registered as a constructor for a given type and subtype.
static intoTable($table)
{}
Definition: Insert.php:13
A generic parent class for Class exceptions.
$entity
Definition: reset.php:8
const ELGG_SHOW_DISABLED_ENTITIES
Definition: constants.php:132
removeAllRelatedRiverItems()
Removes all river items related to this entity.
Definition: ElggEntity.php:639
elgg_get_entities(array $options=[])
Fetches/counts entities or performs a calculation on their properties.
Definition: entities.php:505
Builds queries for filtering entities by their properties in the entities table.
rowToElggStar(\stdClass $row)
Create an Elgg* object from a given entity row.
Exception indicating a user could not be looked up for a permissions check.
Volatile cache for entities.
Definition: EntityCache.php:10
compare(string $x, string $comparison, $y=null, string $type=null, bool $case_sensitive=null)
Build value comparison clause.
$user
Definition: ban.php:7
deleteMetadata(string $name=null)
Deletes all metadata on this object (metadata.entity_guid = $this->guid).
Definition: ElggEntity.php:486
EntityPreloader $entity_preloader
Definition: EntityTable.php:44
updateLastAction(\ElggEntity $entity, int $posted=null)
Update the last_action column in the entities table for $entity.
deleteAccessCollectionMemberships()
Remove the membership of all access collections for this entity (if the entity is a user) ...
$results
Definition: content.php:22
const ELGG_VALUE_TIMESTAMP
Definition: constants.php:115
elgg_delete_directory(string $directory, bool $leave_base_directory=false)
Delete a directory and all its contents.
Definition: filestore.php:51
static fromTable($table, $alias=null)
{}
Definition: Select.php:13
$posted
Definition: comment.php:86
exists(int $guid)
Does an entity exist?
getLogger()
Returns logger.
Definition: Loggable.php:37
merge($parts=null, $boolean= 'AND')
Merges multiple composite expressions with a boolean.
const ELGG_VALUE_STRING
Definition: constants.php:112
In memory cache of known metadata values stored by entity.
$subtype
Definition: delete.php:23
updateRow(int $guid,\stdClass $row)
Update entity table row.
$query
__construct(Config $config, Database $db, EntityCache $entity_cache, MetadataCache $metadata_cache, EventsService $events, SessionManagerService $session_manager, Translator $translator)
Constructor.
Definition: EntityTable.php:69
deleteEntityProperties(\ElggEntity $entity)
Clear data from secondary tables.
_elgg_services()
Get the global service provider.
Definition: elgglib.php:346
getRow(int $guid, int $user_guid=null)
Returns a database row from the entities table.
Preload entities based on properties of fetched objects.
$qb
Definition: queue.php:12
$attributes
Elgg AJAX loader.
Definition: ajax_loader.php:10
setEntityClass(string $type, string $subtype, string $class= '')
Sets class constructor name for entities with given type and subtype.
Definition: EntityTable.php:97
insertRow(\stdClass $row, array $attributes=[])
Adds a new row to the entity table.
static fromTable($table, $alias=null)
{}
Definition: Delete.php:13
Entity table database service.
Definition: EntityTable.php:26
$guid
Reset an ElggUpgrade.
Definition: reset.php:6