Passed
Push — master ( 238097...919157 )
by Jerome
04:43
created

UserCapabilities::canAnnotate()   C

Complexity

Conditions 7
Paths 9

Size

Total Lines 32
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 7.0796

Importance

Changes 0
Metric Value
cc 7
eloc 19
nc 9
nop 3
dl 0
loc 32
ccs 15
cts 17
cp 0.8824
crap 7.0796
rs 6.7272
c 0
b 0
f 0
1
<?php
2
3
namespace Elgg;
4
5
use Elgg\Database\EntityTable;
6
use Elgg\Database\EntityTable\UserFetchFailureException;
7
use ElggAnnotation;
8
use ElggEntity;
9
use ElggFile;
10
use ElggRiverItem;
11
use ElggMetadata;
12
use ElggSession;
13
use InvalidArgumentException;
14
15
/**
16
 * WARNING: API IN FLUX. DO NOT USE DIRECTLY.
17
 *
18
 * Use the elgg_* versions instead.
19
 *
20
 * @access private
21
 * @since 2.2
22
 */
23
class UserCapabilities {
24
25
	/**
26
	 * @var PluginHooksService $hooks
27
	 */
28
	private $hooks;
29
30
	/**
31
	 * @var EntityTable
32
	 */
33
	private $entities;
34
35
	/**
36
	 * @var ElggSession
37
	 */
38
	private $session;
39
40
	/**
41
	 * Constructor
42
	 *
43
	 * @param PluginHooksService $hooks    Plugin hooks service
44
	 * @param EntityTable        $entities Entity table
45
	 * @param ElggSession        $session  Session
46
	 */
47 198
	public function __construct(PluginHooksService $hooks, EntityTable $entities, ElggSession $session) {
48 198
		$this->hooks = $hooks;
49 198
		$this->entities = $entities;
50 198
		$this->session = $session;
51 198
	}
52
53
	/**
54
	 * Decides if the access system should be ignored for a user.
55
	 *
56
	 * Returns true (meaning ignore access) if either of these 2 conditions are true:
57
	 *   1) an admin user guid is passed to this function.
58
	 *   2) {@link elgg_get_ignore_access()} returns true.
59
	 *
60
	 * @see elgg_set_ignore_access()
61
	 *
62
	 * @param int $user_guid The user to check against.
63
	 *
64
	 * @return bool
65
	 */
66 262
	public function canBypassPermissionsCheck($user_guid = 0) {
67 262
		if ($this->session->getIgnoreAccess()) {
68
			// Checking ignored access first to avoid infinite loops,
69
			// when trying to fetch a user by guid
70 262
			return true;
71
		}
72
73
		try {
74 262
			$user = $this->entities->getUserForPermissionsCheck($user_guid);
75 262
		} catch (UserFetchFailureException $e) {
76 262
			return false;
77
		}
78
79 179
		return $user && $user->isAdmin();
80
	}
81
82
	/**
83
	 * Can a user edit this entity?
84
	 *
85
	 * @tip Can be overridden by registering for the permissions_check plugin hook.
86
	 *
87
	 * @param ElggEntity $entity    Object entity
88
	 * @param int        $user_guid The user GUID, optionally (default: logged in user)
89
	 *
90
	 * @return bool Whether this entity is editable by the given user.
91
	 * @see elgg_set_ignore_access()
92
	 */
93 23
	public function canEdit(ElggEntity $entity, $user_guid = 0) {
94 23
		if ($this->canBypassPermissionsCheck($user_guid)) {
95 4
			return true;
96
		}
97
98
		try {
99 23
			$user = $this->entities->getUserForPermissionsCheck($user_guid);
100
		} catch (UserFetchFailureException $e) {
101
			return false;
102
		}
103
104
		// Test user if possible - should default to false unless a plugin hook says otherwise
105 23
		$default = call_user_func(function () use ($entity, $user) {
106 23
			if (!$user) {
107 1
				return false;
108
			}
109
110
			// favor the persisted attributes if not saved
111 22
			$attrs = array_merge(
112
					[
113 22
				'owner_guid' => $entity->owner_guid,
114 22
				'container_guid' => $entity->container_guid,
115 22
					], $entity->getOriginalAttributes()
116
			);
117
118 22
			if ($attrs['owner_guid'] == $user->guid) {
119 15
				return true;
120
			}
121
122 11
			if ($attrs['container_guid'] == $user->guid) {
123 3
				return true;
124
			}
125
126 11
			if ($entity->guid == $user->guid) {
127 2
				return true;
128
			}
129
130 10
			$container = $this->entities->get($attrs['container_guid']);
131
132 10
			return ($container && $container->canEdit($user->guid));
1 ignored issue
show
Bug introduced by
The method canEdit does only exist in ElggEntity, but not in stdClass.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
133 23
		});
134
135 23
		$params = ['entity' => $entity, 'user' => $user];
136 23
		return $this->hooks->trigger('permissions_check', $entity->getType(), $params, $default);
137
	}
138
139
	/**
140
	 * Can a user delete this entity?
141
	 *
142
	 * @tip Can be overridden by registering for the permissions_check:delete plugin hook.
143
	 *
144
	 * @param ElggEntity $entity    Object entity
145
	 * @param int        $user_guid The user GUID, optionally (default: logged in user)
146
	 *
147
	 * @return bool Whether this entity is deletable by the given user.
148
	 * @since 1.11
149
	 * @see elgg_set_ignore_access()
150
	 */
151 4
	public function canDelete(ElggEntity $entity, $user_guid = 0) {
152 4
		if ($this->canBypassPermissionsCheck($user_guid)) {
153
			return true;
154
		}
155
156
		try {
157 4
			$user = $this->entities->getUserForPermissionsCheck($user_guid);
158
		} catch (UserFetchFailureException $e) {
159
			return false;
160
		}
161
162 4
		$return = $entity->canEdit($user_guid);
163
164
		$params = [
165 4
			'entity' => $entity,
166 4
			'user' => $user
167
		];
168 4
		return $this->hooks->trigger('permissions_check:delete', $entity->getType(), $params, $return);
169
	}
170
171
	/**
172
	 * Can a user delete this river item?
173
	 *
174
	 * @tip Can be overridden by registering for the "permissions_check:delete", "river" plugin hook.
175
	 *
176
	 * @param ElggRiverItem $item      River item
177
	 * @param int           $user_guid The user GUID, optionally (default: logged in user)
178
	 *
179
	 * @return bool Whether this river item should be considered deletable by the given user.
180
	 * @since 2.3
181
	 * @see elgg_set_ignore_access()
182
	 */
183 View Code Duplication
	public function canDeleteRiverItem(ElggRiverItem $item, $user_guid = 0) {
0 ignored issues
show
Duplication introduced by
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...
184
		if ($this->canBypassPermissionsCheck($user_guid)) {
185
			return true;
186
		}
187
188
		try {
189
			$user = $this->entities->getUserForPermissionsCheck($user_guid);
190
		} catch (UserFetchFailureException $e) {
191
			return false;
192
		}
193
194
		$params = [
195
			'item' => $item,
196
			'user' => $user,
197
		];
198
		return $this->hooks->trigger('permissions_check:delete', 'river', $params, false);
199
	}
200
201
	/**
202
	 * Can a user edit metadata on this entity?
203
	 *
204
	 * If no specific metadata is passed, it returns whether the user can
205
	 * edit any metadata on the entity.
206
	 *
207
	 * @tip Can be overridden by by registering for the permissions_check:metadata
208
	 * plugin hook.
209
	 *
210
	 * @param ElggEntity   $entity    Object entity
211
	 * @param int          $user_guid The user GUID, optionally (default: logged in user)
212
	 * @param ElggMetadata $metadata  The piece of metadata to specifically check or null for any metadata
213
	 *
214
	 * @return bool
215
	 * @see elgg_set_ignore_access()
216
	 */
217 8
	public function canEditMetadata(ElggEntity $entity, $user_guid = 0, ElggMetadata $metadata = null) {
218 8
		if (!$entity->guid) {
219
			// @todo cannot edit metadata on unsaved entity?
220 1
			return false;
221
		}
222
223 7
		if ($this->canBypassPermissionsCheck($user_guid)) {
224 1
			return true;
225
		}
226
227
		try {
228 7
			$user = $this->entities->getUserForPermissionsCheck($user_guid);
229
		} catch (UserFetchFailureException $e) {
230
			return false;
231
		}
232
233 7
		if ($user) {
234 7
			$user_guid = $user->guid;
235
		}
236
237
		// if metadata is not owned or owned by the user, then can edit
238 7
		if ($metadata && ($metadata->owner_guid == 0 || $metadata->owner_guid == $user_guid)) {
239 6
			$return = true;
240
		} else {
241 3
			$return = $entity->canEdit($user_guid);
242
		}
243
244
		// metadata and user may be null
245
		$params = [
246 7
			'entity' => $entity,
247 7
			'user' => $user,
248 7
			'metadata' => $metadata
249
		];
250 7
		return $this->hooks->trigger('permissions_check:metadata', $entity->getType(), $params, $return);
251
	}
252
253
	/**
254
	 * Determines whether or not the user can edit this annotation
255
	 *
256
	 * @param Elggentity     $entity     Object entity
257
	 * @param int            $user_guid  The GUID of the user (defaults to currently logged in user)
258
	 * @param ElggAnnotation $annotation Annotation
259
	 *
260
	 * @return bool
261
	 * @see elgg_set_ignore_access()
262
	 */
263 5
	public function canEditAnnotation(ElggEntity $entity, $user_guid = 0, ElggAnnotation $annotation = null) {
264 5
		if (!$annotation) {
265
			return false;
266
		}
267
268 5
		if ($this->canBypassPermissionsCheck($user_guid)) {
269 3
			return true;
270
		}
271
272
		try {
273 5
			$user = $this->entities->getUserForPermissionsCheck($user_guid);
274
		} catch (UserFetchFailureException $e) {
275
			return false;
276
		}
277
278 5
		$result = false;
279
280 5
		if ($user) {
281
			// If the owner of annotation is the specified user, they can edit.
282 5
			if ($annotation->owner_guid == $user->guid) {
283 4
				$result = true;
284
			}
285
286
			// If the user can edit the entity this is attached to, they can edit.
287 5
			if ($result == false && $entity->canEdit($user->guid)) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
288 1
				$result = true;
289
			}
290
		}
291
292
		// Trigger plugin hook - note that $user may be null
293
		$params = [
294 5
			'entity' => $entity,
295 5
			'user' => $user,
296 5
			'annotation' => $annotation
297
		];
298
299 5
		return $this->hooks->trigger('permissions_check', 'annotation', $params, $result);
300
	}
301
	
302
	/**
303
	 * Can a user add an entity to this container
304
	 *
305
	 * @param ElggEntity $entity    Container entity
306
	 * @param int        $user_guid The GUID of the user creating the entity (0 for logged in user).
307
	 * @param string     $type      The type of entity we're looking to write
308
	 * @param string     $subtype   The subtype of the entity we're looking to write
309
	 *
310
	 * @return bool
311
	 * @see elgg_set_ignore_access()
312
	 */
313 6
	public function canWriteToContainer(ElggEntity $entity, $user_guid = 0, $type = 'all', $subtype = 'all') {
314
		try {
315 6
			$user = $this->entities->getUserForPermissionsCheck($user_guid);
316 1
		} catch (UserFetchFailureException $e) {
317 1
			return false;
318
		}
319
320 6
		if ($user) {
321 6
			$user_guid = $user->guid;
322
		}
323
324
		$params = [
325 6
			'container' => $entity,
326 6
			'user' => $user,
327 6
			'subtype' => $subtype
328
		];
329
330
		// Unlike permissions, logic check can be used to prevent certain entity
331
		// types from being contained by other entity types,
332
		// e.g. discussion reply objects can only be contained by discussion objects.
333
		// This hook can also be used to apply status logic, e.g. to disallow
334
		// new replies in closed discussions.
335
		// We do not take a stand hence the return is null. This can be used by
336
		// handlers to check if another hook has modified the value.
337 6
		$logic_check = $this->hooks->trigger('container_logic_check', $type, $params);
338
339 6
		if ($logic_check === false) {
340 2
			return false;
341
		}
342
343 6
		if ($this->canBypassPermissionsCheck($user_guid)) {
344 1
			return true;
345
		}
346
347 6
		$return = false;
348 6
		if ($entity) {
349
			// If the user can edit the container, they can also write to it
350 6
			if ($entity->canEdit($user_guid)) {
351 5
				$return = true;
352
			}
353
		}
354
355
		// Container permissions can prevent users from writing to an entity.
356
		// For instance, these permissions can prevent non-group members from writing
357
		// content to the group.
358 6
		return $this->hooks->trigger('container_permissions_check', $type, $params, $return);
359
	}
360
361
	/**
362
	 * Can a user comment on an entity?
363
	 *
364
	 * @tip Can be overridden by registering for the permissions_check:comment,
365
	 * <entity type> plugin hook.
366
	 *
367
	 * @param ElggEntity $entity    Object entity
368
	 * @param int        $user_guid User guid (default is logged in user)
369
	 * @param bool       $default   Default permission
370
	 * @return bool
371
	 */
372 5 View Code Duplication
	public function canComment(ElggEntity $entity, $user_guid = 0, $default = null) {
0 ignored issues
show
Duplication introduced by
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...
373 5
		if ($this->canBypassPermissionsCheck($user_guid)) {
374 2
			return true;
375
		}
376
377
		try {
378 5
			$user = $this->entities->getUserForPermissionsCheck($user_guid);
379
		} catch (UserFetchFailureException $e) {
380
			return false;
381
		}
382
383
		// By default, we don't take a position of whether commenting is allowed
384
		// because it is handled by the subclasses of \ElggEntity
385
		$params = [
386 5
			'entity' => $entity,
387 5
			'user' => $user
388
		];
389 5
		return $this->hooks->trigger('permissions_check:comment', $entity->getType(), $params, $default);
390
	}
391
392
	/**
393
	 * Can a user annotate an entity?
394
	 *
395
	 * @tip Can be overridden by registering for the plugin hook [permissions_check:annotate:<name>,
396
	 * <entity type>] or [permissions_check:annotate, <entity type>]. The hooks are called in that order.
397
	 *
398
	 * @tip If you want logged out users to annotate an object, do not call
399
	 * canAnnotate(). It's easier than using the plugin hook.
400
	 *
401
	 * @param ElggEntity $entity          Objet entity
402
	 * @param int        $user_guid       User guid (default is logged in user)
403
	 * @param string     $annotation_name The name of the annotation (default is unspecified)
404
	 *
405
	 * @return bool
406
	 */
407 8
	public function canAnnotate(ElggEntity $entity, $user_guid = 0, $annotation_name = '') {
408 8
		if ($annotation_name === null || $annotation_name === false) {
409
			// accepting these for BC
410 1
			$annotation_name = '';
411 8
		} elseif (!is_string($annotation_name)) {
412 4
			throw new InvalidArgumentException(__METHOD__ . ' expects \$annotation_name to be a string');
413
		}
414
415 4
		if ($this->canBypassPermissionsCheck($user_guid)) {
416 2
			return true;
417
		}
418
419
		try {
420 4
			$user = $this->entities->getUserForPermissionsCheck($user_guid);
421
		} catch (UserFetchFailureException $e) {
422
			return false;
423
		}
424
425 4
		$return = (bool) $user;
426
427
		$params = [
428 4
			'entity' => $entity,
429 4
			'user' => $user,
430 4
			'annotation_name' => $annotation_name,
431
		];
432
433 4
		if (!empty($annotation_name)) {
434 4
			$return = $this->hooks->trigger("permissions_check:annotate:$annotation_name", $entity->getType(), $params, $return);
435
		}
436
437 4
		return $this->hooks->trigger('permissions_check:annotate', $entity->getType(), $params, $return);
438
	}
439
440
	/**
441
	 * Can a user download a file?
442
	 *
443
	 * @tip Can be overridden by registering for the permissions_check:download,file plugin hook.
444
	 *
445
	 * @param ElggFile $entity    File entity
446
	 * @param int      $user_guid User guid (default is logged in user)
447
	 * @param bool     $default   Default permission
448
	 *
449
	 * @return bool
450
	 */
451 2 View Code Duplication
	public function canDownload(ElggFile $entity, $user_guid = 0, $default = true) {
0 ignored issues
show
Duplication introduced by
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...
452 2
		if ($this->canBypassPermissionsCheck($user_guid)) {
453 1
			return true;
454
		}
455
456
		try {
457 2
			$user = $this->entities->getUserForPermissionsCheck($user_guid);
458
		} catch (UserFetchFailureException $e) {
459
			return false;
460
		}
461
462
		$params = [
463 2
			'entity' => $entity,
464 2
			'user' => $user
465
		];
466
467 2
		return $this->hooks->trigger('permissions_check:download', 'file', $params, $default);
468
	}
469
470
}
471