Issues (946)

engine/classes/Elgg/Database/AccessCollections.php (1 issue)

1
<?php
2
3
namespace Elgg\Database;
4
5
use Elgg\Config;
6
use Elgg\Database;
7
use Elgg\Database\EntityTable\UserFetchFailureException;
8
use Elgg\I18n\Translator;
9
use Elgg\PluginHooksService;
10
use Elgg\UserCapabilities;
11
use ElggCache;
12
use ElggEntity;
13
use ElggSession;
14
use ElggUser;
15
16
/**
17
 * WARNING: API IN FLUX. DO NOT USE DIRECTLY.
18
 *
19
 * @internal
20
 *
21
 * @package    Elgg.Core
22
 * @subpackage Database
23
 * @since      1.10.0
24
 */
25
class AccessCollections {
26
27
	/**
28
	 * @var Config
29
	 */
30
	protected $config;
31
32
	/**
33
	 * @var Database
34
	 */
35
	protected $db;
36
37
	/**
38
	 * @vars ElggCache
39
	 */
40
	protected $access_cache;
41
42
	/**
43
	 * @var PluginHooksService
44
	 */
45
	protected $hooks;
46
47
	/**
48
	 * @var ElggSession
49
	 */
50
	protected $session;
51
52
	/**
53
	 * @var EntityTable
54
	 */
55
	protected $entities;
56
57
	/**
58
	 * @var UserCapabilities
59
	 */
60
	protected $capabilities;
61
62
	/**
63
	 * @var Translator
64
	 */
65
	protected $translator;
66
67
	/**
68
	 * @var string
69
	 */
70
	protected $table;
71
72
	/**
73
	 * @var string
74
	 */
75
	protected $membership_table;
76
77
	/**
78
	 * @var bool
79
	 */
80
	protected $init_complete = false;
81
82
	/**
83
	 * Constructor
84
	 *
85
	 * @param Config             $config       Config
86
	 * @param Database           $db           Database
87
	 * @param EntityTable        $entities     Entity table
88
	 * @param UserCapabilities   $capabilities User capabilities
89
	 * @param ElggCache          $cache        Access cache
90
	 * @param PluginHooksService $hooks        Hooks
91
	 * @param ElggSession        $session      Session
92
	 * @param Translator         $translator   Translator
93
	 */
94 641
	public function __construct(
95
		Config $config,
96
		Database $db,
97
		EntityTable $entities,
98
		UserCapabilities $capabilities,
99
		ElggCache $cache,
100
		PluginHooksService $hooks,
101
		ElggSession $session,
102
		Translator $translator) {
103 641
		$this->config = $config;
104 641
		$this->db = $db;
105 641
		$this->entities = $entities;
106 641
		$this->capabilities = $capabilities;
107 641
		$this->access_cache = $cache;
108 641
		$this->hooks = $hooks;
109 641
		$this->session = $session;
110 641
		$this->translator = $translator;
111
112 641
		$this->table = "{$this->db->prefix}access_collections";
113 641
		$this->membership_table = "{$this->db->prefix}access_collection_membership";
114 641
	}
115
116
	/**
117
	 * Mark the access system as initialized
118
	 *
119
	 * @return void
120
	 */
121 105
	public function markInitComplete() {
122 105
		$this->init_complete = true;
123 105
	}
124
125
	/**
126
	 * Returns a string of access_ids for $user_guid appropriate for inserting into an SQL IN clause.
127
	 *
128
	 * @see get_access_array()
129
	 *
130
	 * @param int  $user_guid User ID; defaults to currently logged in user
131
	 * @param bool $flush     If set to true, will refresh the access list from the
132
	 *                        database rather than using this function's cache.
133
	 *
134
	 * @return string A concatenated string of access collections suitable for using in an SQL IN clause
135
	 */
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
148
	/**
149
	 * Returns an array of access IDs a user is permitted to see.
150
	 *
151
	 * Can be overridden with the 'access:collections:read', 'user' plugin hook.
152
	 * @warning A callback for that plugin hook needs to either not retrieve data
153
	 * from the database that would use the access system (triggering the plugin again)
154
	 * or ignore the second call. Otherwise, an infinite loop will be created.
155
	 *
156
	 * This returns a list of all the collection ids a user owns or belongs
157
	 * to plus public and logged in access levels. If the user is an admin, it includes
158
	 * the private access level.
159
	 *
160
	 * @internal this is only used in core for creating the SQL where clause when
161
	 * retrieving content from the database. The friends access level is handled by
162
	 * _elgg_get_access_where_sql().
163
	 *
164
	 * @see get_write_access_array() for the access levels that a user can write to.
165
	 *
166
	 * @param int  $user_guid User ID; defaults to currently logged in user
167
	 * @param bool $flush     If set to true, will refresh the access ids from the
168
	 *                        database rather than using this function's cache.
169
	 *
170
	 * @return array An array of access collections ids
171
	 */
172 1185
	public function getAccessArray($user_guid = 0, $flush = false) {
173 1185
		$cache = $this->access_cache;
174
175 1185
		if ($flush) {
176 1
			$cache->clear();
177
		}
178
179 1185
		if ($user_guid == 0) {
180 1132
			$user_guid = $this->session->getLoggedInUserGuid();
181
		}
182
183 1185
		$user_guid = (int) $user_guid;
184
185 1185
		$hash = $user_guid . 'get_access_array';
186
187 1185
		if ($cache[$hash]) {
188 135
			$access_array = $cache[$hash];
189
		} else {
190
			// Public access is always visible
191 1185
			$access_array = [ACCESS_PUBLIC];
192
193
			// The following can only return sensible data for a known user.
194 1185
			if ($user_guid) {
195 74
				$access_array[] = ACCESS_LOGGED_IN;
196
197
				// Get ACLs that user owns or is a member of
198
				$query = "
199
					SELECT ac.id
200 74
					FROM {$this->table} ac
201
					WHERE ac.owner_guid = :user_guid
202
					OR EXISTS (SELECT 1
203 74
							   FROM {$this->membership_table}
204
							   WHERE access_collection_id = ac.id
205
							   AND user_guid = :user_guid)
206
				";
207
208 74
				$collections = $this->db->getData($query, null, [
209 74
					':user_guid' => $user_guid,
210
				]);
211
212 74
				if (!empty($collections)) {
213 37
					foreach ($collections as $collection) {
214 37
						$access_array[] = (int) $collection->id;
215
					}
216
				}
217
218 74
				$ignore_access = $this->capabilities->canBypassPermissionsCheck($user_guid);
219
220 74
				if ($ignore_access === true) {
221 1
					$access_array[] = ACCESS_PRIVATE;
222
				}
223
			}
224
225 1185
			if ($this->init_complete) {
226 691
				$cache[$hash] = $access_array;
227
			}
228
		}
229
230
		$options = [
231 1185
			'user_id' => $user_guid,
232
		];
233
234
		// see the warning in the docs for this function about infinite loop potential
235 1185
		return $this->hooks->trigger('access:collections:read', 'user', $options, $access_array);
236
	}
237
238
	/**
239
	 * Can a user access an entity.
240
	 *
241
	 * @warning If a logged in user doesn't have access to an entity, the
242
	 * core engine will not load that entity.
243
	 *
244
	 * @tip This is mostly useful for checking if a user other than the logged in
245
	 * user has access to an entity that is currently loaded.
246
	 *
247
	 * @todo This function would be much more useful if we could pass the guid of the
248
	 * entity to test access for. We need to be able to tell whether the entity exists
249
	 * and whether the user has access to the entity.
250
	 *
251
	 * @param ElggEntity $entity The entity to check access for.
252
	 * @param ElggUser   $user   Optionally user to check access for. Defaults to
253
	 *                           logged in user (which is a useless default).
254
	 *
255
	 * @return bool
256
	 */
257 271
	public function hasAccessToEntity($entity, $user = null) {
258 271
		if (!$entity instanceof \ElggEntity) {
259
			return false;
260
		}
261
262 271
		if ($entity->access_id == ACCESS_PUBLIC) {
263
			// Public entities are always accessible
264 254
			return true;
265
		}
266
267 51
		$user_guid = isset($user) ? (int) $user->guid : _elgg_services()->session->getLoggedInUserGuid();
268
269 51
		if ($user_guid && $user_guid == $entity->owner_guid) {
270
			// Owners have access to their own content
271 35
			return true;
272
		}
273
274 20
		if ($user_guid && $entity->access_id == ACCESS_LOGGED_IN) {
275
			// Existing users have access to entities with logged in access
276 10
			return true;
277
		}
278
279
		// See #7159. Must not allow ignore access to affect query
280 11
		$ia = _elgg_services()->session->setIgnoreAccess(false);
281
282 11
		$row = $this->entities->getRow($entity->guid, $user_guid);
283
284 11
		_elgg_services()->session->setIgnoreAccess($ia);
285
286 11
		return !empty($row);
287
	}
288
289
	/**
290
	 * Returns an array of access permissions that the user is allowed to save content with.
291
	 * Permissions returned are of the form (id => 'name').
292
	 *
293
	 * Example return value in English:
294
	 * array(
295
	 *     0 => 'Private',
296
	 *    -2 => 'Friends',
297
	 *     1 => 'Logged in users',
298
	 *     2 => 'Public',
299
	 *    34 => 'My favorite friends',
300
	 * );
301
	 *
302
	 * Plugin hook of 'access:collections:write', 'user'
303
	 *
304
	 * @warning this only returns access collections that the user owns plus the
305
	 * standard access levels. It does not return access collections that the user
306
	 * belongs to such as the access collection for a group.
307
	 *
308
	 * @param int   $user_guid    The user's GUID.
309
	 * @param bool  $flush        If this is set to true, this will ignore a cached access array
310
	 * @param array $input_params Some parameters passed into an input/access view
311
	 *
312
	 * @return array List of access permissions
313
	 */
314 6
	public function getWriteAccessArray($user_guid = 0, $flush = false, array $input_params = []) {
315 6
		$cache = $this->access_cache;
316
317 6
		if ($flush) {
318 4
			$cache->clear();
319
		}
320
321 6
		if ($user_guid == 0) {
322 1
			$user_guid = $this->session->getLoggedInUserGuid();
323
		}
324
325 6
		$user_guid = (int) $user_guid;
326
327 6
		$hash = $user_guid . 'get_write_access_array';
328
329 6
		if ($cache[$hash]) {
330 1
			$access_array = $cache[$hash];
331
		} else {
332
			$access_array = [
333 6
				ACCESS_PRIVATE => $this->getReadableAccessLevel(ACCESS_PRIVATE),
334 6
				ACCESS_LOGGED_IN => $this->getReadableAccessLevel(ACCESS_LOGGED_IN),
335 6
				ACCESS_PUBLIC => $this->getReadableAccessLevel(ACCESS_PUBLIC)
336
			];
337
338 6
			$collections = $this->getEntityCollections(['owner_guid' => $user_guid]);
339 6
			if (!empty($collections)) {
340 5
				foreach ($collections as $collection) {
341 5
					$access_array[$collection->id] = $collection->getDisplayName();
342
				}
343
			}
344
345 6
			if ($this->init_complete) {
346 6
				$cache[$hash] = $access_array;
347
			}
348
		}
349
350
		$options = [
351 6
			'user_id' => $user_guid,
352 6
			'input_params' => $input_params,
353
		];
354
		
355 6
		$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 6
		foreach ([ACCESS_LOGGED_IN, ACCESS_PUBLIC] as $access) {
359 6
			if (!isset($access_array[$access])) {
360 1
				continue;
361
			}
362
		
363 6
			$temp = $access_array[$access];
364 6
			unset($access_array[$access]);
365 6
			$access_array[$access] = $temp;
366
		}
367
		
368
		
369 6
		return $access_array;
370
	}
371
372
	/**
373
	 * Can the user change this access collection?
374
	 *
375
	 * Use the plugin hook of 'access:collections:write', 'user' to change this.
376
	 * @see get_write_access_array() for details on the hook.
377
	 *
378
	 * Respects access control disabling for admin users and {@link elgg_call()}
379
	 *
380
	 * @see get_write_access_array()
381
	 *
382
	 * @param int   $collection_id The collection id
383
	 * @param mixed $user_guid     The user GUID to check for. Defaults to logged in user.
384
	 * @return bool
385
	 */
386 3
	public function canEdit($collection_id, $user_guid = null) {
387
		try {
388 3
			$user = $this->entities->getUserForPermissionsCheck($user_guid);
389
		} catch (UserFetchFailureException $e) {
390
			return false;
391
		}
392
393 3
		$collection = $this->get($collection_id);
394
395 3
		if (!$user instanceof \ElggUser || !$collection instanceof \ElggAccessCollection) {
396 1
			return false;
397
		}
398
399 3
		if ($this->capabilities->canBypassPermissionsCheck($user->guid)) {
400 1
			return true;
401
		}
402
403 3
		$write_access = $this->getWriteAccessArray($user->guid, true);
404 3
		return array_key_exists($collection_id, $write_access);
405
	}
406
407
	/**
408
	 * Creates a new access collection.
409
	 *
410
	 * Access colletions allow plugins and users to create granular access
411
	 * for entities.
412
	 *
413
	 * Triggers plugin hook 'access:collections:addcollection', 'collection'
414
	 *
415
	 * @internal Access collections are stored in the access_collections table.
416
	 * Memberships to collections are in access_collections_membership.
417
	 *
418
	 * @param string $name       The name of the collection.
419
	 * @param int    $owner_guid The GUID of the owner (default: currently logged in user).
420
	 * @param string $subtype    The subtype indicates the usage of the acl
421
	 *
422
	 * @return int|false The collection ID if successful and false on failure.
423
	 */
424 208
	public function create($name, $owner_guid = 0, $subtype = null) {
425 208
		$name = trim($name);
426 208
		if (empty($name)) {
427
			return false;
428
		}
429
430 208
		if (isset($subtype)) {
431 195
			$subtype = trim($subtype);
432 195
			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 208
		if ($owner_guid == 0) {
439 6
			$owner_guid = $this->session->getLoggedInUserGuid();
440
		}
441
442
		$query = "
443 208
			INSERT INTO {$this->table}
444
			SET name = :name,
445
				subtype = :subtype,
446
				owner_guid = :owner_guid
447
		";
448
449
		$params = [
450 208
			':name' => $name,
451 208
			':subtype' => $subtype,
452 208
			':owner_guid' => (int) $owner_guid,
453
		];
454
455 208
		$id = $this->db->insertData($query, $params);
456 208
		if (!$id) {
457
			return false;
458
		}
459
460 208
		$this->access_cache->clear();
461
462
		$hook_params = [
463 208
			'collection_id' => $id,
464 208
			'name' => $name,
465 208
			'subtype' => $subtype,
466 208
			'owner_guid' => $owner_guid,
467
		];
468
469 208
		if (!$this->hooks->trigger('access:collections:addcollection', 'collection', $hook_params, true)) {
470
			$this->delete($id);
471
			return false;
472
		}
473
474 208
		return $id;
475
	}
476
477
	/**
478
	 * Renames an access collection
479
	 *
480
	 * @param int    $collection_id ID of the collection
481
	 * @param string $name          The name of the collection
482
	 * @return bool
483
	 */
484 1
	public function rename($collection_id, $name) {
485
486
		$query = "
487 1
			UPDATE {$this->table}
488
			SET name = :name
489
			WHERE id = :id
490
		";
491
492
		$params = [
493 1
			':name' => $name,
494 1
			':id' => (int) $collection_id,
495
		];
496
497 1
		if ($this->db->insertData($query, $params)) {
498
			$this->access_cache->clear();
499
			return (int) $collection_id;
0 ignored issues
show
Bug Best Practice introduced by Ismayil Khayredinov
The expression return (int)$collection_id returns the type integer which is incompatible with the documented return type boolean.
Loading history...
500
		}
501
502 1
		return false;
503
	}
504
505
506
	/**
507
	 * Updates the membership in an access collection.
508
	 *
509
	 * @warning Expects a full list of all members that should
510
	 * be part of the access collection
511
	 *
512
	 * @note This will run all hooks associated with adding or removing
513
	 * members to access collections.
514
	 *
515
	 * @param int   $collection_id ID of the collection.
516
	 * @param array $new_members   Array of member entities or GUIDs
517
	 * @return bool
518
	 */
519 1
	public function update($collection_id, array $new_members = []) {
520 1
		$acl = $this->get($collection_id);
521
522 1
		if (!$acl instanceof \ElggAccessCollection) {
523
			return false;
524
		}
525
526 1
		$to_guid = function($elem) {
527 1
			if (empty($elem)) {
528
				return 0;
529
			}
530 1
			if (is_object($elem)) {
531 1
				return (int) $elem->guid;
532
			}
533 1
			return (int) $elem;
534 1
		};
535
536 1
		$current_members = [];
537 1
		$new_members = array_map($to_guid, $new_members);
538
539 1
		$current_members_batch = $this->getMembers($collection_id, [
540 1
			'batch' => true,
541
			'limit' => 0,
542
			'callback' => false,
543
		]);
544
545 1
		foreach ($current_members_batch as $row) {
546 1
			$current_members[] = $to_guid($row);
547
		}
548
549 1
		$remove_members = array_diff($current_members, $new_members);
550 1
		$add_members = array_diff($new_members, $current_members);
551
552 1
		$result = true;
553
554 1
		foreach ($add_members as $guid) {
555 1
			$result = $result && $this->addUser($guid, $collection_id);
556
		}
557
558 1
		foreach ($remove_members as $guid) {
559 1
			$result = $result && $this->removeUser($guid, $collection_id);
560
		}
561
562 1
		$this->access_cache->clear();
563
564 1
		return $result;
565
	}
566
567
	/**
568
	 * Deletes a collection and its membership information
569
	 *
570
	 * @param int $collection_id ID of the collection
571
	 * @return bool
572
	 */
573 58
	public function delete($collection_id) {
574 58
		$collection_id = (int) $collection_id;
575
576
		$params = [
577 58
			'collection_id' => $collection_id,
578
		];
579
580 58
		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 58
			DELETE FROM {$this->membership_table}
587
			WHERE access_collection_id = :access_collection_id
588
		";
589 58
		$this->db->deleteData($query, [
590 58
			':access_collection_id' => $collection_id,
591
		]);
592
593
		$query = "
594 58
			DELETE FROM {$this->table}
595
			WHERE id = :id
596
		";
597 58
		$result = $this->db->deleteData($query, [
598 58
			':id' => $collection_id,
599
		]);
600
601 58
		$this->access_cache->clear();
602
603 58
		return (bool) $result;
604
	}
605
606
	/**
607
	 * Transforms a database row to an instance of ElggAccessCollection
608
	 *
609
	 * @param \stdClass $row Database row
610
	 * @return \ElggAccessCollection
611
	 */
612 70
	public function rowToElggAccessCollection(\stdClass $row) {
613 70
		return new \ElggAccessCollection($row);
614
	}
615
616
	/**
617
	 * Get a specified access collection
618
	 *
619
	 * @note This doesn't return the members of an access collection,
620
	 * just the database row of the actual collection.
621
	 *
622
	 * @see get_members_of_access_collection()
623
	 *
624
	 * @param int $collection_id The collection ID
625
	 * @return \ElggAccessCollection|false
626
	 */
627 27
	public function get($collection_id) {
628
629 27
		$callback = [$this, 'rowToElggAccessCollection'];
630
631
		$query = "
632 27
			SELECT * FROM {$this->table}
633
			WHERE id = :id
634
		";
635
636 27
		$result = $this->db->getDataRow($query, $callback, [
637 27
			':id' => (int) $collection_id,
638
		]);
639
640 27
		if (empty($result)) {
641 2
			return false;
642
		}
643
644 27
		return $result;
645
	}
646
647
	/**
648
	 * Check if user is already in the collection
649
	 *
650
	 * @param int $user_guid     GUID of the user
651
	 * @param int $collection_id ID of the collection
652
	 * @return bool
653
	 */
654 2
	public function hasUser($user_guid, $collection_id) {
655
		$options = [
656 2
			'guids' => (int) $user_guid,
657
			'count' => true,
658
		];
659 2
		return (bool) $this->getMembers($collection_id, $options);
660
	}
661
662
	/**
663
	 * Adds a user to an access collection.
664
	 *
665
	 * Triggers the 'access:collections:add_user', 'collection' plugin hook.
666
	 *
667
	 * @param int $user_guid     GUID of the user to add
668
	 * @param int $collection_id ID of the collection to add them to
669
	 * @return bool
670
	 */
671 18
	public function addUser($user_guid, $collection_id) {
672
673 18
		$collection = $this->get($collection_id);
674
675 18
		if (!$collection instanceof \ElggAccessCollection) {
676
			return false;
677
		}
678
679 18
		if (!$this->entities->exists($user_guid)) {
680
			return false;
681
		}
682
683
		$hook_params = [
684 18
			'collection_id' => $collection->id,
685 18
			'user_guid' => (int) $user_guid
686
		];
687
688 18
		$result = $this->hooks->trigger('access:collections:add_user', 'collection', $hook_params, true);
689 18
		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 18
			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 18
		$result = $this->db->insertData($query, [
702 18
			':access_collection_id' => (int) $collection->id,
703 18
			':user_guid' => (int) $user_guid,
704
		]);
705
706 18
		$this->access_cache->clear();
707
708 18
		return $result !== false;
709
	}
710
711
	/**
712
	 * Removes a user from an access collection.
713
	 *
714
	 * Triggers the 'access:collections:remove_user', 'collection' plugin hook.
715
	 *
716
	 * @param int $user_guid     GUID of the user
717
	 * @param int $collection_id ID of the collection
718
	 * @return bool
719
	 */
720 10
	public function removeUser($user_guid, $collection_id) {
721
722
		$params = [
723 10
			'collection_id' => (int) $collection_id,
724 10
			'user_guid' => (int) $user_guid,
725
		];
726
727 10
		if (!$this->hooks->trigger('access:collections:remove_user', 'collection', $params, true)) {
728
			return false;
729
		}
730
731
		$query = "
732 10
			DELETE FROM {$this->membership_table}
733
			WHERE access_collection_id = :access_collection_id
734
				AND user_guid = :user_guid
735
		";
736
737 10
		$this->access_cache->clear();
738
739 10
		return (bool) $this->db->deleteData($query, [
740 10
			':access_collection_id' => (int) $collection_id,
741 10
			':user_guid' => (int) $user_guid,
742
		]);
743
	}
744
745
	/**
746
	 * Returns access collections
747
	 *
748
	 * @param array $options Options to get access collections by
749
	 *                       Supported are 'owner_guid', 'subtype'
750
	 * @return \ElggAccessCollection[]
751
	 */
752 286
	public function getEntityCollections($options = []) {
753
754 286
		$callback = [$this, 'rowToElggAccessCollection'];
755
756 286
		$supported_options = ['owner_guid', 'subtype'];
757
758 286
		$wheres = [];
759 286
		$params = [];
760 286
		foreach ($supported_options as $option) {
761 286
			$option_value = elgg_extract($option, $options);
762 286
			if (!isset($option_value)) {
763 278
				continue;
764
			}
765 286
			$wheres[] = "{$option} = :{$option}";
766 286
			$params[":{$option}"] = $option_value;
767
		}
768
769 286
		$query = "SELECT * FROM {$this->table}";
770 286
		if (!empty($wheres)) {
771 286
			$query .= ' WHERE ' . implode(' AND ', $wheres);
772
		}
773 286
		$query .= ' ORDER BY name ASC';
774
775 286
		return $this->db->getData($query, $callback, $params);
776
	}
777
778
	/**
779
	 * Get members of an access collection
780
	 *
781
	 * @param int   $collection_id The collection's ID
782
	 * @param array $options       Ege* options
783
	 * @return \ElggData[]|int|mixed
784
	 */
785
	public function getMembers($collection_id, array $options = []) {
786 4
		$options['wheres'][] = function(QueryBuilder $qb, $table_alias) use ($collection_id) {
787 4
			$qb->join($table_alias, 'access_collection_membership', 'acm', $qb->compare('acm.user_guid', '=', "$table_alias.guid"));
788 4
			return $qb->compare('acm.access_collection_id', '=', $collection_id, ELGG_VALUE_INTEGER);
789
		};
790
791 4
		return Entities::find($options);
792
	}
793
794
	/**
795
	 * Return an array of collections that the entity is member of
796
	 *
797
	 * @param int $member_guid GUID of th member
798
	 *
799
	 * @return \ElggAccessCollection[]|false
800
	 */
801 77
	public function getCollectionsByMember($member_guid) {
802
803 77
		$callback = [$this, 'rowToElggAccessCollection'];
804
805
		$query = "
806 77
			SELECT ac.* FROM {$this->table} ac
807 77
				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 77
		return $this->db->getData($query, $callback, [
814 77
			':member_guid' => (int) $member_guid,
815
		]);
816
	}
817
818
	/**
819
	 * Return the name of an ACCESS_* constant or an access collection,
820
	 * but only if the logged in user owns the access collection or is an admin.
821
	 * Ownership requirement prevents us from exposing names of access collections
822
	 * that current user has been added to by other members and may contain
823
	 * sensitive classification of the current user (e.g. close friends vs acquaintances).
824
	 *
825
	 * Returns a string in the language of the user for global access levels, e.g.'Public, 'Friends', 'Logged in', 'Private';
826
	 * or a name of the owned access collection, e.g. 'My work colleagues';
827
	 * or a name of the group or other access collection, e.g. 'Group: Elgg technical support';
828
	 * or 'Limited' if the user access is restricted to read-only, e.g. a friends collection the user was added to
829
	 *
830
	 * @param int $entity_access_id The entity's access id
831
	 *
832
	 * @return string
833
	 * @since 1.11
834
	 */
835 6
	public function getReadableAccessLevel($entity_access_id) {
836 6
		$access = (int) $entity_access_id;
837
838 6
		$translator = $this->translator;
839
840
		// Check if entity access id is a defined global constant
841
		$access_array = [
842 6
			ACCESS_PRIVATE => $translator->translate('access:label:private'),
843 6
			ACCESS_FRIENDS => $translator->translate('access:label:friends'),
844 6
			ACCESS_LOGGED_IN => $translator->translate('access:label:logged_in'),
845 6
			ACCESS_PUBLIC => $translator->translate('access:label:public'),
846
		];
847
848 6
		if (array_key_exists($access, $access_array)) {
849 6
			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
}
866