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;
15 use Elgg\Traits\Loggable;
16 use Elgg\Traits\TimeUsing;
17 
24 class EntityTable {
25 
26  use Loggable;
27  use TimeUsing;
28 
32  public const TABLE_NAME = 'entities';
33 
34  public const DEFAULT_JOIN_ALIAS = 'e';
35 
36  protected array $deleted_guids = [];
37 
38  protected array $trashed_guids = [];
39 
40  protected array $entity_classes = [];
41 
53  public function __construct(
54  protected Config $config,
55  protected Database $db,
56  protected EntityCache $entity_cache,
57  protected MetadataCache $metadata_cache,
58  protected EventsService $events,
59  protected SessionManagerService $session_manager,
60  protected Translator $translator
61  ) {
62  }
63 
74  public function setEntityClass(string $type, string $subtype, string $class = ''): void {
75  if (!in_array($type, Config::ENTITY_TYPES)) {
76  throw new DomainException("{$type} is not a valid entity type");
77  }
78 
79  $this->entity_classes[$type][$subtype] = $class;
80  }
81 
91  public function getEntityClass(string $type, string $subtype): string {
92 
93  $class_name = $this->entity_classes[$type][$subtype] ?? '';
94  if ($class_name && !class_exists($class_name)) {
95  $this->getLogger()->error("Class '{$class_name}' was not found");
96  $class_name = '';
97  }
98 
99  if (empty($class_name)) {
100  $map = [
101  'object' => \ElggUndefinedObject::class,
102  'user' => \ElggUser::class,
103  'group' => \ElggGroup::class,
104  'site' => \ElggSite::class,
105  ];
106  if (isset($map[$type])) {
107  $class_name = $map[$type];
108  }
109  }
110 
111  if (!is_a($class_name, \ElggEntity::class, true)) {
112  $this->getLogger()->error("{$class_name} must extend " . \ElggEntity::class);
113  return '';
114  }
115 
116  return $class_name;
117  }
118 
126  public function getEntityClasses(): array {
127  return $this->entity_classes;
128  }
129 
144  public function getRow(int $guid, ?int $user_guid = null): ?\stdClass {
145  if ($guid < 1) {
146  return null;
147  }
148 
149  $where = new EntityWhereClause();
150  $where->guids = $guid;
151  $where->viewer_guid = $user_guid;
152 
153  $select = Select::fromTable(self::TABLE_NAME, self::DEFAULT_JOIN_ALIAS);
154  $select->select("{$select->getTableAlias()}.*");
155  $select->addClause($where);
156 
157  return $this->db->getDataRow($select) ?: null;
158  }
159 
171  public function insertRow(\stdClass $row, array $attributes = []): int {
172  $insert = Insert::intoTable(self::TABLE_NAME);
173  $insert->values([
174  'type' => $insert->param($row->type, ELGG_VALUE_STRING),
175  'subtype' => $insert->param($row->subtype, ELGG_VALUE_STRING),
176  'owner_guid' => $insert->param($row->owner_guid, ELGG_VALUE_GUID),
177  'container_guid' => $insert->param($row->container_guid, ELGG_VALUE_GUID),
178  'access_id' => $insert->param($row->access_id, ELGG_VALUE_ID),
179  'time_created' => $insert->param($row->time_created, ELGG_VALUE_TIMESTAMP),
180  'time_updated' => $insert->param($row->time_updated, ELGG_VALUE_TIMESTAMP),
181  'last_action' => $insert->param($row->last_action, ELGG_VALUE_TIMESTAMP),
182  ]);
183 
184  return $this->db->insertData($insert);
185  }
186 
195  public function updateRow(int $guid, \stdClass $row): bool {
196  $update = Update::table(self::TABLE_NAME);
197  $update->set('owner_guid', $update->param($row->owner_guid, ELGG_VALUE_GUID))
198  ->set('container_guid', $update->param($row->container_guid, ELGG_VALUE_GUID))
199  ->set('access_id', $update->param($row->access_id, ELGG_VALUE_ID))
200  ->set('time_created', $update->param($row->time_created, ELGG_VALUE_TIMESTAMP))
201  ->set('time_updated', $update->param($row->time_updated, ELGG_VALUE_TIMESTAMP))
202  ->where($update->compare('guid', '=', $guid, ELGG_VALUE_GUID));
203 
204  return $this->db->updateData($update);
205  }
206 
216  public function rowToElggStar(\stdClass $row): ?\ElggEntity {
217  if (!isset($row->type) || !isset($row->subtype)) {
218  return null;
219  }
220 
221  $class_name = $this->getEntityClass($row->type, $row->subtype);
222  if (!$class_name) {
223  return null;
224  }
225 
226  return new $class_name($row);
227  }
228 
236  public function invalidateCache(int $guid): void {
238  $entity = $this->get($guid);
239  if ($entity instanceof \ElggEntity) {
240  $entity->invalidateCache();
241  }
242  });
243  }
244 
258  public function get(int $guid, ?string $type = null, ?string $subtype = null): ?\ElggEntity {
259  $entity = $this->entity_cache->load($guid);
260  if ($entity instanceof \ElggEntity &&
261  (!isset($type) || $entity->type === $type) &&
262  (!isset($subtype) || $entity->subtype === $subtype)
263  ) {
264  return $entity;
265  }
266 
267  $row = $this->getRow($guid);
268  if (empty($row)) {
269  return null;
270  }
271 
272  if (isset($type) && $row->type !== $type) {
273  return null;
274  }
275 
276  if (isset($subtype) && $row->subtype !== $subtype) {
277  return null;
278  }
279 
280  $entity = $this->rowToElggStar($row);
281  if ($entity instanceof \ElggEntity) {
282  $entity->cache();
283  }
284 
285  return $entity;
286  }
287 
300  public function exists(int $guid): bool {
302  // need to ignore access and show hidden entities to check existence
303  return !empty($this->getRow($guid));
304  });
305  }
306 
320  public function fetch(QueryBuilder $query, array $options = []): array {
321  $results = $this->db->getData($query, $options['callback']);
322  if (empty($results)) {
323  return [];
324  }
325 
326  /* @var $preload \ElggEntity[] */
327  $preload = array_filter($results, function ($e) {
328  return $e instanceof \ElggEntity;
329  });
330 
331  $this->metadata_cache->populateFromEntities($preload);
332 
333  $props_to_preload = [];
334  if (elgg_extract('preload_owners', $options, false)) {
335  $props_to_preload[] = 'owner_guid';
336  }
337 
338  if (elgg_extract('preload_containers', $options, false)) {
339  $props_to_preload[] = 'container_guid';
340  }
341 
342  if (!empty($props_to_preload)) {
343  _elgg_services()->entityPreloader->preload($preload, $props_to_preload);
344  }
345 
346  return $results;
347  }
348 
357  public function updateTimeDeleted(\ElggEntity $entity, ?int $deleted = null): int {
358  if ($deleted === null) {
359  $deleted = $this->getCurrentTime()->getTimestamp();
360  }
361 
362  $update = Update::table(self::TABLE_NAME);
363  $update->set('time_deleted', $update->param($deleted, ELGG_VALUE_TIMESTAMP))
364  ->where($update->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
365 
366  $this->db->updateData($update);
367 
368  return (int) $deleted;
369  }
370 
382  public function updateLastAction(\ElggEntity $entity, ?int $posted = null): int {
383  if ($posted === null) {
384  $posted = $this->getCurrentTime()->getTimestamp();
385  }
386 
387  $update = Update::table(self::TABLE_NAME);
388  $update->set('last_action', $update->param($posted, ELGG_VALUE_TIMESTAMP))
389  ->where($update->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
390 
391  $this->db->updateData($update);
392 
393  return (int) $posted;
394  }
395 
404  public function getUserForPermissionsCheck(?int $guid = null): ?\ElggUser {
405  if (empty($guid) || $guid === $this->session_manager->getLoggedInUserGuid()) {
406  return $this->session_manager->getLoggedInUser();
407  }
408 
410  // need to ignore access and show hidden entities for potential hidden/disabled users
411  return $this->get($guid, 'user');
412  });
413 
414  if (!$user instanceof \ElggUser) {
415  // requested to check access for a specific user_guid, but there is no user entity, so the caller
416  // should cancel the check and return false
417  $message = $this->translator->translate('UserFetchFailureException', [$guid]);
418 
420  }
421 
422  return $user;
423  }
424 
432  public function restore(\ElggEntity $entity): bool {
433  $qb = Update::table(self::TABLE_NAME);
434  $qb->set('deleted', $qb->param('no', ELGG_VALUE_STRING))
435  ->set('time_deleted', $qb->param(0, ELGG_VALUE_TIMESTAMP))
436  ->where($qb->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
437 
438  return $this->db->updateData($qb);
439  }
440 
448  public function enable(\ElggEntity $entity): bool {
449  $qb = Update::table(self::TABLE_NAME);
450  $qb->set('enabled', $qb->param('yes', ELGG_VALUE_STRING))
451  ->where($qb->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
452 
453  return $this->db->updateData($qb);
454  }
455 
463  public function disable(\ElggEntity $entity): bool {
464  $qb = Update::table(self::TABLE_NAME);
465  $qb->set('enabled', $qb->param('no', ELGG_VALUE_STRING))
466  ->where($qb->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
467 
468  return $this->db->updateData($qb);
469  }
470 
479  public function delete(\ElggEntity $entity, bool $recursive = true): bool {
480  if (!$entity->guid) {
481  return false;
482  }
483 
484  set_time_limit(0);
485 
486  return $this->events->triggerSequence('delete', $entity->type, $entity, function(\ElggEntity $entity) use ($recursive) {
487  if ($entity instanceof \ElggUser) {
488  // ban to prevent using the site during delete
489  $entity->ban();
490  }
491 
492  // we're going to delete this entity, log the guid to prevent deadloops
493  $this->deleted_guids[] = $entity->guid;
494 
495  if ($recursive) {
496  $this->deleteRelatedEntities($entity);
497  }
498 
499  $this->deleteEntityProperties($entity);
500 
501  $qb = Delete::fromTable(self::TABLE_NAME);
502  $qb->where($qb->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
503 
504  return (bool) $this->db->deleteData($qb);
505  });
506  }
507 
516  public function trash(\ElggEntity $entity, bool $recursive = true): bool {
517  if (!$entity->guid) {
518  return false;
519  }
520 
521  if (!$this->config->trash_enabled) {
522  return $this->delete($entity, $recursive);
523  }
524 
525  if ($entity->isDeleted()) {
526  // already trashed
527  return true;
528  }
529 
530  return $this->events->triggerSequence('trash', $entity->type, $entity, function(\ElggEntity $entity) use ($recursive) {
531  $unban_after = false;
532  if ($entity instanceof \ElggUser && !$entity->isBanned()) {
533  // temporarily ban to prevent using the site during disable
534  $entity->ban();
535  $unban_after = true;
536  }
537 
538  $this->trashed_guids[] = $entity->guid;
539 
540  if ($recursive) {
541  set_time_limit(0);
542 
543  $this->trashRelatedEntities($entity);
544  }
545 
546  $deleter_guid = elgg_get_logged_in_user_guid();
547  if (!empty($deleter_guid)) {
548  $entity->addRelationship($deleter_guid, 'deleted_by');
549  }
550 
551  $qb = Update::table(self::TABLE_NAME);
552  $qb->set('deleted', $qb->param('yes', ELGG_VALUE_STRING))
553  ->where($qb->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
554 
555  $trashed = $this->db->updateData($qb);
556 
557  $entity->updateTimeDeleted();
558 
559  if ($unban_after) {
560  $entity->unban();
561  }
562 
563  if ($trashed) {
564  $entity->invalidateCache();
565  }
566 
567  return $trashed;
568  });
569  }
570 
578  protected function deleteRelatedEntities(\ElggEntity $entity): void {
579  // Temporarily overriding access controls
581  /* @var $batch \ElggBatch */
583  'wheres' => function (QueryBuilder $qb, $main_alias) use ($entity) {
584  $ors = $qb->merge([
585  $qb->compare("{$main_alias}.owner_guid", '=', $entity->guid, ELGG_VALUE_GUID),
586  $qb->compare("{$main_alias}.container_guid", '=', $entity->guid, ELGG_VALUE_GUID),
587  ], 'OR');
588 
589  return $qb->merge([
590  $ors,
591  $qb->compare("{$main_alias}.guid", 'neq', $entity->guid, ELGG_VALUE_GUID),
592  ]);
593  },
594  'limit' => false,
595  'batch' => true,
596  'batch_inc_offset' => false,
597  ]);
598 
599  /* @var $e \ElggEntity */
600  foreach ($batch as $e) {
601  if (in_array($e->guid, $this->deleted_guids)) {
602  // prevent deadloops, doing this here in case of large deletes which could cause query length issues
603  $batch->reportFailure();
604  continue;
605  }
606 
607  if (!$e->delete(true, true)) {
608  $batch->reportFailure();
609  }
610  }
611  });
612  }
613 
621  protected function trashRelatedEntities(\ElggEntity $entity): void {
622  // Temporarily overriding access controls
624  /* @var $batch \ElggBatch */
626  'wheres' => function (QueryBuilder $qb, $main_alias) use ($entity) {
627  $ors = $qb->merge([
628  $qb->compare("{$main_alias}.owner_guid", '=', $entity->guid, ELGG_VALUE_GUID),
629  $qb->compare("{$main_alias}.container_guid", '=', $entity->guid, ELGG_VALUE_GUID),
630  ], 'OR');
631 
632  return $qb->merge([
633  $ors,
634  $qb->compare("{$main_alias}.guid", 'neq', $entity->guid, ELGG_VALUE_GUID),
635  ]);
636  },
637  'limit' => false,
638  'batch' => true,
639  'batch_inc_offset' => false,
640  ]);
641 
642  /* @var $e \ElggEntity */
643  foreach ($batch as $e) {
644  if (in_array($e->guid, $this->trashed_guids)) {
645  // prevent deadloops, doing this here in case of large deletes which could cause query length issues
646  $batch->reportFailure();
647  continue;
648  }
649 
650  if (!$e->delete(true, false)) {
651  $batch->reportFailure();
652  }
653 
654  $e->addRelationship($entity->guid, 'deleted_with');
655  }
656  });
657  }
658 
666  protected function deleteEntityProperties(\ElggEntity $entity): void {
667  // Temporarily overriding access controls and disable system_log to save performance
669  $entity->removeAllRelatedRiverItems();
670  $entity->deleteOwnedAccessCollections();
671  $entity->deleteAccessCollectionMemberships();
672  // remove relationships without events
673  // can't use DI provided service because of circular reference
674  _elgg_services()->relationshipsTable->removeAll($entity->guid, '', false, '', false);
675  _elgg_services()->relationshipsTable->removeAll($entity->guid, '', true, '', false);
676  $entity->deleteOwnedAnnotations();
677  $entity->deleteAnnotations();
678  $entity->deleteMetadata();
679  _elgg_services()->delayedEmailQueueTable->deleteAllRecipientRows($entity->guid);
680  });
681 
682  $dir = new \Elgg\EntityDirLocator($entity->guid);
683  $file_path = _elgg_services()->config->dataroot . $dir;
684  elgg_delete_directory($file_path);
685  }
686 }
$entity
Definition: reset.php:8
$guid
Reset an ElggUpgrade.
Definition: reset.php:6
$deleted
Definition: delete.php:25
$subtype
Definition: delete.php:22
$type
Definition: delete.php:21
return[ 'admin/delete_admin_notices'=>['access'=> 'admin'], 'admin/menu/save'=>['access'=> 'admin'], 'admin/plugins/activate'=>['access'=> 'admin'], 'admin/plugins/activate_all'=>['access'=> 'admin'], 'admin/plugins/deactivate'=>['access'=> 'admin'], 'admin/plugins/deactivate_all'=>['access'=> 'admin'], 'admin/plugins/set_priority'=>['access'=> 'admin'], 'admin/security/security_txt'=>['access'=> 'admin'], 'admin/security/settings'=>['access'=> 'admin'], 'admin/security/regenerate_site_secret'=>['access'=> 'admin'], 'admin/site/cache/invalidate'=>['access'=> 'admin'], 'admin/site/flush_cache'=>['access'=> 'admin'], 'admin/site/icons'=>['access'=> 'admin'], 'admin/site/set_maintenance_mode'=>['access'=> 'admin'], 'admin/site/set_robots'=>['access'=> 'admin'], 'admin/site/theme'=>['access'=> 'admin'], 'admin/site/unlock_upgrade'=>['access'=> 'admin'], 'admin/site/settings'=>['access'=> 'admin'], 'admin/upgrade'=>['access'=> 'admin'], 'admin/upgrade/reset'=>['access'=> 'admin'], 'admin/user/ban'=>['access'=> 'admin'], 'admin/user/bulk/ban'=>['access'=> 'admin'], 'admin/user/bulk/delete'=>['access'=> 'admin'], 'admin/user/bulk/unban'=>['access'=> 'admin'], 'admin/user/bulk/validate'=>['access'=> 'admin'], 'admin/user/change_email'=>['access'=> 'admin'], 'admin/user/delete'=>['access'=> 'admin'], 'admin/user/login_as'=>['access'=> 'admin'], 'admin/user/logout_as'=>[], 'admin/user/makeadmin'=>['access'=> 'admin'], 'admin/user/resetpassword'=>['access'=> 'admin'], 'admin/user/removeadmin'=>['access'=> 'admin'], 'admin/user/unban'=>['access'=> 'admin'], 'admin/user/validate'=>['access'=> 'admin'], 'annotation/delete'=>[], 'avatar/upload'=>[], 'comment/save'=>[], 'diagnostics/download'=>['access'=> 'admin', 'controller'=> \Elgg\Diagnostics\DownloadController::class,], 'entity/chooserestoredestination'=>[], 'entity/delete'=>[], 'entity/mute'=>[], 'entity/restore'=>[], 'entity/subscribe'=>[], 'entity/trash'=>[], 'entity/unmute'=>[], 'entity/unsubscribe'=>[], 'login'=>['access'=> 'logged_out'], 'logout'=>[], 'notifications/mute'=>['access'=> 'public'], 'plugins/settings/remove'=>['access'=> 'admin'], 'plugins/settings/save'=>['access'=> 'admin'], 'plugins/usersettings/save'=>[], 'register'=>['access'=> 'logged_out', 'middleware'=>[\Elgg\Router\Middleware\RegistrationAllowedGatekeeper::class,],], 'river/delete'=>[], 'settings/notifications'=>[], 'settings/notifications/subscriptions'=>[], 'user/changepassword'=>['access'=> 'public'], 'user/requestnewpassword'=>['access'=> 'public'], 'useradd'=>['access'=> 'admin'], 'usersettings/save'=>[], 'widgets/add'=>[], 'widgets/delete'=>[], 'widgets/move'=>[], 'widgets/save'=>[],]
Definition: actions.php:76
$attributes
Elgg AJAX loader.
Definition: ajax_loader.php:10
$class
Definition: summary.php:44
$user
Definition: ban.php:7
$query
Volatile cache for entities.
Definition: EntityCache.php:12
In memory cache of known metadata values stored by entity.
Builds queries for filtering entities by their properties in the entities table.
Entity table database service.
Definition: EntityTable.php:24
updateRow(int $guid, \stdClass $row)
Update entity table row.
fetch(QueryBuilder $query, array $options=[])
Returns an array of entities with optional filtering.
insertRow(\stdClass $row, array $attributes=[])
Adds a new row to the entity table.
invalidateCache(int $guid)
Invalidate cache for entity.
getUserForPermissionsCheck(?int $guid=null)
Get a user by GUID even if the entity is hidden or disabled.
setEntityClass(string $type, string $subtype, string $class='')
Sets class constructor name for entities with given type and subtype.
Definition: EntityTable.php:74
deleteRelatedEntities(\ElggEntity $entity)
Deletes entities owned or contained by the entity being deletes.
getEntityClasses()
Returns the currently registered entity classes.
getEntityClass(string $type, string $subtype)
Returns class name registered as a constructor for a given type and subtype The classname is also val...
Definition: EntityTable.php:91
__construct(protected Config $config, protected Database $db, protected EntityCache $entity_cache, protected MetadataCache $metadata_cache, protected EventsService $events, protected SessionManagerService $session_manager, protected Translator $translator)
Constructor.
Definition: EntityTable.php:53
exists(int $guid)
Does an entity exist?
updateLastAction(\ElggEntity $entity, ?int $posted=null)
Update the last_action column in the entities table for $entity.
rowToElggStar(\stdClass $row)
Create an Elgg* object from a given entity row.
trash(\ElggEntity $entity, bool $recursive=true)
Trash an entity (not quite delete but close)
enable(\ElggEntity $entity)
Enables entity.
getRow(int $guid, ?int $user_guid=null)
Returns a database row from the entities table.
restore(\ElggEntity $entity)
Restore entity.
deleteEntityProperties(\ElggEntity $entity)
Clear data from secondary tables.
updateTimeDeleted(\ElggEntity $entity, ?int $deleted=null)
Update the time_deleted column in the entities table for $entity.
trashRelatedEntities(\ElggEntity $entity)
Trash entities owned or contained by the entity being trashed.
disable(\ElggEntity $entity)
Disables entity.
Query builder for inserting data into the database.
Definition: Insert.php:8
Database abstraction query builder.
Query builder for updating data in the database.
Definition: Update.php:8
The Elgg database.
Definition: Database.php:26
Events service.
Exception indicating a user could not be looked up for a permissions check.
Exception thrown if a value does not adhere to a defined valid data domain.
const ELGG_VALUE_STRING
Definition: constants.php:112
const ELGG_IGNORE_ACCESS
elgg_call() flags
Definition: constants.php:121
const ELGG_VALUE_ID
Definition: constants.php:114
const ELGG_SHOW_DISABLED_ENTITIES
Definition: constants.php:123
const ELGG_DISABLE_SYSTEM_LOG
Definition: constants.php:125
const ELGG_VALUE_GUID
Definition: constants.php:113
const ELGG_VALUE_TIMESTAMP
Definition: constants.php:115
const ELGG_SHOW_DELETED_ENTITIES
Definition: constants.php:127
if($who_can_change_language==='nobody') elseif($who_can_change_language==='admin_only' &&!elgg_is_admin_logged_in()) $options
Definition: language.php:20
$config
Advanced site settings, debugging section.
Definition: debugging.php:6
foreach($recommendedExtensions as $extension) if(empty(ini_get('session.gc_probability'))||empty(ini_get('session.gc_divisor'))) $db
$posted
Definition: sidebar.php:21
_elgg_services()
Get the global service provider.
Definition: elgglib.php:343
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:296
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:246
elgg_delete_directory(string $directory, bool $leave_base_directory=false)
Delete a directory and all its contents.
Definition: filestore.php:51
elgg_get_entities(array $options=[])
Fetches/counts entities or performs a calculation on their properties.
Definition: entities.php:507
$user_guid
Definition: login_as.php:10
$qb
Definition: queue.php:14
if(parse_url(elgg_get_site_url(), PHP_URL_PATH) !=='/') if(file_exists(elgg_get_root_path() . 'robots.txt'))
Set robots.txt.
Definition: robots.php:10
elgg_get_logged_in_user_guid()
Return the current logged in user by guid.
Definition: sessions.php:34
$results
Definition: content.php:22