Elgg  Version 4.x
EntityTable.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Database;
4 
8 use Elgg\Config;
9 use Elgg\Database;
19 
26 class EntityTable {
27 
28  use Loggable;
29  use TimeUsing;
30 
34  const TABLE_NAME = 'entities';
35 
39  protected $config;
40 
44  protected $db;
45 
49  protected $entity_classes;
50 
54  protected $entity_cache;
55 
59  protected $entity_preloader;
60 
64  protected $metadata_cache;
65 
70 
74  protected $events;
75 
79  protected $session;
80 
84  protected $translator;
85 
89  protected $deleted_guids = [];
90 
103  public function __construct(
104  Config $config,
105  Database $db,
112  ) {
113  $this->config = $config;
114  $this->db = $db;
115  $this->entity_cache = $entity_cache;
116  $this->metadata_cache = $metadata_cache;
117  $this->private_settings_cache = $private_settings_cache;
118  $this->events = $events;
119  $this->session = $session;
120  $this->translator = $translator;
121  }
122 
133  public function setEntityClass($type, $subtype, $class = '') {
134  if (!in_array($type, Config::ENTITY_TYPES)) {
135  throw new InvalidParameterException("$type is not a valid entity type");
136  }
137 
138  $this->entity_classes[$type][$subtype] = $class;
139  }
140 
149  public function getEntityClass($type, $subtype) {
150  if (isset($this->entity_classes[$type][$subtype])) {
151  return $this->entity_classes[$type][$subtype];
152  }
153 
154  return '';
155  }
156 
171  public function getRow($guid, $user_guid = null) {
172 
173  if (!$guid) {
174  return false;
175  }
176 
177  $where = new EntityWhereClause();
178  $where->guids = (int) $guid;
179  $where->viewer_guid = $user_guid;
180 
181  $select = Select::fromTable(self::TABLE_NAME, 'e');
182  $select->select('e.*');
183  $select->addClause($where);
184 
185  return $this->db->getDataRow($select) ?: false;
186  }
187 
199  public function insertRow(\stdClass $row, array $attributes = []) {
200  $insert = Insert::intoTable(self::TABLE_NAME);
201  $insert->values([
202  'type' => $insert->param($row->type, ELGG_VALUE_STRING),
203  'subtype' => $insert->param($row->subtype, ELGG_VALUE_STRING),
204  'owner_guid' => $insert->param($row->owner_guid, ELGG_VALUE_GUID),
205  'container_guid' => $insert->param($row->container_guid, ELGG_VALUE_GUID),
206  'access_id' => $insert->param($row->access_id, ELGG_VALUE_ID),
207  'time_created' => $insert->param($row->time_created, ELGG_VALUE_TIMESTAMP),
208  'time_updated' => $insert->param($row->time_updated, ELGG_VALUE_TIMESTAMP),
209  'last_action' => $insert->param($row->last_action, ELGG_VALUE_TIMESTAMP),
210  ]);
211 
212  return $this->db->insertData($insert);
213  }
214 
223  public function updateRow(int $guid, \stdClass $row) {
224  $update = Update::table(self::TABLE_NAME);
225  $update->set('owner_guid', $update->param($row->owner_guid, ELGG_VALUE_GUID))
226  ->set('container_guid', $update->param($row->container_guid, ELGG_VALUE_GUID))
227  ->set('access_id', $update->param($row->access_id, ELGG_VALUE_ID))
228  ->set('time_created', $update->param($row->time_created, ELGG_VALUE_TIMESTAMP))
229  ->set('time_updated', $update->param($row->time_updated, ELGG_VALUE_TIMESTAMP))
230  ->where($update->compare('guid', '=', $guid, ELGG_VALUE_GUID));
231 
232  return $this->db->updateData($update);
233  }
234 
246  public function rowToElggStar(\stdClass $row) {
247  if (!isset($row->guid) || !isset($row->subtype)) {
248  return false;
249  }
250 
251  $class_name = $this->getEntityClass($row->type, $row->subtype);
252  if ($class_name && !class_exists($class_name)) {
253  $this->getLogger()->error("Class '$class_name' was not found, missing plugin?");
254  $class_name = '';
255  }
256 
257  if (!$class_name) {
258  $map = [
259  'object' => \ElggObject::class,
260  'user' => \ElggUser::class,
261  'group' => \ElggGroup::class,
262  'site' => \ElggSite::class,
263  ];
264 
265  if (isset($map[$row->type])) {
266  $class_name = $map[$row->type];
267  } else {
268  throw new InvalidParameterException("Entity type {$row->type} is not supported.");
269  }
270  }
271 
272  $entity = new $class_name($row);
273  if (!$entity instanceof \ElggEntity) {
274  throw new ClassException("$class_name must extend " . \ElggEntity::class);
275  }
276 
277  return $entity;
278  }
279 
287  public function getFromCache($guid) {
288  $entity = $this->entity_cache->load($guid);
289  if ($entity) {
290  return $entity;
291  }
292 
293  $entity = _elgg_services()->sessionCache->entities->load($guid);
294  if (!$entity instanceof \ElggEntity) {
295  return false;
296  }
297 
298  // Validate accessibility if from cache
300  return false;
301  }
302 
303  $entity->cache(false);
304 
305  return $entity;
306  }
307 
314  public function invalidateCache($guid) {
316  $entity = $this->get($guid);
317  if ($entity instanceof \ElggEntity) {
318  $entity->invalidateCache();
319  }
320  });
321  }
322 
336  public function get($guid, $type = null, $subtype = null) {
337  // We could also use: if (!(int) $guid) { return false },
338  // but that evaluates to a false positive for $guid = true.
339  // This is a bit slower, but more thorough.
340  if (!is_numeric($guid) || $guid === 0 || $guid === '0') {
341  return false;
342  }
343 
344  $guid = (int) $guid;
345 
346  $entity = $this->getFromCache($guid);
347  if (
348  $entity instanceof \ElggEntity &&
349  (!isset($type) || $entity->type === $type) &&
350  (!isset($subtype) || $entity->subtype === $subtype)
351  ) {
352  return $entity;
353  }
354 
355  $row = $this->getRow($guid);
356  if (empty($row)) {
357  return false;
358  }
359 
360  if (isset($type) && $row->type !== $type) {
361  return false;
362  }
363 
364  if (isset($subtype) && $row->subtype !== $subtype) {
365  return false;
366  }
367 
368  $entity = $row;
369 
370  if ($entity instanceof \stdClass) {
371  // Need to check for \stdClass because the unit test mocker returns \ElggEntity classes
372  $entity = $this->rowToElggStar($entity);
373  }
374 
375  $entity->cache();
376 
377  return $entity;
378  }
379 
392  public function exists($guid) {
393 
395  // need to ignore access and show hidden entities to check existence
396  return $this->getRow($guid);
397  });
398 
399  return !empty($result);
400  }
401 
415  public function fetch(QueryBuilder $query, array $options = []) {
416  $results = $this->db->getData($query, $options['callback']);
417 
418  if (empty($results)) {
419  return [];
420  }
421 
422  /* @var $preload \ElggEntity[] */
423  $preload = array_filter($results, function ($e) {
424  return $e instanceof \ElggEntity;
425  });
426 
427  $this->metadata_cache->populateFromEntities($preload);
428 
429  if (elgg_extract('preload_private_settings', $options, false)) {
430  $this->private_settings_cache->populateFromEntities($preload);
431  }
432 
433  $props_to_preload = [];
434  if (elgg_extract('preload_owners', $options, false)) {
435  $props_to_preload[] = 'owner_guid';
436  }
437  if (elgg_extract('preload_containers', $options, false)) {
438  $props_to_preload[] = 'container_guid';
439  }
440 
441  if ($props_to_preload) {
442  _elgg_services()->entityPreloader->preload($preload, $props_to_preload);
443  }
444 
445  return $results;
446  }
447 
459  public function updateLastAction(\ElggEntity $entity, int $posted = null) {
460 
461  if ($posted === null) {
462  $posted = $this->getCurrentTime()->getTimestamp();
463  }
464 
465  $update = Update::table(self::TABLE_NAME);
466  $update->set('last_action', $update->param($posted, ELGG_VALUE_TIMESTAMP))
467  ->where($update->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
468 
469  $this->db->updateData($update);
470 
471  return (int) $posted;
472  }
473 
482  public function getUserForPermissionsCheck($guid = 0) {
483  if (!$guid || $guid === $this->session->getLoggedInUserGuid()) {
484  return $this->session->getLoggedInUser();
485  }
486 
488  // need to ignore access and show hidden entities for potential hidden/disabled users
489  return $this->get($guid, 'user');
490  });
491 
492  if (!$user instanceof \ElggUser) {
493  // requested to check access for a specific user_guid, but there is no user entity, so the caller
494  // should cancel the check and return false
495  $message = $this->translator->translate('UserFetchFailureException', [$guid]);
496 
498  }
499 
500  return $user;
501  }
502 
510  public function enable(\ElggEntity $entity): bool {
511  $qb = Update::table(self::TABLE_NAME);
512  $qb->set('enabled', $qb->param('yes', ELGG_VALUE_STRING))
513  ->where($qb->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
514 
515  return $this->db->updateData($qb);
516  }
517 
525  public function disable(\ElggEntity $entity): bool {
526  $qb = Update::table(self::TABLE_NAME);
527  $qb->set('enabled', $qb->param('no', ELGG_VALUE_STRING))
528  ->where($qb->compare('guid', '=', $entity->guid, ELGG_VALUE_GUID));
529 
530  return $this->db->updateData($qb);
531  }
532 
541  public function delete(\ElggEntity $entity, $recursive = true) {
542  $guid = $entity->guid;
543  if (!$guid) {
544  return false;
545  }
546 
547  if (!$this->events->triggerBefore('delete', $entity->type, $entity)) {
548  return false;
549  }
550 
551  $this->events->trigger('delete', $entity->type, $entity);
552 
553  if ($entity instanceof \ElggUser) {
554  // ban to prevent using the site during delete
555  $entity->ban();
556  }
557 
558  // we're going to delete this entity, log the guid to prevent deadloops
559  $this->deleted_guids[] = $entity->guid;
560 
561  if ($recursive) {
562  $this->deleteRelatedEntities($entity);
563  }
564 
565  $this->deleteEntityProperties($entity);
566 
567  $qb = Delete::fromTable(self::TABLE_NAME);
568  $qb->where($qb->compare('guid', '=', $guid, ELGG_VALUE_GUID));
569 
570  $this->db->deleteData($qb);
571 
572  $this->events->triggerAfter('delete', $entity->type, $entity);
573 
574  return true;
575  }
576 
584  protected function deleteRelatedEntities(\ElggEntity $entity) {
585  // Temporarily overriding access controls
586  elgg_call(ELGG_IGNORE_ACCESS | ELGG_SHOW_DISABLED_ENTITIES, function() use ($entity) {
587  /* @var $batch \ElggBatch */
589  'wheres' => function (QueryBuilder $qb, $main_alias) use ($entity) {
590  $ors = $qb->merge([
591  $qb->compare("{$main_alias}.owner_guid", '=', $entity->guid, ELGG_VALUE_GUID),
592  $qb->compare("{$main_alias}.container_guid", '=', $entity->guid, ELGG_VALUE_GUID),
593  ], 'OR');
594 
595  return $qb->merge([
596  $ors,
597  $qb->compare("{$main_alias}.guid", 'neq', $entity->guid, ELGG_VALUE_GUID),
598  ]);
599  },
600  'limit' => false,
601  'batch' => true,
602  'batch_inc_offset' => false,
603  ]);
604  /* @var $e \ElggEntity */
605  foreach ($batch as $e) {
606  if (in_array($e->guid, $this->deleted_guids)) {
607  // prevent deadloops, doing this here in case of large deletes which could cause query length issues
608  $batch->reportFailure();
609  continue;
610  }
611 
612  if (!$this->delete($e, true)) {
613  $batch->reportFailure();
614  }
615  }
616  });
617  }
618 
626  protected function deleteEntityProperties(\ElggEntity $entity) {
627  // Temporarily overriding access controls and disable system_log to save performance
629  $entity->removeAllRelatedRiverItems();
630  $entity->removeAllPrivateSettings();
631  $entity->deleteOwnedAccessCollections();
633  // remove relationships without events
634  // can't use DI provided service because of circular reference
635  _elgg_services()->relationshipsTable->removeAll($entity->guid, '', false, '', false);
636  _elgg_services()->relationshipsTable->removeAll($entity->guid, '', true, '', false);
637  $entity->deleteOwnedAnnotations();
638  $entity->deleteAnnotations();
639  $entity->deleteMetadata();
640  _elgg_services()->delayedEmailQueueTable->deleteAllRecipientRows($entity->guid);
641  });
642 
643  $dir = new \Elgg\EntityDirLocator($entity->guid);
644  $file_path = _elgg_services()->config->dataroot . $dir;
645  elgg_delete_directory($file_path);
646  }
647 }
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:731
deleteOwnedAccessCollections()
Remove all access collections owned by this entity.
const ELGG_DISABLE_SYSTEM_LOG
Definition: constants.php:150
removeAllPrivateSettings()
Removes all private settings.
Definition: ElggEntity.php:688
$user_guid
Definition: login_as.php:10
getRow($guid, $user_guid=null)
Returns a database row from the entities table.
getEntityClass($type, $subtype)
Returns class name registered as a constructor for a given type and subtype.
deleteMetadata($name=null)
Deletes all metadata on this object (metadata.entity_guid = $this->guid).
Definition: ElggEntity.php:495
static table($table, $alias=null)
{}
Definition: Update.php:13
deleteAnnotations($name=null)
Deletes all annotations on this object (annotations.entity_guid = $this->guid).
Definition: ElggEntity.php:718
const ENTITY_TYPES
Definition: Config.php:238
The Elgg database.
Definition: Database.php:24
deleteOwnedAnnotations($name=null)
Deletes all annotations owned by this object (annotations.owner_guid = $this->guid).
Definition: ElggEntity.php:748
disable(\ElggEntity $entity)
Disables entity.
const ELGG_VALUE_GUID
Definition: constants.php:128
Events service.
$subtype
Definition: delete.php:22
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:129
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.
$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
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:146
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:148
removeAllRelatedRiverItems()
Removes all river items related to this entity.
Definition: ElggEntity.php:702
invalidateCache($guid)
Invalidate cache for entity.
elgg_get_entities(array $options=[])
Fetches/counts entities or performs a calculation on their properties.
Definition: entities.php:545
Builds queries for filtering entities by their properties in the entities table.
rowToElggStar(\stdClass $row)
Create an Elgg* object from a given entity row.
In memory cache of known private settings values stored by entity.
Exception indicating a user could not be looked up for a permissions check.
Volatile cache for entities.
Definition: EntityCache.php:14
$user
Definition: ban.php:7
compare($x, $comparison, $y=null, $type=null, $case_sensitive=null)
Build value comparison clause.
updateLastAction(\ElggEntity $entity, int $posted=null)
Update the last_action column in the entities table for $guid.
elgg ElggEntity
Definition: deprecated.js:437
__construct(Config $config, Database $db, EntityCache $entity_cache, MetadataCache $metadata_cache, PrivateSettingsCache $private_settings_cache, EventsService $events,\ElggSession $session, Translator $translator)
Constructor.
deleteAccessCollectionMemberships()
Remove the membership of all access collections for this entity (if the entity is a user) ...
$results
Definition: content.php:22
exists($guid)
Does an entity exist?
elgg_extract($key, $array, $default=null, $strict=true)
Checks for $array[$key] and returns its value if it exists, else returns $default.
Definition: elgglib.php:686
const ELGG_VALUE_TIMESTAMP
Definition: constants.php:130
elgg_delete_directory(string $directory, bool $leave_base_directory=false)
Delete a directory and all its contents.
Definition: filestore.php:80
static fromTable($table, $alias=null)
{}
Definition: Select.php:13
$posted
Definition: comment.php:86
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:127
In memory cache of known metadata values stored by entity.
updateRow(int $guid,\stdClass $row)
Update entity table row.
$query
has_access_to_entity(\ElggEntity $entity,\ElggUser $user=null)
Can a user access an entity.
Definition: access.php:95
setEntityClass($type, $subtype, $class= '')
Sets class constructor name for entities with given type and subtype.
getFromCache($guid)
Get an entity from the in-memory or memcache caches.
deleteEntityProperties(\ElggEntity $entity)
Clear data from secondary tables.
_elgg_services()
Get the global service provider.
Definition: elgglib.php:777
$qb
Definition: queue.php:11
$attributes
Elgg AJAX loader.
Definition: ajax_loader.php:10
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
getUserForPermissionsCheck($guid=0)
Get a user by GUID even if the entity is hidden or disabled.