Passed
Push — master ( c0a3a7...3b84a4 )
by Jeroen
58:51
created

engine/classes/Elgg/Database/AccessCollections.php (2 issues)

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
 * @access private
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 484
	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 484
		$this->config = $config;
104 484
		$this->db = $db;
105 484
		$this->entities = $entities;
106 484
		$this->capabilities = $capabilities;
107 484
		$this->access_cache = $cache;
108 484
		$this->hooks = $hooks;
109 484
		$this->session = $session;
110 484
		$this->translator = $translator;
111
112 484
		$this->table = "{$this->db->prefix}access_collections";
113 484
		$this->membership_table = "{$this->db->prefix}access_collection_membership";
114 484
	}
115
116
	/**
117
	 * Mark the access system as initialized
118
	 *
119
	 * @return void
120
	 */
121 18
	public function markInitComplete() {
122 18
		$this->init_complete = true;
123 18
	}
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
	 * @access private
136
	 */
137 1
	public function getAccessList($user_guid = 0, $flush = false) {
138 1
		$access_array = $this->getAccessArray($user_guid, $flush);
139 1
		$access_ids = implode(',', $access_array);
140 1
		$list = "($access_ids)";
141
142
		// for BC, populate the cache
143 1
		$hash = $user_guid . 'get_access_list';
144 1
		$this->access_cache->add($hash, $list);
145
146 1
		return $list;
147
	}
148
149
	/**
150
	 * Returns an array of access IDs a user is permitted to see.
151
	 *
152
	 * Can be overridden with the 'access:collections:read', 'user' plugin hook.
153
	 * @warning A callback for that plugin hook needs to either not retrieve data
154
	 * from the database that would use the access system (triggering the plugin again)
155
	 * or ignore the second call. Otherwise, an infinite loop will be created.
156
	 *
157
	 * This returns a list of all the collection ids a user owns or belongs
158
	 * to plus public and logged in access levels. If the user is an admin, it includes
159
	 * the private access level.
160
	 *
161
	 * @internal this is only used in core for creating the SQL where clause when
162
	 * retrieving content from the database. The friends access level is handled by
163
	 * _elgg_get_access_where_sql().
164
	 *
165
	 * @see get_write_access_array() for the access levels that a user can write to.
166
	 *
167
	 * @param int  $user_guid User ID; defaults to currently logged in user
168
	 * @param bool $flush     If set to true, will refresh the access ids from the
169
	 *                        database rather than using this function's cache.
170
	 *
171
	 * @return array An array of access collections ids
172
	 */
173 1179
	public function getAccessArray($user_guid = 0, $flush = false) {
174 1179
		$cache = $this->access_cache;
175
176 1179
		if ($flush) {
177 1
			$cache->clear();
178
		}
179
180 1179
		if ($user_guid == 0) {
181 1143
			$user_guid = $this->session->getLoggedInUserGuid();
182
		}
183
184 1179
		$user_guid = (int) $user_guid;
185
186 1179
		$hash = $user_guid . 'get_access_array';
187
188 1179
		if ($cache[$hash]) {
189 185
			$access_array = $cache[$hash];
190
		} else {
191
			// Public access is always visible
192 1179
			$access_array = [ACCESS_PUBLIC];
193
194
			// The following can only return sensible data for a known user.
195 1179
			if ($user_guid) {
196 54
				$access_array[] = ACCESS_LOGGED_IN;
197
198
				// Get ACLs that user owns or is a member of
199
				$query = "
200
					SELECT ac.id
201 54
					FROM {$this->table} ac
202
					WHERE ac.owner_guid = :user_guid
203
					OR EXISTS (SELECT 1
204 54
							   FROM {$this->membership_table}
205
							   WHERE access_collection_id = ac.id
206
							   AND user_guid = :user_guid)
207
				";
208
209 54
				$collections = $this->db->getData($query, null, [
210 54
					':user_guid' => $user_guid,
211
				]);
212
213 54
				if ($collections) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $collections of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
214 24
					foreach ($collections as $collection) {
215 24
						$access_array[] = (int) $collection->id;
216
					}
217
				}
218
219 54
				$ignore_access = $this->capabilities->canBypassPermissionsCheck($user_guid);
220
221 54
				if ($ignore_access == true) {
222 1
					$access_array[] = ACCESS_PRIVATE;
223
				}
224
			}
225
226 1179
			if ($this->init_complete) {
227 709
				$cache[$hash] = $access_array;
228
			}
229
		}
230
231
		$options = [
232 1179
			'user_id' => $user_guid,
233
		];
234
235
		// see the warning in the docs for this function about infinite loop potential
236 1179
		return $this->hooks->trigger('access:collections:read', 'user', $options, $access_array);
237
	}
238
239
	/**
240
	 * Can a user access an entity.
241
	 *
242
	 * @warning If a logged in user doesn't have access to an entity, the
243
	 * core engine will not load that entity.
244
	 *
245
	 * @tip This is mostly useful for checking if a user other than the logged in
246
	 * user has access to an entity that is currently loaded.
247
	 *
248
	 * @todo This function would be much more useful if we could pass the guid of the
249
	 * entity to test access for. We need to be able to tell whether the entity exists
250
	 * and whether the user has access to the entity.
251
	 *
252
	 * @param ElggEntity $entity The entity to check access for.
253
	 * @param ElggUser   $user   Optionally user to check access for. Defaults to
254
	 *                           logged in user (which is a useless default).
255
	 *
256
	 * @return bool
257
	 */
258 184
	public function hasAccessToEntity($entity, $user = null) {
259 184
		if (!$entity instanceof \ElggEntity) {
260
			return false;
261
		}
262
263 184
		if ($entity->access_id == ACCESS_PUBLIC) {
264
			// Public entities are always accessible
265 174
			return true;
266
		}
267
268 33
		$user_guid = isset($user) ? (int) $user->guid : elgg_get_logged_in_user_guid();
269
270 33
		if ($user_guid && $user_guid == $entity->owner_guid) {
271
			// Owners have access to their own content
272 27
			return true;
273
		}
274
275 10
		if ($user_guid && $entity->access_id == ACCESS_LOGGED_IN) {
276
			// Existing users have access to entities with logged in access
277 7
			return true;
278
		}
279
280
		// See #7159. Must not allow ignore access to affect query
281 4
		$ia = elgg_set_ignore_access(false);
282
283 4
		$row = $this->entities->getRow($entity->guid, $user_guid);
284
285 4
		elgg_set_ignore_access($ia);
286
287 4
		return !empty($row);
288
	}
289
290
	/**
291
	 * Returns an array of access permissions that the user is allowed to save content with.
292
	 * Permissions returned are of the form (id => 'name').
293
	 *
294
	 * Example return value in English:
295
	 * array(
296
	 *     0 => 'Private',
297
	 *    -2 => 'Friends',
298
	 *     1 => 'Logged in users',
299
	 *     2 => 'Public',
300
	 *    34 => 'My favorite friends',
301
	 * );
302
	 *
303
	 * Plugin hook of 'access:collections:write', 'user'
304
	 *
305
	 * @warning this only returns access collections that the user owns plus the
306
	 * standard access levels. It does not return access collections that the user
307
	 * belongs to such as the access collection for a group.
308
	 *
309
	 * @param int   $user_guid    The user's GUID.
310
	 * @param bool  $flush        If this is set to true, this will ignore a cached access array
311
	 * @param array $input_params Some parameters passed into an input/access view
312
	 *
313
	 * @return array List of access permissions
314
	 */
315 6
	public function getWriteAccessArray($user_guid = 0, $flush = false, array $input_params = []) {
316 6
		$cache = $this->access_cache;
317
318 6
		if ($flush) {
319 4
			$cache->clear();
320
		}
321
322 6
		if ($user_guid == 0) {
323 1
			$user_guid = $this->session->getLoggedInUserGuid();
324
		}
325
326 6
		$user_guid = (int) $user_guid;
327
328 6
		$hash = $user_guid . 'get_write_access_array';
329
330 6
		if ($cache[$hash]) {
331 1
			$access_array = $cache[$hash];
332
		} else {
333
			$access_array = [
334 6
				ACCESS_PRIVATE => $this->getReadableAccessLevel(ACCESS_PRIVATE),
335 6
				ACCESS_LOGGED_IN => $this->getReadableAccessLevel(ACCESS_LOGGED_IN),
336 6
				ACCESS_PUBLIC => $this->getReadableAccessLevel(ACCESS_PUBLIC)
337
			];
338
339 6
			$collections = $this->getEntityCollections(['owner_guid' => $user_guid]);
340 6
			if ($collections) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $collections of type ElggAccessCollection[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
341 5
				foreach ($collections as $collection) {
342 5
					$access_array[$collection->id] = $collection->getDisplayName();
343
				}
344
			}
345
346 6
			if ($this->init_complete) {
347 6
				$cache[$hash] = $access_array;
348
			}
349
		}
350
351
		$options = [
352 6
			'user_id' => $user_guid,
353 6
			'input_params' => $input_params,
354
		];
355
		
356 6
		$access_array = $this->hooks->trigger('access:collections:write', 'user', $options, $access_array);
357
		
358
		// move logged in and public to the end of the array
359 6
		foreach ([ACCESS_LOGGED_IN, ACCESS_PUBLIC] as $access) {
360 6
			if (!isset($access_array[$access])) {
361 1
				continue;
362
			}
363
		
364 6
			$temp = $access_array[$access];
365 6
			unset($access_array[$access]);
366 6
			$access_array[$access] = $temp;
367
		}
368
		
369
		
370 6
		return $access_array;
371
	}
372
373
	/**
374
	 * Can the user change this access collection?
375
	 *
376
	 * Use the plugin hook of 'access:collections:write', 'user' to change this.
377
	 * @see get_write_access_array() for details on the hook.
378
	 *
379
	 * Respects access control disabling for admin users and {@link elgg_set_ignore_access()}
380
	 *
381
	 * @see get_write_access_array()
382
	 *
383
	 * @param int   $collection_id The collection id
384
	 * @param mixed $user_guid     The user GUID to check for. Defaults to logged in user.
385
	 * @return bool
386
	 */
387 3
	public function canEdit($collection_id, $user_guid = null) {
388
		try {
389 3
			$user = $this->entities->getUserForPermissionsCheck($user_guid);
390
		} catch (UserFetchFailureException $e) {
391
			return false;
392
		}
393
394 3
		$collection = $this->get($collection_id);
395
396 3
		if (!$user || !$collection) {
397 1
			return false;
398
		}
399
400 3
		if ($this->capabilities->canBypassPermissionsCheck($user->guid)) {
401 1
			return true;
402
		}
403
404 3
		$write_access = $this->getWriteAccessArray($user->guid, true);
405 3
		return array_key_exists($collection_id, $write_access);
406
	}
407
408
	/**
409
	 * Creates a new access collection.
410
	 *
411
	 * Access colletions allow plugins and users to create granular access
412
	 * for entities.
413
	 *
414
	 * Triggers plugin hook 'access:collections:addcollection', 'collection'
415
	 *
416
	 * @internal Access collections are stored in the access_collections table.
417
	 * Memberships to collections are in access_collections_membership.
418
	 *
419
	 * @param string $name       The name of the collection.
420
	 * @param int    $owner_guid The GUID of the owner (default: currently logged in user).
421
	 * @param string $subtype    The subtype indicates the usage of the acl
422
	 *
423
	 * @return int|false The collection ID if successful and false on failure.
424
	 */
425 83
	public function create($name, $owner_guid = 0, $subtype = null) {
426 83
		$name = trim($name);
427 83
		if (empty($name)) {
428
			return false;
429
		}
430
431 83
		if (isset($subtype)) {
432 70
			$subtype = trim($subtype);
433 70
			if (strlen($subtype) > 255) {
434
				_elgg_services()->logger->error("The subtype length for access collections cannot be greater than 255");
435
				return false;
436
			}
437
		}
438
439 83
		if ($owner_guid == 0) {
440 6
			$owner_guid = $this->session->getLoggedInUserGuid();
441
		}
442
443
		$query = "
444 83
			INSERT INTO {$this->table}
445
			SET name = :name,
446
				subtype = :subtype,
447
				owner_guid = :owner_guid
448
		";
449
450
		$params = [
451 83
			':name' => $name,
452 83
			':subtype' => $subtype,
453 83
			':owner_guid' => (int) $owner_guid,
454
		];
455
456 83
		$id = $this->db->insertData($query, $params);
457 83
		if (!$id) {
458
			return false;
459
		}
460
461 83
		$this->access_cache->clear();
462
463
		$hook_params = [
464 83
			'collection_id' => $id,
465 83
			'name' => $name,
466 83
			'subtype' => $subtype,
467 83
			'owner_guid' => $owner_guid,
468
		];
469
470 83
		if (!$this->hooks->trigger('access:collections:addcollection', 'collection', $hook_params, true)) {
471
			$this->delete($id);
472
			return false;
473
		}
474
475 83
		return $id;
476
	}
477
478
	/**
479
	 * Renames an access collection
480
	 *
481
	 * @param int    $collection_id ID of the collection
482
	 * @param string $name          The name of the collection
483
	 * @return bool
484
	 */
485 1
	public function rename($collection_id, $name) {
486
487
		$query = "
488 1
			UPDATE {$this->table}
489
			SET name = :name
490
			WHERE id = :id
491
		";
492
493
		$params = [
494 1
			':name' => $name,
495 1
			':id' => (int) $collection_id,
496
		];
497
498 1
		if ($this->db->insertData($query, $params)) {
499
			$this->access_cache->clear();
500
			return (int) $collection_id;
501
		}
502
503 1
		return false;
504
	}
505
506
507
	/**
508
	 * Updates the membership in an access collection.
509
	 *
510
	 * @warning Expects a full list of all members that should
511
	 * be part of the access collection
512
	 *
513
	 * @note This will run all hooks associated with adding or removing
514
	 * members to access collections.
515
	 *
516
	 * @param int   $collection_id ID of the collection.
517
	 * @param array $new_members   Array of member entities or GUIDs
518
	 * @return bool
519
	 */
520 1
	public function update($collection_id, array $new_members = []) {
521 1
		$acl = $this->get($collection_id);
522
523 1
		if (!$acl) {
524
			return false;
525
		}
526
527 1
		$to_guid = function($elem) {
528 1
			if (empty($elem)) {
529
				return 0;
530
			}
531 1
			if (is_object($elem)) {
532 1
				return (int) $elem->guid;
533
			}
534 1
			return (int) $elem;
535 1
		};
536
537 1
		$current_members = [];
538 1
		$new_members = array_map($to_guid, $new_members);
539
540 1
		$current_members_batch = $this->getMembers($collection_id, [
541 1
			'batch' => true,
542
			'limit' => 0,
543
			'callback' => false,
544
		]);
545
546 1
		foreach ($current_members_batch as $row) {
547 1
			$current_members[] = $to_guid($row);
548
		}
549
550 1
		$remove_members = array_diff($current_members, $new_members);
551 1
		$add_members = array_diff($new_members, $current_members);
552
553 1
		$result = true;
554
555 1
		foreach ($add_members as $guid) {
556 1
			$result = $result && $this->addUser($guid, $collection_id);
557
		}
558
559 1
		foreach ($remove_members as $guid) {
560 1
			$result = $result && $this->removeUser($guid, $collection_id);
561
		}
562
563 1
		$this->access_cache->clear();
564
565 1
		return $result;
566
	}
567
568
	/**
569
	 * Deletes a collection and its membership information
570
	 *
571
	 * @param int $collection_id ID of the collection
572
	 * @return bool
573
	 */
574 53
	public function delete($collection_id) {
575 53
		$collection_id = (int) $collection_id;
576
577
		$params = [
578 53
			'collection_id' => $collection_id,
579
		];
580
581 53
		if (!$this->hooks->trigger('access:collections:deletecollection', 'collection', $params, true)) {
582
			return false;
583
		}
584
585
		// Deleting membership doesn't affect result of deleting ACL.
586
		$query = "
587 53
			DELETE FROM {$this->membership_table}
588
			WHERE access_collection_id = :access_collection_id
589
		";
590 53
		$this->db->deleteData($query, [
591 53
			':access_collection_id' => $collection_id,
592
		]);
593
594
		$query = "
595 53
			DELETE FROM {$this->table}
596
			WHERE id = :id
597
		";
598 53
		$result = $this->db->deleteData($query, [
599 53
			':id' => $collection_id,
600
		]);
601
602 53
		$this->access_cache->clear();
603
604 53
		return (bool) $result;
605
	}
606
607
	/**
608
	 * Transforms a database row to an instance of ElggAccessCollection
609
	 *
610
	 * @param \stdClass $row Database row
611
	 * @return \ElggAccessCollection
612
	 */
613 56
	public function rowToElggAccessCollection(\stdClass $row) {
614 56
		return new \ElggAccessCollection($row);
615
	}
616
617
	/**
618
	 * Get a specified access collection
619
	 *
620
	 * @note This doesn't return the members of an access collection,
621
	 * just the database row of the actual collection.
622
	 *
623
	 * @see get_members_of_access_collection()
624
	 *
625
	 * @param int $collection_id The collection ID
626
	 * @return \ElggAccessCollection|false
627
	 */
628 18
	public function get($collection_id) {
629
630 18
		$callback = [$this, 'rowToElggAccessCollection'];
631
632
		$query = "
633 18
			SELECT * FROM {$this->table}
634
			WHERE id = :id
635
		";
636
637 18
		$result = $this->db->getDataRow($query, $callback, [
638 18
			':id' => (int) $collection_id,
639
		]);
640
641 18
		if (empty($result)) {
642 2
			return false;
643
		}
644
645 18
		return $result;
646
	}
647
648
	/**
649
	 * Check if user is already in the collection
650
	 *
651
	 * @param int $user_guid     GUID of the user
652
	 * @param int $collection_id ID of the collection
653
	 * @return bool
654
	 */
655 2
	public function hasUser($user_guid, $collection_id) {
656
		$options = [
657 2
			'guids' => (int) $user_guid,
658
			'count' => true,
659
		];
660 2
		return (bool) $this->getMembers($collection_id, $options);
661
	}
662
663
	/**
664
	 * Adds a user to an access collection.
665
	 *
666
	 * Triggers the 'access:collections:add_user', 'collection' plugin hook.
667
	 *
668
	 * @param int $user_guid     GUID of the user to add
669
	 * @param int $collection_id ID of the collection to add them to
670
	 * @return bool
671
	 */
672 14
	public function addUser($user_guid, $collection_id) {
673
674 14
		$collection = $this->get($collection_id);
675
676 14
		if (!$collection) {
677
			return false;
678
		}
679
680 14
		if (!$this->entities->exists($user_guid)) {
681
			return false;
682
		}
683
684
		$hook_params = [
685 14
			'collection_id' => $collection->id,
686 14
			'user_guid' => (int) $user_guid
687
		];
688
689 14
		$result = $this->hooks->trigger('access:collections:add_user', 'collection', $hook_params, true);
690 14
		if ($result == false) {
691
			return false;
692
		}
693
694
		// if someone tries to insert the same data twice, we do a no-op on duplicate key
695
		$query = "
696 14
			INSERT INTO {$this->membership_table}
697
				SET access_collection_id = :access_collection_id,
698
				    user_guid = :user_guid
699
				ON DUPLICATE KEY UPDATE user_guid = user_guid
700
		";
701
702 14
		$result = $this->db->insertData($query, [
703 14
			':access_collection_id' => (int) $collection->id,
704 14
			':user_guid' => (int) $user_guid,
705
		]);
706
707 14
		$this->access_cache->clear();
708
709 14
		return $result !== false;
710
	}
711
712
	/**
713
	 * Removes a user from an access collection.
714
	 *
715
	 * Triggers the 'access:collections:remove_user', 'collection' plugin hook.
716
	 *
717
	 * @param int $user_guid     GUID of the user
718
	 * @param int $collection_id ID of the collection
719
	 * @return bool
720
	 */
721 8
	public function removeUser($user_guid, $collection_id) {
722
723
		$params = [
724 8
			'collection_id' => (int) $collection_id,
725 8
			'user_guid' => (int) $user_guid,
726
		];
727
728 8
		if (!$this->hooks->trigger('access:collections:remove_user', 'collection', $params, true)) {
729
			return false;
730
		}
731
732
		$query = "
733 8
			DELETE FROM {$this->membership_table}
734
			WHERE access_collection_id = :access_collection_id
735
				AND user_guid = :user_guid
736
		";
737
738 8
		$this->access_cache->clear();
739
740 8
		return (bool) $this->db->deleteData($query, [
741 8
			':access_collection_id' => (int) $collection_id,
742 8
			':user_guid' => (int) $user_guid,
743
		]);
744
	}
745
746
	/**
747
	 * Returns access collections
748
	 *
749
	 * @param array $options Options to get access collections by
750
	 *                       Supported are 'owner_guid', 'subtype'
751
	 * @return \ElggAccessCollection[]
752
	 */
753 218
	public function getEntityCollections($options = []) {
754
755 218
		$callback = [$this, 'rowToElggAccessCollection'];
756
757 218
		$supported_options = ['owner_guid', 'subtype'];
758
759 218
		$wheres = [];
760 218
		$params = [];
761 218
		foreach ($supported_options as $option) {
762 218
			$option_value = elgg_extract($option, $options);
763 218
			if (!isset($option_value)) {
764 214
				continue;
765
			}
766 218
			$wheres[] = "{$option} = :{$option}";
767 218
			$params[":{$option}"] = $option_value;
768
		}
769
770 218
		$query = "SELECT * FROM {$this->table}";
771 218
		if (!empty($wheres)) {
772 218
			$query .= ' WHERE ' . implode(' AND ', $wheres);
773
		}
774 218
		$query .= ' ORDER BY name ASC';
775
776 218
		return $this->db->getData($query, $callback, $params);
777
	}
778
779
	/**
780
	 * Get members of an access collection
781
	 *
782
	 * @param int   $collection_id The collection's ID
783
	 * @param array $options       Ege* options
784
	 * @return ElggEntity[]|false
785
	 */
786
	public function getMembers($collection_id, array $options = []) {
787 4
		$options['wheres'][] = function(QueryBuilder $qb, $table_alias) use ($collection_id) {
788 4
			$qb->join($table_alias, 'access_collection_membership', 'acm', $qb->compare('acm.user_guid', '=', "$table_alias.guid"));
789 4
			return $qb->compare('acm.access_collection_id', '=', $collection_id, 'integer');
790
		};
791
792 4
		return Entities::find($options);
793
	}
794
795
	/**
796
	 * Return an array of collections that the entity is member of
797
	 *
798
	 * @param int $member_guid GUID of th member
799
	 *
800
	 * @return \ElggAccessCollection[]|false
801
	 */
802 70
	public function getCollectionsByMember($member_guid) {
803
804 70
		$callback = [$this, 'rowToElggAccessCollection'];
805
806
		$query = "
807 70
			SELECT ac.* FROM {$this->table} ac
808 70
				JOIN {$this->membership_table} acm
809
					ON ac.id = acm.access_collection_id
810
				WHERE acm.user_guid = :member_guid
811
				ORDER BY name ASC
812
		";
813
814 70
		return $this->db->getData($query, $callback, [
815 70
			':member_guid' => (int) $member_guid,
816
		]);
817
	}
818
819
	/**
820
	 * Return the name of an ACCESS_* constant or an access collection,
821
	 * but only if the logged in user owns the access collection or is an admin.
822
	 * Ownership requirement prevents us from exposing names of access collections
823
	 * that current user has been added to by other members and may contain
824
	 * sensitive classification of the current user (e.g. close friends vs acquaintances).
825
	 *
826
	 * Returns a string in the language of the user for global access levels, e.g.'Public, 'Friends', 'Logged in', 'Private';
827
	 * or a name of the owned access collection, e.g. 'My work colleagues';
828
	 * or a name of the group or other access collection, e.g. 'Group: Elgg technical support';
829
	 * or 'Limited' if the user access is restricted to read-only, e.g. a friends collection the user was added to
830
	 *
831
	 * @param int $entity_access_id The entity's access id
832
	 *
833
	 * @return string
834
	 * @since 1.11
835
	 */
836 6
	public function getReadableAccessLevel($entity_access_id) {
837 6
		$access = (int) $entity_access_id;
838
839 6
		$translator = $this->translator;
840
841
		// Check if entity access id is a defined global constant
842
		$access_array = [
843 6
			ACCESS_PRIVATE => $translator->translate('access:label:private'),
844 6
			ACCESS_FRIENDS => $translator->translate('access:label:friends'),
845 6
			ACCESS_LOGGED_IN => $translator->translate('access:label:logged_in'),
846 6
			ACCESS_PUBLIC => $translator->translate('access:label:public'),
847
		];
848
849 6
		if (array_key_exists($access, $access_array)) {
850 6
			return $access_array[$access];
851
		}
852
853
		// Entity access id is probably a custom access collection
854
		// Check if the user has write access to it and can see it's label
855
		// Admins should always be able to see the readable version
856
		$collection = $this->get($access);
857
858
		$user_guid = $this->session->getLoggedInUserGuid();
859
		
860
		if (!$collection || !$collection->canEdit()) {
861
			// return 'Limited' if the collection can not be loaded or it can not be edited
862
			return $translator->translate('access:limited:label');
863
		}
864
865
		return $collection->getDisplayName();
866
	}
867
868
}
869