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