Elgg  Version 6.2
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' => \ElggObject::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  $parents = class_parents($class_name);
112  if ($parents === false || !isset($parents[\ElggEntity::class])) {
113  $this->getLogger()->error("{$class_name} must extend " . \ElggEntity::class);
114  return '';
115  }
116 
117  return $class_name;
118  }
119 
134  public function getRow(int $guid, ?int $user_guid = null): ?\stdClass {
135  if ($guid < 0) {
136  return null;
137  }
138 
139  $where = new EntityWhereClause();
140  $where->guids = $guid;
141  $where->viewer_guid = $user_guid;
142 
143  $select = Select::fromTable(self::TABLE_NAME, self::DEFAULT_JOIN_ALIAS);
144  $select->select("{$select->getTableAlias()}.*");
145  $select->addClause($where);
146 
147  return $this->db->getDataRow($select) ?: null;
148  }
149 
161  public function insertRow(\stdClass $row, array $attributes = []): int {
162  $insert = Insert::intoTable(self::TABLE_NAME);
163  $insert->values([
164  'type' => $insert->param($row->type, ELGG_VALUE_STRING),
165  'subtype' => $insert->param($row->subtype, ELGG_VALUE_STRING),
166  'owner_guid' => $insert->param($row->owner_guid, ELGG_VALUE_GUID),
167  'container_guid' => $insert->param($row->container_guid, ELGG_VALUE_GUID),
168  'access_id' => $insert->param($row->access_id, ELGG_VALUE_ID),
169  'time_created' => $insert->param($row->time_created, ELGG_VALUE_TIMESTAMP),
170  'time_updated' => $insert->param($row->time_updated, ELGG_VALUE_TIMESTAMP),
171  'last_action' => $insert->param($row->last_action, ELGG_VALUE_TIMESTAMP),
172  ]);
173 
174  return $this->db->insertData($insert);
175  }
176 
185  public function updateRow(int $guid, \stdClass $row): bool {
186  $update = Update::table(self::TABLE_NAME);
187  $update->set('owner_guid', $update->param($row->owner_guid, ELGG_VALUE_GUID))
188  ->set('container_guid', $update->param($row->container_guid, ELGG_VALUE_GUID))
189  ->set('access_id', $update->param($row->access_id, ELGG_VALUE_ID))
190  ->set('time_created', $update->param($row->time_created, ELGG_VALUE_TIMESTAMP))
191  ->set('time_updated', $update->param($row->time_updated, ELGG_VALUE_TIMESTAMP))
192  ->where($update->compare('guid', '=', $guid, ELGG_VALUE_GUID));
193 
194  return $this->db->updateData($update);
195  }
196 
206  public function rowToElggStar(\stdClass $row): ?\ElggEntity {
207  if (!isset($row->type) || !isset($row->subtype)) {
208  return null;
209  }
210 
211  $class_name = $this->getEntityClass($row->type, $row->subtype);
212  if (!$class_name) {
213  return null;
214  }
215 
216  return new $class_name($row);
217  }
218 
226  public function invalidateCache(int $guid): void {
228  $entity = $this->get($guid);
229  if ($entity instanceof \ElggEntity) {
230  $entity->invalidateCache();
231  }
232  });
233  }
234 
248  public function get(int $guid, ?string $type = null, ?string $subtype = null): ?\ElggEntity {
249  $entity = $this->entity_cache->load($guid);
250  if ($entity instanceof \ElggEntity &&
251  (!isset($type) || $entity->type === $type) &&
252  (!isset($subtype) || $entity->subtype === $subtype)
253  ) {
254  return $entity;
255  }
256 
257  $row = $this->getRow($guid);
258  if (empty($row)) {
259  return null;
260  }
261 
262  if (isset($type) && $row->type !== $type) {
263  return null;
264  }
265 
266  if (isset($subtype) && $row->subtype !== $subtype) {
267  return null;
268  }
269 
270  $entity = $this->rowToElggStar($row);
271  if ($entity instanceof \ElggEntity) {
272  $entity->cache();
273  }
274 
275  return $entity;
276  }
277 
290  public function exists(int $guid): bool {
292  // need to ignore access and show hidden entities to check existence
293  return !empty($this->getRow($guid));
294  });
295  }
296 
310  public function fetch(QueryBuilder $query, array $options = []): array {
311  $results = $this->db->getData($query, $options['callback']);
312  if (empty($results)) {
313  return [];
314  }
315 
316  /* @var $preload \ElggEntity[] */
317  $preload = array_filter($results, function ($e) {
318  return $e instanceof \ElggEntity;
319  });
320 
321  $this->metadata_cache->populateFromEntities($preload);
322 
323  $props_to_preload = [];
324  if (elgg_extract('preload_owners', $options, false)) {
325  $props_to_preload[] = 'owner_guid';
326  }
327 
328  if (elgg_extract('preload_containers', $options, false)) {
329  $props_to_preload[] = 'container_guid';
330  }
331 
332  if (!empty($props_to_preload)) {
333  _elgg_services()->entityPreloader->preload($preload, $props_to_preload);
334  }
335 
336  return $results;
337  }
338 
347  public function updateTimeDeleted(\ElggEntity $entity, ?int $deleted = null): int {
348  if ($deleted === null) {
349  $deleted = $this->getCurrentTime()->getTimestamp();
350  }
351 
352  $update = Update::table(self::TABLE_NAME);
353  $update->set('time_deleted', $update->param($deleted, ELGG_VALUE_TIMESTAMP))
354  ->where($update->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
355 
356  $this->db->updateData($update);
357 
358  return (int) $deleted;
359  }
360 
372  public function updateLastAction(\ElggEntity $entity, ?int $posted = null): int {
373  if ($posted === null) {
374  $posted = $this->getCurrentTime()->getTimestamp();
375  }
376 
377  $update = Update::table(self::TABLE_NAME);
378  $update->set('last_action', $update->param($posted, ELGG_VALUE_TIMESTAMP))
379  ->where($update->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
380 
381  $this->db->updateData($update);
382 
383  return (int) $posted;
384  }
385 
394  public function getUserForPermissionsCheck(?int $guid = null): ?\ElggUser {
395  if (empty($guid) || $guid === $this->session_manager->getLoggedInUserGuid()) {
396  return $this->session_manager->getLoggedInUser();
397  }
398 
400  // need to ignore access and show hidden entities for potential hidden/disabled users
401  return $this->get($guid, 'user');
402  });
403 
404  if (!$user instanceof \ElggUser) {
405  // requested to check access for a specific user_guid, but there is no user entity, so the caller
406  // should cancel the check and return false
407  $message = $this->translator->translate('UserFetchFailureException', [$guid]);
408 
410  }
411 
412  return $user;
413  }
414 
422  public function restore(\ElggEntity $entity): bool {
423  $qb = Update::table(self::TABLE_NAME);
424  $qb->set('deleted', $qb->param('no', ELGG_VALUE_STRING))
425  ->set('time_deleted', $qb->param(0, ELGG_VALUE_TIMESTAMP))
426  ->where($qb->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
427 
428  return $this->db->updateData($qb);
429  }
430 
438  public function enable(\ElggEntity $entity): bool {
439  $qb = Update::table(self::TABLE_NAME);
440  $qb->set('enabled', $qb->param('yes', ELGG_VALUE_STRING))
441  ->where($qb->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
442 
443  return $this->db->updateData($qb);
444  }
445 
453  public function disable(\ElggEntity $entity): bool {
454  $qb = Update::table(self::TABLE_NAME);
455  $qb->set('enabled', $qb->param('no', ELGG_VALUE_STRING))
456  ->where($qb->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
457 
458  return $this->db->updateData($qb);
459  }
460 
469  public function delete(\ElggEntity $entity, bool $recursive = true): bool {
470  if (!$entity->guid) {
471  return false;
472  }
473 
474  set_time_limit(0);
475 
476  return $this->events->triggerSequence('delete', $entity->type, $entity, function(\ElggEntity $entity) use ($recursive) {
477  if ($entity instanceof \ElggUser) {
478  // ban to prevent using the site during delete
479  $entity->ban();
480  }
481 
482  // we're going to delete this entity, log the guid to prevent deadloops
483  $this->deleted_guids[] = $entity->guid;
484 
485  if ($recursive) {
486  $this->deleteRelatedEntities($entity);
487  }
488 
489  $this->deleteEntityProperties($entity);
490 
491  $qb = Delete::fromTable(self::TABLE_NAME);
492  $qb->where($qb->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
493 
494  return (bool) $this->db->deleteData($qb);
495  });
496  }
497 
506  public function trash(\ElggEntity $entity, bool $recursive = true): bool {
507  if (!$entity->guid) {
508  return false;
509  }
510 
511  if (!$this->config->trash_enabled) {
512  return $this->delete($entity, $recursive);
513  }
514 
515  if ($entity->isDeleted()) {
516  // already trashed
517  return true;
518  }
519 
520  return $this->events->triggerSequence('trash', $entity->type, $entity, function(\ElggEntity $entity) use ($recursive) {
521  $unban_after = false;
522  if ($entity instanceof \ElggUser && !$entity->isBanned()) {
523  // temporarily ban to prevent using the site during disable
524  $entity->ban();
525  $unban_after = true;
526  }
527 
528  $this->trashed_guids[] = $entity->guid;
529 
530  if ($recursive) {
531  set_time_limit(0);
532 
533  $this->trashRelatedEntities($entity);
534  }
535 
536  $deleter_guid = elgg_get_logged_in_user_guid();
537  if (!empty($deleter_guid)) {
538  $entity->addRelationship($deleter_guid, 'deleted_by');
539  }
540 
541  $qb = Update::table(self::TABLE_NAME);
542  $qb->set('deleted', $qb->param('yes', ELGG_VALUE_STRING))
543  ->where($qb->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
544 
545  $trashed = $this->db->updateData($qb);
546 
547  $entity->updateTimeDeleted();
548 
549  if ($unban_after) {
550  $entity->unban();
551  }
552 
553  if ($trashed) {
554  $entity->invalidateCache();
555  }
556 
557  return $trashed;
558  });
559  }
560 
568  protected function deleteRelatedEntities(\ElggEntity $entity): void {
569  // Temporarily overriding access controls
571  /* @var $batch \ElggBatch */
573  'wheres' => function (QueryBuilder $qb, $main_alias) use ($entity) {
574  $ors = $qb->merge([
575  $qb->compare("{$main_alias}.owner_guid", '=', $entity->guid, ELGG_VALUE_GUID),
576  $qb->compare("{$main_alias}.container_guid", '=', $entity->guid, ELGG_VALUE_GUID),
577  ], 'OR');
578 
579  return $qb->merge([
580  $ors,
581  $qb->compare("{$main_alias}.guid", 'neq', $entity->guid, ELGG_VALUE_GUID),
582  ]);
583  },
584  'limit' => false,
585  'batch' => true,
586  'batch_inc_offset' => false,
587  ]);
588 
589  /* @var $e \ElggEntity */
590  foreach ($batch as $e) {
591  if (in_array($e->guid, $this->deleted_guids)) {
592  // prevent deadloops, doing this here in case of large deletes which could cause query length issues
593  $batch->reportFailure();
594  continue;
595  }
596 
597  if (!$e->delete(true, true)) {
598  $batch->reportFailure();
599  }
600  }
601  });
602  }
603 
611  protected function trashRelatedEntities(\ElggEntity $entity): void {
612  // Temporarily overriding access controls
614  /* @var $batch \ElggBatch */
616  'wheres' => function (QueryBuilder $qb, $main_alias) use ($entity) {
617  $ors = $qb->merge([
618  $qb->compare("{$main_alias}.owner_guid", '=', $entity->guid, ELGG_VALUE_GUID),
619  $qb->compare("{$main_alias}.container_guid", '=', $entity->guid, ELGG_VALUE_GUID),
620  ], 'OR');
621 
622  return $qb->merge([
623  $ors,
624  $qb->compare("{$main_alias}.guid", 'neq', $entity->guid, ELGG_VALUE_GUID),
625  ]);
626  },
627  'limit' => false,
628  'batch' => true,
629  'batch_inc_offset' => false,
630  ]);
631 
632  /* @var $e \ElggEntity */
633  foreach ($batch as $e) {
634  if (in_array($e->guid, $this->trashed_guids)) {
635  // prevent deadloops, doing this here in case of large deletes which could cause query length issues
636  $batch->reportFailure();
637  continue;
638  }
639 
640  if (!$e->delete(true, false)) {
641  $batch->reportFailure();
642  }
643 
644  $e->addRelationship($entity->guid, 'deleted_with');
645  }
646  });
647  }
648 
656  protected function deleteEntityProperties(\ElggEntity $entity): void {
657  // Temporarily overriding access controls and disable system_log to save performance
659  $entity->removeAllRelatedRiverItems();
660  $entity->deleteOwnedAccessCollections();
661  $entity->deleteAccessCollectionMemberships();
662  // remove relationships without events
663  // can't use DI provided service because of circular reference
664  _elgg_services()->relationshipsTable->removeAll($entity->guid, '', false, '', false);
665  _elgg_services()->relationshipsTable->removeAll($entity->guid, '', true, '', false);
666  $entity->deleteOwnedAnnotations();
667  $entity->deleteAnnotations();
668  $entity->deleteMetadata();
669  _elgg_services()->delayedEmailQueueTable->deleteAllRecipientRows($entity->guid);
670  });
671 
672  $dir = new \Elgg\EntityDirLocator($entity->guid);
673  $file_path = _elgg_services()->config->dataroot . $dir;
674  elgg_delete_directory($file_path);
675  }
676 }
$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'], '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:73
$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.
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:353
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:306
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:256
elgg_get_entities(array $options=[])
Fetches/counts entities or performs a calculation on their properties.
Definition: entities.php:507
elgg_delete_directory(string $directory, bool $leave_base_directory=false)
Delete a directory and all its contents.
Definition: filestore.php:51
$user_guid
Definition: login_as.php:10
$qb
Definition: queue.php:12
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