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) { |
|
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) { |
|
0 ignored issues
–
show
|
|||
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) { |
|
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; |
|
0 ignored issues
–
show
|
|||
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 |
When comparing two booleans, it is generally considered safer to use the strict comparison operator.