Merge pull request #15833 from yashodha/participant_edit
[civicrm-core.git] / CRM / Contact / BAO / Contact / Permission.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
fee14197 4 | CiviCRM version 5 |
6a488035 5 +--------------------------------------------------------------------+
f299f7db 6 | Copyright CiviCRM LLC (c) 2004-2020 |
6a488035
TO
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
d25dd0ee 26 */
6a488035
TO
27
28/**
29 *
30 * @package CRM
f299f7db 31 * @copyright CiviCRM LLC (c) 2004-2020
6a488035
TO
32 */
33class CRM_Contact_BAO_Contact_Permission {
34
0978cb9c
SL
35 /**
36 * @var bool
37 */
38 public static $useTempTable = TRUE;
39
40 /**
41 * Set whether to use a temporary table or not when building ACL Cache
42 * @param bool $useTemporaryTable
43 */
44 public static function setUseTemporaryTable($useTemporaryTable = TRUE) {
45 self::$useTempTable = $useTemporaryTable;
46 }
47
48 /**
49 * Get variable for determining if we should use Temporary Table or not
50 * @return bool
51 */
52 public static function getUseTemporaryTable() {
53 return self::$useTempTable;
54 }
55
dddf4bf6 56 /**
e4541c56 57 * Check which of the given contact IDs the logged in user
340be2e7 58 * has permissions for the operation type according to:
59 * - general permissions (e.g. 'edit all contacts')
60 * - deletion status (unless you have 'access deleted contacts')
61 * - ACL
62 * - permissions inherited through relationships (also second degree if enabled)
dddf4bf6 63 *
64 * @param array $contact_ids
65 * Contact IDs.
340be2e7 66 * @param int $type the type of operation (view|edit)
dddf4bf6 67 *
68 * @see CRM_Contact_BAO_Contact_Permission::allow
69 *
70 * @return array
69078420 71 * list of contact IDs the logged in user has the given permission for
dddf4bf6 72 */
73 public static function allowList($contact_ids, $type = CRM_Core_Permission::VIEW) {
be2fb01f 74 $result_set = [];
dddf4bf6 75 if (empty($contact_ids)) {
76 // empty contact lists would cause trouble in the SQL. And be pointless.
77 return $result_set;
78 }
79
80 // make sure the the general permissions are given
e4541c56 81 if (CRM_Core_Permission::check('edit all contacts')
19f13a7c 82 || $type == CRM_Core_Permission::VIEW && CRM_Core_Permission::check('view all contacts')
dddf4bf6 83 ) {
67df1408 84
dddf4bf6 85 // if the general permission is there, all good
67df1408 86 if (CRM_Core_Permission::check('access deleted contacts')) {
e8a0f9e0 87 // if user can access deleted contacts -> fine
67df1408 88 return $contact_ids;
e4541c56 89 }
90 else {
67df1408 91 // if the user CANNOT access deleted contacts, these need to be filtered
340be2e7 92 $contact_id_list = implode(',', $contact_ids);
67df1408 93 $filter_query = "SELECT DISTINCT(id) FROM civicrm_contact WHERE id IN ($contact_id_list) AND is_deleted = 0";
94 $query = CRM_Core_DAO::executeQuery($filter_query);
95 while ($query->fetch()) {
96 $result_set[(int) $query->id] = TRUE;
97 }
98 return array_keys($result_set);
99 }
dddf4bf6 100 }
101
102 // get logged in user
340be2e7 103 $contactID = CRM_Core_Session::getLoggedInContactID();
dddf4bf6 104 if (empty($contactID)) {
be2fb01f 105 return [];
dddf4bf6 106 }
107
108 // make sure the cache is filled
109 self::cache($contactID, $type);
110
111 // compile query
dddf4bf6 112 $operation = ($type == CRM_Core_Permission::VIEW) ? 'View' : 'Edit';
113
114 // add clause for deleted contacts, if the user doesn't have the permission to access them
163bfad3 115 $LEFT_JOIN_DELETED = $AND_CAN_ACCESS_DELETED = '';
dddf4bf6 116 if (!CRM_Core_Permission::check('access deleted contacts')) {
67df1408 117 $LEFT_JOIN_DELETED = "LEFT JOIN civicrm_contact ON civicrm_contact.id = contact_id";
e4541c56 118 $AND_CAN_ACCESS_DELETED = "AND civicrm_contact.is_deleted = 0";
dddf4bf6 119 }
120
121 // RUN the query
340be2e7 122 $contact_id_list = implode(',', $contact_ids);
dddf4bf6 123 $query = "
124SELECT contact_id
125 FROM civicrm_acl_contact_cache
126 {$LEFT_JOIN_DELETED}
9aea8e14 127WHERE contact_id IN ({$contact_id_list})
dddf4bf6 128 AND user_id = {$contactID}
129 AND operation = '{$operation}'
130 {$AND_CAN_ACCESS_DELETED}";
131 $result = CRM_Core_DAO::executeQuery($query);
132 while ($result->fetch()) {
67df1408 133 $result_set[(int) $result->contact_id] = TRUE;
dddf4bf6 134 }
135
e4541c56 136 // if some have been rejected, double check for permissions inherited by relationship
dddf4bf6 137 if (count($result_set) < count($contact_ids)) {
69078420 138 $rejected_contacts = array_diff_key($contact_ids, $result_set);
98445ac5 139 // @todo consider storing these to the acl cache for next time, since we have fetched.
f871c3a9 140 $allowed_by_relationship = self::relationshipList($rejected_contacts, $type);
67df1408 141 foreach ($allowed_by_relationship as $contact_id) {
142 $result_set[(int) $contact_id] = TRUE;
143 }
dddf4bf6 144 }
145
67df1408 146 return array_keys($result_set);
dddf4bf6 147 }
148
6a488035 149 /**
fe482240 150 * Check if the logged in user has permissions for the operation type.
6a488035 151 *
77c5b619
TO
152 * @param int $id
153 * Contact id.
77b97be7 154 * @param int|string $type the type of operation (view|edit)
6a488035 155 *
acb1052e 156 * @return bool
a6c01b45 157 * true if the user has permission, false otherwise
6a488035 158 */
00be9182 159 public static function allow($id, $type = CRM_Core_Permission::VIEW) {
9a41e168 160 // get logged in user
340be2e7 161 $contactID = CRM_Core_Session::getLoggedInContactID();
9a41e168 162
2b8d25f0 163 // first: check if contact is trying to view own contact
135367a6 164 if ($contactID == $id && ($type == CRM_Core_Permission::VIEW && CRM_Core_Permission::check('view my contact')
165 || $type == CRM_Core_Permission::EDIT && CRM_Core_Permission::check('edit my contact'))
2b8d25f0 166 ) {
167 return TRUE;
168 }
6a488035 169
e97c66ff 170 // FIXME: push this somewhere below, to not give this permission so many rights
6a488035
TO
171 $isDeleted = (bool) CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $id, 'is_deleted');
172 if (CRM_Core_Permission::check('access deleted contacts') && $isDeleted) {
173 return TRUE;
174 }
175
176 // short circuit for admin rights here so we avoid unneeeded queries
177 // some duplication of code, but we skip 3-5 queries
178 if (CRM_Core_Permission::check('edit all contacts') ||
179 ($type == CRM_ACL_API::VIEW && CRM_Core_Permission::check('view all contacts'))
180 ) {
181 return TRUE;
182 }
183
9a41e168 184 // check permission based on relationship, CRM-2963
be2fb01f 185 if (self::relationshipList([$id], $type)) {
6a488035
TO
186 return TRUE;
187 }
188
8210399a 189 // We should probably do a cheap check whether it's in the cache first.
9a41e168 190 // check permission based on ACL
be2fb01f
CW
191 $tables = [];
192 $whereTables = [];
6a488035 193
8d0a0fd0 194 $permission = CRM_ACL_API::whereClause($type, $tables, $whereTables, NULL, FALSE, FALSE, TRUE);
6a488035
TO
195 $from = CRM_Contact_BAO_Query::fromClause($whereTables);
196
197 $query = "
680a52de 198SELECT contact_a.id
6a488035 199 $from
680a52de 200WHERE contact_a.id = %1 AND $permission
201 LIMIT 1
202";
6a488035 203
be2fb01f 204 if (CRM_Core_DAO::singleValueQuery($query, [1 => [$id, 'Integer']])) {
680a52de 205 return TRUE;
206 }
5f652ac7 207 return FALSE;
6a488035
TO
208 }
209
210 /**
5b27235a 211 * Fill the acl contact cache for this ACLed contact id if empty.
6a488035 212 *
5b27235a 213 * @param int $userID - contact_id of the ACLed user
dd244018 214 * @param int|string $type the type of operation (view|edit)
5b27235a
SL
215 * @param bool $force - Should we force a recompute.
216 *
6a488035 217 */
00be9182 218 public static function cache($userID, $type = CRM_Core_Permission::VIEW, $force = FALSE) {
340be2e7 219 // FIXME: maybe find a better way of keeping track of this. @eileen pointed out
220 // that somebody might flush the cache away from under our feet,
5af77f03 221 // but the alternative would be a SQL call every time this is called,
340be2e7 222 // and a complete rebuild if the result was an empty set...
04adc5f0 223 if (!isset(Civi::$statics[__CLASS__]['processed'])) {
224 Civi::$statics[__CLASS__]['processed'] = [
225 CRM_Core_Permission::VIEW => [],
226 CRM_Core_Permission::EDIT => [],
227 ];
228 }
6a488035 229
c0e87307 230 if ($type == CRM_Core_Permission::VIEW) {
6a488035
TO
231 $operationClause = " operation IN ( 'Edit', 'View' ) ";
232 $operation = 'View';
233 }
234 else {
235 $operationClause = " operation = 'Edit' ";
236 $operation = 'Edit';
237 }
be2fb01f 238 $queryParams = [1 => [$userID, 'Integer']];
6a488035
TO
239
240 if (!$force) {
c0e87307 241 // skip if already calculated
04adc5f0 242 if (!empty(Civi::$statics[__CLASS__]['processed'][$type][$userID])) {
6a488035
TO
243 return;
244 }
245
246 // run a query to see if the cache is filled
247 $sql = "
21ca2cb6 248SELECT count(*)
6a488035
TO
249FROM civicrm_acl_contact_cache
250WHERE user_id = %1
251AND $operationClause
252";
9aea8e14 253 $count = CRM_Core_DAO::singleValueQuery($sql, $queryParams);
6a488035 254 if ($count > 0) {
04adc5f0 255 Civi::$statics[__CLASS__]['processed'][$type][$userID] = 1;
6a488035
TO
256 return;
257 }
258 }
259
e5faffc4
SL
260 // grab a lock so other processes don't compete and do the same query
261 $lock = Civi::lockManager()->acquire("data.core.aclcontact.{$userID}");
262 if (!$lock->isAcquired()) {
263 // this can cause inconsistent results since we don't know if the other process
264 // will fill up the cache before our calling routine needs it.
265 // The default 3 second timeout should be enough for the other process to finish.
266 // However this routine does not return the status either, so basically
267 // its a "lets return and hope for the best"
268 return;
269 }
270
be2fb01f
CW
271 $tables = [];
272 $whereTables = [];
6a488035 273
9aea8e14 274 $permission = CRM_ACL_API::whereClause($type, $tables, $whereTables, $userID, FALSE, FALSE, TRUE);
6a488035
TO
275
276 $from = CRM_Contact_BAO_Query::fromClause($whereTables);
e5faffc4
SL
277 /* Ends up something like this:
278 CREATE TEMPORARY TABLE civicrm_temp_acl_contact_cache1310 (SELECT DISTINCT 2960 as user_id, contact_a.id as contact_id, 'View' as operation
279 FROM civicrm_contact contact_a LEFT JOIN civicrm_group_contact_cache `civicrm_group_contact_cache-ACL` ON contact_a.id = `civicrm_group_contact_cache-ACL`.contact_id
280 LEFT JOIN civicrm_acl_contact_cache ac ON ac.user_id = 2960 AND ac.contact_id = contact_a.id AND ac.operation = 'View'
281 WHERE ( `civicrm_group_contact_cache-ACL`.group_id IN (14, 25, 46, 47, 48, 49, 50, 51) ) AND (contact_a.is_deleted = 0)
282 AND ac.user_id IS NULL*/
283 /*$sql = "SELECT DISTINCT $userID as user_id, contact_a.id as contact_id, '{$operation}' as operation
284 $from
285 LEFT JOIN civicrm_acl_contact_cache ac ON ac.user_id = $userID AND ac.contact_id = contact_a.id AND ac.operation = '{$operation}'
286 WHERE $permission
287 AND ac.user_id IS NULL
288 ";*/
289 $sql = "SELECT DISTINCT $userID as user_id, contact_a.id as contact_id, '{$operation}' as operation
6a488035 290 $from
e5faffc4 291WHERE $permission";
0978cb9c
SL
292 $useTempTable = self::getUseTemporaryTable();
293 if ($useTempTable) {
294 $aclContactsTempTable = CRM_Utils_SQL_TempTable::build()->setCategory('aclccache')->setMemory();
295 $tempTable = $aclContactsTempTable->getName();
296 $aclContactsTempTable->createWithColumns('user_id int, contact_id int, operation varchar(255), UNIQUE UI_user_contact_operation (user_id,contact_id,operation)');
297 CRM_Core_DAO::executeQuery("INSERT INTO {$tempTable} (user_id, contact_id, operation) {$sql}");
298 CRM_Core_DAO::executeQuery("INSERT IGNORE INTO civicrm_acl_contact_cache (user_id, contact_id, operation) SELECT user_id, contact_id, operation FROM {$tempTable}");
299 $aclContactsTempTable->drop();
300 }
301 else {
302 CRM_Core_DAO::executeQuery("INSERT IGNORE INTO civicrm_acl_contact_cache (user_id, contact_id, operation) {$sql}");
303 }
9aea8e14 304
305 // Add in a row for the logged in contact. Do not try to combine with the above query or an ugly OR will appear in
306 // the permission clause.
307 if (CRM_Core_Permission::check('edit my contact') ||
308 ($type == CRM_Core_Permission::VIEW && CRM_Core_Permission::check('view my contact'))) {
d0f057f4
EM
309 if (!CRM_Core_DAO::singleValueQuery("
310 SELECT count(*) FROM civicrm_acl_contact_cache WHERE user_id = %1 AND contact_id = %1 AND operation = '{$operation}' LIMIT 1", $queryParams)) {
e5faffc4 311 CRM_Core_DAO::executeQuery("INSERT IGNORE INTO civicrm_acl_contact_cache ( user_id, contact_id, operation ) VALUES(%1, %1, '{$operation}')", $queryParams);
9aea8e14 312 }
313 }
04adc5f0 314 Civi::$statics[__CLASS__]['processed'][$type][$userID] = 1;
e5faffc4 315 $lock->release();
6a488035
TO
316 }
317
86538308
EM
318 /**
319 * @param string $contactAlias
86538308
EM
320 *
321 * @return array
322 */
188fd46b 323 public static function cacheClause($contactAlias = 'contact_a') {
6a488035
TO
324 if (CRM_Core_Permission::check('view all contacts') ||
325 CRM_Core_Permission::check('edit all contacts')
326 ) {
327 if (is_array($contactAlias)) {
be2fb01f 328 $wheres = [];
6a488035
TO
329 foreach ($contactAlias as $alias) {
330 // CRM-6181
331 $wheres[] = "$alias.is_deleted = 0";
332 }
be2fb01f 333 return [NULL, '(' . implode(' AND ', $wheres) . ')'];
6a488035
TO
334 }
335 else {
336 // CRM-6181
be2fb01f 337 return [NULL, "$contactAlias.is_deleted = 0"];
6a488035
TO
338 }
339 }
340
188fd46b 341 $contactID = (int) CRM_Core_Session::getLoggedInContactID();
6a488035
TO
342 self::cache($contactID);
343
344 if (is_array($contactAlias) && !empty($contactAlias)) {
345 //More than one contact alias
be2fb01f 346 $clauses = [];
6a488035
TO
347 foreach ($contactAlias as $k => $alias) {
348 $clauses[] = " INNER JOIN civicrm_acl_contact_cache aclContactCache_{$k} ON {$alias}.id = aclContactCache_{$k}.contact_id AND aclContactCache_{$k}.user_id = $contactID ";
349 }
350
351 $fromClause = implode(" ", $clauses);
352 $whereClase = NULL;
353 }
354 else {
355 $fromClause = " INNER JOIN civicrm_acl_contact_cache aclContactCache ON {$contactAlias}.id = aclContactCache.contact_id ";
b49db103 356 $whereClase = " aclContactCache.user_id = $contactID AND $contactAlias.is_deleted = 0";
6a488035
TO
357 }
358
be2fb01f 359 return [$fromClause, $whereClase];
6a488035
TO
360 }
361
188fd46b 362 /**
5af77f03 363 * Generate acl subquery that can be placed in the WHERE clause of a query or the ON clause of a JOIN.
364 *
365 * This is specifically for VIEW operations.
188fd46b 366 *
0b80f0b4 367 * @return string|null
188fd46b 368 */
b53bcc5d 369 public static function cacheSubquery() {
be2fb01f 370 if (!CRM_Core_Permission::check([['view all contacts', 'edit all contacts']])) {
188fd46b
CW
371 $contactID = (int) CRM_Core_Session::getLoggedInContactID();
372 self::cache($contactID);
0b80f0b4 373 return "IN (SELECT contact_id FROM civicrm_acl_contact_cache WHERE user_id = $contactID)";
188fd46b 374 }
0b80f0b4 375 return NULL;
188fd46b
CW
376 }
377
dddf4bf6 378 /**
379 * Filter a list of contact_ids by the ones that the
380 * currently active user as a permissioned relationship with
381 *
382 * @param array $contact_ids
383 * List of contact IDs to be filtered
384 *
f871c3a9
AS
385 * @param int $type
386 * access type CRM_Core_Permission::VIEW or CRM_Core_Permission::EDIT
387 *
dddf4bf6 388 * @return array
389 * List of contact IDs that the user has permissions for
390 */
f871c3a9 391 public static function relationshipList($contact_ids, $type) {
be2fb01f 392 $result_set = [];
e4541c56 393
dddf4bf6 394 // no processing empty lists (avoid SQL errors as well)
395 if (empty($contact_ids)) {
be2fb01f 396 return [];
dddf4bf6 397 }
398
399 // get the currently logged in user
340be2e7 400 $contactID = CRM_Core_Session::getLoggedInContactID();
dddf4bf6 401 if (empty($contactID)) {
be2fb01f 402 return [];
dddf4bf6 403 }
404
405 // compile a list of queries (later to UNION)
be2fb01f 406 $queries = [];
dddf4bf6 407 $contact_id_list = implode(',', $contact_ids);
408
2f5aa3cd 409 // add a select statement for each direction
be2fb01f 410 $directions = [['from' => 'a', 'to' => 'b'], ['from' => 'b', 'to' => 'a']];
67df1408 411
f871c3a9 412 // CRM_Core_Permission::VIEW is satisfied by either CRM_Contact_BAO_Relationship::VIEW or CRM_Contact_BAO_Relationship::EDIT
da2c7679
AS
413 if ($type == CRM_Core_Permission::VIEW) {
414 $is_perm_condition = ' IN ( ' . CRM_Contact_BAO_Relationship::EDIT . ' , ' . CRM_Contact_BAO_Relationship::VIEW . ' ) ';
415 }
416 else {
417 $is_perm_condition = ' = ' . CRM_Contact_BAO_Relationship::EDIT;
418 }
f871c3a9 419
67df1408 420 // NORMAL/SINGLE DEGREE RELATIONSHIPS
dddf4bf6 421 foreach ($directions as $direction) {
422 $user_id_column = "contact_id_{$direction['from']}";
423 $contact_id_column = "contact_id_{$direction['to']}";
424
425 // add clause for deleted contacts, if the user doesn't have the permission to access them
67df1408 426 $LEFT_JOIN_DELETED = $AND_CAN_ACCESS_DELETED = '';
dddf4bf6 427 if (!CRM_Core_Permission::check('access deleted contacts')) {
67df1408 428 $LEFT_JOIN_DELETED = "LEFT JOIN civicrm_contact ON civicrm_contact.id = {$contact_id_column} ";
e4541c56 429 $AND_CAN_ACCESS_DELETED = "AND civicrm_contact.is_deleted = 0";
dddf4bf6 430 }
431
432 $queries[] = "
340be2e7 433SELECT civicrm_relationship.{$contact_id_column} AS contact_id
9aea8e14 434 FROM civicrm_relationship
dddf4bf6 435 {$LEFT_JOIN_DELETED}
436 WHERE civicrm_relationship.{$user_id_column} = {$contactID}
437 AND civicrm_relationship.{$contact_id_column} IN ({$contact_id_list})
438 AND civicrm_relationship.is_active = 1
f871c3a9 439 AND civicrm_relationship.is_permission_{$direction['from']}_{$direction['to']} {$is_perm_condition}
dddf4bf6 440 $AND_CAN_ACCESS_DELETED";
e4541c56 441 }
dddf4bf6 442
340be2e7 443 // FIXME: secondDegRelPermissions should be a setting
444 $config = CRM_Core_Config::singleton();
dddf4bf6 445 if ($config->secondDegRelPermissions) {
340be2e7 446 foreach ($directions as $first_direction) {
447 foreach ($directions as $second_direction) {
448 // add clause for deleted contacts, if the user doesn't have the permission to access them
449 $LEFT_JOIN_DELETED = $AND_CAN_ACCESS_DELETED = '';
450 if (!CRM_Core_Permission::check('access deleted contacts')) {
451 $LEFT_JOIN_DELETED = "LEFT JOIN civicrm_contact first_degree_contact ON first_degree_contact.id = second_degree_relationship.contact_id_{$second_direction['from']}\n";
452 $LEFT_JOIN_DELETED .= "LEFT JOIN civicrm_contact second_degree_contact ON second_degree_contact.id = second_degree_relationship.contact_id_{$second_direction['to']} ";
453 $AND_CAN_ACCESS_DELETED = "AND first_degree_contact.is_deleted = 0\n";
454 $AND_CAN_ACCESS_DELETED .= "AND second_degree_contact.is_deleted = 0 ";
dddf4bf6 455 }
340be2e7 456
457 $queries[] = "
458SELECT second_degree_relationship.contact_id_{$second_direction['to']} AS contact_id
459 FROM civicrm_relationship first_degree_relationship
2f5aa3cd 460 LEFT JOIN civicrm_relationship second_degree_relationship ON first_degree_relationship.contact_id_{$first_direction['to']} = second_degree_relationship.contact_id_{$second_direction['from']}
340be2e7 461 {$LEFT_JOIN_DELETED}
462 WHERE first_degree_relationship.contact_id_{$first_direction['from']} = {$contactID}
463 AND second_degree_relationship.contact_id_{$second_direction['to']} IN ({$contact_id_list})
464 AND first_degree_relationship.is_active = 1
f871c3a9 465 AND first_degree_relationship.is_permission_{$first_direction['from']}_{$first_direction['to']} {$is_perm_condition}
340be2e7 466 AND second_degree_relationship.is_active = 1
f871c3a9 467 AND second_degree_relationship.is_permission_{$second_direction['from']}_{$second_direction['to']} {$is_perm_condition}
340be2e7 468 $AND_CAN_ACCESS_DELETED";
dddf4bf6 469 }
470 }
471 }
472
473 // finally UNION the queries and call
340be2e7 474 $query = "(" . implode(")\nUNION DISTINCT (", $queries) . ")";
dddf4bf6 475 $result = CRM_Core_DAO::executeQuery($query);
476 while ($result->fetch()) {
67df1408 477 $result_set[(int) $result->contact_id] = TRUE;
dddf4bf6 478 }
67df1408 479 return array_keys($result_set);
dddf4bf6 480 }
481
86538308 482 /**
100fef9d 483 * @param int $contactID
c490a46a 484 * @param CRM_Core_Form $form
86538308
EM
485 * @param bool $redirect
486 *
487 * @return bool
488 */
00be9182 489 public static function validateOnlyChecksum($contactID, &$form, $redirect = TRUE) {
6a488035
TO
490 // check if this is of the format cs=XXX
491 if (!CRM_Contact_BAO_Contact_Utils::validChecksum($contactID,
353ffa53
TO
492 CRM_Utils_Request::retrieve('cs', 'String', $form, FALSE)
493 )
494 ) {
6a488035
TO
495 if ($redirect) {
496 // also set a message in the UF framework
497 $message = ts('You do not have permission to edit this contact record. Contact the site administrator if you need assistance.');
498 CRM_Utils_System::setUFMessage($message);
499
500 $config = CRM_Core_Config::singleton();
501 CRM_Core_Error::statusBounce($message,
502 $config->userFrameworkBaseURL
503 );
504 // does not come here, we redirect in the above statement
505 }
506 return FALSE;
507 }
508
a9a1ea2c 509 // set appropriate AUTH source
e8f14831 510 self::initChecksumAuthSrc(TRUE, $form);
a9a1ea2c 511
6a488035
TO
512 // so here the contact is posing as $contactID, lets set the logging contact ID variable
513 // CRM-8965
514 CRM_Core_DAO::executeQuery('SET @civicrm_user_id = %1',
be2fb01f 515 [1 => [$contactID, 'Integer']]
6a488035 516 );
77b97be7 517
6a488035
TO
518 return TRUE;
519 }
520
86538308
EM
521 /**
522 * @param bool $checkSumValidationResult
523 * @param null $form
524 */
00be9182 525 public static function initChecksumAuthSrc($checkSumValidationResult = FALSE, $form = NULL) {
a9a1ea2c 526 $session = CRM_Core_Session::singleton();
e8f14831 527 if ($checkSumValidationResult && $form && CRM_Utils_Request::retrieve('cs', 'String', $form, FALSE)) {
a9a1ea2c
DS
528 // if result is already validated, and url has cs, set the flag.
529 $session->set('authSrc', CRM_Core_Permission::AUTH_SRC_CHECKSUM);
0db6c3e1 530 }
4c9b6178 531 elseif (($session->get('authSrc') & CRM_Core_Permission::AUTH_SRC_CHECKSUM) == CRM_Core_Permission::AUTH_SRC_CHECKSUM) {
77b97be7 532 // if checksum wasn't present in REQUEST OR checksum result validated as FALSE,
a9a1ea2c
DS
533 // and flag was already set exactly as AUTH_SRC_CHECKSUM, unset it.
534 $session->set('authSrc', CRM_Core_Permission::AUTH_SRC_UNKNOWN);
535 }
536 }
537
86538308 538 /**
100fef9d 539 * @param int $contactID
c490a46a 540 * @param CRM_Core_Form $form
86538308
EM
541 * @param bool $redirect
542 *
543 * @return bool
544 */
00be9182 545 public static function validateChecksumContact($contactID, &$form, $redirect = TRUE) {
6a488035
TO
546 if (!self::allow($contactID, CRM_Core_Permission::EDIT)) {
547 // check if this is of the format cs=XXX
548 return self::validateOnlyChecksum($contactID, $form, $redirect);
549 }
550 return TRUE;
551 }
96025800 552
6a488035 553}