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;
18 
25 class EntityTable {
26 
27  use Loggable;
28  use TimeUsing;
29 
33  public const TABLE_NAME = 'entities';
34 
35  public const DEFAULT_JOIN_ALIAS = 'e';
36 
37  protected Config $config;
38 
39  protected Database $db;
40 
42 
44 
46 
48 
50 
51  protected array $deleted_guids = [];
52 
53  protected array $trashed_guids = [];
54 
55  protected array $entity_classes = [];
56 
68  public function __construct(
69  Config $config,
70  Database $db,
71  EntityCache $entity_cache,
72  MetadataCache $metadata_cache,
73  EventsService $events,
74  SessionManagerService $session_manager,
75  Translator $translator
76  ) {
77  $this->config = $config;
78  $this->db = $db;
79  $this->entity_cache = $entity_cache;
80  $this->metadata_cache = $metadata_cache;
81  $this->events = $events;
82  $this->session_manager = $session_manager;
83  $this->translator = $translator;
84  }
85 
96  public function setEntityClass(string $type, string $subtype, string $class = ''): void {
97  if (!in_array($type, Config::ENTITY_TYPES)) {
98  throw new DomainException("{$type} is not a valid entity type");
99  }
100 
101  $this->entity_classes[$type][$subtype] = $class;
102  }
103 
112  public function getEntityClass(string $type, string $subtype): string {
113  return $this->entity_classes[$type][$subtype] ?? '';
114  }
115 
130  public function getRow(int $guid, int $user_guid = null): ?\stdClass {
131  if ($guid < 0) {
132  return null;
133  }
134 
135  $where = new EntityWhereClause();
136  $where->guids = $guid;
137  $where->viewer_guid = $user_guid;
138 
139  $select = Select::fromTable(self::TABLE_NAME, self::DEFAULT_JOIN_ALIAS);
140  $select->select("{$select->getTableAlias()}.*");
141  $select->addClause($where);
142 
143  return $this->db->getDataRow($select) ?: null;
144  }
145 
157  public function insertRow(\stdClass $row, array $attributes = []): int {
158  $insert = Insert::intoTable(self::TABLE_NAME);
159  $insert->values([
160  'type' => $insert->param($row->type, ELGG_VALUE_STRING),
161  'subtype' => $insert->param($row->subtype, ELGG_VALUE_STRING),
162  'owner_guid' => $insert->param($row->owner_guid, ELGG_VALUE_GUID),
163  'container_guid' => $insert->param($row->container_guid, ELGG_VALUE_GUID),
164  'access_id' => $insert->param($row->access_id, ELGG_VALUE_ID),
165  'time_created' => $insert->param($row->time_created, ELGG_VALUE_TIMESTAMP),
166  'time_updated' => $insert->param($row->time_updated, ELGG_VALUE_TIMESTAMP),
167  'last_action' => $insert->param($row->last_action, ELGG_VALUE_TIMESTAMP),
168  ]);
169 
170  return $this->db->insertData($insert);
171  }
172 
181  public function updateRow(int $guid, \stdClass $row): bool {
182  $update = Update::table(self::TABLE_NAME);
183  $update->set('owner_guid', $update->param($row->owner_guid, ELGG_VALUE_GUID))
184  ->set('container_guid', $update->param($row->container_guid, ELGG_VALUE_GUID))
185  ->set('access_id', $update->param($row->access_id, ELGG_VALUE_ID))
186  ->set('time_created', $update->param($row->time_created, ELGG_VALUE_TIMESTAMP))
187  ->set('time_updated', $update->param($row->time_updated, ELGG_VALUE_TIMESTAMP))
188  ->where($update->compare('guid', '=', $guid, ELGG_VALUE_GUID));
189 
190  return $this->db->updateData($update);
191  }
192 
204  public function rowToElggStar(\stdClass $row): ?\ElggEntity {
205  if (!isset($row->guid) || !isset($row->subtype)) {
206  return null;
207  }
208 
209  $class_name = $this->getEntityClass($row->type, $row->subtype);
210  if ($class_name && !class_exists($class_name)) {
211  $this->getLogger()->error("Class '{$class_name}' was not found, missing plugin?");
212  $class_name = '';
213  }
214 
215  if (!$class_name) {
216  $map = [
217  'object' => \ElggObject::class,
218  'user' => \ElggUser::class,
219  'group' => \ElggGroup::class,
220  'site' => \ElggSite::class,
221  ];
222 
223  if (isset($map[$row->type])) {
224  $class_name = $map[$row->type];
225  } else {
226  throw new DomainException("Entity type {$row->type} is not supported.");
227  }
228  }
229 
230  $entity = new $class_name($row);
231  if (!$entity instanceof \ElggEntity) {
232  throw new ClassException("{$class_name} must extend " . \ElggEntity::class);
233  }
234 
235  return $entity;
236  }
237 
245  public function invalidateCache(int $guid): void {
247  $entity = $this->get($guid);
248  if ($entity instanceof \ElggEntity) {
249  $entity->invalidateCache();
250  }
251  });
252  }
253 
267  public function get(int $guid, string $type = null, string $subtype = null): ?\ElggEntity {
268  $entity = $this->entity_cache->load($guid);
269  if ($entity instanceof \ElggEntity &&
270  (!isset($type) || $entity->type === $type) &&
271  (!isset($subtype) || $entity->subtype === $subtype)
272  ) {
273  return $entity;
274  }
275 
276  $row = $this->getRow($guid);
277  if (empty($row)) {
278  return null;
279  }
280 
281  if (isset($type) && $row->type !== $type) {
282  return null;
283  }
284 
285  if (isset($subtype) && $row->subtype !== $subtype) {
286  return null;
287  }
288 
289  $entity = $row;
290 
291  if ($entity instanceof \stdClass) {
292  // Need to check for \stdClass because the unit test mocker returns \ElggEntity classes
293  $entity = $this->rowToElggStar($entity);
294  }
295 
296  $entity->cache();
297 
298  return $entity;
299  }
300 
313  public function exists(int $guid): bool {
315  // need to ignore access and show hidden entities to check existence
316  return !empty($this->getRow($guid));
317  });
318  }
319 
333  public function fetch(QueryBuilder $query, array $options = []): array {
334  $results = $this->db->getData($query, $options['callback']);
335  if (empty($results)) {
336  return [];
337  }
338 
339  /* @var $preload \ElggEntity[] */
340  $preload = array_filter($results, function ($e) {
341  return $e instanceof \ElggEntity;
342  });
343 
344  $this->metadata_cache->populateFromEntities($preload);
345 
346  $props_to_preload = [];
347  if (elgg_extract('preload_owners', $options, false)) {
348  $props_to_preload[] = 'owner_guid';
349  }
350 
351  if (elgg_extract('preload_containers', $options, false)) {
352  $props_to_preload[] = 'container_guid';
353  }
354 
355  if (!empty($props_to_preload)) {
356  _elgg_services()->entityPreloader->preload($preload, $props_to_preload);
357  }
358 
359  return $results;
360  }
361 
370  public function updateTimeDeleted(\ElggEntity $entity, int $deleted = null): int {
371  if ($deleted === null) {
372  $deleted = $this->getCurrentTime()->getTimestamp();
373  }
374 
375  $update = Update::table(self::TABLE_NAME);
376  $update->set('time_deleted', $update->param($deleted, ELGG_VALUE_TIMESTAMP))
377  ->where($update->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
378 
379  $this->db->updateData($update);
380 
381  return (int) $deleted;
382  }
383 
395  public function updateLastAction(\ElggEntity $entity, int $posted = null): int {
396  if ($posted === null) {
397  $posted = $this->getCurrentTime()->getTimestamp();
398  }
399 
400  $update = Update::table(self::TABLE_NAME);
401  $update->set('last_action', $update->param($posted, ELGG_VALUE_TIMESTAMP))
402  ->where($update->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
403 
404  $this->db->updateData($update);
405 
406  return (int) $posted;
407  }
408 
417  public function getUserForPermissionsCheck(int $guid = null): ?\ElggUser {
418  if (empty($guid) || $guid === $this->session_manager->getLoggedInUserGuid()) {
419  return $this->session_manager->getLoggedInUser();
420  }
421 
423  // need to ignore access and show hidden entities for potential hidden/disabled users
424  return $this->get($guid, 'user');
425  });
426 
427  if (!$user instanceof \ElggUser) {
428  // requested to check access for a specific user_guid, but there is no user entity, so the caller
429  // should cancel the check and return false
430  $message = $this->translator->translate('UserFetchFailureException', [$guid]);
431 
433  }
434 
435  return $user;
436  }
437 
445  public function restore(\ElggEntity $entity): bool {
446  $qb = Update::table(self::TABLE_NAME);
447  $qb->set('deleted', $qb->param('no', ELGG_VALUE_STRING))
448  ->set('time_deleted', $qb->param(0, ELGG_VALUE_TIMESTAMP))
449  ->where($qb->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
450 
451  return $this->db->updateData($qb);
452  }
453 
461  public function enable(\ElggEntity $entity): bool {
462  $qb = Update::table(self::TABLE_NAME);
463  $qb->set('enabled', $qb->param('yes', ELGG_VALUE_STRING))
464  ->where($qb->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
465 
466  return $this->db->updateData($qb);
467  }
468 
476  public function disable(\ElggEntity $entity): bool {
477  $qb = Update::table(self::TABLE_NAME);
478  $qb->set('enabled', $qb->param('no', ELGG_VALUE_STRING))
479  ->where($qb->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
480 
481  return $this->db->updateData($qb);
482  }
483 
492  public function delete(\ElggEntity $entity, bool $recursive = true): bool {
493  if (!$entity->guid) {
494  return false;
495  }
496 
497  set_time_limit(0);
498 
499  return $this->events->triggerSequence('delete', $entity->type, $entity, function(\ElggEntity $entity) use ($recursive) {
500  if ($entity instanceof \ElggUser) {
501  // ban to prevent using the site during delete
502  $entity->ban();
503  }
504 
505  // we're going to delete this entity, log the guid to prevent deadloops
506  $this->deleted_guids[] = $entity->guid;
507 
508  if ($recursive) {
509  $this->deleteRelatedEntities($entity);
510  }
511 
512  $this->deleteEntityProperties($entity);
513 
514  $qb = Delete::fromTable(self::TABLE_NAME);
515  $qb->where($qb->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
516 
517  return (bool) $this->db->deleteData($qb);
518  });
519  }
520 
529  public function trash(\ElggEntity $entity, bool $recursive = true): bool {
530  if (!$entity->guid) {
531  return false;
532  }
533 
534  if (!$this->config->trash_enabled) {
535  return $this->delete($entity, $recursive);
536  }
537 
538  if ($entity->isDeleted()) {
539  // already trashed
540  return true;
541  }
542 
543  return $this->events->triggerSequence('trash', $entity->type, $entity, function(\ElggEntity $entity) use ($recursive) {
544  $unban_after = false;
545  if ($entity instanceof \ElggUser && !$entity->isBanned()) {
546  // temporarily ban to prevent using the site during disable
547  $entity->ban();
548  $unban_after = true;
549  }
550 
551  $this->trashed_guids[] = $entity->guid;
552 
553  if ($recursive) {
554  set_time_limit(0);
555 
556  $this->trashRelatedEntities($entity);
557  }
558 
559  $deleter_guid = elgg_get_logged_in_user_guid();
560  if (!empty($deleter_guid)) {
561  $entity->addRelationship($deleter_guid, 'delete_by');
562  }
563 
564  $qb = Update::table(self::TABLE_NAME);
565  $qb->set('deleted', $qb->param('yes', ELGG_VALUE_STRING))
566  ->where($qb->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
567 
568  $trashed = $this->db->updateData($qb);
569 
570  $entity->updateTimeDeleted();
571 
572  if ($unban_after) {
573  $entity->unban();
574  }
575 
576  if ($trashed) {
577  $entity->invalidateCache();
578  }
579 
580  return $trashed;
581  });
582  }
583 
591  protected function deleteRelatedEntities(\ElggEntity $entity): void {
592  // Temporarily overriding access controls
594  /* @var $batch \ElggBatch */
596  'wheres' => function (QueryBuilder $qb, $main_alias) use ($entity) {
597  $ors = $qb->merge([
598  $qb->compare("{$main_alias}.owner_guid", '=', $entity->guid, ELGG_VALUE_GUID),
599  $qb->compare("{$main_alias}.container_guid", '=', $entity->guid, ELGG_VALUE_GUID),
600  ], 'OR');
601 
602  return $qb->merge([
603  $ors,
604  $qb->compare("{$main_alias}.guid", 'neq', $entity->guid, ELGG_VALUE_GUID),
605  ]);
606  },
607  'limit' => false,
608  'batch' => true,
609  'batch_inc_offset' => false,
610  ]);
611 
612  /* @var $e \ElggEntity */
613  foreach ($batch as $e) {
614  if (in_array($e->guid, $this->deleted_guids)) {
615  // prevent deadloops, doing this here in case of large deletes which could cause query length issues
616  $batch->reportFailure();
617  continue;
618  }
619 
620  if (!$e->delete(true, true)) {
621  $batch->reportFailure();
622  }
623  }
624  });
625  }
626 
634  protected function trashRelatedEntities(\ElggEntity $entity): void {
635  // Temporarily overriding access controls
637  /* @var $batch \ElggBatch */
639  'wheres' => function (QueryBuilder $qb, $main_alias) use ($entity) {
640  $ors = $qb->merge([
641  $qb->compare("{$main_alias}.owner_guid", '=', $entity->guid, ELGG_VALUE_GUID),
642  $qb->compare("{$main_alias}.container_guid", '=', $entity->guid, ELGG_VALUE_GUID),
643  ], 'OR');
644 
645  return $qb->merge([
646  $ors,
647  $qb->compare("{$main_alias}.guid", 'neq', $entity->guid, ELGG_VALUE_GUID),
648  ]);
649  },
650  'limit' => false,
651  'batch' => true,
652  'batch_inc_offset' => false,
653  ]);
654 
655  /* @var $e \ElggEntity */
656  foreach ($batch as $e) {
657  if (in_array($e->guid, $this->trashed_guids)) {
658  // prevent deadloops, doing this here in case of large deletes which could cause query length issues
659  $batch->reportFailure();
660  continue;
661  }
662 
663  if (!$e->delete(true, false)) {
664  $batch->reportFailure();
665  }
666 
667  $e->addRelationship($entity->guid, 'deleted_with');
668  }
669  });
670  }
671 
679  protected function deleteEntityProperties(\ElggEntity $entity): void {
680  // Temporarily overriding access controls and disable system_log to save performance
682  $entity->removeAllRelatedRiverItems();
683  $entity->deleteOwnedAccessCollections();
685  // remove relationships without events
686  // can't use DI provided service because of circular reference
687  _elgg_services()->relationshipsTable->removeAll($entity->guid, '', false, '', false);
688  _elgg_services()->relationshipsTable->removeAll($entity->guid, '', true, '', false);
689  $entity->deleteOwnedAnnotations();
690  $entity->deleteAnnotations();
691  $entity->deleteMetadata();
692  _elgg_services()->delayedEmailQueueTable->deleteAllRecipientRows($entity->guid);
693  });
694 
695  $dir = new \Elgg\EntityDirLocator($entity->guid);
696  $file_path = _elgg_services()->config->dataroot . $dir;
697  elgg_delete_directory($file_path);
698  }
699 }
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:304
deleteOwnedAccessCollections()
Remove all access collections owned by this entity.
const ELGG_DISABLE_SYSTEM_LOG
Definition: constants.php:134
static table(string $table)
Returns a QueryBuilder for updating data in a given table.
Definition: Update.php:17
$user_guid
Definition: login_as.php:10
$deleted
Definition: delete.php:25
invalidateCache()
Invalidate cache for entity.
const ENTITY_TYPES
Definition: Config.php:263
The Elgg database.
Definition: Database.php:26
SessionManagerService $session_manager
Definition: EntityTable.php:47
deleteOwnedAnnotations(string $name=null)
Deletes all annotations owned by this object (annotations.owner_guid = $this->guid).
Definition: ElggEntity.php:693
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:21
deleteAnnotations(string $name=null)
Deletes all annotations on this object (annotations.entity_guid = $this->guid).
Definition: ElggEntity.php:666
const ELGG_VALUE_ID
Definition: constants.php:114
trait TimeUsing
Adds methods for setting the current time (for testing)
Definition: TimeUsing.php:10
addRelationship(int $guid_two, string $relationship)
Add a relationship between this an another entity.
Definition: ElggEntity.php:552
deleteRelatedEntities(\ElggEntity $entity)
Deletes entities owned or contained by the entity being deletes.
static intoTable(string $table)
Returns a QueryBuilder for inserting data in a given table.
Definition: Insert.php:17
getUserForPermissionsCheck(int $guid=null)
Get a user by GUID even if the entity is hidden or disabled.
$class
Definition: summary.php:44
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:43
getCurrentTime($modifier= '')
Get the (cloned) time.
Definition: TimeUsing.php:25
if($who_can_change_language=== 'nobody') elseif($who_can_change_language=== 'admin_only'&&!elgg_is_admin_logged_in()) $options
Definition: language.php:20
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.
trashRelatedEntities(\ElggEntity $entity)
Trash entities owned or contained by the entity being trashed.
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:649
elgg_get_entities(array $options=[])
Fetches/counts entities or performs a calculation on their properties.
Definition: entities.php:507
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
updateTimeDeleted(\ElggEntity $entity, int $deleted=null)
Update the time_deleted column in the entities table for $entity.
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:496
static fromTable(string $table)
Returns a QueryBuilder for deleting data from a given table.
Definition: Delete.php:17
updateLastAction(\ElggEntity $entity, int $posted=null)
Update the last_action column in the entities table for $entity.
isDeleted()
Is the entity marked as deleted.
restore(\ElggEntity $entity)
Restore entity.
deleteAccessCollectionMemberships()
Remove the membership of all access collections for this entity (if the entity is a user) ...
trash(\ElggEntity $entity, bool $recursive=true)
Trash an entity (not quite delete but close)
$results
Definition: content.php:22
const ELGG_SHOW_DELETED_ENTITIES
Definition: constants.php:136
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
$posted
Definition: comment.php:77
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:22
updateRow(int $guid,\stdClass $row)
Update entity table row.
$query
updateTimeDeleted(int $deleted=null)
Update the time_deleted column in the entities table.
__construct(Config $config, Database $db, EntityCache $entity_cache, MetadataCache $metadata_cache, EventsService $events, SessionManagerService $session_manager, Translator $translator)
Constructor.
Definition: EntityTable.php:68
static fromTable(string $table, string $alias=null)
Returns a QueryBuilder for selecting data from a given table.
Definition: Select.php:18
deleteEntityProperties(\ElggEntity $entity)
Clear data from secondary tables.
_elgg_services()
Get the global service provider.
Definition: elgglib.php:351
getRow(int $guid, int $user_guid=null)
Returns a database row from the entities table.
$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:96
insertRow(\stdClass $row, array $attributes=[])
Adds a new row to the entity table.
elgg_get_logged_in_user_guid()
Return the current logged in user by guid.
Definition: sessions.php:34
Entity table database service.
Definition: EntityTable.php:25
$guid
Reset an ElggUpgrade.
Definition: reset.php:6