Remove getACLRoles
[civicrm-core.git] / CRM / ACL / BAO / ACL.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17
18 /**
19 * Access Control List
20 */
21 class CRM_ACL_BAO_ACL extends CRM_ACL_DAO_ACL {
22 /**
23 * @var string
24 */
25 public static $_entityTable = NULL;
26 public static $_objectTable = NULL;
27 public static $_operation = NULL;
28
29 public static $_fieldKeys = NULL;
30
31 /**
32 * Available operations for pseudoconstant.
33 *
34 * @return array
35 */
36 public static function operation() {
37 if (!self::$_operation) {
38 self::$_operation = [
39 'View' => ts('View'),
40 'Edit' => ts('Edit'),
41 'Create' => ts('Create'),
42 'Delete' => ts('Delete'),
43 'Search' => ts('Search'),
44 'All' => ts('All'),
45 ];
46 }
47 return self::$_operation;
48 }
49
50 /**
51 * Construct an associative array of an ACL rule's properties
52 *
53 * @param string $format
54 * Sprintf format for array.
55 * @param bool $hideEmpty
56 * Only return elements that have a value set.
57 *
58 * @return array
59 * Assoc. array of the ACL rule's properties
60 */
61 public function toArray($format = '%s', $hideEmpty = FALSE) {
62 $result = [];
63
64 if (!self::$_fieldKeys) {
65 $fields = CRM_ACL_DAO_ACL::fields();
66 self::$_fieldKeys = array_keys($fields);
67 }
68
69 foreach (self::$_fieldKeys as $field) {
70 $result[$field] = $this->$field;
71 }
72 return $result;
73 }
74
75 /**
76 * Get all ACLs granted to a contact through all group memberships.
77 *
78 * @param int $contact_id
79 * The contact's ID.
80 * @param bool $aclRoles
81 * Include ACL Roles?.
82 *
83 * @return array
84 * Assoc array of ACL rules
85 * @throws \CRM_Core_Exception
86 */
87 protected static function getGroupACLs($contact_id, $aclRoles = FALSE) {
88 $contact_id = CRM_Utils_Type::escape($contact_id, 'Integer');
89
90 $results = [];
91
92 if ($contact_id) {
93 $query = "
94 SELECT acl.*
95 FROM civicrm_acl acl
96 INNER JOIN civicrm_group_contact group_contact
97 ON acl.entity_id = group_contact.group_id
98 WHERE acl.entity_table = 'civicrm_group'
99 AND group_contact.contact_id = $contact_id
100 AND group_contact.status = 'Added'";
101
102 $rule = CRM_Core_DAO::executeQuery($query);
103
104 while ($rule->fetch()) {
105 $results[$rule->id] = $rule->toArray();
106 }
107 }
108
109 if ($aclRoles) {
110 $results += self::getGroupACLRoles($contact_id);
111 }
112
113 return $results;
114 }
115
116 /**
117 * Get all of the ACLs for a contact through ACL groups owned by Contact.
118 * groups.
119 *
120 * @param int $contact_id
121 * ID of a contact to search for.
122 *
123 * @return array
124 * Array of assoc. arrays of ACL rules
125 * @throws \CRM_Core_Exception
126 */
127 protected static function getGroupACLRoles($contact_id) {
128 $contact_id = CRM_Utils_Type::escape($contact_id, 'Integer');
129
130 $query = " SELECT acl.*
131 FROM civicrm_acl acl
132 INNER JOIN civicrm_option_group og
133 ON og.name = 'acl_role'
134 INNER JOIN civicrm_option_value ov
135 ON acl.entity_table = 'civicrm_acl_role'
136 AND ov.option_group_id = og.id
137 AND acl.entity_id = ov.value
138 AND ov.is_active = 1
139 INNER JOIN civicrm_acl_entity_role acl_entity_role
140 ON acl_entity_role.acl_role_id = acl.entity_id
141 AND acl_entity_role.is_active = 1
142 INNER JOIN civicrm_group_contact group_contact
143 ON acl_entity_role.entity_id = group_contact.group_id
144 AND acl_entity_role.entity_table = 'civicrm_group'
145 WHERE acl.entity_table = 'civicrm_acl_role'
146 AND acl.is_active = 1
147 AND group_contact.contact_id = $contact_id
148 AND group_contact.status = 'Added'";
149
150 $results = [];
151
152 $rule = CRM_Core_DAO::executeQuery($query);
153
154 while ($rule->fetch()) {
155 $results[$rule->id] = $rule->toArray();
156 }
157
158 // also get all acls for "Any Role" case
159 // and authenticated User Role if present
160 $roles = "0";
161 $session = CRM_Core_Session::singleton();
162 if ($session->get('ufID') > 0) {
163 $roles .= ",2";
164 }
165
166 $query = "
167 SELECT acl.*
168 FROM civicrm_acl acl
169 WHERE acl.entity_id IN ( $roles )
170 AND acl.entity_table = 'civicrm_acl_role'
171 ";
172
173 $rule = CRM_Core_DAO::executeQuery($query);
174 while ($rule->fetch()) {
175 $results[$rule->id] = $rule->toArray();
176 }
177
178 return $results;
179 }
180
181 /**
182 * Get all ACLs owned by a given contact, including domain and group-level.
183 *
184 * @param int $contact_id
185 * The contact ID.
186 *
187 * @return array
188 * Assoc array of ACL rules
189 *
190 * @throws \CRM_Core_Exception
191 */
192 public static function getAllByContact($contact_id) {
193 $result = [];
194
195 /* First, the contact-specific ACLs, including ACL Roles */
196 if ($contact_id) {
197 $query = " SELECT acl.*
198 FROM civicrm_acl acl
199 WHERE acl.entity_table = 'civicrm_contact'
200 AND acl.entity_id = $contact_id";
201
202 $rule = CRM_Core_DAO::executeQuery($query);
203
204 while ($rule->fetch()) {
205 $result[$rule->id] = $rule->toArray();
206 }
207 }
208
209 /* Then, all ACLs granted through group membership */
210 $result += self::getGroupACLs($contact_id, TRUE);
211
212 return $result;
213 }
214
215 /**
216 * @param array $params
217 *
218 * @return CRM_ACL_DAO_ACL
219 */
220 public static function create($params) {
221 $dao = new CRM_ACL_DAO_ACL();
222 $dao->copyValues($params);
223 $dao->save();
224 return $dao;
225 }
226
227 /**
228 * @param array $params
229 * @param array $defaults
230 */
231 public static function retrieve(&$params, &$defaults) {
232 CRM_Core_DAO::commonRetrieve('CRM_ACL_DAO_ACL', $params, $defaults);
233 }
234
235 /**
236 * Update the is_active flag in the db.
237 *
238 * @param int $id
239 * Id of the database record.
240 * @param bool $is_active
241 * Value we want to set the is_active field.
242 *
243 * @return bool
244 * true if we found and updated the object, else false
245 */
246 public static function setIsActive($id, $is_active) {
247 Civi::cache('fields')->flush();
248 // reset ACL and system caches.
249 CRM_Core_BAO_Cache::resetCaches();
250
251 return CRM_Core_DAO::setFieldValue('CRM_ACL_DAO_ACL', $id, 'is_active', $is_active);
252 }
253
254 /**
255 * @param $str
256 * @param int $contactID
257 *
258 * @return bool
259 */
260 public static function check($str, $contactID) {
261
262 $acls = CRM_ACL_BAO_Cache::build($contactID);
263
264 $aclKeys = array_keys($acls);
265 $aclKeys = implode(',', $aclKeys);
266
267 if (empty($aclKeys)) {
268 return FALSE;
269 }
270
271 $query = "
272 SELECT count( a.id )
273 FROM civicrm_acl_cache c, civicrm_acl a
274 WHERE c.acl_id = a.id
275 AND a.is_active = 1
276 AND a.object_table = %1
277 AND a.id IN ( $aclKeys )
278 ";
279 $params = [1 => [$str, 'String']];
280
281 $count = CRM_Core_DAO::singleValueQuery($query, $params);
282 return (bool) $count;
283 }
284
285 /**
286 * @param $type
287 * @param $tables
288 * @param $whereTables
289 * @param int $contactID
290 *
291 * @return null|string
292 */
293 public static function whereClause($type, &$tables, &$whereTables, $contactID = NULL) {
294 $acls = CRM_ACL_BAO_Cache::build($contactID);
295
296 $whereClause = NULL;
297 $clauses = [];
298
299 if (!empty($acls)) {
300 $aclKeys = array_keys($acls);
301 $aclKeys = implode(',', $aclKeys);
302
303 $query = "
304 SELECT a.operation, a.object_id
305 FROM civicrm_acl_cache c, civicrm_acl a
306 WHERE c.acl_id = a.id
307 AND a.is_active = 1
308 AND a.object_table = 'civicrm_saved_search'
309 AND a.id IN ( $aclKeys )
310 ORDER BY a.object_id
311 ";
312
313 $dao = CRM_Core_DAO::executeQuery($query);
314
315 // do an or of all the where clauses u see
316 $ids = [];
317 while ($dao->fetch()) {
318 // make sure operation matches the type TODO
319 if (self::matchType($type, $dao->operation)) {
320 if (!$dao->object_id) {
321 $ids = [];
322 $whereClause = ' ( 1 ) ';
323 break;
324 }
325 $ids[] = $dao->object_id;
326 }
327 }
328
329 if (!empty($ids)) {
330 $ids = implode(',', $ids);
331 $query = "
332 SELECT g.*
333 FROM civicrm_group g
334 WHERE g.id IN ( $ids )
335 AND g.is_active = 1
336 ";
337 $dao = CRM_Core_DAO::executeQuery($query);
338 $groupIDs = [];
339 $groupContactCacheClause = FALSE;
340 while ($dao->fetch()) {
341 $groupIDs[] = $dao->id;
342
343 if (($dao->saved_search_id || $dao->children || $dao->parents)) {
344 if ($dao->cache_date == NULL) {
345 CRM_Contact_BAO_GroupContactCache::load($dao);
346 }
347 $groupContactCacheClause = " UNION SELECT contact_id FROM civicrm_group_contact_cache WHERE group_id IN (" . implode(', ', $groupIDs) . ")";
348 }
349
350 }
351
352 if ($groupIDs) {
353 $clauses[] = "(
354 `contact_a`.id IN (
355 SELECT contact_id FROM civicrm_group_contact WHERE group_id IN (" . implode(', ', $groupIDs) . ") AND status = 'Added'
356 $groupContactCacheClause
357 )
358 )";
359 }
360 }
361 }
362
363 if (!empty($clauses)) {
364 $whereClause = ' ( ' . implode(' OR ', $clauses) . ' ) ';
365 }
366
367 // call the hook to get additional whereClauses
368 CRM_Utils_Hook::aclWhereClause($type, $tables, $whereTables, $contactID, $whereClause);
369
370 if (empty($whereClause)) {
371 $whereClause = ' ( 0 ) ';
372 }
373
374 return $whereClause;
375 }
376
377 /**
378 * @param int $type
379 * @param int $contactID
380 * @param string $tableName
381 * @param null $allGroups
382 * @param null $includedGroups
383 *
384 * @return array
385 */
386 public static function group(
387 $type,
388 $contactID = NULL,
389 $tableName = 'civicrm_saved_search',
390 $allGroups = NULL,
391 $includedGroups = NULL
392 ) {
393 $userCacheKey = "{$contactID}_{$type}_{$tableName}_" . CRM_Core_Config::domainID() . '_' . md5(implode(',', array_merge((array) $allGroups, (array) $includedGroups)));
394 if (empty(Civi::$statics[__CLASS__]['permissioned_groups'])) {
395 Civi::$statics[__CLASS__]['permissioned_groups'] = [];
396 }
397 if (!empty(Civi::$statics[__CLASS__]['permissioned_groups'][$userCacheKey])) {
398 return Civi::$statics[__CLASS__]['permissioned_groups'][$userCacheKey];
399 }
400
401 if ($allGroups == NULL) {
402 $allGroups = CRM_Contact_BAO_Contact::buildOptions('group_id', 'get');
403 }
404
405 $acls = CRM_ACL_BAO_Cache::build($contactID);
406
407 $ids = [];
408 if (!empty($acls)) {
409 $aclKeys = array_keys($acls);
410 $aclKeys = implode(',', $aclKeys);
411
412 $cacheKey = CRM_Utils_Cache::cleanKey("$type-$tableName-$aclKeys");
413 $cache = CRM_Utils_Cache::singleton();
414 $ids = $cache->get($cacheKey);
415 if (!is_array($ids)) {
416 $ids = [];
417 $query = "
418 SELECT a.operation, a.object_id
419 FROM civicrm_acl_cache c, civicrm_acl a
420 WHERE c.acl_id = a.id
421 AND a.is_active = 1
422 AND a.object_table = %1
423 AND a.id IN ( $aclKeys )
424 GROUP BY a.operation,a.object_id
425 ORDER BY a.object_id
426 ";
427 $params = [1 => [$tableName, 'String']];
428 $dao = CRM_Core_DAO::executeQuery($query, $params);
429 while ($dao->fetch()) {
430 if ($dao->object_id) {
431 if (self::matchType($type, $dao->operation)) {
432 $ids[] = $dao->object_id;
433 }
434 }
435 else {
436 // this user has got the permission for all objects of this type
437 // check if the type matches
438 if (self::matchType($type, $dao->operation)) {
439 foreach ($allGroups as $id => $dontCare) {
440 $ids[] = $id;
441 }
442 }
443 break;
444 }
445 }
446 $cache->set($cacheKey, $ids);
447 }
448 }
449
450 if (empty($ids) && !empty($includedGroups) &&
451 is_array($includedGroups)
452 ) {
453 // This is pretty alarming - we 'sometimes' include all included groups
454 // seems problematic per https://lab.civicrm.org/dev/core/-/issues/1879
455 $ids = $includedGroups;
456 }
457 if ($contactID) {
458 $groupWhere = '';
459 if (!empty($allGroups)) {
460 $groupWhere = " AND id IN (" . implode(',', array_keys($allGroups)) . ")";
461 }
462 // Contacts create hidden groups from search results. They should be able to retrieve their own.
463 $ownHiddenGroupsList = CRM_Core_DAO::singleValueQuery("
464 SELECT GROUP_CONCAT(id) FROM civicrm_group WHERE is_hidden =1 AND created_id = $contactID
465 $groupWhere
466 ");
467 if ($ownHiddenGroupsList) {
468 $ownHiddenGroups = explode(',', $ownHiddenGroupsList);
469 $ids = array_merge((array) $ids, $ownHiddenGroups);
470 }
471
472 }
473
474 CRM_Utils_Hook::aclGroup($type, $contactID, $tableName, $allGroups, $ids);
475 Civi::$statics[__CLASS__]['permissioned_groups'][$userCacheKey] = $ids;
476 return $ids;
477 }
478
479 /**
480 * @param int $type
481 * @param $operation
482 *
483 * @return bool
484 */
485 protected static function matchType($type, $operation) {
486 $typeCheck = FALSE;
487 switch ($operation) {
488 case 'All':
489 $typeCheck = TRUE;
490 break;
491
492 case 'View':
493 if ($type == CRM_ACL_API::VIEW) {
494 $typeCheck = TRUE;
495 }
496 break;
497
498 case 'Edit':
499 if ($type == CRM_ACL_API::VIEW || $type == CRM_ACL_API::EDIT) {
500 $typeCheck = TRUE;
501 }
502 break;
503
504 case 'Create':
505 if ($type == CRM_ACL_API::CREATE) {
506 $typeCheck = TRUE;
507 }
508 break;
509
510 case 'Delete':
511 if ($type == CRM_ACL_API::DELETE) {
512 $typeCheck = TRUE;
513 }
514 break;
515
516 case 'Search':
517 if ($type == CRM_ACL_API::SEARCH) {
518 $typeCheck = TRUE;
519 }
520 break;
521 }
522 return $typeCheck;
523 }
524
525 /**
526 * Delete ACL records.
527 *
528 * @param int $aclId
529 * ID of the ACL record to be deleted.
530 *
531 */
532 public static function del($aclId) {
533 // delete all entries from the acl cache
534 CRM_ACL_BAO_Cache::resetCache();
535
536 $acl = new CRM_ACL_DAO_ACL();
537 $acl->id = $aclId;
538 $acl->delete();
539 }
540
541 }