Elgg  Version 3.0
AccessCollections.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Database;
4 
5 use Elgg\Config;
6 use Elgg\Database;
11 use ElggCache;
12 use ElggEntity;
13 use ElggSession;
14 use ElggUser;
15 
26 
30  protected $config;
31 
35  protected $db;
36 
40  protected $access_cache;
41 
45  protected $hooks;
46 
50  protected $session;
51 
55  protected $entities;
56 
60  protected $capabilities;
61 
65  protected $translator;
66 
70  protected $table;
71 
75  protected $membership_table;
76 
80  protected $init_complete = false;
81 
94  public function __construct(
96  Database $db,
99  ElggCache $cache,
103  $this->config = $config;
104  $this->db = $db;
105  $this->entities = $entities;
106  $this->capabilities = $capabilities;
107  $this->access_cache = $cache;
108  $this->hooks = $hooks;
109  $this->session = $session;
110  $this->translator = $translator;
111 
112  $this->table = "{$this->db->prefix}access_collections";
113  $this->membership_table = "{$this->db->prefix}access_collection_membership";
114  }
115 
121  public function markInitComplete() {
122  $this->init_complete = true;
123  }
124 
136  public function getAccessList($user_guid = 0, $flush = false) {
137  $access_array = $this->getAccessArray($user_guid, $flush);
138  $access_ids = implode(',', $access_array);
139  $list = "($access_ids)";
140 
141  // for BC, populate the cache
142  $hash = $user_guid . 'get_access_list';
143  $this->access_cache->add($hash, $list);
144 
145  return $list;
146  }
147 
172  public function getAccessArray($user_guid = 0, $flush = false) {
173  $cache = $this->access_cache;
174 
175  if ($flush) {
176  $cache->clear();
177  }
178 
179  if ($user_guid == 0) {
180  $user_guid = $this->session->getLoggedInUserGuid();
181  }
182 
183  $user_guid = (int) $user_guid;
184 
185  $hash = $user_guid . 'get_access_array';
186 
187  if ($cache[$hash]) {
188  $access_array = $cache[$hash];
189  } else {
190  // Public access is always visible
191  $access_array = [ACCESS_PUBLIC];
192 
193  // The following can only return sensible data for a known user.
194  if ($user_guid) {
195  $access_array[] = ACCESS_LOGGED_IN;
196 
197  // Get ACLs that user owns or is a member of
198  $query = "
199  SELECT ac.id
200  FROM {$this->table} ac
201  WHERE ac.owner_guid = :user_guid
202  OR EXISTS (SELECT 1
203  FROM {$this->membership_table}
204  WHERE access_collection_id = ac.id
205  AND user_guid = :user_guid)
206  ";
207 
208  $collections = $this->db->getData($query, null, [
209  ':user_guid' => $user_guid,
210  ]);
211 
212  if (!empty($collections)) {
213  foreach ($collections as $collection) {
214  $access_array[] = (int) $collection->id;
215  }
216  }
217 
218  $ignore_access = $this->capabilities->canBypassPermissionsCheck($user_guid);
219 
220  if ($ignore_access === true) {
221  $access_array[] = ACCESS_PRIVATE;
222  }
223  }
224 
225  if ($this->init_complete) {
226  $cache[$hash] = $access_array;
227  }
228  }
229 
230  $options = [
231  'user_id' => $user_guid,
232  ];
233 
234  // see the warning in the docs for this function about infinite loop potential
235  return $this->hooks->trigger('access:collections:read', 'user', $options, $access_array);
236  }
237 
257  public function hasAccessToEntity($entity, $user = null) {
258  if (!$entity instanceof \ElggEntity) {
259  return false;
260  }
261 
262  if ($entity->access_id == ACCESS_PUBLIC) {
263  // Public entities are always accessible
264  return true;
265  }
266 
267  $user_guid = isset($user) ? (int) $user->guid : _elgg_services()->session->getLoggedInUserGuid();
268 
269  if ($user_guid && $user_guid == $entity->owner_guid) {
270  // Owners have access to their own content
271  return true;
272  }
273 
274  if ($user_guid && $entity->access_id == ACCESS_LOGGED_IN) {
275  // Existing users have access to entities with logged in access
276  return true;
277  }
278 
279  // See #7159. Must not allow ignore access to affect query
280  $ia = _elgg_services()->session->setIgnoreAccess(false);
281 
282  $row = $this->entities->getRow($entity->guid, $user_guid);
283 
284  _elgg_services()->session->setIgnoreAccess($ia);
285 
286  return !empty($row);
287  }
288 
314  public function getWriteAccessArray($user_guid = 0, $flush = false, array $input_params = []) {
315  $cache = $this->access_cache;
316 
317  if ($flush) {
318  $cache->clear();
319  }
320 
321  if ($user_guid == 0) {
322  $user_guid = $this->session->getLoggedInUserGuid();
323  }
324 
325  $user_guid = (int) $user_guid;
326 
327  $hash = $user_guid . 'get_write_access_array';
328 
329  if ($cache[$hash]) {
330  $access_array = $cache[$hash];
331  } else {
332  $access_array = [
336  ];
337 
338  $collections = $this->getEntityCollections(['owner_guid' => $user_guid]);
339  if (!empty($collections)) {
340  foreach ($collections as $collection) {
341  $access_array[$collection->id] = $collection->getDisplayName();
342  }
343  }
344 
345  if ($this->init_complete) {
346  $cache[$hash] = $access_array;
347  }
348  }
349 
350  $options = [
351  'user_id' => $user_guid,
352  'input_params' => $input_params,
353  ];
354 
355  $access_array = $this->hooks->trigger('access:collections:write', 'user', $options, $access_array);
356 
357  // move logged in and public to the end of the array
358  foreach ([ACCESS_LOGGED_IN, ACCESS_PUBLIC] as $access) {
359  if (!isset($access_array[$access])) {
360  continue;
361  }
362 
363  $temp = $access_array[$access];
364  unset($access_array[$access]);
365  $access_array[$access] = $temp;
366  }
367 
368 
369  return $access_array;
370  }
371 
386  public function canEdit($collection_id, $user_guid = null) {
387  try {
388  $user = $this->entities->getUserForPermissionsCheck($user_guid);
389  } catch (UserFetchFailureException $e) {
390  return false;
391  }
392 
393  $collection = $this->get($collection_id);
394 
395  if (!$user instanceof \ElggUser || !$collection instanceof \ElggAccessCollection) {
396  return false;
397  }
398 
399  if ($this->capabilities->canBypassPermissionsCheck($user->guid)) {
400  return true;
401  }
402 
403  $write_access = $this->getWriteAccessArray($user->guid, true);
404  return array_key_exists($collection_id, $write_access);
405  }
406 
424  public function create($name, $owner_guid = 0, $subtype = null) {
425  $name = trim($name);
426  if (empty($name)) {
427  return false;
428  }
429 
430  if (isset($subtype)) {
431  $subtype = trim($subtype);
432  if (strlen($subtype) > 255) {
433  _elgg_services()->logger->error("The subtype length for access collections cannot be greater than 255");
434  return false;
435  }
436  }
437 
438  if ($owner_guid == 0) {
439  $owner_guid = $this->session->getLoggedInUserGuid();
440  }
441 
442  $query = "
443  INSERT INTO {$this->table}
444  SET name = :name,
445  subtype = :subtype,
446  owner_guid = :owner_guid
447  ";
448 
449  $params = [
450  ':name' => $name,
451  ':subtype' => $subtype,
452  ':owner_guid' => (int) $owner_guid,
453  ];
454 
455  $id = $this->db->insertData($query, $params);
456  if (!$id) {
457  return false;
458  }
459 
460  $this->access_cache->clear();
461 
462  $hook_params = [
463  'collection_id' => $id,
464  'name' => $name,
465  'subtype' => $subtype,
466  'owner_guid' => $owner_guid,
467  ];
468 
469  if (!$this->hooks->trigger('access:collections:addcollection', 'collection', $hook_params, true)) {
470  $this->delete($id);
471  return false;
472  }
473 
474  return $id;
475  }
476 
484  public function rename($collection_id, $name) {
485 
486  $query = "
487  UPDATE {$this->table}
488  SET name = :name
489  WHERE id = :id
490  ";
491 
492  $params = [
493  ':name' => $name,
494  ':id' => (int) $collection_id,
495  ];
496 
497  if ($this->db->insertData($query, $params)) {
498  $this->access_cache->clear();
499  return (int) $collection_id;
500  }
501 
502  return false;
503  }
504 
505 
519  public function update($collection_id, array $new_members = []) {
520  $acl = $this->get($collection_id);
521 
522  if (!$acl instanceof \ElggAccessCollection) {
523  return false;
524  }
525 
526  $to_guid = function($elem) {
527  if (empty($elem)) {
528  return 0;
529  }
530  if (is_object($elem)) {
531  return (int) $elem->guid;
532  }
533  return (int) $elem;
534  };
535 
536  $current_members = [];
537  $new_members = array_map($to_guid, $new_members);
538 
539  $current_members_batch = $this->getMembers($collection_id, [
540  'batch' => true,
541  'limit' => 0,
542  'callback' => false,
543  ]);
544 
545  foreach ($current_members_batch as $row) {
546  $current_members[] = $to_guid($row);
547  }
548 
549  $remove_members = array_diff($current_members, $new_members);
550  $add_members = array_diff($new_members, $current_members);
551 
552  $result = true;
553 
554  foreach ($add_members as $guid) {
555  $result = $result && $this->addUser($guid, $collection_id);
556  }
557 
558  foreach ($remove_members as $guid) {
559  $result = $result && $this->removeUser($guid, $collection_id);
560  }
561 
562  $this->access_cache->clear();
563 
564  return $result;
565  }
566 
573  public function delete($collection_id) {
574  $collection_id = (int) $collection_id;
575 
576  $params = [
577  'collection_id' => $collection_id,
578  ];
579 
580  if (!$this->hooks->trigger('access:collections:deletecollection', 'collection', $params, true)) {
581  return false;
582  }
583 
584  // Deleting membership doesn't affect result of deleting ACL.
585  $query = "
586  DELETE FROM {$this->membership_table}
587  WHERE access_collection_id = :access_collection_id
588  ";
589  $this->db->deleteData($query, [
590  ':access_collection_id' => $collection_id,
591  ]);
592 
593  $query = "
594  DELETE FROM {$this->table}
595  WHERE id = :id
596  ";
597  $result = $this->db->deleteData($query, [
598  ':id' => $collection_id,
599  ]);
600 
601  $this->access_cache->clear();
602 
603  return (bool) $result;
604  }
605 
612  public function rowToElggAccessCollection(\stdClass $row) {
613  return new \ElggAccessCollection($row);
614  }
615 
627  public function get($collection_id) {
628 
629  $callback = [$this, 'rowToElggAccessCollection'];
630 
631  $query = "
632  SELECT * FROM {$this->table}
633  WHERE id = :id
634  ";
635 
636  $result = $this->db->getDataRow($query, $callback, [
637  ':id' => (int) $collection_id,
638  ]);
639 
640  if (empty($result)) {
641  return false;
642  }
643 
644  return $result;
645  }
646 
654  public function hasUser($user_guid, $collection_id) {
655  $options = [
656  'guids' => (int) $user_guid,
657  'count' => true,
658  ];
659  return (bool) $this->getMembers($collection_id, $options);
660  }
661 
671  public function addUser($user_guid, $collection_id) {
672 
673  $collection = $this->get($collection_id);
674 
675  if (!$collection instanceof \ElggAccessCollection) {
676  return false;
677  }
678 
679  if (!$this->entities->exists($user_guid)) {
680  return false;
681  }
682 
683  $hook_params = [
684  'collection_id' => $collection->id,
685  'user_guid' => (int) $user_guid
686  ];
687 
688  $result = $this->hooks->trigger('access:collections:add_user', 'collection', $hook_params, true);
689  if ($result == false) {
690  return false;
691  }
692 
693  // if someone tries to insert the same data twice, we do a no-op on duplicate key
694  $query = "
695  INSERT INTO {$this->membership_table}
696  SET access_collection_id = :access_collection_id,
697  user_guid = :user_guid
698  ON DUPLICATE KEY UPDATE user_guid = user_guid
699  ";
700 
701  $result = $this->db->insertData($query, [
702  ':access_collection_id' => (int) $collection->id,
703  ':user_guid' => (int) $user_guid,
704  ]);
705 
706  $this->access_cache->clear();
707 
708  return $result !== false;
709  }
710 
720  public function removeUser($user_guid, $collection_id) {
721 
722  $params = [
723  'collection_id' => (int) $collection_id,
724  'user_guid' => (int) $user_guid,
725  ];
726 
727  if (!$this->hooks->trigger('access:collections:remove_user', 'collection', $params, true)) {
728  return false;
729  }
730 
731  $query = "
732  DELETE FROM {$this->membership_table}
733  WHERE access_collection_id = :access_collection_id
734  AND user_guid = :user_guid
735  ";
736 
737  $this->access_cache->clear();
738 
739  return (bool) $this->db->deleteData($query, [
740  ':access_collection_id' => (int) $collection_id,
741  ':user_guid' => (int) $user_guid,
742  ]);
743  }
744 
752  public function getEntityCollections($options = []) {
753 
754  $callback = [$this, 'rowToElggAccessCollection'];
755 
756  $supported_options = ['owner_guid', 'subtype'];
757 
758  $wheres = [];
759  $params = [];
760  foreach ($supported_options as $option) {
761  $option_value = elgg_extract($option, $options);
762  if (!isset($option_value)) {
763  continue;
764  }
765  $wheres[] = "{$option} = :{$option}";
766  $params[":{$option}"] = $option_value;
767  }
768 
769  $query = "SELECT * FROM {$this->table}";
770  if (!empty($wheres)) {
771  $query .= ' WHERE ' . implode(' AND ', $wheres);
772  }
773  $query .= ' ORDER BY name ASC';
774 
775  return $this->db->getData($query, $callback, $params);
776  }
777 
785  public function getMembers($collection_id, array $options = []) {
786  $options['wheres'][] = function(QueryBuilder $qb, $table_alias) use ($collection_id) {
787  $qb->join($table_alias, 'access_collection_membership', 'acm', $qb->compare('acm.user_guid', '=', "$table_alias.guid"));
788  return $qb->compare('acm.access_collection_id', '=', $collection_id, ELGG_VALUE_INTEGER);
789  };
790 
791  return Entities::find($options);
792  }
793 
801  public function getCollectionsByMember($member_guid) {
802 
803  $callback = [$this, 'rowToElggAccessCollection'];
804 
805  $query = "
806  SELECT ac.* FROM {$this->table} ac
807  JOIN {$this->membership_table} acm
808  ON ac.id = acm.access_collection_id
809  WHERE acm.user_guid = :member_guid
810  ORDER BY name ASC
811  ";
812 
813  return $this->db->getData($query, $callback, [
814  ':member_guid' => (int) $member_guid,
815  ]);
816  }
817 
835  public function getReadableAccessLevel($entity_access_id) {
836  $access = (int) $entity_access_id;
837 
839 
840  // Check if entity access id is a defined global constant
841  $access_array = [
842  ACCESS_PRIVATE => $translator->translate('access:label:private'),
843  ACCESS_FRIENDS => $translator->translate('access:label:friends'),
844  ACCESS_LOGGED_IN => $translator->translate('access:label:logged_in'),
845  ACCESS_PUBLIC => $translator->translate('access:label:public'),
846  ];
847 
848  if (array_key_exists($access, $access_array)) {
849  return $access_array[$access];
850  }
851 
852  // Entity access id is probably a custom access collection
853  // Check if the user has write access to it and can see it's label
854  // Admins should always be able to see the readable version
855  $collection = $this->get($access);
856 
857  if (!$collection instanceof \ElggAccessCollection || !$collection->canEdit()) {
858  // return 'Limited' if the collection can not be loaded or it can not be edited
859  return $translator->translate('access:limited:label');
860  }
861 
862  return $collection->getDisplayName();
863  }
864 
865 }
$query
Definition: groups.php:8
if(!$user||!$user->canDelete()) $name
Definition: delete.php:22
$params
Saves global plugin settings.
Definition: save.php:13
removeUser($user_guid, $collection_id)
Removes a user from an access collection.
static find(array $options=[])
Build and execute a new query from an array of legacy options.
Definition: Repository.php:85
canEdit($user_guid=null)
Check if user can this collection.
hasAccessToEntity($entity, $user=null)
Can a user access an entity.
const ACCESS_FRIENDS
Definition: constants.php:15
markInitComplete()
Mark the access system as initialized.
const ELGG_VALUE_INTEGER
Value types.
Definition: constants.php:138
rename($collection_id, $name)
Renames an access collection.
$subtype
Definition: delete.php:22
Database abstraction query builder.
$input_params
Definition: livesearch.php:12
$guid
Removes an admin notice.
$options
Elgg admin footer.
Definition: footer.php:6
getAccessArray($user_guid=0, $flush=false)
Returns an array of access IDs a user is permitted to see.
create($name, $owner_guid=0, $subtype=null)
Creates a new access collection.
$user_guid
Validate a user.
Definition: validate.php:6
$owner_guid
addUser($user_guid, $collection_id)
Adds a user to an access collection.
$id
River item delete action.
Definition: delete.php:6
getMembers($collection_id, array $options=[])
Get members of an access collection.
getWriteAccessArray($user_guid=0, $flush=false, array $input_params=[])
Returns an array of access permissions that the user is allowed to save content with.
const ACCESS_PRIVATE
Definition: constants.php:12
$entity
Definition: reset.php:8
const ACCESS_LOGGED_IN
Definition: constants.php:13
getEntityCollections($options=[])
Returns access collections.
rowToElggAccessCollection(\stdClass $row)
Transforms a database row to an instance of ElggAccessCollection.
getCollectionsByMember($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.
hasUser($user_guid, $collection_id)
Check if user is already in the collection.
join($fromAlias, $join, $alias, $condition=null)
{}
$user
Definition: ban.php:7
compare($x, $comparison, $y=null, $type=null, $case_sensitive=null)
Build value comparison clause.
elgg ElggUser
Definition: ElggUser.js:12
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:1131
getAccessList($user_guid=0, $flush=false)
Returns a string of access_ids for $user_guid appropriate for inserting into an SQL IN clause...
__construct(Config $config, Database $db, EntityTable $entities, UserCapabilities $capabilities, ElggCache $cache, PluginHooksService $hooks, ElggSession $session, Translator $translator)
Constructor.
getReadableAccessLevel($entity_access_id)
Return the name of an ACCESS_* constant or an access collection, but only if the logged in user owns ...
_elgg_services()
Get the global service provider.
Definition: elgglib.php:1292
const ACCESS_PUBLIC
Definition: constants.php:14
WARNING: API IN FLUX.
elgg ElggEntity
Definition: ElggEntity.js:15
canEdit($collection_id, $user_guid=null)
Can the user change this access collection?
WARNING: API IN FLUX.
Definition: EntityTable.php:38
$access
Definition: save.php:18
update($collection_id, array $new_members=[])
Updates the membership in an access collection.