Elgg  Version 5.1
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;
19 
26 class EntityTable {
27 
28  use Loggable;
29  use TimeUsing;
30 
34  const TABLE_NAME = 'entities';
35 
36  protected Config $config;
37 
38  protected Database $db;
39 
41 
43 
45 
47 
49 
51 
52  protected array $deleted_guids = [];
53 
54  protected array $entity_classes = [];
55 
67  public function __construct(
68  Config $config,
69  Database $db,
70  EntityCache $entity_cache,
71  MetadataCache $metadata_cache,
72  EventsService $events,
73  SessionManagerService $session_manager,
74  Translator $translator
75  ) {
76  $this->config = $config;
77  $this->db = $db;
78  $this->entity_cache = $entity_cache;
79  $this->metadata_cache = $metadata_cache;
80  $this->events = $events;
81  $this->session_manager = $session_manager;
82  $this->translator = $translator;
83  }
84 
95  public function setEntityClass(string $type, string $subtype, string $class = ''): void {
96  if (!in_array($type, Config::ENTITY_TYPES)) {
97  throw new DomainException("{$type} is not a valid entity type");
98  }
99 
100  $this->entity_classes[$type][$subtype] = $class;
101  }
102 
111  public function getEntityClass(string $type, string $subtype): string {
112  return $this->entity_classes[$type][$subtype] ?? '';
113  }
114 
129  public function getRow(int $guid, int $user_guid = null): ?\stdClass {
130  if ($guid < 0) {
131  return null;
132  }
133 
134  $where = new EntityWhereClause();
135  $where->guids = $guid;
136  $where->viewer_guid = $user_guid;
137 
138  $select = Select::fromTable(self::TABLE_NAME, 'e');
139  $select->select('e.*');
140  $select->addClause($where);
141 
142  return $this->db->getDataRow($select) ?: null;
143  }
144 
156  public function insertRow(\stdClass $row, array $attributes = []): int {
157  $insert = Insert::intoTable(self::TABLE_NAME);
158  $insert->values([
159  'type' => $insert->param($row->type, ELGG_VALUE_STRING),
160  'subtype' => $insert->param($row->subtype, ELGG_VALUE_STRING),
161  'owner_guid' => $insert->param($row->owner_guid, ELGG_VALUE_GUID),
162  'container_guid' => $insert->param($row->container_guid, ELGG_VALUE_GUID),
163  'access_id' => $insert->param($row->access_id, ELGG_VALUE_ID),
164  'time_created' => $insert->param($row->time_created, ELGG_VALUE_TIMESTAMP),
165  'time_updated' => $insert->param($row->time_updated, ELGG_VALUE_TIMESTAMP),
166  'last_action' => $insert->param($row->last_action, ELGG_VALUE_TIMESTAMP),
167  ]);
168 
169  return $this->db->insertData($insert);
170  }
171 
180  public function updateRow(int $guid, \stdClass $row): bool {
181  $update = Update::table(self::TABLE_NAME);
182  $update->set('owner_guid', $update->param($row->owner_guid, ELGG_VALUE_GUID))
183  ->set('container_guid', $update->param($row->container_guid, ELGG_VALUE_GUID))
184  ->set('access_id', $update->param($row->access_id, ELGG_VALUE_ID))
185  ->set('time_created', $update->param($row->time_created, ELGG_VALUE_TIMESTAMP))
186  ->set('time_updated', $update->param($row->time_updated, ELGG_VALUE_TIMESTAMP))
187  ->where($update->compare('guid', '=', $guid, ELGG_VALUE_GUID));
188 
189  return $this->db->updateData($update);
190  }
191 
203  public function rowToElggStar(\stdClass $row): ?\ElggEntity {
204  if (!isset($row->guid) || !isset($row->subtype)) {
205  return null;
206  }
207 
208  $class_name = $this->getEntityClass($row->type, $row->subtype);
209  if ($class_name && !class_exists($class_name)) {
210  $this->getLogger()->error("Class '{$class_name}' was not found, missing plugin?");
211  $class_name = '';
212  }
213 
214  if (!$class_name) {
215  $map = [
216  'object' => \ElggObject::class,
217  'user' => \ElggUser::class,
218  'group' => \ElggGroup::class,
219  'site' => \ElggSite::class,
220  ];
221 
222  if (isset($map[$row->type])) {
223  $class_name = $map[$row->type];
224  } else {
225  throw new DomainException("Entity type {$row->type} is not supported.");
226  }
227  }
228 
229  $entity = new $class_name($row);
230  if (!$entity instanceof \ElggEntity) {
231  throw new ClassException("{$class_name} must extend " . \ElggEntity::class);
232  }
233 
234  return $entity;
235  }
236 
244  public function getFromCache(int $guid): ?\ElggEntity {
245  $entity = $this->entity_cache->load($guid);
246  if ($entity) {
247  return $entity;
248  }
249 
250  $entity = _elgg_services()->sessionCache->entities->load($guid);
251  if (!$entity instanceof \ElggEntity) {
252  return null;
253  }
254 
255  // Validate accessibility if from cache
256  if (!elgg_get_ignore_access() && !$entity->hasAccess()) {
257  return null;
258  }
259 
260  $entity->cache(false);
261 
262  return $entity;
263  }
264 
272  public function invalidateCache(int $guid): void {
273  elgg_call(ELGG_IGNORE_ACCESS | ELGG_SHOW_DISABLED_ENTITIES, function() use ($guid) {
274  $entity = $this->get($guid);
275  if ($entity instanceof \ElggEntity) {
276  $entity->invalidateCache();
277  }
278  });
279  }
280 
294  public function get(int $guid, string $type = null, string $subtype = null): ?\ElggEntity {
295  $entity = $this->getFromCache($guid);
296  if ($entity instanceof \ElggEntity &&
297  (!isset($type) || $entity->type === $type) &&
298  (!isset($subtype) || $entity->subtype === $subtype)
299  ) {
300  return $entity;
301  }
302 
303  $row = $this->getRow($guid);
304  if (empty($row)) {
305  return null;
306  }
307 
308  if (isset($type) && $row->type !== $type) {
309  return null;
310  }
311 
312  if (isset($subtype) && $row->subtype !== $subtype) {
313  return null;
314  }
315 
316  $entity = $row;
317 
318  if ($entity instanceof \stdClass) {
319  // Need to check for \stdClass because the unit test mocker returns \ElggEntity classes
320  $entity = $this->rowToElggStar($entity);
321  }
322 
323  $entity->cache();
324 
325  return $entity;
326  }
327 
340  public function exists(int $guid): bool {
341  return elgg_call(ELGG_IGNORE_ACCESS | ELGG_SHOW_DISABLED_ENTITIES, function() use ($guid) {
342  // need to ignore access and show hidden entities to check existence
343  return !empty($this->getRow($guid));
344  });
345  }
346 
360  public function fetch(QueryBuilder $query, array $options = []): array {
361  $results = $this->db->getData($query, $options['callback']);
362  if (empty($results)) {
363  return [];
364  }
365 
366  /* @var $preload \ElggEntity[] */
367  $preload = array_filter($results, function ($e) {
368  return $e instanceof \ElggEntity;
369  });
370 
371  $this->metadata_cache->populateFromEntities($preload);
372 
373  $props_to_preload = [];
374  if (elgg_extract('preload_owners', $options, false)) {
375  $props_to_preload[] = 'owner_guid';
376  }
377 
378  if (elgg_extract('preload_containers', $options, false)) {
379  $props_to_preload[] = 'container_guid';
380  }
381 
382  if (!empty($props_to_preload)) {
383  _elgg_services()->entityPreloader->preload($preload, $props_to_preload);
384  }
385 
386  return $results;
387  }
388 
400  public function updateLastAction(\ElggEntity $entity, int $posted = null): int {
401  if ($posted === null) {
402  $posted = $this->getCurrentTime()->getTimestamp();
403  }
404 
405  $update = Update::table(self::TABLE_NAME);
406  $update->set('last_action', $update->param($posted, ELGG_VALUE_TIMESTAMP))
407  ->where($update->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
408 
409  $this->db->updateData($update);
410 
411  return (int) $posted;
412  }
413 
422  public function getUserForPermissionsCheck(int $guid = null): ?\ElggUser {
423  if (empty($guid) || $guid === $this->session_manager->getLoggedInUserGuid()) {
424  return $this->session_manager->getLoggedInUser();
425  }
426 
427  $user = elgg_call(ELGG_IGNORE_ACCESS | ELGG_SHOW_DISABLED_ENTITIES, function() use ($guid) {
428  // need to ignore access and show hidden entities for potential hidden/disabled users
429  return $this->get($guid, 'user');
430  });
431 
432  if (!$user instanceof \ElggUser) {
433  // requested to check access for a specific user_guid, but there is no user entity, so the caller
434  // should cancel the check and return false
435  $message = $this->translator->translate('UserFetchFailureException', [$guid]);
436 
438  }
439 
440  return $user;
441  }
442 
450  public function enable(\ElggEntity $entity): bool {
451  $qb = Update::table(self::TABLE_NAME);
452  $qb->set('enabled', $qb->param('yes', ELGG_VALUE_STRING))
453  ->where($qb->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
454 
455  return $this->db->updateData($qb);
456  }
457 
465  public function disable(\ElggEntity $entity): bool {
466  $qb = Update::table(self::TABLE_NAME);
467  $qb->set('enabled', $qb->param('no', ELGG_VALUE_STRING))
468  ->where($qb->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
469 
470  return $this->db->updateData($qb);
471  }
472 
481  public function delete(\ElggEntity $entity, bool $recursive = true): bool {
482  $guid = $entity->guid;
483  if (!$guid) {
484  return false;
485  }
486 
487  if (!$this->events->triggerBefore('delete', $entity->type, $entity)) {
488  return false;
489  }
490 
491  $this->events->trigger('delete', $entity->type, $entity);
492 
493  if ($entity instanceof \ElggUser) {
494  // ban to prevent using the site during delete
495  $entity->ban();
496  }
497 
498  // we're going to delete this entity, log the guid to prevent deadloops
499  $this->deleted_guids[] = $entity->guid;
500 
501  if ($recursive) {
502  $this->deleteRelatedEntities($entity);
503  }
504 
505  $this->deleteEntityProperties($entity);
506 
507  $qb = Delete::fromTable(self::TABLE_NAME);
508  $qb->where($qb->compare('guid', '=', $guid, ELGG_VALUE_GUID));
509 
510  $this->db->deleteData($qb);
511 
512  $this->events->triggerAfter('delete', $entity->type, $entity);
513 
514  return true;
515  }
516 
524  protected function deleteRelatedEntities(\ElggEntity $entity): void {
525  // Temporarily overriding access controls
526  elgg_call(ELGG_IGNORE_ACCESS | ELGG_SHOW_DISABLED_ENTITIES, function() use ($entity) {
527  /* @var $batch \ElggBatch */
529  'wheres' => function (QueryBuilder $qb, $main_alias) use ($entity) {
530  $ors = $qb->merge([
531  $qb->compare("{$main_alias}.owner_guid", '=', $entity->guid, ELGG_VALUE_GUID),
532  $qb->compare("{$main_alias}.container_guid", '=', $entity->guid, ELGG_VALUE_GUID),
533  ], 'OR');
534 
535  return $qb->merge([
536  $ors,
537  $qb->compare("{$main_alias}.guid", 'neq', $entity->guid, ELGG_VALUE_GUID),
538  ]);
539  },
540  'limit' => false,
541  'batch' => true,
542  'batch_inc_offset' => false,
543  ]);
544 
545  /* @var $e \ElggEntity */
546  foreach ($batch as $e) {
547  if (in_array($e->guid, $this->deleted_guids)) {
548  // prevent deadloops, doing this here in case of large deletes which could cause query length issues
549  $batch->reportFailure();
550  continue;
551  }
552 
553  if (!$this->delete($e, true)) {
554  $batch->reportFailure();
555  }
556  }
557  });
558  }
559 
567  protected function deleteEntityProperties(\ElggEntity $entity): void {
568  // Temporarily overriding access controls and disable system_log to save performance
570  $entity->removeAllRelatedRiverItems();
571  $entity->deleteOwnedAccessCollections();
573  // remove relationships without events
574  // can't use DI provided service because of circular reference
575  _elgg_services()->relationshipsTable->removeAll($entity->guid, '', false, '', false);
576  _elgg_services()->relationshipsTable->removeAll($entity->guid, '', true, '', false);
577  $entity->deleteOwnedAnnotations();
578  $entity->deleteAnnotations();
579  $entity->deleteMetadata();
580  _elgg_services()->delayedEmailQueueTable->deleteAllRecipientRows($entity->guid);
581  });
582 
583  $dir = new \Elgg\EntityDirLocator($entity->guid);
584  $file_path = _elgg_services()->config->dataroot . $dir;
585  elgg_delete_directory($file_path);
586  }
587 }
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:299
deleteOwnedAccessCollections()
Remove all access collections owned by this entity.
const ELGG_DISABLE_SYSTEM_LOG
Definition: constants.php:134
$user_guid
Definition: login_as.php:10
getFromCache(int $guid)
Get an entity from the in-memory or memcache caches.
static table($table, $alias=null)
{}
Definition: Update.php:13
const ENTITY_TYPES
Definition: Config.php:260
The Elgg database.
Definition: Database.php:25
SessionManagerService $session_manager
Definition: EntityTable.php:48
deleteOwnedAnnotations(string $name=null)
Deletes all annotations owned by this object (annotations.owner_guid = $this->guid).
Definition: ElggEntity.php:678
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:22
deleteAnnotations(string $name=null)
Deletes all annotations on this object (annotations.entity_guid = $this->guid).
Definition: ElggEntity.php:652
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.
getUserForPermissionsCheck(int $guid=null)
Get a user by GUID even if the entity is hidden or disabled.
$options
Elgg admin footer.
Definition: footer.php:6
$class
Definition: summary.php:44
elgg_get_ignore_access()
Functions for Elgg&#39;s access system for entities, metadata, and annotations.
Definition: access.php:16
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:44
getCurrentTime($modifier= '')
Get the (cloned) time.
Definition: TimeUsing.php:25
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.
static intoTable($table)
{}
Definition: Insert.php:13
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:636
elgg_get_entities(array $options=[])
Fetches/counts entities or performs a calculation on their properties.
Definition: entities.php:504
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
$user
Definition: ban.php:7
compare($x, $comparison, $y=null, $type=null, $case_sensitive=null)
Build value comparison clause.
deleteMetadata(string $name=null)
Deletes all metadata on this object (metadata.entity_guid = $this->guid).
Definition: ElggEntity.php:483
EntityPreloader $entity_preloader
Definition: EntityTable.php:42
updateLastAction(\ElggEntity $entity, int $posted=null)
Update the last_action column in the entities table for $entity.
deleteAccessCollectionMemberships()
Remove the membership of all access collections for this entity (if the entity is a user) ...
$results
Definition: content.php:22
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
static fromTable($table, $alias=null)
{}
Definition: Select.php:13
$posted
Definition: comment.php:89
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:23
updateRow(int $guid,\stdClass $row)
Update entity table row.
$query
__construct(Config $config, Database $db, EntityCache $entity_cache, MetadataCache $metadata_cache, EventsService $events, SessionManagerService $session_manager, Translator $translator)
Constructor.
Definition: EntityTable.php:67
deleteEntityProperties(\ElggEntity $entity)
Clear data from secondary tables.
_elgg_services()
Get the global service provider.
Definition: elgglib.php:346
getRow(int $guid, int $user_guid=null)
Returns a database row from the entities table.
Preload entities based on properties of fetched objects.
$qb
Definition: queue.php:11
$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:95
insertRow(\stdClass $row, array $attributes=[])
Adds a new row to the entity table.
static fromTable($table, $alias=null)
{}
Definition: Delete.php:13
Entity table database service.
Definition: EntityTable.php:26
$guid
Reset an ElggUpgrade.
Definition: reset.php:6