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;
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 }
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
const ELGG_DISABLE_SYSTEM_LOG
Definition: constants.php:125
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
$posted
Definition: sidebar.php:21
invalidateCache()
Invalidate cache for entity.
const ENTITY_TYPES
Definition: Config.php:267
The Elgg database.
Definition: Database.php:26
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
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.
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
$config
Advanced site settings, debugging section.
Definition: debugging.php:6
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:121
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 The classname is also val...
Definition: EntityTable.php:91
trashRelatedEntities(\ElggEntity $entity)
Trash entities owned or contained by the entity being trashed.
$entity
Definition: reset.php:8
const ELGG_SHOW_DISABLED_ENTITIES
Definition: constants.php:123
removeAllRelatedRiverItems()
Removes all river items related to this entity.
Definition: ElggEntity.php:349
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.
foreach($recommendedExtensions as $extension) if(empty(ini_get('session.gc_probability'))||empty(ini_get('session.gc_divisor'))) $db
Exception indicating a user could not be looked up for a permissions check.
Volatile cache for entities.
Definition: EntityCache.php:12
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
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.
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:127
__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
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
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.
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:74
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:24
$guid
Reset an ElggUpgrade.
Definition: reset.php:6