Completed
Push — master ( caf222...deba87 )
by Jeroen
72:32 queued 44:47
created

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

1
<?php
2
3
namespace Elgg\Database;
4
5
use Elgg\Config as Conf;
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 ElggEntity;
12
use ElggSession;
13
use ElggStaticVariableCache;
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 Conf
29
	 */
30
	protected $config;
31
32
	/**
33
	 * @var Database
34
	 */
35
	protected $db;
36
37
	/**
38
	 * @vars \ElggStateVariableCache
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 Conf                    $config       Config
86
	 * @param Database                $db           Database
87
	 * @param EntityTable             $entities     Entity table
88
	 * @param UserCapabilities        $capabilities User capabilities
89
	 * @param ElggStaticVariableCache $cache        Access cache
90
	 * @param PluginHooksService      $hooks        Hooks
91
	 * @param ElggSession             $session      Session
92
	 * @param Translator              $translator   Translator
93
	 */
94 3711
	public function __construct(
95
			Conf $config,
96
			Database $db,
97
			EntityTable $entities,
98
			UserCapabilities $capabilities,
99
			ElggStaticVariableCache $cache,
100
			PluginHooksService $hooks,
101
			ElggSession $session,
102
			Translator $translator) {
103 3711
		$this->config = $config;
104 3711
		$this->db = $db;
105 3711
		$this->entities = $entities;
106 3711
		$this->capabilities = $capabilities;
107 3711
		$this->access_cache = $cache;
108 3711
		$this->hooks = $hooks;
109 3711
		$this->session = $session;
110 3711
		$this->translator = $translator;
111
112 3711
		$this->table = "{$this->db->prefix}access_collections";
113 3711
		$this->membership_table = "{$this->db->prefix}access_collection_membership";
114 3711
	}
115
116
	/**
117
	 * Mark the access system as initialized
118
	 *
119
	 * @return void
120
	 */
121 293
	public function markInitComplete() {
122 293
		$this->init_complete = true;
123 293
	}
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 3711
	public function getAccessList($user_guid = 0, $flush = false) {
138 3711
		$access_array = $this->getAccessArray($user_guid, $flush);
139 3711
		$access_ids = implode(',', $access_array);
140 3711
		$list = "($access_ids)";
141
142
		// for BC, populate the cache
143 3711
		$hash = $user_guid . 'get_access_list';
144 3711
		$this->access_cache->add($hash, $list);
145
146 3711
		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 3711
	public function getAccessArray($user_guid = 0, $flush = false) {
174 3711
		$cache = $this->access_cache;
175
176 3711
		if ($flush) {
177 1
			$cache->clear();
178
		}
179
180 3711
		if ($user_guid == 0) {
181 3711
			$user_guid = $this->session->getLoggedInUserGuid();
182
		}
183
184 3711
		$user_guid = (int) $user_guid;
185
186 3711
		$hash = $user_guid . 'get_access_array';
187
188 3711
		if ($cache[$hash]) {
189 1
			$access_array = $cache[$hash];
190
		} else {
191
			// Public access is always visible
192 3711
			$access_array = [ACCESS_PUBLIC];
193
194
			// The following can only return sensible data for a known user.
195 3711
			if ($user_guid) {
196 3432
				$access_array[] = ACCESS_LOGGED_IN;
197
198
				// Get ACLs that user owns or is a member of
199
				$query = "
200
					SELECT ac.id
201 3432
					FROM {$this->table} ac
202
					WHERE ac.owner_guid = :user_guid
203
					OR EXISTS (SELECT 1
204 3432
							   FROM {$this->membership_table}
205
							   WHERE access_collection_id = ac.id
206
							   AND user_guid = :user_guid)
207
				";
208
209 3432
				$collections = $this->db->getData($query, null, [
210 3432
					':user_guid' => $user_guid,
211
				]);
212
213 3432
				if ($collections) {
214 1
					foreach ($collections as $collection) {
215 1
						$access_array[] = (int) $collection->id;
216
					}
217
				}
218
219 3432
				$ignore_access = $this->capabilities->canBypassPermissionsCheck($user_guid);
220
221 3432
				if ($ignore_access == true) {
222 22
					$access_array[] = ACCESS_PRIVATE;
223
				}
224
			}
225
226 3711
			if ($this->init_complete) {
227 290
				$cache[$hash] = $access_array;
228
			}
229
		}
230
231
		$options = [
232 3711
			'user_id' => $user_guid,
233
		];
234
235
		// see the warning in the docs for this function about infinite loop potential
236 3711
		return $this->hooks->trigger('access:collections:read', 'user', $options, $access_array);
237
	}
238
239
	/**
240
	 * Returns the SQL where clause for enforcing read access to data.
241
	 *
242
	 * Note that if this code is executed in privileged mode it will return (1=1).
243
	 *
244
	 * Otherwise it returns a where clause to retrieve the data that a user has
245
	 * permission to read.
246
	 *
247
	 * Plugin authors can hook into the 'get_sql', 'access' plugin hook to modify,
248
	 * remove, or add to the where clauses. The plugin hook will pass an array with the current
249
	 * ors and ands to the function in the form:
250
	 *  array(
251
	 *      'ors' => array(),
252
	 *      'ands' => array()
253
	 *  )
254
	 *
255
	 * The results will be combined into an SQL where clause in the form:
256
	 *  ((or1 OR or2 OR orN) AND (and1 AND and2 AND andN))
257
	 *
258
	 * @param array $options Array in format:
259
	 *
260
	 * 	table_alias => STR Optional table alias. This is based on the select and join clauses.
261
	 *                     Default is 'e'.
262
	 *
263
	 *  user_guid => INT Optional GUID for the user that we are retrieving data for.
264
	 *                   Defaults to the logged in user if null.
265
	 *                   Passing 0 will build a query for a logged out user (even if there is a logged in user)
266
	 *
267
	 *  use_enabled_clause => BOOL Optional. Should we append the enabled clause? The default
268
	 *                             is set by access_show_hidden_entities().
269
	 *
270
	 *  access_column => STR Optional access column name. Default is 'access_id'.
271
	 *
272
	 *  owner_guid_column => STR Optional owner_guid column. Default is 'owner_guid'.
273
	 *
274
	 *  guid_column => STR Optional guid_column. Default is 'guid'.
275
	 *
276
	 * @return string
277
	 * @access private
278
	 */
279 3711
	public function getWhereSql(array $options = []) {
280
281
		$defaults = [
282 3711
			'table_alias' => 'e',
283 3711
			'user_guid' => $this->session->getLoggedInUserGuid(),
284 3711
			'use_enabled_clause' => !access_get_show_hidden_status(),
285 3711
			'access_column' => 'access_id',
286 3711
			'owner_guid_column' => 'owner_guid',
287 3711
			'guid_column' => 'guid',
288
		];
289
290 3711
		foreach ($options as $key => $value) {
291 3711
			if (is_null($value)) {
292
				// remove null values so we don't loose defaults in array_merge
293 3711
				unset($options[$key]);
294
			}
295
		}
296
297 3711
		$options = array_merge($defaults, $options);
298
299
		// just in case someone passes a . at the end
300 3711
		$options['table_alias'] = rtrim($options['table_alias'], '.');
301
302 3711
		foreach (['table_alias', 'access_column', 'owner_guid_column', 'guid_column'] as $key) {
303 3711
			$options[$key] = sanitize_string($options[$key]);
304
		}
305 3711
		$options['user_guid'] = sanitize_int($options['user_guid'], false);
306
307
		// only add dot if we have an alias or table name
308 3711
		$table_alias = $options['table_alias'] ? $options['table_alias'] . '.' : '';
309
310 3711
		if (!isset($options['ignore_access'])) {
311 3711
			$options['ignore_access'] = $this->capabilities->canBypassPermissionsCheck($options['user_guid']);
312
		}
313
314
		$clauses = [
315 3711
			'ors' => [],
316
			'ands' => []
317
		];
318
319 3711
		$prefix = $this->db->prefix;
320
321 3711
		if ($options['ignore_access']) {
322 3711
			$clauses['ors']['ignore_access'] = '1 = 1';
323 3711
		} else if ($options['user_guid']) {
324
			// include content of user's friends
325 3430
			$clauses['ors']['friends_access'] = "$table_alias{$options['access_column']} = " . ACCESS_FRIENDS . "
326 3430
				AND $table_alias{$options['owner_guid_column']} IN (
327 3430
					SELECT guid_one FROM {$prefix}entity_relationships
328 3430
					WHERE relationship = 'friend' AND guid_two = {$options['user_guid']}
329
				)";
330
331
			// include user's content
332 3430
			$clauses['ors']['owner_access'] = "$table_alias{$options['owner_guid_column']} = {$options['user_guid']}";
333
		}
334
335
		// include standard accesses (public, logged in, access collections)
336 3711
		if (!$options['ignore_access']) {
337 3711
			$access_list = $this->getAccessList($options['user_guid']);
338 3711
			$clauses['ors']['acl_access'] = "$table_alias{$options['access_column']} IN {$access_list}";
339
		}
340
341 3711
		if ($options['use_enabled_clause']) {
342 3711
			$clauses['ands']['use_enabled'] = "{$table_alias}enabled = 'yes'";
343
		}
344
345 3711
		$clauses = $this->hooks->trigger('get_sql', 'access', $options, $clauses);
346
347 3711
		$clauses_str = '';
348 3711
		if (is_array($clauses['ors']) && $clauses['ors']) {
349 3711
			$clauses_str = '(' . implode(' OR ', $clauses['ors']) . ')';
350
		}
351
352 3711
		if (is_array($clauses['ands']) && $clauses['ands']) {
353 3711
			if ($clauses_str) {
354 3711
				$clauses_str .= ' AND ';
355
			}
356 3711
			$clauses_str .= '(' . implode(' AND ', $clauses['ands']) . ')';
357
		}
358
359 3711
		if (empty($clauses_str)) {
360
			$clauses_str = '1 = 1';
361
		}
362
		
363 3711
		return "($clauses_str)";
364
	}
365
366
	/**
367
	 * Can a user access an entity.
368
	 *
369
	 * @warning If a logged in user doesn't have access to an entity, the
370
	 * core engine will not load that entity.
371
	 *
372
	 * @tip This is mostly useful for checking if a user other than the logged in
373
	 * user has access to an entity that is currently loaded.
374
	 *
375
	 * @todo This function would be much more useful if we could pass the guid of the
376
	 * entity to test access for. We need to be able to tell whether the entity exists
377
	 * and whether the user has access to the entity.
378
	 *
379
	 * @param ElggEntity $entity The entity to check access for.
380
	 * @param ElggUser   $user   Optionally user to check access for. Defaults to
381
	 *                           logged in user (which is a useless default).
382
	 *
383
	 * @return bool
384
	 */
385 62
	public function hasAccessToEntity($entity, $user = null) {
386 62
		if (!$entity instanceof \ElggEntity) {
387
			return false;
388
		}
389
390 62
		if ($entity->access_id == ACCESS_PUBLIC) {
391
			// Public entities are always accessible
392 54
			return true;
393
		}
394
395 12
		$user_guid = isset($user) ? (int) $user->guid : elgg_get_logged_in_user_guid();
396
397 12
		if ($user_guid && $user_guid == $entity->owner_guid) {
398
			// Owners have access to their own content
399 3
			return true;
400
		}
401
402 10
		if ($user_guid && $entity->access_id == ACCESS_LOGGED_IN) {
403
			// Existing users have access to entities with logged in access
404 8
			return true;
405
		}
406
407
		// See #7159. Must not allow ignore access to affect query
408 3
		$ia = elgg_set_ignore_access(false);
409
410 3
		$row = $this->entities->getRow($entity->guid, $user_guid);
411
412 3
		elgg_set_ignore_access($ia);
413
414 3
		return !empty($row);
415
	}
416
417
	/**
418
	 * Returns an array of access permissions that the user is allowed to save content with.
419
	 * Permissions returned are of the form (id => 'name').
420
	 *
421
	 * Example return value in English:
422
	 * array(
423
	 *     0 => 'Private',
424
	 *    -2 => 'Friends',
425
	 *     1 => 'Logged in users',
426
	 *     2 => 'Public',
427
	 *    34 => 'My favorite friends',
428
	 * );
429
	 *
430
	 * Plugin hook of 'access:collections:write', 'user'
431
	 *
432
	 * @warning this only returns access collections that the user owns plus the
433
	 * standard access levels. It does not return access collections that the user
434
	 * belongs to such as the access collection for a group.
435
	 *
436
	 * @param int   $user_guid    The user's GUID.
437
	 * @param bool  $flush        If this is set to true, this will ignore a cached access array
438
	 * @param array $input_params Some parameters passed into an input/access view
439
	 *
440
	 * @return array List of access permissions
441
	 */
442 4
	public function getWriteAccessArray($user_guid = 0, $flush = false, array $input_params = []) {
443 4
		$cache = $this->access_cache;
444
445 4
		if ($flush) {
446 4
			$cache->clear();
447
		}
448
449 4
		if ($user_guid == 0) {
450
			$user_guid = $this->session->getLoggedInUserGuid();
451
		}
452
453 4
		$user_guid = (int) $user_guid;
454
455 4
		$hash = $user_guid . 'get_write_access_array';
456
457 4
		if ($cache[$hash]) {
458
			$access_array = $cache[$hash];
459
		} else {
460
			$access_array = [
461 4
				ACCESS_PRIVATE => $this->getReadableAccessLevel(ACCESS_PRIVATE),
462 4
				ACCESS_LOGGED_IN => $this->getReadableAccessLevel(ACCESS_LOGGED_IN),
463 4
				ACCESS_PUBLIC => $this->getReadableAccessLevel(ACCESS_PUBLIC)
464
			];
465
466 4
			$collections = $this->getEntityCollections($user_guid);
467 4
			if ($collections) {
468 3
				foreach ($collections as $collection) {
469 3
					$access_array[$collection->id] = $collection->name;
470
				}
471
			}
472
473 4
			if ($this->init_complete) {
474 4
				$cache[$hash] = $access_array;
475
			}
476
		}
477
478
		$options = [
479 4
			'user_id' => $user_guid,
480 4
			'input_params' => $input_params,
481
		];
482 4
		return $this->hooks->trigger('access:collections:write', 'user', $options, $access_array);
483
	}
484
485
	/**
486
	 * Can the user change this access collection?
487
	 *
488
	 * Use the plugin hook of 'access:collections:write', 'user' to change this.
489
	 * @see get_write_access_array() for details on the hook.
490
	 *
491
	 * Respects access control disabling for admin users and {@link elgg_set_ignore_access()}
492
	 *
493
	 * @see get_write_access_array()
494
	 *
495
	 * @param int   $collection_id The collection id
496
	 * @param mixed $user_guid     The user GUID to check for. Defaults to logged in user.
497
	 * @return bool
498
	 */
499 3
	public function canEdit($collection_id, $user_guid = null) {
500
		try {
501 3
			$user = $this->entities->getUserForPermissionsCheck($user_guid);
502
		} catch (UserFetchFailureException $e) {
503
			return false;
504
		}
505
506 3
		$collection = $this->get($collection_id);
507
508 3
		if (!$user || !$collection) {
509 1
			return false;
510
		}
511
512 3
		if ($this->capabilities->canBypassPermissionsCheck($user->guid)) {
513 1
			return true;
514
		}
515
516 3
		$write_access = $this->getWriteAccessArray($user->guid, true);
517 3
		return array_key_exists($collection_id, $write_access);
518
	}
519
520
	/**
521
	 * Creates a new access collection.
522
	 *
523
	 * Access colletions allow plugins and users to create granular access
524
	 * for entities.
525
	 *
526
	 * Triggers plugin hook 'access:collections:addcollection', 'collection'
527
	 *
528
	 * @internal Access collections are stored in the access_collections table.
529
	 * Memberships to collections are in access_collections_membership.
530
	 *
531
	 * @param string $name       The name of the collection.
532
	 * @param int    $owner_guid The GUID of the owner (default: currently logged in user).
533
	 *
534
	 * @return int|false The collection ID if successful and false on failure.
535
	 */
536 12
	public function create($name, $owner_guid = 0) {
537 12
		$name = trim($name);
538 12
		if (empty($name)) {
539
			return false;
540
		}
541
542 12
		if ($owner_guid == 0) {
543 6
			$owner_guid = $this->session->getLoggedInUserGuid();
544
		}
545
546
		$query = "
547 12
			INSERT INTO {$this->table}
548
			SET name = :name,
549
				owner_guid = :owner_guid
550
		";
551
552
		$params = [
553 12
			':name' => $name,
554 12
			':owner_guid' => (int) $owner_guid,
555
		];
556
557 12
		$id = $this->db->insertData($query, $params);
558 12
		if (!$id) {
559
			return false;
560
		}
561
562 12
		$this->access_cache->clear();
563
564
		$hook_params = [
565 12
			'collection_id' => $id,
566 12
			'name' => $name,
567 12
			'owner_guid' => $owner_guid,
568
		];
569
570 12
		if (!$this->hooks->trigger('access:collections:addcollection', 'collection', $hook_params, true)) {
571
			$this->delete($id);
572
			return false;
573
		}
574
575 12
		return $id;
576
	}
577
578
	/**
579
	 * Renames an access collection
580
	 *
581
	 * @param int    $collection_id ID of the collection
582
	 * @param string $name          The name of the collection
583
	 * @return bool
584
	 */
585 1
	public function rename($collection_id, $name) {
586
587
		$query = "
588 1
			UPDATE {$this->table}
589
			SET name = :name
590
			WHERE id = :id
591
		";
592
593
		$params = [
594 1
			':name' => $name,
595 1
			':id' => (int) $collection_id,
596
		];
597
598 1
		if ($this->db->insertData($query, $params)) {
599
			$this->access_cache->clear();
600
			return (int) $collection_id;
601
		}
602
603 1
		return false;
604
	}
605
606
607
	/**
608
	 * Updates the membership in an access collection.
609
	 *
610
	 * @warning Expects a full list of all members that should
611
	 * be part of the access collection
612
	 *
613
	 * @note This will run all hooks associated with adding or removing
614
	 * members to access collections.
615
	 *
616
	 * @param int   $collection_id ID of the collection.
617
	 * @param array $new_members   Array of member entities or GUIDs
618
	 * @return bool
619
	 */
620 1
	public function update($collection_id, array $new_members = []) {
621 1
		$acl = $this->get($collection_id);
622
623 1
		if (!$acl) {
624
			return false;
625
		}
626
		
627 1
		$to_guid = function($elem) {
628 1
			if (empty($elem)) {
629
				return 0;
630
			}
631 1
			if (is_object($elem)) {
632 1
				return (int) $elem->guid;
633
			}
634 1
			return (int) $elem;
635 1
		};
636
		
637 1
		$current_members = [];
638 1
		$new_members = array_map($to_guid, $new_members);
639
640 1
		$current_members_batch = $this->getMembers($collection_id, [
641 1
			'batch' => true,
642
			'limit' => 0,
643
			'callback' => false,
644
		]);
645
646 1
		foreach ($current_members_batch as $row) {
647 1
			$current_members[] = $to_guid($row);
648
		}
649
650 1
		$remove_members = array_diff($current_members, $new_members);
651 1
		$add_members = array_diff($new_members, $current_members);
652
653 1
		$result = true;
654
655 1
		foreach ($add_members as $guid) {
656 1
			$result = $result && $this->addUser($guid, $collection_id);
657
		}
658
659 1
		foreach ($remove_members as $guid) {
660 1
			$result = $result && $this->removeUser($guid, $collection_id);
661
		}
662
663 1
		$this->access_cache->clear();
664
665 1
		return $result;
666
	}
667
668
	/**
669
	 * Deletes a collection and its membership information
670
	 *
671
	 * @param int $collection_id ID of the collection
672
	 * @return bool
673
	 */
674 12
	public function delete($collection_id) {
675 12
		$collection_id = (int) $collection_id;
676
677
		$params = [
678 12
			'collection_id' => $collection_id,
679
		];
680
681 12
		if (!$this->hooks->trigger('access:collections:deletecollection', 'collection', $params, true)) {
682
			return false;
683
		}
684
685
		// Deleting membership doesn't affect result of deleting ACL.
686
		$query = "
687 12
			DELETE FROM {$this->membership_table}
688
			WHERE access_collection_id = :access_collection_id
689
		";
690 12
		$this->db->deleteData($query, [
691 12
			':access_collection_id' => $collection_id,
692
		]);
693
694
		$query = "
695 12
			DELETE FROM {$this->table}
696
			WHERE id = :id
697
		";
698 12
		$result = $this->db->deleteData($query, [
699 12
			':id' => $collection_id,
700
		]);
701
702 12
		$this->access_cache->clear();
703
		
704 12
		return (bool) $result;
705
	}
706
707
	/**
708
	 * Transforms a database row to an instance of ElggAccessCollection
709
	 *
710
	 * @param \stdClass $row Database row
711
	 * @return \ElggAccessCollection
712
	 */
713 11
	public function rowToElggAccessCollection(\stdClass $row) {
714 11
		return new \ElggAccessCollection($row);
715
	}
716
717
	/**
718
	 * Get a specified access collection
719
	 *
720
	 * @note This doesn't return the members of an access collection,
721
	 * just the database row of the actual collection.
722
	 *
723
	 * @see get_members_of_access_collection()
724
	 *
725
	 * @param int $collection_id The collection ID
726
	 * @return \ElggAccessCollection|false
727
	 */
728 10
	public function get($collection_id) {
729
730 10
		$callback = [$this, 'rowToElggAccessCollection'];
731
732
		$query = "
733 10
			SELECT * FROM {$this->table}
734
			WHERE id = :id
735
		";
736
737 10
		return $this->db->getDataRow($query, $callback, [
738 10
			':id' => (int) $collection_id,
739
		]);
740
	}
741
742
	/**
743
	 * Check if user is already in the collection
744
	 *
745
	 * @param int $user_guid     GUID of the user
746
	 * @param int $collection_id ID of the collection
747
	 * @return bool
748
	 */
749 2
	public function hasUser($user_guid, $collection_id) {
750
		$options = [
751 2
			'guids' => (int) $user_guid,
752
			'count' => true,
753
		];
754 2
		return (bool) $this->getMembers($collection_id, $options);
755
	}
756
757
	/**
758
	 * Adds a user to an access collection.
759
	 *
760
	 * Triggers the 'access:collections:add_user', 'collection' plugin hook.
761
	 *
762
	 * @param int $user_guid     GUID of the user to add
763
	 * @param int $collection_id ID of the collection to add them to
764
	 * @return bool
765
	 */
766 6
	public function addUser($user_guid, $collection_id) {
767
768 6
		$collection = $this->get($collection_id);
769
770 6
		if (!$collection) {
771
			return false;
772
		}
773
774 6
		if (!$this->entities->exists($user_guid)) {
775
			return false;
776
		}
777
778
		$hook_params = [
779 6
			'collection_id' => $collection->id,
780 6
			'user_guid' => (int) $user_guid
781
		];
782
783 6
		$result = $this->hooks->trigger('access:collections:add_user', 'collection', $hook_params, true);
784 6
		if ($result == false) {
785
			return false;
786
		}
787
788
		// if someone tries to insert the same data twice, we do a no-op on duplicate key
789
		$query = "
790 6
			INSERT INTO {$this->membership_table}
791
				SET access_collection_id = :access_collection_id,
792
				    user_guid = :user_guid
793
				ON DUPLICATE KEY UPDATE user_guid = user_guid
794
		";
795
796 6
		$result = $this->db->insertData($query, [
797 6
			':access_collection_id' => (int) $collection->id,
798 6
			':user_guid' => (int) $user_guid,
799
		]);
800
801 6
		$this->access_cache->clear();
802
		
803 6
		return $result !== false;
804
	}
805
806
	/**
807
	 * Removes a user from an access collection.
808
	 *
809
	 * Triggers the 'access:collections:remove_user', 'collection' plugin hook.
810
	 *
811
	 * @param int $user_guid     GUID of the user
812
	 * @param int $collection_id ID of the collection
813
	 * @return bool
814
	 */
815 5
	public function removeUser($user_guid, $collection_id) {
816
817
		$params = [
818 5
			'collection_id' => (int) $collection_id,
819 5
			'user_guid' => (int) $user_guid,
820
		];
821
822 5
		if (!$this->hooks->trigger('access:collections:remove_user', 'collection', $params, true)) {
823
			return false;
824
		}
825
826
		$query = "
827 5
			DELETE FROM {$this->membership_table}
828
			WHERE access_collection_id = :access_collection_id
829
				AND user_guid = :user_guid
830
		";
831
832 5
		$this->access_cache->clear();
833
834 5
		return (bool) $this->db->deleteData($query, [
835 5
			':access_collection_id' => (int) $collection_id,
836 5
			':user_guid' => (int) $user_guid,
837
		]);
838
	}
839
840
	/**
841
	 * Returns access collections owned by the user
842
	 *
843
	 * @param int $owner_guid GUID of the owner
844
	 * @return \ElggAccessCollection[]|false
845
	 */
846 192 View Code Duplication
	public function getEntityCollections($owner_guid) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
847
848 192
		$callback = [$this, 'rowToElggAccessCollection'];
849
850
		$query = "
851 192
			SELECT * FROM {$this->table}
852
				WHERE owner_guid = :owner_guid
853
				ORDER BY name ASC
854
		";
855
856
		$params = [
857 192
			':owner_guid' => (int) $owner_guid,
858
		];
859
860 192
		return $this->db->getData($query, $callback, $params);
861
	}
862
863
	/**
864
	 * Get members of an access collection
865
	 *
866
	 * @param int   $collection_id The collection's ID
867
	 * @param array $options       Ege* options
868
	 * @return ElggEntity[]|false
869
	 */
870 4
	public function getMembers($collection_id, array $options = []) {
871
872 4
		$options['joins'][] = "JOIN {$this->membership_table} acm";
873
874 4
		$collection_id = (int) $collection_id;
875 4
		$options['wheres'][] = "e.guid = acm.user_guid AND acm.access_collection_id = {$collection_id}";
876
877 4
		return $this->entities->getEntities($options);
878
	}
879
880
	/**
881
	 * Return an array of collections that the entity is member of
882
	 *
883
	 * @param int $member_guid GUID of th member
884
	 *
885
	 * @return \ElggAccessCollection[]|false
886
	 */
887 62 View Code Duplication
	public function getCollectionsByMember($member_guid) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
888
889 62
		$callback = [$this, 'rowToElggAccessCollection'];
890
891
		$query = "
892 62
			SELECT ac.* FROM {$this->table} ac
893 62
				JOIN {$this->membership_table} acm
894
					ON ac.id = acm.access_collection_id
895
				WHERE acm.user_guid = :member_guid
896
				ORDER BY name ASC
897
		";
898
899 62
		return $this->db->getData($query, $callback, [
900 62
			':member_guid' => (int) $member_guid,
901
		]);
902
	}
903
904
	/**
905
	 * Return the name of an ACCESS_* constant or an access collection,
906
	 * but only if the logged in user owns the access collection or is an admin.
907
	 * Ownership requirement prevents us from exposing names of access collections
908
	 * that current user has been added to by other members and may contain
909
	 * sensitive classification of the current user (e.g. close friends vs acquaintances).
910
	 *
911
	 * Returns a string in the language of the user for global access levels, e.g.'Public, 'Friends', 'Logged in', 'Private';
912
	 * or a name of the owned access collection, e.g. 'My work colleagues';
913
	 * or a name of the group or other access collection, e.g. 'Group: Elgg technical support';
914
	 * or 'Limited' if the user access is restricted to read-only, e.g. a friends collection the user was added to
915
	 *
916
	 * @param int $entity_access_id The entity's access id
917
	 *
918
	 * @return string
919
	 * @since 1.11
920
	 */
921 4
	public function getReadableAccessLevel($entity_access_id) {
922 4
		$access = (int) $entity_access_id;
923
924 4
		$translator = $this->translator;
925
926
		// Check if entity access id is a defined global constant
927
		$access_array = [
928 4
			ACCESS_PRIVATE => $translator->translate('access:label:private'),
929 4
			ACCESS_FRIENDS => $translator->translate('access:label:friends'),
930 4
			ACCESS_LOGGED_IN => $translator->translate('access:label:logged_in'),
931 4
			ACCESS_PUBLIC => $translator->translate('access:label:public'),
932
		];
933
934 4
		if (array_key_exists($access, $access_array)) {
935 4
			return $access_array[$access];
936
		}
937
938
		// Entity access id is probably a custom access collection
939
		// Check if the user has write access to it and can see it's label
940
		// Admins should always be able to see the readable version
941
		$collection = $this->get($access);
942
943
		$user_guid = $this->session->getLoggedInUserGuid();
944
		
945
		if (!$collection || !$user_guid) {
946
			// return 'Limited' if there is no logged in user or collection can not be loaded
947
			return $translator->translate('access:limited:label');
948
		}
949
950
		return $collection->getDisplayName();
951
	}
952
953
}
954