Completed
Push — 3.0 ( 9dd29c...237018 )
by Jeroen
53:05
created

engine/lib/access.php (1 issue)

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
 * Allow disabled entities and metadata to be returned by getter functions
15
 *
16
 * @global bool $ENTITY_SHOW_HIDDEN_OVERRIDE
17
 * @access private
18
 * @deprecated 3.0
19
 */
20
global $ENTITY_SHOW_HIDDEN_OVERRIDE;
21
22
/**
23
 * Set if Elgg's access system should be ignored.
24
 *
25
 * The access system will not return entities in any getter functions if the
26
 * user doesn't have access. This removes this restriction.
27
 *
28
 * When the access system is being ignored, all checks for create, retrieve,
29
 * update, and delete should pass. This affects all the canEdit() and related
30
 * methods.
31
 *
32
 * @tip Use this to access entities in automated scripts
33
 * when no user is logged in.
34
 *
35
 * @warning This will not show disabled entities.
36
 * Use {@link access_show_hidden_entities()} to access disabled entities.
37
 *
38
 * @note Internal: The access override is checked in elgg_override_permissions(). It is
39
 * registered for the 'permissions_check' hooks to override the access system for
40
 * the canEdit() and canWriteToContainer() methods.
41
 *
42
 * @note Internal: This clears the access cache.
43
 *
44
 * @note Internal: For performance reasons this is done at the database access clause level.
45
 *
46
 * @param bool $ignore If true, disables all access checks.
47
 *
48
 * @return bool Previous ignore_access setting.
49
 * @since 1.7.0
50
 * @see elgg_get_ignore_access()
51
 */
52
function elgg_set_ignore_access($ignore = true) {
53 277
	return elgg()->session->setIgnoreAccess($ignore);
54
}
55
56
/**
57
 * Get current ignore access setting.
58
 *
59
 * @return bool
60
 * @since 1.7.0
61
 * @see elgg_set_ignore_access()
62
 */
63
function elgg_get_ignore_access() {
64 395
	return elgg()->session->getIgnoreAccess();
65
}
66
67
/**
68
 * Return a string of access_ids for $user_guid appropriate for inserting into an SQL IN clause.
69
 *
70
 * @uses get_access_array
71
 *
72
 * @see get_access_array()
73
 *
74
 * @param int  $user_guid User ID; defaults to currently logged in user
75
 * @param int  $ignored   Ignored parameter
76
 * @param bool $flush     If set to true, will refresh the access list from the
77
 *                        database rather than using this function's cache.
78
 *
79
 * @return string A list of access collections suitable for using in an SQL call
80
 * @access private
81
 */
82
function get_access_list($user_guid = 0, $ignored = 0, $flush = false) {
83 1
	return _elgg_services()->accessCollections->getAccessList($user_guid, $flush);
84
}
85
86
/**
87
 * Returns an array of access IDs a user is permitted to see.
88
 *
89
 * Can be overridden with the 'access:collections:read', 'user' plugin hook.
90
 * @warning A callback for that plugin hook needs to either not retrieve data
91
 * from the database that would use the access system (triggering the plugin again)
92
 * or ignore the second call. Otherwise, an infinite loop will be created.
93
 *
94
 * This returns a list of all the collection ids a user owns or belongs
95
 * to plus public and logged in access levels. If the user is an admin, it includes
96
 * the private access level.
97
 *
98
 * @note Internal: this is only used in core for creating the SQL where clause when
99
 * retrieving content from the database. The friends access level is handled by
100
 * {@link \Elgg\Database\Clauses\AccessWhereClause}
101
 *
102
 * @see get_write_access_array() for the access levels that a user can write to.
103
 *
104
 * @param int  $user_guid User ID; defaults to currently logged in user
105
 * @param int  $ignored   Ignored parameter
106
 * @param bool $flush     If set to true, will refresh the access ids from the
107
 *                        database rather than using this function's cache.
108
 *
109
 * @return array An array of access collections ids
110
 */
111
function get_access_array($user_guid = 0, $ignored = 0, $flush = false) {
112 3
	return _elgg_services()->accessCollections->getAccessArray($user_guid, $flush);
113
}
114
115
/**
116
 * Gets the default access permission.
117
 *
118
 * This returns the default access level for the site or optionally of the user.
119
 * If want you to change the default access based on group of other information,
120
 * use the 'default', 'access' plugin hook.
121
 *
122
 * @param ElggUser $user         The user for whom we're getting default access. Defaults to logged in user.
123
 * @param array    $input_params Parameters passed into an input/access view
124
 *
125
 * @return int default access id (see ACCESS defines in elgglib.php)
126
 */
127
function get_default_access(ElggUser $user = null, array $input_params = []) {
128
	// site default access
129 8
	$default_access = _elgg_config()->default_access;
130
131
	// user default access if enabled
132 8
	if (_elgg_config()->allow_user_default_access) {
133 7
		$user = $user ? $user : elgg()->session->getLoggedInUser();
134 7
		if ($user) {
135 5
			$user_access = $user->getPrivateSetting('elgg_default_access');
136 5
			if ($user_access !== null) {
137 1
				$default_access = $user_access;
138
			}
139
		}
140
	}
141
142
	$params = [
143 8
		'user' => $user,
144 8
		'default_access' => $default_access,
145 8
		'input_params' => $input_params,
146
	];
147 8
	return _elgg_services()->hooks->trigger('default', 'access', $params, $default_access);
148
}
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 6049
	elgg()->session->setDisabledEntityVisibility($show_hidden);
158 6049
}
159
160
/**
161
 * Return current status of showing disabled entities.
162
 *
163
 * @return bool
164
 */
165
function access_get_show_hidden_status() {
166 17
	return elgg()->session->getDisabledEntityVisibility();
167
}
168
169
/**
170
 * Can a user access an entity.
171
 *
172
 * @warning If a logged in user doesn't have access to an entity, the
173
 * core engine will not load that entity.
174
 *
175
 * @tip This is mostly useful for checking if a user other than the logged in
176
 * user has access to an entity that is currently loaded.
177
 *
178
 * @todo This function would be much more useful if we could pass the guid of the
179
 * entity to test access for. We need to be able to tell whether the entity exists
180
 * and whether the user has access to the entity.
181
 *
182
 * @param \ElggEntity $entity The entity to check access for.
183
 * @param \ElggUser   $user   Optionally user to check access for. Defaults to
184
 *                           logged in user (which is a useless default).
185
 *
186
 * @return bool
187
 */
188
function has_access_to_entity($entity, $user = null) {
189 201
	return _elgg_services()->accessCollections->hasAccessToEntity($entity, $user);
190
}
191
192
/**
193
 * Returns an array of access permissions that the user is allowed to save content with.
194
 * Permissions returned are of the form (id => 'name').
195
 *
196
 * Example return value in English:
197
 * array(
198
 *     0 => 'Private',
199
 *    -2 => 'Friends',
200
 *     1 => 'Logged in users',
201
 *     2 => 'Public',
202
 *    34 => 'My favorite friends',
203
 * );
204
 *
205
 * Plugin hook of 'access:collections:write', 'user'
206
 *
207
 * @warning this only returns access collections that the user owns plus the
208
 * standard access levels. It does not return access collections that the user
209
 * belongs to such as the access collection for a group.
210
 *
211
 * @param int   $user_guid    The user's GUID.
212
 * @param int   $ignored      Ignored parameter
213
 * @param bool  $flush        If this is set to true, this will ignore a cached access array
214
 * @param array $input_params Some parameters passed into an input/access view
215
 *
216
 * @return array List of access permissions
217
 */
218
function get_write_access_array($user_guid = 0, $ignored = 0, $flush = false, array $input_params = []) {
219 3
	return _elgg_services()->accessCollections->getWriteAccessArray($user_guid, $flush, $input_params);
220
}
221
222
/**
223
 * Can the user change this access collection?
224
 *
225
 * Use the plugin hook of 'access:collections:write', 'user' to change this.
226
 * @see get_write_access_array() for details on the hook.
227
 *
228
 * Respects access control disabling for admin users and {@link elgg_set_ignore_access()}
229
 *
230
 * @see get_write_access_array()
231
 *
232
 * @param int   $collection_id The collection id
233
 * @param mixed $user_guid     The user GUID to check for. Defaults to logged in user.
234
 * @return bool
235
 */
236
function can_edit_access_collection($collection_id, $user_guid = null) {
237 2
	return _elgg_services()->accessCollections->canEdit($collection_id, $user_guid);
238
}
239
240
/**
241
 * Creates a new access collection.
242
 *
243
 * Access colletions allow plugins and users to create granular access
244
 * for entities.
245
 *
246
 * Triggers plugin hook 'access:collections:addcollection', 'collection'
247
 *
248
 * @note Internal: Access collections are stored in the access_collections table.
249
 * Memberships to collections are in access_collections_membership.
250
 *
251
 * @param string $name       The name of the collection.
252
 * @param int    $owner_guid The GUID of the owner (default: currently logged in user).
253
 * @param string $subtype    The subtype indicates the usage of the acl
254
 *
255
 * @return int|false The collection ID if successful and false on failure.
256
 * @see update_access_collection()
257
 * @see delete_access_collection()
258
 */
259
function create_access_collection($name, $owner_guid = 0, $subtype = null) {
260 160
	return _elgg_services()->accessCollections->create($name, $owner_guid, $subtype);
261
}
262
263
/**
264
 * Updates the membership in an access collection.
265
 *
266
 * @warning Expects a full list of all members that should
267
 * be part of the access collection
268
 *
269
 * @note This will run all hooks associated with adding or removing
270
 * members to access collections.
271
 *
272
 * @param int   $collection_id The ID of the collection.
273
 * @param array $members       Array of member GUIDs
274
 *
275
 * @return bool
276
 * @see add_user_to_access_collection()
277
 * @see remove_user_from_access_collection()
278
 */
279
function update_access_collection($collection_id, $members) {
280 1
	return _elgg_services()->accessCollections->update($collection_id, $members);
281
}
282
283
/**
284
 * Deletes a specified access collection and its membership.
285
 *
286
 * @param int $collection_id The collection ID
287
 *
288
 * @return bool
289
 * @see create_access_collection()
290
 * @see update_access_collection()
291
 */
292
function delete_access_collection($collection_id) {
293 7
	return _elgg_services()->accessCollections->delete($collection_id);
294
}
295
296
/**
297
 * Get a specified access collection
298
 *
299
 * @note This doesn't return the members of an access collection,
300
 * just the database row of the actual collection.
301
 *
302
 * @see get_members_of_access_collection()
303
 *
304
 * @param int $collection_id The collection ID
305
 *
306
 * @return ElggAccessCollection|false
307
 */
308
function get_access_collection($collection_id) {
309 23
	return _elgg_services()->accessCollections->get($collection_id);
310
}
311
312
/**
313
 * Adds a user to an access collection.
314
 *
315
 * Triggers the 'access:collections:add_user', 'collection' plugin hook.
316
 *
317
 * @param int $user_guid     The GUID of the user to add
318
 * @param int $collection_id The ID of the collection to add them to
319
 *
320
 * @return bool
321
 * @see update_access_collection()
322
 * @see remove_user_from_access_collection()
323
 */
324
function add_user_to_access_collection($user_guid, $collection_id) {
325 4
	return _elgg_services()->accessCollections->addUser($user_guid, $collection_id);
326
}
327
328
/**
329
 * Removes a user from an access collection.
330
 *
331
 * Triggers the 'access:collections:remove_user', 'collection' plugin hook.
332
 *
333
 * @param int $user_guid     The user GUID
334
 * @param int $collection_id The access collection ID
335
 *
336
 * @return bool
337
 * @see update_access_collection()
338
 * @see remove_user_from_access_collection()
339
 */
340
function remove_user_from_access_collection($user_guid, $collection_id) {
341 1
	return _elgg_services()->accessCollections->removeUser($user_guid, $collection_id);
342
}
343
344
/**
345
 * Returns access collections
346
 *
347
 * @param array $options array of options to get access collections by
348
 * @return \ElggAccessCollection[]
349
 */
350
function elgg_get_access_collections($options = []) {
351
	return _elgg_services()->accessCollections->getEntityCollections($options);
352
}
353
354
/**
355
 * Get all of members of an access collection
356
 *
357
 * @param int   $collection_id The collection's ID
358
 * @param bool  $guids_only    If set to true, will only return the members' GUIDs (default: false)
359
 * @param array $options       ege* options
360
 *
361
 * @return ElggUser[]|int[]|false guids or entities if successful, false if not
362
 * @see add_user_to_access_collection()
363
 */
364
function get_members_of_access_collection($collection_id, $guids_only = false, array $options = []) {
365 2
	if (!isset($options['limit'])) {
366 2
		$options['limit'] = 0;
367
	}
368
369 2
	if (!$guids_only) {
370
		return _elgg_services()->accessCollections->getMembers($collection_id, $options);
0 ignored issues
show
Bug Best Practice introduced by
The expression return _elgg_services()-...ollection_id, $options) also could return the type integer which is incompatible with the documented return type ElggUser[]|false|integer[].
Loading history...
371
	}
372
373 2
	$guids = [];
374 2
	$options['callback'] = false;
375 2
	$rows = _elgg_services()->accessCollections->getMembers($collection_id, $options);
376 2
	foreach ($rows as $row) {
377 1
		$guids[] = $row->guid;
378
	}
379 2
	return $guids;
380
}
381
/**
382
 * Return the name of an ACCESS_* constant or an access collection,
383
 * but only if the logged in user has write access to it.
384
 * Write access requirement prevents us from exposing names of access collections
385
 * that current user has been added to by other members and may contain
386
 * sensitive classification of the current user (e.g. close friends vs acquaintances).
387
 *
388
 * Returns a string in the language of the user for global access levels, e.g.'Public, 'Friends', 'Logged in', 'Public';
389
 * or a name of the owned access collection, e.g. 'My work colleagues';
390
 * or a name of the group or other access collection, e.g. 'Group: Elgg technical support';
391
 * or 'Limited' if the user access is restricted to read-only, e.g. a friends collection the user was added to
392
 *
393
 * @param int $entity_access_id The entity's access id
394
 * @return string
395
 * @since 1.7.0
396
 */
397
function get_readable_access_level($entity_access_id) {
398
	return _elgg_services()->accessCollections->getReadableAccessLevel($entity_access_id);
399
}
400
401
/**
402
 * A quick and dirty way to make sure the access permissions have been correctly set up
403
 *
404
 * @elgg_event_handler init system
405
 *
406
 * @return void
407
 */
408
function access_init() {
409 62
	_elgg_services()->accessCollections->markInitComplete();
410 62
}
411
412
/**
413
 * Creates a Friends ACL for a user
414
 *
415
 * @elgg_event 'create', 'user'
416
 *
417
 * @param \Elgg\Event $event event
418
 *
419
 * @return void
420
 *
421
 * @since 3.0.0
422
 *
423
 * @internal
424
 */
425
function access_friends_acl_create(\Elgg\Event $event) {
426 141
	$user = $event->getObject();
427 141
	if (!($user instanceof \ElggUser)) {
428
		return;
429
	}
430
	
431 141
	create_access_collection('friends', $user->guid, 'friends');
432 141
}
433
434
/**
435
 * Adds the friend to the user friend ACL
436
 *
437
 * @elgg_event 'create', 'relationship'
438
 *
439
 * @param \Elgg\Event $event event
440
 *
441
 * @return void
442
 *
443
 * @since 3.0.0
444
 *
445
 * @internal
446
 */
447
function access_friends_acl_add_friend(\Elgg\Event $event) {
448 31
	$relationship_object = $event->getObject();
449 31
	if (!($relationship_object instanceof \ElggRelationship)) {
450
		return;
451
	}
452
	
453 31
	if ($relationship_object->relationship !== 'friend') {
454 31
		return;
455
	}
456
	
457
	$user = get_user($relationship_object->guid_one);
458
	$friend = get_user($relationship_object->guid_two);
459
	
460
	if (!$user || !$friend) {
461
		return;
462
	}
463
	
464
	$acl = $user->getOwnedAccessCollection('friends');
465
	if (empty($acl)) {
466
		return;
467
	}
468
	$acl->addMember($friend->guid);
469
}
470
471
/**
472
 * Add the friend to the user friends ACL
473
 *
474
 * @elgg_event 'delete', 'relationship'
475
 *
476
 * @param \Elgg\Event $event event
477
 *
478
 * @return void
479
 *
480
 * @since 3.0.0
481
 *
482
 * @internal
483
 */
484
function access_friends_acl_remove_friend(\Elgg\Event $event) {
485 11
	$relationship_object = $event->getObject();
486 11
	if (!($relationship_object instanceof \ElggRelationship)) {
487
		return;
488
	}
489
	
490 11
	if ($relationship_object->relationship !== 'friend') {
491 11
		return;
492
	}
493
	
494
	$user = get_user($relationship_object->guid_one);
495
	$friend = get_user($relationship_object->guid_two);
496
	
497
	if (!$user || !$friend) {
498
		return;
499
	}
500
	
501
	$acl = $user->getOwnedAccessCollection('friends');
502
	if (empty($acl)) {
503
		return;
504
	}
505
	
506
	$acl->removeMember($friend->guid);
507
}
508
509
/**
510
 * Return the name of a friends ACL
511
 *
512
 * @elgg_event 'access_collection:name', 'access_collection'
513
 *
514
 * @param \Elgg\Hook $hook hook
515
 *
516
 * @return string|void
517
 *
518
 * @since 3.0.0
519
 *
520
 * @internal
521
 */
522
function access_friends_acl_get_name(\Elgg\Hook $hook) {
523 4
	$access_collection = $hook->getParam('access_collection');
524 4
	if (!($access_collection instanceof ElggAccessCollection)) {
525
		return;
526
	}
527
	
528 4
	if ($access_collection->getSubtype() !== 'friends') {
529 4
		return;
530
	}
531
	
532 1
	return elgg_echo('access:label:friends');
533
}
534
535
/**
536
 * Runs unit tests for the access library
537
 *
538
 * @param string $hook   'unit_test'
539
 * @param string $type   'system'
540
 * @param array  $value  current return value
541
 * @param array  $params supplied params
542
 *
543
 * @return array
544
 *
545
 * @access private
546
 * @codeCoverageIgnore
547
 */
548
function access_test($hook, $type, $value, $params) {
549
	$value[] = ElggCoreAccessCollectionsTest::class;
550
	return $value;
551
}
552
553
/**
554
 * @see \Elgg\Application::loadCore Do not do work here. Just register for events.
555
 */
556
return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
557
	// Tell the access functions the system has booted, plugins are loaded,
558
	// and the user is logged in so it can start caching
559 64
	$events->registerHandler('ready', 'system', 'access_init');
560
	
561
	// friends ACL events
562 64
	$events->registerHandler('create', 'user', 'access_friends_acl_create');
563 64
	$events->registerHandler('create', 'relationship', 'access_friends_acl_add_friend');
564 64
	$events->registerHandler('delete', 'relationship', 'access_friends_acl_remove_friend');
565 64
	$hooks->registerHandler('access_collection:name', 'access_collection', 'access_friends_acl_get_name');
566
567 64
	$hooks->registerHandler('unit_test', 'system', 'access_test');
568
};
569