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
|
|||
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
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
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 |
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.