Elgg  Version master
AccessCollections.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Database;
4 
7 use Elgg\Config;
8 use Elgg\Database;
16 
24 
25  use Loggable;
26 
30  const TABLE_NAME = 'access_collections';
31 
35  const MEMBERSHIP_TABLE_NAME = 'access_collection_membership';
36 
37  protected Config $config;
38 
39  protected Database $db;
40 
42 
44 
46 
48 
50 
52 
53  protected bool $init_complete = false;
54 
67  public function __construct(
68  Config $config,
69  Database $db,
70  EntityTable $entities,
71  UserCapabilities $capabilities,
72  BaseCache $cache,
73  EventsService $events,
74  SessionManagerService $session_manager,
75  Translator $translator) {
76  $this->config = $config;
77  $this->db = $db;
78  $this->entities = $entities;
79  $this->capabilities = $capabilities;
80  $this->access_cache = $cache;
81  $this->events = $events;
82  $this->session_manager = $session_manager;
83  $this->translator = $translator;
84  }
85 
91  public function markInitComplete(): void {
92  $this->init_complete = true;
93  }
94 
119  public function getAccessArray(int $user_guid = 0, bool $flush = false): array {
120  $cache = $this->access_cache;
121 
122  if ($flush) {
123  $cache->clear();
124  }
125 
126  if ($user_guid === 0) {
127  $user_guid = $this->session_manager->getLoggedInUserGuid();
128  }
129 
130  $hash = $user_guid . 'get_access_array';
131 
132  if ($cache[$hash]) {
133  $access_array = $cache[$hash];
134  } else {
135  // Public access is always visible
136  $access_array = [ACCESS_PUBLIC];
137 
138  // The following can only return sensible data for a known user.
139  if ($user_guid) {
140  $access_array[] = ACCESS_LOGGED_IN;
141 
142  // Get ACLs that user owns or is a member of
143  $select = Select::fromTable(self::TABLE_NAME);
144 
145  $membership_query = $select->subquery(self::MEMBERSHIP_TABLE_NAME);
146  $membership_query->select('access_collection_id')
147  ->where($select->compare('user_guid', '=', $user_guid, ELGG_VALUE_GUID));
148 
149  $select->select('id')
150  ->where($select->compare('owner_guid', '=', $user_guid, ELGG_VALUE_GUID))
151  ->orWhere($select->compare('id', 'in', $membership_query->getSQL()));
152 
153  $collections = $this->db->getData($select);
154  if (!empty($collections)) {
155  foreach ($collections as $collection) {
156  $access_array[] = (int) $collection->id;
157  }
158  }
159 
160  $ignore_access = $this->capabilities->canBypassPermissionsCheck($user_guid);
161 
162  if ($ignore_access === true) {
163  $access_array[] = ACCESS_PRIVATE;
164  }
165  }
166 
167  if ($this->init_complete) {
168  $cache[$hash] = $access_array;
169  }
170  }
171 
172  $options = [
173  'user_id' => $user_guid,
174  ];
175 
176  // see the warning in the docs for this function about infinite loop potential
177  return $this->events->triggerResults('access:collections:read', 'user', $options, $access_array);
178  }
179 
194  public function hasAccessToEntity(\ElggEntity $entity, int $user_guid = 0): bool {
195  if ($entity->access_id === ACCESS_PUBLIC) {
196  // Public entities are always accessible
197  return true;
198  }
199 
200  try {
201  $user = $this->entities->getUserForPermissionsCheck($user_guid);
202  $user_guid = $user ? $user->guid : 0; // No GUID given and not logged in
203  } catch (UserFetchFailureException $e) {
204  // Not a user GUID
205  $user_guid = 0;
206  }
207 
208  if ($user_guid === $entity->owner_guid) {
209  // Owners have access to their own content
210  return true;
211  }
212 
213  if (!empty($user_guid) && $entity->access_id === ACCESS_LOGGED_IN) {
214  // Existing users have access to entities with logged in access
215  return true;
216  }
217 
218  // See #7159. Must not allow ignore access to affect query
219  $row = elgg_call(ELGG_ENFORCE_ACCESS, function() use ($entity, $user_guid) {
220  return $this->entities->getRow($entity->guid, $user_guid);
221  });
222 
223  return !empty($row);
224  }
225 
249  public function getWriteAccessArray(int $user_guid = 0, bool $flush = false, array $input_params = []): array {
250  $cache = $this->access_cache;
251 
252  if ($flush) {
253  $cache->clear();
254  }
255 
256  if ($user_guid === 0) {
257  $user_guid = $this->session_manager->getLoggedInUserGuid();
258  }
259 
260  $hash = $user_guid . 'get_write_access_array';
261 
262  if ($cache[$hash]) {
263  $access_array = $cache[$hash];
264  } else {
265  $access_array = [
269  ];
270 
271  $access_array += $this->getCollectionsForWriteAccess($user_guid);
272 
273  if ($this->init_complete) {
274  $cache[$hash] = $access_array;
275  }
276  }
277 
278  $options = [
279  'user_id' => $user_guid,
280  'input_params' => $input_params,
281  ];
282 
283  $access_array = $this->events->triggerResults('access:collections:write', 'user', $options, $access_array);
284 
285  // move logged in and public to the end of the array
286  foreach ([ACCESS_LOGGED_IN, ACCESS_PUBLIC] as $access) {
287  if (!isset($access_array[$access])) {
288  continue;
289  }
290 
291  $temp = $access_array[$access];
292  unset($access_array[$access]);
293  $access_array[$access] = $temp;
294  }
295 
296 
297  return $access_array;
298  }
299 
308  protected function getCollectionsForWriteAccess(int $owner_guid): array {
309  $subtypes = $this->events->triggerResults('access:collections:write:subtypes', 'user', ['owner_guid' => $owner_guid], []);
310 
311  $select = Select::fromTable(self::TABLE_NAME);
312 
313  $ors = [
314  $select->compare('subtype', 'is null'),
315  ];
316  if (!empty($subtypes)) {
317  $ors[] = $select->compare('subtype', 'in', $subtypes, ELGG_VALUE_STRING);
318  }
319 
320  $select->select('*')
321  ->where($select->compare('owner_guid', '=', $owner_guid, ELGG_VALUE_GUID))
322  ->andWhere($select->merge($ors, 'OR'))
323  ->orderBy('name', 'ASC');
324 
325  $collections = $this->db->getData($select, [$this, 'rowToElggAccessCollection']);
326  if (empty($collections)) {
327  return [];
328  }
329 
330  $result = [];
331  foreach ($collections as $collection) {
332  $result[$collection->id] = $collection->getDisplayName();
333  }
334 
335  return $result;
336  }
337 
351  public function canEdit(int $collection_id, int $user_guid = null): bool {
352  try {
353  $user = $this->entities->getUserForPermissionsCheck($user_guid);
354  } catch (UserFetchFailureException $e) {
355  return false;
356  }
357 
358  if (!$user instanceof \ElggUser) {
359  return false;
360  }
361 
362  $collection = $this->get($collection_id);
363  if (!$collection instanceof \ElggAccessCollection) {
364  return false;
365  }
366 
367  if ($this->capabilities->canBypassPermissionsCheck($user->guid)) {
368  return true;
369  }
370 
371  $write_access = $this->getWriteAccessArray($user->guid, true);
372  return array_key_exists($collection_id, $write_access);
373  }
374 
390  public function create(\ElggAccessCollection $acl): bool {
391  if (!empty($acl->id)) {
392  return $this->update($acl);
393  }
394 
395  return $this->events->triggerSequence('create', $acl->getType(), $acl, function (\ElggAccessCollection $acl) {
396  $insert = Insert::intoTable(self::TABLE_NAME);
397  $insert->values([
398  'name' => $insert->param($acl->name, ELGG_VALUE_STRING),
399  'subtype' => $insert->param($acl->subtype, ELGG_VALUE_STRING),
400  'owner_guid' => $insert->param($acl->owner_guid, ELGG_VALUE_GUID),
401  ]);
402 
403  $id = $this->db->insertData($insert);
404  if (empty($id)) {
405  return false;
406  }
407 
408  $acl->id = $id;
409 
410  $this->access_cache->clear();
411 
412  return true;
413  });
414  }
415 
423  public function update(\ElggAccessCollection $acl): bool {
424  if (empty($acl->id)) {
425  return $this->create($acl);
426  }
427 
428  return $this->events->triggerSequence('update', $acl->getType(), $acl, function (\ElggAccessCollection $acl) {
429  $update = Update::table(self::TABLE_NAME);
430  $update->set('name', $update->param($acl->name, ELGG_VALUE_STRING))
431  ->set('subtype', $update->param($acl->subtype, ELGG_VALUE_STRING))
432  ->set('owner_guid', $update->param($acl->owner_guid, ELGG_VALUE_GUID))
433  ->where($update->compare('id', '=', $acl->id, ELGG_VALUE_ID));
434 
435  if (!$this->db->updateData($update)) {
436  return false;
437  }
438 
439  $this->access_cache->clear();
440 
441  return true;
442  });
443  }
444 
452  public function delete(\ElggAccessCollection $acl): bool {
453  if (empty($acl->id)) {
454  return false;
455  }
456 
457  return $this->events->triggerSequence('delete', $acl->getType(), $acl, function (\ElggAccessCollection $acl) {
458  $delete = Delete::fromTable(self::TABLE_NAME);
459  $delete->where($delete->compare('id', '=', $acl->id, ELGG_VALUE_ID));
460 
461  if (!$this->db->deleteData($delete)) {
462  return false;
463  }
464 
465  // cleanup access collection membership (doesn't affect the result of deleting the ACL)
466  $delete_membership = Delete::fromTable(self::MEMBERSHIP_TABLE_NAME);
467  $delete_membership->where($delete_membership->compare('access_collection_id', '=', $acl->id, ELGG_VALUE_ID));
468 
469  $this->db->deleteData($delete_membership);
470 
471  // clear cache
472  $this->access_cache->clear();
473 
474  return true;
475  });
476  }
477 
485  public function rowToElggAccessCollection(\stdClass $row): \ElggAccessCollection {
486  return new \ElggAccessCollection($row);
487  }
488 
496  public function get(int $collection_id): ?\ElggAccessCollection {
497  $query = Select::fromTable(self::TABLE_NAME);
498  $query->select('*')
499  ->where($query->compare('id', '=', $collection_id, ELGG_VALUE_ID));
500 
501  return $this->db->getDataRow($query, [$this, 'rowToElggAccessCollection']) ?: null;
502  }
503 
512  public function hasUser(int $user_guid, int $collection_id): bool {
513  $options = [
514  'guids' => $user_guid,
515  'count' => true,
516  ];
517  return (bool) $this->getMembers($collection_id, $options);
518  }
519 
530  public function addUser(int $user_guid, int $collection_id): bool {
531  $collection = $this->get($collection_id);
532  if (!$collection instanceof \ElggAccessCollection) {
533  return false;
534  }
535 
536  $user = $this->entities->get($user_guid);
537  if (!$user instanceof \ElggUser) {
538  return false;
539  }
540 
541  $event_params = [
542  'collection_id' => $collection->id,
543  'user_guid' => $user_guid
544  ];
545 
546  // @todo https://github.com/Elgg/Elgg/issues/10823
547  $result = $this->events->triggerResults('access:collections:add_user', 'collection', $event_params, true);
548  if ($result === false) {
549  return false;
550  }
551 
552  // if someone tries to insert the same data twice, we catch the exception and return true
553  $insert = Insert::intoTable(self::MEMBERSHIP_TABLE_NAME);
554  $insert->values([
555  'access_collection_id' => $insert->param($collection_id, ELGG_VALUE_ID),
556  'user_guid' => $insert->param($user_guid, ELGG_VALUE_GUID),
557  ]);
558 
559  try {
560  $result = $this->db->insertData($insert);
561  } catch (DatabaseException $e) {
562  $prev = $e->getPrevious();
563  if ($prev instanceof UniqueConstraintViolationException) {
564  // duplicate key exception, catched for performance reasons
565  return true;
566  }
567 
568  throw $e;
569  }
570 
571  $this->access_cache->clear();
572 
573  return $result !== false;
574  }
575 
586  public function removeUser(int $user_guid, int $collection_id): bool {
587 
588  $params = [
589  'collection_id' => $collection_id,
590  'user_guid' => $user_guid,
591  ];
592 
593  // @todo https://github.com/Elgg/Elgg/issues/10823
594  if (!$this->events->triggerResults('access:collections:remove_user', 'collection', $params, true)) {
595  return false;
596  }
597 
598  $delete = Delete::fromTable(self::MEMBERSHIP_TABLE_NAME);
599  $delete->where($delete->compare('access_collection_id', '=', $collection_id, ELGG_VALUE_ID))
600  ->andWhere($delete->compare('user_guid', '=', $user_guid, ELGG_VALUE_GUID));
601 
602  $this->access_cache->clear();
603 
604  return (bool) $this->db->deleteData($delete);
605  }
606 
615  public function getEntityCollections(array $options = []): array {
616  $supported_options = ['owner_guid', 'subtype'];
617 
618  $select = Select::fromTable(self::TABLE_NAME);
619  $select->select('*')
620  ->orderBy('name', 'ASC');
621 
622  foreach ($supported_options as $option) {
623  $option_value = elgg_extract($option, $options);
624  if (!isset($option_value)) {
625  continue;
626  }
627 
628  switch ($option) {
629  case 'owner_guid':
630  $select->andWhere($select->compare($option, '=', $option_value, ELGG_VALUE_GUID));
631  break;
632  case 'subtype':
633  $select->andWhere($select->compare($option, '=', $option_value, ELGG_VALUE_STRING));
634  break;
635  }
636  }
637 
638  return $this->db->getData($select, [$this, 'rowToElggAccessCollection']);
639  }
640 
649  public function getMembers(int $collection_id, array $options = []) {
650  $options['wheres'][] = function(QueryBuilder $qb, $table_alias) use ($collection_id) {
651  $qb->join($table_alias, self::MEMBERSHIP_TABLE_NAME, 'acm', $qb->compare('acm.user_guid', '=', "{$table_alias}.guid"));
652 
653  return $qb->compare('acm.access_collection_id', '=', $collection_id, ELGG_VALUE_INTEGER);
654  };
655 
656  return Entities::find($options);
657  }
658 
666  public function getCollectionsByMember(int $member_guid): array {
667  $select = Select::fromTable(self::TABLE_NAME, 'ac');
668  $select->join('ac', self::MEMBERSHIP_TABLE_NAME, 'acm', $select->compare('ac.id', '=', 'acm.access_collection_id'));
669 
670  $select->select('ac.*')
671  ->where($select->compare('acm.user_guid', '=', $member_guid, ELGG_VALUE_GUID))
672  ->orderBy('name', 'ASC');
673 
674  return $this->db->getData($select, [$this, 'rowToElggAccessCollection']);
675  }
676 
694  public function getReadableAccessLevel(int $entity_access_id): string {
695  $translator = $this->translator;
696 
697  // Check if entity access id is a defined global constant
698  $access_array = [
699  ACCESS_PRIVATE => $translator->translate('access:label:private'),
700  ACCESS_FRIENDS => $translator->translate('access:label:friends'),
701  ACCESS_LOGGED_IN => $translator->translate('access:label:logged_in'),
702  ACCESS_PUBLIC => $translator->translate('access:label:public'),
703  ];
704 
705  if (array_key_exists($entity_access_id, $access_array)) {
706  return $access_array[$entity_access_id];
707  }
708 
709  // Entity access id is probably a custom access collection
710  // Check if the user has write access to it and can see it's label
711  // Admins should always be able to see the readable version
712  $collection = $this->get($entity_access_id);
713 
714  if (!$collection instanceof \ElggAccessCollection || !$collection->canEdit()) {
715  // return 'Limited' if the collection can not be loaded or it can not be edited
716  return $translator->translate('access:limited:label');
717  }
718 
719  return $collection->getDisplayName();
720  }
721 }
getReadableAccessLevel(int $entity_access_id)
Return the name of an ACCESS_* constant or an access collection, but only if the logged in user owns ...
canEdit(int $user_guid=null)
Check if user can edit this collection.
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
$event_params
Definition: save.php:31
removeUser(int $user_guid, int $collection_id)
Removes a user from an access collection.
$user_guid
Definition: login_as.php:10
hasAccessToEntity(\ElggEntity $entity, int $user_guid=0)
Can a user access an entity.
$params
Saves global plugin settings.
Definition: save.php:13
const ELGG_ENFORCE_ACCESS
Definition: constants.php:131
static find(array $options=[])
Build and execute a new query from an array of legacy options.
Definition: Repository.php:110
static table($table, $alias=null)
{}
Definition: Update.php:13
The Elgg database.
Definition: Database.php:25
const ACCESS_FRIENDS
Definition: constants.php:13
if(elgg_view_exists("widgets/{$widget->handler}/edit")) $access
Definition: save.php:19
markInitComplete()
Mark the access system as initialized.
const ELGG_VALUE_INTEGER
Value types.
Definition: constants.php:111
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
canEdit(int $collection_id, int $user_guid=null)
Can the user change this access collection?
const ELGG_VALUE_GUID
Definition: constants.php:113
Events service.
Database abstraction query builder.
$input_params
Definition: livesearch.php:14
$delete
translate(string $message_key, array $args=[], string $language= '')
Given a message key, returns an appropriately translated full-text string.
Definition: Translator.php:84
clear()
Clear out all the contents of the cache.
const ELGG_VALUE_ID
Definition: constants.php:114
SessionManagerService $session_manager
getCollectionsForWriteAccess(int $owner_guid)
Returns an array of access collections to be used in the write access array.
$options
Elgg admin footer.
Definition: footer.php:6
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
getWriteAccessArray(int $user_guid=0, bool $flush=false, array $input_params=[])
Returns an array of access permissions that the user is allowed to save content with.
trait Loggable
Enables adding a logger.
Definition: Loggable.php:14
$owner_guid
static intoTable($table)
{}
Definition: Insert.php:13
const ACCESS_PRIVATE
Definition: constants.php:10
$entity
Definition: reset.php:8
const ACCESS_LOGGED_IN
Definition: constants.php:11
getCollectionsByMember(int $member_guid)
Return an array of collections that the entity is member of.
Exception indicating a user could not be looked up for a permissions check.
rowToElggAccessCollection(\stdClass $row)
Transforms a database row to an instance of ElggAccessCollection.
join($fromAlias, $join, $alias, $condition=null)
{}
A generic parent class for database exceptions.
$user
Definition: ban.php:7
compare($x, $comparison, $y=null, $type=null, $case_sensitive=null)
Build value comparison clause.
The Elgg cache base class.
Definition: BaseCache.php:9
getEntityCollections(array $options=[])
Returns access collections.
create(\ElggAccessCollection $acl)
Creates a new access collection.
static fromTable($table, $alias=null)
{}
Definition: Select.php:13
__construct(Config $config, Database $db, EntityTable $entities, UserCapabilities $capabilities, BaseCache $cache, EventsService $events, SessionManagerService $session_manager, Translator $translator)
Constructor.
addUser(int $user_guid, int $collection_id)
Adds a user to an access collection.
const ELGG_VALUE_STRING
Definition: constants.php:112
$query
$subtypes
getAccessArray(int $user_guid=0, bool $flush=false)
Returns an array of access IDs a user is permitted to see.
update(\ElggAccessCollection $acl)
Update an existing access collection.
const ACCESS_PUBLIC
Definition: constants.php:12
User capabilities service.
Access collections database service.
$id
Generic annotation delete action.
Definition: delete.php:6
$qb
Definition: queue.php:11
static fromTable($table, $alias=null)
{}
Definition: Delete.php:13
Entity table database service.
Definition: EntityTable.php:26
hasUser(int $user_guid, int $collection_id)
Check if user is already in the collection.
getMembers(int $collection_id, array $options=[])
Get members of an access collection.