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 | * @internal |
||
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 | 629 | 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 | 629 | $this->config = $config; |
|
104 | 629 | $this->db = $db; |
|
105 | 629 | $this->entities = $entities; |
|
106 | 629 | $this->capabilities = $capabilities; |
|
107 | 629 | $this->access_cache = $cache; |
|
108 | 629 | $this->hooks = $hooks; |
|
109 | 629 | $this->session = $session; |
|
110 | 629 | $this->translator = $translator; |
|
111 | |||
112 | 629 | $this->table = "{$this->db->prefix}access_collections"; |
|
113 | 629 | $this->membership_table = "{$this->db->prefix}access_collection_membership"; |
|
114 | 629 | } |
|
115 | |||
116 | /** |
||
117 | * Mark the access system as initialized |
||
118 | * |
||
119 | * @return void |
||
120 | */ |
||
121 | 89 | public function markInitComplete() { |
|
122 | 89 | $this->init_complete = true; |
|
123 | 89 | } |
|
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 | */ |
||
136 | public function getAccessList($user_guid = 0, $flush = false) { |
||
137 | $access_array = $this->getAccessArray($user_guid, $flush); |
||
138 | $access_ids = implode(',', $access_array); |
||
139 | $list = "($access_ids)"; |
||
140 | |||
141 | // for BC, populate the cache |
||
142 | $hash = $user_guid . 'get_access_list'; |
||
143 | $this->access_cache->add($hash, $list); |
||
144 | |||
145 | return $list; |
||
146 | } |
||
147 | |||
148 | /** |
||
149 | * Returns an array of access IDs a user is permitted to see. |
||
150 | * |
||
151 | * Can be overridden with the 'access:collections:read', 'user' plugin hook. |
||
152 | * @warning A callback for that plugin hook needs to either not retrieve data |
||
153 | * from the database that would use the access system (triggering the plugin again) |
||
154 | * or ignore the second call. Otherwise, an infinite loop will be created. |
||
155 | * |
||
156 | * This returns a list of all the collection ids a user owns or belongs |
||
157 | * to plus public and logged in access levels. If the user is an admin, it includes |
||
158 | * the private access level. |
||
159 | * |
||
160 | * @internal this is only used in core for creating the SQL where clause when |
||
161 | * retrieving content from the database. The friends access level is handled by |
||
162 | * _elgg_get_access_where_sql(). |
||
163 | * |
||
164 | * @see get_write_access_array() for the access levels that a user can write to. |
||
165 | * |
||
166 | * @param int $user_guid User ID; defaults to currently logged in user |
||
167 | * @param bool $flush If set to true, will refresh the access ids from the |
||
168 | * database rather than using this function's cache. |
||
169 | * |
||
170 | * @return array An array of access collections ids |
||
171 | */ |
||
172 | 1182 | public function getAccessArray($user_guid = 0, $flush = false) { |
|
173 | 1182 | $cache = $this->access_cache; |
|
174 | |||
175 | 1182 | if ($flush) { |
|
176 | 1 | $cache->clear(); |
|
177 | } |
||
178 | |||
179 | 1182 | if ($user_guid == 0) { |
|
180 | 1130 | $user_guid = $this->session->getLoggedInUserGuid(); |
|
181 | } |
||
182 | |||
183 | 1182 | $user_guid = (int) $user_guid; |
|
184 | |||
185 | 1182 | $hash = $user_guid . 'get_access_array'; |
|
186 | |||
187 | 1182 | if ($cache[$hash]) { |
|
188 | 133 | $access_array = $cache[$hash]; |
|
189 | } else { |
||
190 | // Public access is always visible |
||
191 | 1182 | $access_array = [ACCESS_PUBLIC]; |
|
192 | |||
193 | // The following can only return sensible data for a known user. |
||
194 | 1182 | if ($user_guid) { |
|
195 | 73 | $access_array[] = ACCESS_LOGGED_IN; |
|
196 | |||
197 | // Get ACLs that user owns or is a member of |
||
198 | $query = " |
||
199 | SELECT ac.id |
||
200 | 73 | FROM {$this->table} ac |
|
201 | WHERE ac.owner_guid = :user_guid |
||
202 | OR EXISTS (SELECT 1 |
||
203 | 73 | FROM {$this->membership_table} |
|
204 | WHERE access_collection_id = ac.id |
||
205 | AND user_guid = :user_guid) |
||
206 | "; |
||
207 | |||
208 | 73 | $collections = $this->db->getData($query, null, [ |
|
209 | 73 | ':user_guid' => $user_guid, |
|
210 | ]); |
||
211 | |||
212 | 73 | if (!empty($collections)) { |
|
213 | 36 | foreach ($collections as $collection) { |
|
214 | 36 | $access_array[] = (int) $collection->id; |
|
215 | } |
||
216 | } |
||
217 | |||
218 | 73 | $ignore_access = $this->capabilities->canBypassPermissionsCheck($user_guid); |
|
219 | |||
220 | 73 | if ($ignore_access === true) { |
|
221 | 1 | $access_array[] = ACCESS_PRIVATE; |
|
222 | } |
||
223 | } |
||
224 | |||
225 | 1182 | if ($this->init_complete) { |
|
226 | 691 | $cache[$hash] = $access_array; |
|
227 | } |
||
228 | } |
||
229 | |||
230 | $options = [ |
||
231 | 1182 | 'user_id' => $user_guid, |
|
232 | ]; |
||
233 | |||
234 | // see the warning in the docs for this function about infinite loop potential |
||
235 | 1182 | return $this->hooks->trigger('access:collections:read', 'user', $options, $access_array); |
|
236 | } |
||
237 | |||
238 | /** |
||
239 | * Can a user access an entity. |
||
240 | * |
||
241 | * @warning If a logged in user doesn't have access to an entity, the |
||
242 | * core engine will not load that entity. |
||
243 | * |
||
244 | * @tip This is mostly useful for checking if a user other than the logged in |
||
245 | * user has access to an entity that is currently loaded. |
||
246 | * |
||
247 | * @todo This function would be much more useful if we could pass the guid of the |
||
248 | * entity to test access for. We need to be able to tell whether the entity exists |
||
249 | * and whether the user has access to the entity. |
||
250 | * |
||
251 | * @param ElggEntity $entity The entity to check access for. |
||
252 | * @param ElggUser $user Optionally user to check access for. Defaults to |
||
253 | * logged in user (which is a useless default). |
||
254 | * |
||
255 | * @return bool |
||
256 | */ |
||
257 | 271 | public function hasAccessToEntity($entity, $user = null) { |
|
258 | 271 | if (!$entity instanceof \ElggEntity) { |
|
259 | return false; |
||
260 | } |
||
261 | |||
262 | 271 | if ($entity->access_id == ACCESS_PUBLIC) { |
|
263 | // Public entities are always accessible |
||
264 | 255 | return true; |
|
265 | } |
||
266 | |||
267 | 51 | $user_guid = isset($user) ? (int) $user->guid : _elgg_services()->session->getLoggedInUserGuid(); |
|
268 | |||
269 | 51 | if ($user_guid && $user_guid == $entity->owner_guid) { |
|
270 | // Owners have access to their own content |
||
271 | 35 | return true; |
|
272 | } |
||
273 | |||
274 | 20 | if ($user_guid && $entity->access_id == ACCESS_LOGGED_IN) { |
|
275 | // Existing users have access to entities with logged in access |
||
276 | 10 | return true; |
|
277 | } |
||
278 | |||
279 | // See #7159. Must not allow ignore access to affect query |
||
280 | 11 | $ia = _elgg_services()->session->setIgnoreAccess(false); |
|
281 | |||
282 | 11 | $row = $this->entities->getRow($entity->guid, $user_guid); |
|
283 | |||
284 | 11 | _elgg_services()->session->setIgnoreAccess($ia); |
|
285 | |||
286 | 11 | return !empty($row); |
|
287 | } |
||
288 | |||
289 | /** |
||
290 | * Returns an array of access permissions that the user is allowed to save content with. |
||
291 | * Permissions returned are of the form (id => 'name'). |
||
292 | * |
||
293 | * Example return value in English: |
||
294 | * array( |
||
295 | * 0 => 'Private', |
||
296 | * -2 => 'Friends', |
||
297 | * 1 => 'Logged in users', |
||
298 | * 2 => 'Public', |
||
299 | * 34 => 'My favorite friends', |
||
300 | * ); |
||
301 | * |
||
302 | * Plugin hook of 'access:collections:write', 'user' |
||
303 | * |
||
304 | * @warning this only returns access collections that the user owns plus the |
||
305 | * standard access levels. It does not return access collections that the user |
||
306 | * belongs to such as the access collection for a group. |
||
307 | * |
||
308 | * @param int $user_guid The user's GUID. |
||
309 | * @param bool $flush If this is set to true, this will ignore a cached access array |
||
310 | * @param array $input_params Some parameters passed into an input/access view |
||
311 | * |
||
312 | * @return array List of access permissions |
||
313 | */ |
||
314 | 6 | public function getWriteAccessArray($user_guid = 0, $flush = false, array $input_params = []) { |
|
315 | 6 | $cache = $this->access_cache; |
|
316 | |||
317 | 6 | if ($flush) { |
|
318 | 4 | $cache->clear(); |
|
319 | } |
||
320 | |||
321 | 6 | if ($user_guid == 0) { |
|
322 | 1 | $user_guid = $this->session->getLoggedInUserGuid(); |
|
323 | } |
||
324 | |||
325 | 6 | $user_guid = (int) $user_guid; |
|
326 | |||
327 | 6 | $hash = $user_guid . 'get_write_access_array'; |
|
328 | |||
329 | 6 | if ($cache[$hash]) { |
|
330 | 1 | $access_array = $cache[$hash]; |
|
331 | } else { |
||
332 | $access_array = [ |
||
333 | 6 | ACCESS_PRIVATE => $this->getReadableAccessLevel(ACCESS_PRIVATE), |
|
334 | 6 | ACCESS_LOGGED_IN => $this->getReadableAccessLevel(ACCESS_LOGGED_IN), |
|
335 | 6 | ACCESS_PUBLIC => $this->getReadableAccessLevel(ACCESS_PUBLIC) |
|
336 | ]; |
||
337 | |||
338 | 6 | $collections = $this->getEntityCollections(['owner_guid' => $user_guid]); |
|
339 | 6 | if (!empty($collections)) { |
|
340 | 5 | foreach ($collections as $collection) { |
|
341 | 5 | $access_array[$collection->id] = $collection->getDisplayName(); |
|
342 | } |
||
343 | } |
||
344 | |||
345 | 6 | if ($this->init_complete) { |
|
346 | 6 | $cache[$hash] = $access_array; |
|
347 | } |
||
348 | } |
||
349 | |||
350 | $options = [ |
||
351 | 6 | 'user_id' => $user_guid, |
|
352 | 6 | 'input_params' => $input_params, |
|
353 | ]; |
||
354 | |||
355 | 6 | $access_array = $this->hooks->trigger('access:collections:write', 'user', $options, $access_array); |
|
356 | |||
357 | // move logged in and public to the end of the array |
||
358 | 6 | foreach ([ACCESS_LOGGED_IN, ACCESS_PUBLIC] as $access) { |
|
359 | 6 | if (!isset($access_array[$access])) { |
|
360 | 1 | continue; |
|
361 | } |
||
362 | |||
363 | 6 | $temp = $access_array[$access]; |
|
364 | 6 | unset($access_array[$access]); |
|
365 | 6 | $access_array[$access] = $temp; |
|
366 | } |
||
367 | |||
368 | |||
369 | 6 | return $access_array; |
|
370 | } |
||
371 | |||
372 | /** |
||
373 | * Can the user change this access collection? |
||
374 | * |
||
375 | * Use the plugin hook of 'access:collections:write', 'user' to change this. |
||
376 | * @see get_write_access_array() for details on the hook. |
||
377 | * |
||
378 | * Respects access control disabling for admin users and {@link elgg_call()} |
||
379 | * |
||
380 | * @see get_write_access_array() |
||
381 | * |
||
382 | * @param int $collection_id The collection id |
||
383 | * @param mixed $user_guid The user GUID to check for. Defaults to logged in user. |
||
384 | * @return bool |
||
385 | */ |
||
386 | 3 | public function canEdit($collection_id, $user_guid = null) { |
|
387 | try { |
||
388 | 3 | $user = $this->entities->getUserForPermissionsCheck($user_guid); |
|
389 | } catch (UserFetchFailureException $e) { |
||
390 | return false; |
||
391 | } |
||
392 | |||
393 | 3 | $collection = $this->get($collection_id); |
|
394 | |||
395 | 3 | if (!$user instanceof \ElggUser || !$collection instanceof \ElggAccessCollection) { |
|
396 | 1 | return false; |
|
397 | } |
||
398 | |||
399 | 3 | if ($this->capabilities->canBypassPermissionsCheck($user->guid)) { |
|
400 | 1 | return true; |
|
401 | } |
||
402 | |||
403 | 3 | $write_access = $this->getWriteAccessArray($user->guid, true); |
|
404 | 3 | return array_key_exists($collection_id, $write_access); |
|
405 | } |
||
406 | |||
407 | /** |
||
408 | * Creates a new access collection. |
||
409 | * |
||
410 | * Access colletions allow plugins and users to create granular access |
||
411 | * for entities. |
||
412 | * |
||
413 | * Triggers plugin hook 'access:collections:addcollection', 'collection' |
||
414 | * |
||
415 | * @internal Access collections are stored in the access_collections table. |
||
416 | * Memberships to collections are in access_collections_membership. |
||
417 | * |
||
418 | * @param string $name The name of the collection. |
||
419 | * @param int $owner_guid The GUID of the owner (default: currently logged in user). |
||
420 | * @param string $subtype The subtype indicates the usage of the acl |
||
421 | * |
||
422 | * @return int|false The collection ID if successful and false on failure. |
||
423 | */ |
||
424 | 202 | public function create($name, $owner_guid = 0, $subtype = null) { |
|
425 | 202 | $name = trim($name); |
|
426 | 202 | if (empty($name)) { |
|
427 | return false; |
||
428 | } |
||
429 | |||
430 | 202 | if (isset($subtype)) { |
|
431 | 189 | $subtype = trim($subtype); |
|
432 | 189 | if (strlen($subtype) > 255) { |
|
433 | _elgg_services()->logger->error("The subtype length for access collections cannot be greater than 255"); |
||
434 | return false; |
||
435 | } |
||
436 | } |
||
437 | |||
438 | 202 | if ($owner_guid == 0) { |
|
439 | 6 | $owner_guid = $this->session->getLoggedInUserGuid(); |
|
440 | } |
||
441 | |||
442 | $query = " |
||
443 | 202 | INSERT INTO {$this->table} |
|
444 | SET name = :name, |
||
445 | subtype = :subtype, |
||
446 | owner_guid = :owner_guid |
||
447 | "; |
||
448 | |||
449 | $params = [ |
||
450 | 202 | ':name' => $name, |
|
451 | 202 | ':subtype' => $subtype, |
|
452 | 202 | ':owner_guid' => (int) $owner_guid, |
|
453 | ]; |
||
454 | |||
455 | 202 | $id = $this->db->insertData($query, $params); |
|
456 | 202 | if (!$id) { |
|
457 | return false; |
||
458 | } |
||
459 | |||
460 | 202 | $this->access_cache->clear(); |
|
461 | |||
462 | $hook_params = [ |
||
463 | 202 | 'collection_id' => $id, |
|
464 | 202 | 'name' => $name, |
|
465 | 202 | 'subtype' => $subtype, |
|
466 | 202 | 'owner_guid' => $owner_guid, |
|
467 | ]; |
||
468 | |||
469 | 202 | if (!$this->hooks->trigger('access:collections:addcollection', 'collection', $hook_params, true)) { |
|
470 | $this->delete($id); |
||
471 | return false; |
||
472 | } |
||
473 | |||
474 | 202 | return $id; |
|
475 | } |
||
476 | |||
477 | /** |
||
478 | * Renames an access collection |
||
479 | * |
||
480 | * @param int $collection_id ID of the collection |
||
481 | * @param string $name The name of the collection |
||
482 | * @return bool |
||
483 | */ |
||
484 | 1 | public function rename($collection_id, $name) { |
|
485 | |||
486 | $query = " |
||
487 | 1 | UPDATE {$this->table} |
|
488 | SET name = :name |
||
489 | WHERE id = :id |
||
490 | "; |
||
491 | |||
492 | $params = [ |
||
493 | 1 | ':name' => $name, |
|
494 | 1 | ':id' => (int) $collection_id, |
|
495 | ]; |
||
496 | |||
497 | 1 | if ($this->db->insertData($query, $params)) { |
|
498 | $this->access_cache->clear(); |
||
499 | return (int) $collection_id; |
||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||
500 | } |
||
501 | |||
502 | 1 | return false; |
|
503 | } |
||
504 | |||
505 | |||
506 | /** |
||
507 | * Updates the membership in an access collection. |
||
508 | * |
||
509 | * @warning Expects a full list of all members that should |
||
510 | * be part of the access collection |
||
511 | * |
||
512 | * @note This will run all hooks associated with adding or removing |
||
513 | * members to access collections. |
||
514 | * |
||
515 | * @param int $collection_id ID of the collection. |
||
516 | * @param array $new_members Array of member entities or GUIDs |
||
517 | * @return bool |
||
518 | */ |
||
519 | 1 | public function update($collection_id, array $new_members = []) { |
|
520 | 1 | $acl = $this->get($collection_id); |
|
521 | |||
522 | 1 | if (!$acl instanceof \ElggAccessCollection) { |
|
523 | return false; |
||
524 | } |
||
525 | |||
526 | 1 | $to_guid = function($elem) { |
|
527 | 1 | if (empty($elem)) { |
|
528 | return 0; |
||
529 | } |
||
530 | 1 | if (is_object($elem)) { |
|
531 | 1 | return (int) $elem->guid; |
|
532 | } |
||
533 | 1 | return (int) $elem; |
|
534 | 1 | }; |
|
535 | |||
536 | 1 | $current_members = []; |
|
537 | 1 | $new_members = array_map($to_guid, $new_members); |
|
538 | |||
539 | 1 | $current_members_batch = $this->getMembers($collection_id, [ |
|
540 | 1 | 'batch' => true, |
|
541 | 'limit' => 0, |
||
542 | 'callback' => false, |
||
543 | ]); |
||
544 | |||
545 | 1 | foreach ($current_members_batch as $row) { |
|
546 | 1 | $current_members[] = $to_guid($row); |
|
547 | } |
||
548 | |||
549 | 1 | $remove_members = array_diff($current_members, $new_members); |
|
550 | 1 | $add_members = array_diff($new_members, $current_members); |
|
551 | |||
552 | 1 | $result = true; |
|
553 | |||
554 | 1 | foreach ($add_members as $guid) { |
|
555 | 1 | $result = $result && $this->addUser($guid, $collection_id); |
|
556 | } |
||
557 | |||
558 | 1 | foreach ($remove_members as $guid) { |
|
559 | 1 | $result = $result && $this->removeUser($guid, $collection_id); |
|
560 | } |
||
561 | |||
562 | 1 | $this->access_cache->clear(); |
|
563 | |||
564 | 1 | return $result; |
|
565 | } |
||
566 | |||
567 | /** |
||
568 | * Deletes a collection and its membership information |
||
569 | * |
||
570 | * @param int $collection_id ID of the collection |
||
571 | * @return bool |
||
572 | */ |
||
573 | 58 | public function delete($collection_id) { |
|
574 | 58 | $collection_id = (int) $collection_id; |
|
575 | |||
576 | $params = [ |
||
577 | 58 | 'collection_id' => $collection_id, |
|
578 | ]; |
||
579 | |||
580 | 58 | if (!$this->hooks->trigger('access:collections:deletecollection', 'collection', $params, true)) { |
|
581 | return false; |
||
582 | } |
||
583 | |||
584 | // Deleting membership doesn't affect result of deleting ACL. |
||
585 | $query = " |
||
586 | 58 | DELETE FROM {$this->membership_table} |
|
587 | WHERE access_collection_id = :access_collection_id |
||
588 | "; |
||
589 | 58 | $this->db->deleteData($query, [ |
|
590 | 58 | ':access_collection_id' => $collection_id, |
|
591 | ]); |
||
592 | |||
593 | $query = " |
||
594 | 58 | DELETE FROM {$this->table} |
|
595 | WHERE id = :id |
||
596 | "; |
||
597 | 58 | $result = $this->db->deleteData($query, [ |
|
598 | 58 | ':id' => $collection_id, |
|
599 | ]); |
||
600 | |||
601 | 58 | $this->access_cache->clear(); |
|
602 | |||
603 | 58 | return (bool) $result; |
|
604 | } |
||
605 | |||
606 | /** |
||
607 | * Transforms a database row to an instance of ElggAccessCollection |
||
608 | * |
||
609 | * @param \stdClass $row Database row |
||
610 | * @return \ElggAccessCollection |
||
611 | */ |
||
612 | 69 | public function rowToElggAccessCollection(\stdClass $row) { |
|
613 | 69 | return new \ElggAccessCollection($row); |
|
614 | } |
||
615 | |||
616 | /** |
||
617 | * Get a specified access collection |
||
618 | * |
||
619 | * @note This doesn't return the members of an access collection, |
||
620 | * just the database row of the actual collection. |
||
621 | * |
||
622 | * @see get_members_of_access_collection() |
||
623 | * |
||
624 | * @param int $collection_id The collection ID |
||
625 | * @return \ElggAccessCollection|false |
||
626 | */ |
||
627 | 26 | public function get($collection_id) { |
|
628 | |||
629 | 26 | $callback = [$this, 'rowToElggAccessCollection']; |
|
630 | |||
631 | $query = " |
||
632 | 26 | SELECT * FROM {$this->table} |
|
633 | WHERE id = :id |
||
634 | "; |
||
635 | |||
636 | 26 | $result = $this->db->getDataRow($query, $callback, [ |
|
637 | 26 | ':id' => (int) $collection_id, |
|
638 | ]); |
||
639 | |||
640 | 26 | if (empty($result)) { |
|
641 | 2 | return false; |
|
642 | } |
||
643 | |||
644 | 26 | return $result; |
|
645 | } |
||
646 | |||
647 | /** |
||
648 | * Check if user is already in the collection |
||
649 | * |
||
650 | * @param int $user_guid GUID of the user |
||
651 | * @param int $collection_id ID of the collection |
||
652 | * @return bool |
||
653 | */ |
||
654 | 2 | public function hasUser($user_guid, $collection_id) { |
|
655 | $options = [ |
||
656 | 2 | 'guids' => (int) $user_guid, |
|
657 | 'count' => true, |
||
658 | ]; |
||
659 | 2 | return (bool) $this->getMembers($collection_id, $options); |
|
660 | } |
||
661 | |||
662 | /** |
||
663 | * Adds a user to an access collection. |
||
664 | * |
||
665 | * Triggers the 'access:collections:add_user', 'collection' plugin hook. |
||
666 | * |
||
667 | * @param int $user_guid GUID of the user to add |
||
668 | * @param int $collection_id ID of the collection to add them to |
||
669 | * @return bool |
||
670 | */ |
||
671 | 17 | public function addUser($user_guid, $collection_id) { |
|
672 | |||
673 | 17 | $collection = $this->get($collection_id); |
|
674 | |||
675 | 17 | if (!$collection instanceof \ElggAccessCollection) { |
|
676 | return false; |
||
677 | } |
||
678 | |||
679 | 17 | if (!$this->entities->exists($user_guid)) { |
|
680 | return false; |
||
681 | } |
||
682 | |||
683 | $hook_params = [ |
||
684 | 17 | 'collection_id' => $collection->id, |
|
685 | 17 | 'user_guid' => (int) $user_guid |
|
686 | ]; |
||
687 | |||
688 | 17 | $result = $this->hooks->trigger('access:collections:add_user', 'collection', $hook_params, true); |
|
689 | 17 | if ($result == false) { |
|
690 | return false; |
||
691 | } |
||
692 | |||
693 | // if someone tries to insert the same data twice, we do a no-op on duplicate key |
||
694 | $query = " |
||
695 | 17 | INSERT INTO {$this->membership_table} |
|
696 | SET access_collection_id = :access_collection_id, |
||
697 | user_guid = :user_guid |
||
698 | ON DUPLICATE KEY UPDATE user_guid = user_guid |
||
699 | "; |
||
700 | |||
701 | 17 | $result = $this->db->insertData($query, [ |
|
702 | 17 | ':access_collection_id' => (int) $collection->id, |
|
703 | 17 | ':user_guid' => (int) $user_guid, |
|
704 | ]); |
||
705 | |||
706 | 17 | $this->access_cache->clear(); |
|
707 | |||
708 | 17 | return $result !== false; |
|
709 | } |
||
710 | |||
711 | /** |
||
712 | * Removes a user from an access collection. |
||
713 | * |
||
714 | * Triggers the 'access:collections:remove_user', 'collection' plugin hook. |
||
715 | * |
||
716 | * @param int $user_guid GUID of the user |
||
717 | * @param int $collection_id ID of the collection |
||
718 | * @return bool |
||
719 | */ |
||
720 | 10 | public function removeUser($user_guid, $collection_id) { |
|
721 | |||
722 | $params = [ |
||
723 | 10 | 'collection_id' => (int) $collection_id, |
|
724 | 10 | 'user_guid' => (int) $user_guid, |
|
725 | ]; |
||
726 | |||
727 | 10 | if (!$this->hooks->trigger('access:collections:remove_user', 'collection', $params, true)) { |
|
728 | return false; |
||
729 | } |
||
730 | |||
731 | $query = " |
||
732 | 10 | DELETE FROM {$this->membership_table} |
|
733 | WHERE access_collection_id = :access_collection_id |
||
734 | AND user_guid = :user_guid |
||
735 | "; |
||
736 | |||
737 | 10 | $this->access_cache->clear(); |
|
738 | |||
739 | 10 | return (bool) $this->db->deleteData($query, [ |
|
740 | 10 | ':access_collection_id' => (int) $collection_id, |
|
741 | 10 | ':user_guid' => (int) $user_guid, |
|
742 | ]); |
||
743 | } |
||
744 | |||
745 | /** |
||
746 | * Returns access collections |
||
747 | * |
||
748 | * @param array $options Options to get access collections by |
||
749 | * Supported are 'owner_guid', 'subtype' |
||
750 | * @return \ElggAccessCollection[] |
||
751 | */ |
||
752 | 275 | public function getEntityCollections($options = []) { |
|
753 | |||
754 | 275 | $callback = [$this, 'rowToElggAccessCollection']; |
|
755 | |||
756 | 275 | $supported_options = ['owner_guid', 'subtype']; |
|
757 | |||
758 | 275 | $wheres = []; |
|
759 | 275 | $params = []; |
|
760 | 275 | foreach ($supported_options as $option) { |
|
761 | 275 | $option_value = elgg_extract($option, $options); |
|
762 | 275 | if (!isset($option_value)) { |
|
763 | 268 | continue; |
|
764 | } |
||
765 | 275 | $wheres[] = "{$option} = :{$option}"; |
|
766 | 275 | $params[":{$option}"] = $option_value; |
|
767 | } |
||
768 | |||
769 | 275 | $query = "SELECT * FROM {$this->table}"; |
|
770 | 275 | if (!empty($wheres)) { |
|
771 | 275 | $query .= ' WHERE ' . implode(' AND ', $wheres); |
|
772 | } |
||
773 | 275 | $query .= ' ORDER BY name ASC'; |
|
774 | |||
775 | 275 | return $this->db->getData($query, $callback, $params); |
|
776 | } |
||
777 | |||
778 | /** |
||
779 | * Get members of an access collection |
||
780 | * |
||
781 | * @param int $collection_id The collection's ID |
||
782 | * @param array $options Ege* options |
||
783 | * @return \ElggData[]|int|mixed |
||
784 | */ |
||
785 | public function getMembers($collection_id, array $options = []) { |
||
786 | 4 | $options['wheres'][] = function(QueryBuilder $qb, $table_alias) use ($collection_id) { |
|
787 | 4 | $qb->join($table_alias, 'access_collection_membership', 'acm', $qb->compare('acm.user_guid', '=', "$table_alias.guid")); |
|
788 | 4 | return $qb->compare('acm.access_collection_id', '=', $collection_id, ELGG_VALUE_INTEGER); |
|
789 | }; |
||
790 | |||
791 | 4 | return Entities::find($options); |
|
792 | } |
||
793 | |||
794 | /** |
||
795 | * Return an array of collections that the entity is member of |
||
796 | * |
||
797 | * @param int $member_guid GUID of th member |
||
798 | * |
||
799 | * @return \ElggAccessCollection[]|false |
||
800 | */ |
||
801 | 77 | public function getCollectionsByMember($member_guid) { |
|
802 | |||
803 | 77 | $callback = [$this, 'rowToElggAccessCollection']; |
|
804 | |||
805 | $query = " |
||
806 | 77 | SELECT ac.* FROM {$this->table} ac |
|
807 | 77 | JOIN {$this->membership_table} acm |
|
808 | ON ac.id = acm.access_collection_id |
||
809 | WHERE acm.user_guid = :member_guid |
||
810 | ORDER BY name ASC |
||
811 | "; |
||
812 | |||
813 | 77 | return $this->db->getData($query, $callback, [ |
|
814 | 77 | ':member_guid' => (int) $member_guid, |
|
815 | ]); |
||
816 | } |
||
817 | |||
818 | /** |
||
819 | * Return the name of an ACCESS_* constant or an access collection, |
||
820 | * but only if the logged in user owns the access collection or is an admin. |
||
821 | * Ownership requirement prevents us from exposing names of access collections |
||
822 | * that current user has been added to by other members and may contain |
||
823 | * sensitive classification of the current user (e.g. close friends vs acquaintances). |
||
824 | * |
||
825 | * Returns a string in the language of the user for global access levels, e.g.'Public, 'Friends', 'Logged in', 'Private'; |
||
826 | * or a name of the owned access collection, e.g. 'My work colleagues'; |
||
827 | * or a name of the group or other access collection, e.g. 'Group: Elgg technical support'; |
||
828 | * or 'Limited' if the user access is restricted to read-only, e.g. a friends collection the user was added to |
||
829 | * |
||
830 | * @param int $entity_access_id The entity's access id |
||
831 | * |
||
832 | * @return string |
||
833 | * @since 1.11 |
||
834 | */ |
||
835 | 6 | public function getReadableAccessLevel($entity_access_id) { |
|
836 | 6 | $access = (int) $entity_access_id; |
|
837 | |||
838 | 6 | $translator = $this->translator; |
|
839 | |||
840 | // Check if entity access id is a defined global constant |
||
841 | $access_array = [ |
||
842 | 6 | ACCESS_PRIVATE => $translator->translate('access:label:private'), |
|
843 | 6 | ACCESS_FRIENDS => $translator->translate('access:label:friends'), |
|
844 | 6 | ACCESS_LOGGED_IN => $translator->translate('access:label:logged_in'), |
|
845 | 6 | ACCESS_PUBLIC => $translator->translate('access:label:public'), |
|
846 | ]; |
||
847 | |||
848 | 6 | if (array_key_exists($access, $access_array)) { |
|
849 | 6 | return $access_array[$access]; |
|
850 | } |
||
851 | |||
852 | // Entity access id is probably a custom access collection |
||
853 | // Check if the user has write access to it and can see it's label |
||
854 | // Admins should always be able to see the readable version |
||
855 | $collection = $this->get($access); |
||
856 | |||
857 | if (!$collection instanceof \ElggAccessCollection || !$collection->canEdit()) { |
||
858 | // return 'Limited' if the collection can not be loaded or it can not be edited |
||
859 | return $translator->translate('access:limited:label'); |
||
860 | } |
||
861 | |||
862 | return $collection->getDisplayName(); |
||
863 | } |
||
864 | |||
865 | } |
||
866 |