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

engine/lib/access.php (4 issues)

1
<?php
2
/**
3
 * Functions for Elgg's access system for entities, metadata, and annotations.
4
 *
5
 * Access is generally saved in the database as access_id.  This corresponds to
6
 * one of the ACCESS_* constants defined in {@link elgglib.php} or the ID of an
7
 * access collection.
8
 *
9
 * @package Elgg.Core
10
 * @subpackage Access
11
 */
12
13
/**
14
 * Set if Elgg's access system should be ignored.
15
 *
16
 * The access system will not return entities in any getter functions if the
17
 * user doesn't have access. This removes this restriction.
18
 *
19
 * When the access system is being ignored, all checks for create, retrieve,
20
 * update, and delete should pass. This affects all the canEdit() and related
21
 * methods.
22
 *
23
 * @tip Use this to access entities in automated scripts
24
 * when no user is logged in.
25
 *
26
 * @warning This will not show disabled entities.
27
 * Use {@link access_show_hidden_entities()} to access disabled entities.
28
 *
29
 * @note Internal: The access override is checked in elgg_override_permissions(). It is
30
 * registered for the 'permissions_check' hooks to override the access system for
31
 * the canEdit() and canWriteToContainer() methods.
32
 *
33
 * @note Internal: This clears the access cache.
34
 *
35
 * @note Internal: For performance reasons this is done at the database access clause level.
36
 *
37
 * @param bool $ignore If true, disables all access checks.
38
 *
39
 * @return bool Previous ignore_access setting.
40
 * @since 1.7.0
41
 * @see elgg_get_ignore_access()
42
 */
43
function elgg_set_ignore_access($ignore = true) {
44 4200
	return _elgg_services()->session->setIgnoreAccess($ignore);
45
}
46
47
/**
48
 * Get current ignore access setting.
49
 *
50
 * @return bool
51
 * @since 1.7.0
52
 * @see elgg_set_ignore_access()
53
 */
54
function elgg_get_ignore_access() {
55 5389
	return _elgg_services()->session->getIgnoreAccess();
56
}
57
58
/**
59
 * Return a string of access_ids for $user_guid appropriate for inserting into an SQL IN clause.
60
 *
61
 * @uses get_access_array
62
 *
63
 * @see get_access_array()
64
 *
65
 * @param int  $user_guid User ID; defaults to currently logged in user
66
 * @param int  $ignored   Ignored parameter
67
 * @param bool $flush     If set to true, will refresh the access list from the
68
 *                        database rather than using this function's cache.
69
 *
70
 * @return string A list of access collections suitable for using in an SQL call
71
 * @access private
72
 */
73
function get_access_list($user_guid = 0, $ignored = 0, $flush = false) {
74 1
	return _elgg_services()->accessCollections->getAccessList($user_guid, $flush);
75
}
76
77
/**
78
 * Returns an array of access IDs a user is permitted to see.
79
 *
80
 * Can be overridden with the 'access:collections:read', 'user' plugin hook.
81
 * @warning A callback for that plugin hook needs to either not retrieve data
82
 * from the database that would use the access system (triggering the plugin again)
83
 * or ignore the second call. Otherwise, an infinite loop will be created.
84
 *
85
 * This returns a list of all the collection ids a user owns or belongs
86
 * to plus public and logged in access levels. If the user is an admin, it includes
87
 * the private access level.
88
 *
89
 * @note Internal: this is only used in core for creating the SQL where clause when
90
 * retrieving content from the database. The friends access level is handled by
91
 * {@link \Elgg\Database\Clauses\AccessWhereClause}
92
 *
93
 * @see get_write_access_array() for the access levels that a user can write to.
94
 *
95
 * @param int  $user_guid User ID; defaults to currently logged in user
96
 * @param int  $ignored   Ignored parameter
97
 * @param bool $flush     If set to true, will refresh the access ids from the
98
 *                        database rather than using this function's cache.
99
 *
100
 * @return array An array of access collections ids
101
 */
102
function get_access_array($user_guid = 0, $ignored = 0, $flush = false) {
103 3
	return _elgg_services()->accessCollections->getAccessArray($user_guid, $flush);
104
}
105
106
/**
107
 * Gets the default access permission.
108
 *
109
 * This returns the default access level for the site or optionally of the user.
110
 * If want you to change the default access based on group of other information,
111
 * use the 'default', 'access' plugin hook.
112
 *
113
 * @param ElggUser $user         The user for whom we're getting default access. Defaults to logged in user.
114
 * @param array    $input_params Parameters passed into an input/access view
115
 *
116
 * @return int default access id (see ACCESS defines in elgglib.php)
117
 */
118
function get_default_access(ElggUser $user = null, array $input_params = []) {
119
	// site default access
120 7
	$default_access = _elgg_config()->default_access;
121
122
	// user default access if enabled
123 7
	if (_elgg_config()->allow_user_default_access) {
124 1
		$user = $user ? $user : _elgg_services()->session->getLoggedInUser();
125 1
		if ($user) {
126
			$user_access = $user->getPrivateSetting('elgg_default_access');
127
			if ($user_access !== null) {
128
				$default_access = $user_access;
129
			}
130
		}
131
	}
132
133
	$params = [
134 7
		'user' => $user,
135 7
		'default_access' => $default_access,
136 7
		'input_params' => $input_params,
137
	];
138 7
	return _elgg_services()->hooks->trigger('default', 'access', $params, $default_access);
139
}
140
141
/**
142
 * Allow disabled entities and metadata to be returned by getter functions
143
 *
144
 * @todo Replace this with query object!
145
 * @global bool $ENTITY_SHOW_HIDDEN_OVERRIDE
146
 * @access private
147
 */
148
$ENTITY_SHOW_HIDDEN_OVERRIDE = false;
149
150
/**
151
 * Show or hide disabled entities.
152
 *
153
 * @param bool $show_hidden Show disabled entities.
154
 * @return bool
155
 */
156
function access_show_hidden_entities($show_hidden) {
157 5389
	global $ENTITY_SHOW_HIDDEN_OVERRIDE;
158 5389
	$current_value = $ENTITY_SHOW_HIDDEN_OVERRIDE;
159 5389
	$ENTITY_SHOW_HIDDEN_OVERRIDE = $show_hidden;
160 5389
	return $current_value;
161
}
162
163
/**
164
 * Return current status of showing disabled entities.
165
 *
166
 * @return bool
167
 */
168
function access_get_show_hidden_status() {
169 5389
	global $ENTITY_SHOW_HIDDEN_OVERRIDE;
170 5389
	return $ENTITY_SHOW_HIDDEN_OVERRIDE;
171
}
172
173
/**
174
 * Can a user access an entity.
175
 *
176
 * @warning If a logged in user doesn't have access to an entity, the
177
 * core engine will not load that entity.
178
 *
179
 * @tip This is mostly useful for checking if a user other than the logged in
180
 * user has access to an entity that is currently loaded.
181
 *
182
 * @todo This function would be much more useful if we could pass the guid of the
183
 * entity to test access for. We need to be able to tell whether the entity exists
184
 * and whether the user has access to the entity.
185
 *
186
 * @param \ElggEntity $entity The entity to check access for.
187
 * @param \ElggUser   $user   Optionally user to check access for. Defaults to
188
 *                           logged in user (which is a useless default).
189
 *
190
 * @return bool
191
 */
192
function has_access_to_entity($entity, $user = null) {
193 184
	return _elgg_services()->accessCollections->hasAccessToEntity($entity, $user);
194
}
195
196
/**
197
 * Returns an array of access permissions that the user is allowed to save content with.
198
 * Permissions returned are of the form (id => 'name').
199
 *
200
 * Example return value in English:
201
 * array(
202
 *     0 => 'Private',
203
 *    -2 => 'Friends',
204
 *     1 => 'Logged in users',
205
 *     2 => 'Public',
206
 *    34 => 'My favorite friends',
207
 * );
208
 *
209
 * Plugin hook of 'access:collections:write', 'user'
210
 *
211
 * @warning this only returns access collections that the user owns plus the
212
 * standard access levels. It does not return access collections that the user
213
 * belongs to such as the access collection for a group.
214
 *
215
 * @param int   $user_guid    The user's GUID.
216
 * @param int   $ignored      Ignored parameter
217
 * @param bool  $flush        If this is set to true, this will ignore a cached access array
218
 * @param array $input_params Some parameters passed into an input/access view
219
 *
220
 * @return array List of access permissions
221
 */
222
function get_write_access_array($user_guid = 0, $ignored = 0, $flush = false, array $input_params = []) {
1 ignored issue
show
The parameter $ignored is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

222
function get_write_access_array($user_guid = 0, /** @scrutinizer ignore-unused */ $ignored = 0, $flush = false, array $input_params = []) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
223 3
	return _elgg_services()->accessCollections->getWriteAccessArray($user_guid, $flush, $input_params);
224
}
225
226
/**
227
 * Can the user change this access collection?
228
 *
229
 * Use the plugin hook of 'access:collections:write', 'user' to change this.
230
 * @see get_write_access_array() for details on the hook.
231
 *
232
 * Respects access control disabling for admin users and {@link elgg_set_ignore_access()}
233
 *
234
 * @see get_write_access_array()
235
 *
236
 * @param int   $collection_id The collection id
237
 * @param mixed $user_guid     The user GUID to check for. Defaults to logged in user.
238
 * @return bool
239
 */
240
function can_edit_access_collection($collection_id, $user_guid = null) {
241 2
	return _elgg_services()->accessCollections->canEdit($collection_id, $user_guid);
242
}
243
244
/**
245
 * Creates a new access collection.
246
 *
247
 * Access colletions allow plugins and users to create granular access
248
 * for entities.
249
 *
250
 * Triggers plugin hook 'access:collections:addcollection', 'collection'
251
 *
252
 * @note Internal: Access collections are stored in the access_collections table.
253
 * Memberships to collections are in access_collections_membership.
254
 *
255
 * @param string $name       The name of the collection.
256
 * @param int    $owner_guid The GUID of the owner (default: currently logged in user).
257
 * @param string $subtype    The subtype indicates the usage of the acl
258
 *
259
 * @return int|false The collection ID if successful and false on failure.
260
 * @see update_access_collection()
261
 * @see delete_access_collection()
262
 */
263
function create_access_collection($name, $owner_guid = 0, $subtype = null) {
264 83
	return _elgg_services()->accessCollections->create($name, $owner_guid, $subtype);
265
}
266
267
/**
268
 * Updates the membership in an access collection.
269
 *
270
 * @warning Expects a full list of all members that should
271
 * be part of the access collection
272
 *
273
 * @note This will run all hooks associated with adding or removing
274
 * members to access collections.
275
 *
276
 * @param int   $collection_id The ID of the collection.
277
 * @param array $members       Array of member GUIDs
278
 *
279
 * @return bool
280
 * @see add_user_to_access_collection()
281
 * @see remove_user_from_access_collection()
282
 */
283
function update_access_collection($collection_id, $members) {
284 1
	return _elgg_services()->accessCollections->update($collection_id, $members);
285
}
286
287
/**
288
 * Deletes a specified access collection and its membership.
289
 *
290
 * @param int $collection_id The collection ID
291
 *
292
 * @return bool
293
 * @see create_access_collection()
294
 * @see update_access_collection()
295
 */
296
function delete_access_collection($collection_id) {
297 7
	return _elgg_services()->accessCollections->delete($collection_id);
298
}
299
300
/**
301
 * Get a specified access collection
302
 *
303
 * @note This doesn't return the members of an access collection,
304
 * just the database row of the actual collection.
305
 *
306
 * @see get_members_of_access_collection()
307
 *
308
 * @param int $collection_id The collection ID
309
 *
310
 * @return ElggAccessCollection|false
311
 */
312
function get_access_collection($collection_id) {
313 17
	return _elgg_services()->accessCollections->get($collection_id);
314
}
315
316
/**
317
 * Adds a user to an access collection.
318
 *
319
 * Triggers the 'access:collections:add_user', 'collection' plugin hook.
320
 *
321
 * @param int $user_guid     The GUID of the user to add
322
 * @param int $collection_id The ID of the collection to add them to
323
 *
324
 * @return bool
325
 * @see update_access_collection()
326
 * @see remove_user_from_access_collection()
327
 */
328
function add_user_to_access_collection($user_guid, $collection_id) {
329 4
	return _elgg_services()->accessCollections->addUser($user_guid, $collection_id);
330
}
331
332
/**
333
 * Removes a user from an access collection.
334
 *
335
 * Triggers the 'access:collections:remove_user', 'collection' plugin hook.
336
 *
337
 * @param int $user_guid     The user GUID
338
 * @param int $collection_id The access collection ID
339
 *
340
 * @return bool
341
 * @see update_access_collection()
342
 * @see remove_user_from_access_collection()
343
 */
344
function remove_user_from_access_collection($user_guid, $collection_id) {
345 1
	return _elgg_services()->accessCollections->removeUser($user_guid, $collection_id);
346
}
347
348
/**
349
 * Returns access collections
350
 *
351
 * @param array $options array of options to get access collections by
352
 * @return \ElggAccessCollection[]
353
 */
354
function elgg_get_access_collections($options = []) {
355
	return _elgg_services()->accessCollections->getEntityCollections($options);
356
}
357
358
/**
359
 * Get all of members of an access collection
360
 *
361
 * @param int   $collection_id The collection's ID
362
 * @param bool  $guids_only    If set to true, will only return the members' GUIDs (default: false)
363
 * @param array $options       ege* options
364
 *
365
 * @return ElggUser[]|int[]|false guids or entities if successful, false if not
366
 * @see add_user_to_access_collection()
367
 */
368
function get_members_of_access_collection($collection_id, $guids_only = false, array $options = []) {
369 2
	if (!isset($options['limit'])) {
370 2
		$options['limit'] = 0;
371
	}
372
373 2
	if (!$guids_only) {
374
		return _elgg_services()->accessCollections->getMembers($collection_id, $options);
375
	}
376
377 2
	$guids = [];
378 2
	$options['callback'] = false;
379 2
	$rows = _elgg_services()->accessCollections->getMembers($collection_id, $options);
380 2
	foreach ($rows as $row) {
381 1
		$guids[] = $row->guid;
382
	}
383 2
	return $guids;
384
}
385
/**
386
 * Return the name of an ACCESS_* constant or an access collection,
387
 * but only if the logged in user has write access to it.
388
 * Write access requirement prevents us from exposing names of access collections
389
 * that current user has been added to by other members and may contain
390
 * sensitive classification of the current user (e.g. close friends vs acquaintances).
391
 *
392
 * Returns a string in the language of the user for global access levels, e.g.'Public, 'Friends', 'Logged in', 'Public';
393
 * or a name of the owned access collection, e.g. 'My work colleagues';
394
 * or a name of the group or other access collection, e.g. 'Group: Elgg technical support';
395
 * or 'Limited' if the user access is restricted to read-only, e.g. a friends collection the user was added to
396
 *
397
 * @param int $entity_access_id The entity's access id
398
 * @return string
399
 * @since 1.7.0
400
 */
401
function get_readable_access_level($entity_access_id) {
402
	return _elgg_services()->accessCollections->getReadableAccessLevel($entity_access_id);
403
}
404
405
/**
406
 * A quick and dirty way to make sure the access permissions have been correctly set up
407
 *
408
 * @elgg_event_handler init system
409
 *
410
 * @return void
411
 */
412
function access_init() {
413 18
	_elgg_services()->accessCollections->markInitComplete();
414 18
}
415
416
/**
417
 * Creates a Friends ACL for a user
418
 *
419
 * @elgg_event 'create', 'user'
420
 *
421
 * @param \Elgg\Event $event event
422
 *
423
 * @return void
424
 *
425
 * @since 3.0.0
426
 *
427
 * @internal
428
 */
429
function access_friends_acl_create(\Elgg\Event $event) {
430 61
	$user = $event->getObject();
431 61
	if (!($user instanceof \ElggUser)) {
432
		return;
433
	}
434
	
435 61
	create_access_collection('friends', $user->guid, 'friends');
436 61
}
437
438
/**
439
 * Adds the friend to the user friend ACL
440
 *
441
 * @elgg_event 'create', 'relationship'
442
 *
443
 * @param \Elgg\Event $event event
444
 *
445
 * @return void
446
 *
447
 * @since 3.0.0
448
 *
449
 * @internal
450
 */
451
function access_friends_acl_add_friend(\Elgg\Event $event) {
452 24
	$relationship_object = $event->getObject();
453 24
	if (!($relationship_object instanceof \ElggRelationship)) {
454
		return;
455
	}
456
	
457 24
	if ($relationship_object->relationship !== 'friend') {
458 24
		return;
459
	}
460
	
461
	$user = get_user($relationship_object->guid_one);
462
	$friend = get_user($relationship_object->guid_two);
463
	
464
	if (!$user || !$friend) {
465
		return;
466
	}
467
	
468
	$acl = $user->getOwnedAccessCollection('friends');
469
	if (empty($acl)) {
470
		return;
471
	}
472
	$acl->addMember($friend->guid);
473
}
474
475
/**
476
 * Add the friend to the user friends ACL
477
 *
478
 * @elgg_event 'delete', 'relationship'
479
 *
480
 * @param \Elgg\Event $event event
481
 *
482
 * @return void
483
 *
484
 * @since 3.0.0
485
 *
486
 * @internal
487
 */
488
function access_friends_acl_remove_friend(\Elgg\Event $event) {
489 6
	$relationship_object = $event->getObject();
490 6
	if (!($relationship_object instanceof \ElggRelationship)) {
491
		return;
492
	}
493
	
494 6
	if ($relationship_object->relationship !== 'friend') {
495 6
		return;
496
	}
497
	
498
	$user = get_user($relationship_object->guid_one);
499
	$friend = get_user($relationship_object->guid_two);
500
	
501
	if (!$user || !$friend) {
502
		return;
503
	}
504
	
505
	$acl = $user->getOwnedAccessCollection('friends');
506
	if (empty($acl)) {
507
		return;
508
	}
509
	
510
	$acl->removeMember($friend->guid);
511
}
512
513
/**
514
 * Return the name of a friends ACL
515
 *
516
 * @elgg_event 'access_collection:name', 'access_collection'
517
 *
518
 * @param \Elgg\Hook $hook hook
519
 *
520
 * @return string|void
521
 *
522
 * @since 3.0.0
523
 *
524
 * @internal
525
 */
526
function access_friends_acl_get_name(\Elgg\Hook $hook) {
527 4
	$access_collection = $hook->getParam('access_collection');
528 4
	if (!($access_collection instanceof ElggAccessCollection)) {
529
		return;
530
	}
531
	
532 4
	if ($access_collection->getSubtype() !== 'friends') {
533 4
		return;
534
	}
535
	
536 1
	return elgg_echo('access:label:friends');
537
}
538
539
/**
540
 * Runs unit tests for the access library
541
 *
542
 * @param string $hook   'unit_test'
543
 * @param string $type   'system'
544
 * @param array  $value  current return value
545
 * @param array  $params supplied params
546
 *
547
 * @return array
548
 *
549
 * @access private
550
 * @codeCoverageIgnore
551
 */
552
function access_test($hook, $type, $value, $params) {
3 ignored issues
show
The parameter $params is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

552
function access_test($hook, $type, $value, /** @scrutinizer ignore-unused */ $params) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $type is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

552
function access_test($hook, /** @scrutinizer ignore-unused */ $type, $value, $params) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $hook is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

552
function access_test(/** @scrutinizer ignore-unused */ $hook, $type, $value, $params) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
553
	$value[] = ElggCoreAccessCollectionsTest::class;
554
	return $value;
555
}
556
557
/**
558
 * @see \Elgg\Application::loadCore Do not do work here. Just register for events.
559
 */
560
return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
561
	// Tell the access functions the system has booted, plugins are loaded,
562
	// and the user is logged in so it can start caching
563 18
	$events->registerHandler('ready', 'system', 'access_init');
564
	
565
	// friends ACL events
566 18
	$events->registerHandler('create', 'user', 'access_friends_acl_create');
567 18
	$events->registerHandler('create', 'relationship', 'access_friends_acl_add_friend');
568 18
	$events->registerHandler('delete', 'relationship', 'access_friends_acl_remove_friend');
569 18
	$hooks->registerHandler('access_collection:name', 'access_collection', 'access_friends_acl_get_name');
570
571 18
	$hooks->registerHandler('unit_test', 'system', 'access_test');
572
};
573