Merge pull request #16353 from pradpnayak/functionFix
[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 * Get ACL entity table.
33 *
34 * @return array|null
35 */
36 public static function entityTable() {
37 CRM_Core_Error::deprecatedFunctionWarning('unused function to be removed');
38 if (!self::$_entityTable) {
39 self::$_entityTable = [
40 'civicrm_contact' => ts('Contact'),
41 'civicrm_acl_role' => ts('ACL Role'),
42 ];
43 }
44 return self::$_entityTable;
45 }
46
47 /**
48 * @return array|null
49 */
50 public static function objectTable() {
51 CRM_Core_Error::deprecatedFunctionWarning('unused function to be removed');
52 if (!self::$_objectTable) {
53 self::$_objectTable = [
54 'civicrm_contact' => ts('Contact'),
55 'civicrm_group' => ts('Group'),
56 'civicrm_saved_search' => ts('Contact Group'),
57 'civicrm_admin' => ts('Import'),
58 ];
59 }
60 return self::$_objectTable;
61 }
62
63 /**
64 * Available operations for pseudoconstant.
65 *
66 * @return array
67 */
68 public static function operation() {
69 if (!self::$_operation) {
70 self::$_operation = [
71 'View' => ts('View'),
72 'Edit' => ts('Edit'),
73 'Create' => ts('Create'),
74 'Delete' => ts('Delete'),
75 'Search' => ts('Search'),
76 'All' => ts('All'),
77 ];
78 }
79 return self::$_operation;
80 }
81
82 /**
83 * Given a table and id pair, return the filter clause
84 *
85 * @param string $table
86 * The table owning the object.
87 * @param int $id
88 * The ID of the object.
89 * @param array $tables
90 * Tables that will be needed in the FROM.
91 *
92 * @return string|null
93 * WHERE-style clause to filter results,
94 * or null if $table or $id is null
95 *
96 * @throws \CRM_Core_Exception
97 */
98 public static function getClause($table, $id, &$tables) {
99 CRM_Core_Error::deprecatedFunctionWarning('unused function to be removed');
100 $table = CRM_Utils_Type::escape($table, 'String');
101 $id = CRM_Utils_Type::escape($id, 'Integer');
102 $whereTables = [];
103
104 $ssTable = CRM_Contact_BAO_SavedSearch::getTableName();
105
106 if (empty($table)) {
107 return NULL;
108 }
109 elseif ($table == $ssTable) {
110 return CRM_Contact_BAO_SavedSearch::whereClause($id, $tables, $whereTables);
111 }
112 elseif (!empty($id)) {
113 $tables[$table] = TRUE;
114 return "$table.id = $id";
115 }
116 return NULL;
117 }
118
119 /**
120 * Construct an associative array of an ACL rule's properties
121 *
122 * @param string $format
123 * Sprintf format for array.
124 * @param bool $hideEmpty
125 * Only return elements that have a value set.
126 *
127 * @return array
128 * Assoc. array of the ACL rule's properties
129 */
130 public function toArray($format = '%s', $hideEmpty = FALSE) {
131 $result = [];
132
133 if (!self::$_fieldKeys) {
134 $fields = CRM_ACL_DAO_ACL::fields();
135 self::$_fieldKeys = array_keys($fields);
136 }
137
138 foreach (self::$_fieldKeys as $field) {
139 $result[$field] = $this->$field;
140 }
141 return $result;
142 }
143
144 /**
145 * Retrieve ACLs for a contact or group. Note that including a contact id
146 * without a group id will return those ACL rules which are granted
147 * directly to the contact, but not those granted to the contact through
148 * any/all of his group memberships.
149 *
150 * @param int $contact_id
151 * ID of a contact to search for.
152 *
153 * @return array
154 * Array of assoc. arrays of ACL rules
155 *
156 * @throws \CRM_Core_Exception
157 */
158 protected static function getACLs($contact_id = NULL) {
159 $results = [];
160
161 if (empty($contact_id)) {
162 return $results;
163 }
164
165 $contact_id = CRM_Utils_Type::escape($contact_id, 'Integer');
166
167 $rule = new CRM_ACL_BAO_ACL();
168
169 $acl = self::getTableName();
170 $contact = CRM_Contact_BAO_Contact::getTableName();
171 $c2g = CRM_Contact_BAO_GroupContact::getTableName();
172 $group = CRM_Contact_BAO_Group::getTableName();
173
174 $query = " SELECT acl.*
175 FROM $acl acl";
176
177 if (!empty($contact_id)) {
178 $query .= " WHERE acl.entity_table = '$contact'
179 AND acl.entity_id = $contact_id";
180 }
181
182 $rule->query($query);
183
184 while ($rule->fetch()) {
185 $results[$rule->id] = $rule->toArray();
186 }
187
188 $results += self::getACLRoles($contact_id);
189
190 return $results;
191 }
192
193 /**
194 * Get all of the ACLs through ACL groups.
195 *
196 * @param int $contact_id
197 * ID of a contact to search for.
198 *
199 * @return array
200 * Array of assoc. arrays of ACL rules
201 *
202 * @throws \CRM_Core_Exception
203 */
204 protected static function getACLRoles($contact_id = NULL) {
205 $contact_id = CRM_Utils_Type::escape($contact_id, 'Integer');
206
207 $rule = new CRM_ACL_BAO_ACL();
208
209 $contact = CRM_Contact_BAO_Contact::getTableName();
210
211 $query = 'SELECT acl.* FROM civicrm_acl acl';
212 $where = ['acl.entity_table = "civicrm_acl_role" AND acl.entity_id IN (' . implode(',', array_keys(CRM_Core_OptionGroup::values('acl_role'))) . ')'];
213
214 if (!empty($contact_id)) {
215 $where[] = " acl.entity_table = '$contact' AND acl.is_active = 1 AND acl.entity_id = $contact_id";
216 }
217
218 $results = [];
219
220 $rule->query($query . ' WHERE ' . implode(' AND ', $where));
221
222 while ($rule->fetch()) {
223 $results[$rule->id] = $rule->toArray();
224 }
225
226 return $results;
227 }
228
229 /**
230 * Get all ACLs granted to a contact through all group memberships.
231 *
232 * @param int $contact_id
233 * The contact's ID.
234 * @param bool $aclRoles
235 * Include ACL Roles?.
236 *
237 * @return array
238 * Assoc array of ACL rules
239 * @throws \CRM_Core_Exception
240 */
241 protected static function getGroupACLs($contact_id, $aclRoles = FALSE) {
242 $contact_id = CRM_Utils_Type::escape($contact_id, 'Integer');
243
244 $rule = new CRM_ACL_BAO_ACL();
245
246 $acl = self::getTableName();
247 $c2g = CRM_Contact_BAO_GroupContact::getTableName();
248 $group = CRM_Contact_BAO_Group::getTableName();
249 $results = [];
250
251 if ($contact_id) {
252 $query = "
253 SELECT acl.*
254 FROM $acl acl
255 INNER JOIN $c2g group_contact
256 ON acl.entity_id = group_contact.group_id
257 WHERE acl.entity_table = '$group'
258 AND group_contact.contact_id = $contact_id
259 AND group_contact.status = 'Added'";
260
261 $rule->query($query);
262
263 while ($rule->fetch()) {
264 $results[$rule->id] = $rule->toArray();
265 }
266 }
267
268 if ($aclRoles) {
269 $results += self::getGroupACLRoles($contact_id);
270 }
271
272 return $results;
273 }
274
275 /**
276 * Get all of the ACLs for a contact through ACL groups owned by Contact.
277 * groups.
278 *
279 * @param int $contact_id
280 * ID of a contact to search for.
281 *
282 * @return array
283 * Array of assoc. arrays of ACL rules
284 * @throws \CRM_Core_Exception
285 */
286 protected static function getGroupACLRoles($contact_id) {
287 $contact_id = CRM_Utils_Type::escape($contact_id, 'Integer');
288
289 $rule = new CRM_ACL_BAO_ACL();
290
291 $acl = self::getTableName();
292 $aclRole = 'civicrm_acl_role';
293
294 $aclER = CRM_ACL_DAO_EntityRole::getTableName();
295 $c2g = CRM_Contact_BAO_GroupContact::getTableName();
296 $group = CRM_Contact_BAO_Group::getTableName();
297
298 $query = " SELECT acl.*
299 FROM $acl acl
300 INNER JOIN civicrm_option_group og
301 ON og.name = 'acl_role'
302 INNER JOIN civicrm_option_value ov
303 ON acl.entity_table = '$aclRole'
304 AND ov.option_group_id = og.id
305 AND acl.entity_id = ov.value
306 AND ov.is_active = 1
307 INNER JOIN $aclER
308 ON $aclER.acl_role_id = acl.entity_id
309 AND $aclER.is_active = 1
310 INNER JOIN $c2g
311 ON $aclER.entity_id = $c2g.group_id
312 AND $aclER.entity_table = 'civicrm_group'
313 WHERE acl.entity_table = '$aclRole'
314 AND acl.is_active = 1
315 AND $c2g.contact_id = $contact_id
316 AND $c2g.status = 'Added'";
317
318 $results = [];
319
320 $rule->query($query);
321
322 while ($rule->fetch()) {
323 $results[$rule->id] = $rule->toArray();
324 }
325
326 // also get all acls for "Any Role" case
327 // and authenticated User Role if present
328 $roles = "0";
329 $session = CRM_Core_Session::singleton();
330 if ($session->get('ufID') > 0) {
331 $roles .= ",2";
332 }
333
334 $query = "
335 SELECT acl.*
336 FROM $acl acl
337 WHERE acl.entity_id IN ( $roles )
338 AND acl.entity_table = 'civicrm_acl_role'
339 ";
340
341 $rule->query($query);
342 while ($rule->fetch()) {
343 $results[$rule->id] = $rule->toArray();
344 }
345
346 return $results;
347 }
348
349 /**
350 * Get all ACLs owned by a given contact, including domain and group-level.
351 *
352 * @param int $contact_id
353 * The contact ID.
354 *
355 * @return array
356 * Assoc array of ACL rules
357 *
358 * @throws \CRM_Core_Exception
359 */
360 public static function getAllByContact($contact_id) {
361 $result = [];
362
363 /* First, the contact-specific ACLs, including ACL Roles */
364 $result += self::getACLs($contact_id);
365
366 /* Then, all ACLs granted through group membership */
367 $result += self::getGroupACLs($contact_id, TRUE);
368
369 return $result;
370 }
371
372 /**
373 * @param array $params
374 *
375 * @return CRM_ACL_DAO_ACL
376 */
377 public static function create($params) {
378 $dao = new CRM_ACL_DAO_ACL();
379 $dao->copyValues($params);
380 $dao->save();
381 return $dao;
382 }
383
384 /**
385 * @param array $params
386 * @param array $defaults
387 */
388 public static function retrieve(&$params, &$defaults) {
389 CRM_Core_DAO::commonRetrieve('CRM_ACL_DAO_ACL', $params, $defaults);
390 }
391
392 /**
393 * Update the is_active flag in the db.
394 *
395 * @param int $id
396 * Id of the database record.
397 * @param bool $is_active
398 * Value we want to set the is_active field.
399 *
400 * @return bool
401 * true if we found and updated the object, else false
402 */
403 public static function setIsActive($id, $is_active) {
404 Civi::cache('fields')->flush();
405 // reset ACL and system caches.
406 CRM_Core_BAO_Cache::resetCaches();
407
408 return CRM_Core_DAO::setFieldValue('CRM_ACL_DAO_ACL', $id, 'is_active', $is_active);
409 }
410
411 /**
412 * @param $str
413 * @param int $contactID
414 *
415 * @return bool
416 */
417 public static function check($str, $contactID) {
418
419 $acls = CRM_ACL_BAO_Cache::build($contactID);
420
421 $aclKeys = array_keys($acls);
422 $aclKeys = implode(',', $aclKeys);
423
424 if (empty($aclKeys)) {
425 return FALSE;
426 }
427
428 $query = "
429 SELECT count( a.id )
430 FROM civicrm_acl_cache c, civicrm_acl a
431 WHERE c.acl_id = a.id
432 AND a.is_active = 1
433 AND a.object_table = %1
434 AND a.id IN ( $aclKeys )
435 ";
436 $params = [1 => [$str, 'String']];
437
438 $count = CRM_Core_DAO::singleValueQuery($query, $params);
439 return ($count) ? TRUE : FALSE;
440 }
441
442 /**
443 * @param $type
444 * @param $tables
445 * @param $whereTables
446 * @param int $contactID
447 *
448 * @return null|string
449 */
450 public static function whereClause($type, &$tables, &$whereTables, $contactID = NULL) {
451 $acls = CRM_ACL_BAO_Cache::build($contactID);
452
453 $whereClause = NULL;
454 $clauses = [];
455
456 if (!empty($acls)) {
457 $aclKeys = array_keys($acls);
458 $aclKeys = implode(',', $aclKeys);
459
460 $query = "
461 SELECT a.operation, a.object_id
462 FROM civicrm_acl_cache c, civicrm_acl a
463 WHERE c.acl_id = a.id
464 AND a.is_active = 1
465 AND a.object_table = 'civicrm_saved_search'
466 AND a.id IN ( $aclKeys )
467 ORDER BY a.object_id
468 ";
469
470 $dao = CRM_Core_DAO::executeQuery($query);
471
472 // do an or of all the where clauses u see
473 $ids = [];
474 while ($dao->fetch()) {
475 // make sure operation matches the type TODO
476 if (self::matchType($type, $dao->operation)) {
477 if (!$dao->object_id) {
478 $ids = [];
479 $whereClause = ' ( 1 ) ';
480 break;
481 }
482 $ids[] = $dao->object_id;
483 }
484 }
485
486 if (!empty($ids)) {
487 $ids = implode(',', $ids);
488 $query = "
489 SELECT g.*
490 FROM civicrm_group g
491 WHERE g.id IN ( $ids )
492 AND g.is_active = 1
493 ";
494 $dao = CRM_Core_DAO::executeQuery($query);
495 $groupIDs = [];
496 $groupContactCacheClause = FALSE;
497 while ($dao->fetch()) {
498 $groupIDs[] = $dao->id;
499
500 if (($dao->saved_search_id || $dao->children || $dao->parents)) {
501 if ($dao->cache_date == NULL) {
502 CRM_Contact_BAO_GroupContactCache::load($dao);
503 }
504 $groupContactCacheClause = " UNION SELECT contact_id FROM civicrm_group_contact_cache WHERE group_id IN (" . implode(', ', $groupIDs) . ")";
505 }
506
507 }
508
509 if ($groupIDs) {
510 $clauses[] = "(
511 `contact_a`.id IN (
512 SELECT contact_id FROM civicrm_group_contact WHERE group_id IN (" . implode(', ', $groupIDs) . ") AND status = 'Added'
513 $groupContactCacheClause
514 )
515 )";
516 }
517 }
518 }
519
520 if (!empty($clauses)) {
521 $whereClause = ' ( ' . implode(' OR ', $clauses) . ' ) ';
522 }
523
524 // call the hook to get additional whereClauses
525 CRM_Utils_Hook::aclWhereClause($type, $tables, $whereTables, $contactID, $whereClause);
526
527 if (empty($whereClause)) {
528 $whereClause = ' ( 0 ) ';
529 }
530
531 return $whereClause;
532 }
533
534 /**
535 * @param int $type
536 * @param int $contactID
537 * @param string $tableName
538 * @param null $allGroups
539 * @param null $includedGroups
540 *
541 * @return array
542 */
543 public static function group(
544 $type,
545 $contactID = NULL,
546 $tableName = 'civicrm_saved_search',
547 $allGroups = NULL,
548 $includedGroups = NULL
549 ) {
550 $userCacheKey = "{$contactID}_{$type}_{$tableName}_" . CRM_Core_Config::domainID() . '_' . md5(implode(',', array_merge((array) $allGroups, (array) $includedGroups)));
551 if (empty(Civi::$statics[__CLASS__]['permissioned_groups'])) {
552 Civi::$statics[__CLASS__]['permissioned_groups'] = [];
553 }
554 if (!empty(Civi::$statics[__CLASS__]['permissioned_groups'][$userCacheKey])) {
555 return Civi::$statics[__CLASS__]['permissioned_groups'][$userCacheKey];
556 }
557
558 if ($allGroups == NULL) {
559 $allGroups = CRM_Contact_BAO_Contact::buildOptions('group_id', NULL, ['onlyActive' => FALSE]);
560 }
561
562 $acls = CRM_ACL_BAO_Cache::build($contactID);
563
564 $ids = [];
565 if (!empty($acls)) {
566 $aclKeys = array_keys($acls);
567 $aclKeys = implode(',', $aclKeys);
568
569 $cacheKey = CRM_Utils_Cache::cleanKey("$tableName-$aclKeys");
570 $cache = CRM_Utils_Cache::singleton();
571 $ids = $cache->get($cacheKey);
572 if (!$ids) {
573 $ids = [];
574 $query = "
575 SELECT a.operation, a.object_id
576 FROM civicrm_acl_cache c, civicrm_acl a
577 WHERE c.acl_id = a.id
578 AND a.is_active = 1
579 AND a.object_table = %1
580 AND a.id IN ( $aclKeys )
581 GROUP BY a.operation,a.object_id
582 ORDER BY a.object_id
583 ";
584 $params = [1 => [$tableName, 'String']];
585 $dao = CRM_Core_DAO::executeQuery($query, $params);
586 while ($dao->fetch()) {
587 if ($dao->object_id) {
588 if (self::matchType($type, $dao->operation)) {
589 $ids[] = $dao->object_id;
590 }
591 }
592 else {
593 // this user has got the permission for all objects of this type
594 // check if the type matches
595 if (self::matchType($type, $dao->operation)) {
596 foreach ($allGroups as $id => $dontCare) {
597 $ids[] = $id;
598 }
599 }
600 break;
601 }
602 }
603 $cache->set($cacheKey, $ids);
604 }
605 }
606
607 if (empty($ids) && !empty($includedGroups) &&
608 is_array($includedGroups)
609 ) {
610 $ids = $includedGroups;
611 }
612 if ($contactID) {
613 $groupWhere = '';
614 if (!empty($allGroups)) {
615 $groupWhere = " AND id IN (" . implode(',', array_keys($allGroups)) . ")";
616 }
617 // Contacts create hidden groups from search results. They should be able to retrieve their own.
618 $ownHiddenGroupsList = CRM_Core_DAO::singleValueQuery("
619 SELECT GROUP_CONCAT(id) FROM civicrm_group WHERE is_hidden =1 AND created_id = $contactID
620 $groupWhere
621 ");
622 if ($ownHiddenGroupsList) {
623 $ownHiddenGroups = explode(',', $ownHiddenGroupsList);
624 $ids = array_merge((array) $ids, $ownHiddenGroups);
625 }
626
627 }
628
629 CRM_Utils_Hook::aclGroup($type, $contactID, $tableName, $allGroups, $ids);
630 Civi::$statics[__CLASS__]['permissioned_groups'][$userCacheKey] = $ids;
631 return $ids;
632 }
633
634 /**
635 * @param int $type
636 * @param $operation
637 *
638 * @return bool
639 */
640 protected static function matchType($type, $operation) {
641 $typeCheck = FALSE;
642 switch ($operation) {
643 case 'All':
644 $typeCheck = TRUE;
645 break;
646
647 case 'View':
648 if ($type == CRM_ACL_API::VIEW) {
649 $typeCheck = TRUE;
650 }
651 break;
652
653 case 'Edit':
654 if ($type == CRM_ACL_API::VIEW || $type == CRM_ACL_API::EDIT) {
655 $typeCheck = TRUE;
656 }
657 break;
658
659 case 'Create':
660 if ($type == CRM_ACL_API::CREATE) {
661 $typeCheck = TRUE;
662 }
663 break;
664
665 case 'Delete':
666 if ($type == CRM_ACL_API::DELETE) {
667 $typeCheck = TRUE;
668 }
669 break;
670
671 case 'Search':
672 if ($type == CRM_ACL_API::SEARCH) {
673 $typeCheck = TRUE;
674 }
675 break;
676 }
677 return $typeCheck;
678 }
679
680 /**
681 * Delete ACL records.
682 *
683 * @param int $aclId
684 * ID of the ACL record to be deleted.
685 *
686 */
687 public static function del($aclId) {
688 // delete all entries from the acl cache
689 CRM_ACL_BAO_Cache::resetCache();
690
691 $acl = new CRM_ACL_DAO_ACL();
692 $acl->id = $aclId;
693 $acl->delete();
694 }
695
696 }