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