Deprecate CRM_Core_Config::addressSequence()
[civicrm-core.git] / CRM / Contact / BAO / Contact / Utils.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
fee14197 4 | CiviCRM version 5 |
6a488035 5 +--------------------------------------------------------------------+
6b83d5bd 6 | Copyright CiviCRM LLC (c) 2004-2019 |
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
6b83d5bd 31 * @copyright CiviCRM LLC (c) 2004-2019
6a488035
TO
32 */
33class CRM_Contact_BAO_Contact_Utils {
34
35 /**
67d19299 36 * Given a contact type, get the contact image.
6a488035 37 *
77c5b619
TO
38 * @param string $contactType
39 * Contact type.
40 * @param bool $urlOnly
41 * If we need to return only image url.
42 * @param int $contactId
43 * Contact id.
44 * @param bool $addProfileOverlay
45 * If profile overlay class should be added.
6a488035
TO
46 *
47 * @return string
6a488035 48 */
00be9182 49 public static function getImage($contactType, $urlOnly = FALSE, $contactId = NULL, $addProfileOverlay = TRUE) {
be2fb01f 50 static $imageInfo = [];
6a488035 51
7a0bea5c 52 $contactType = CRM_Utils_Array::explodePadded($contactType);
6a488035
TO
53 $contactType = $contactType[0];
54
55 if (!array_key_exists($contactType, $imageInfo)) {
be2fb01f 56 $imageInfo[$contactType] = [];
6a488035 57
be2fb01f
CW
58 $typeInfo = [];
59 $params = ['name' => $contactType];
6a488035
TO
60 CRM_Contact_BAO_ContactType::retrieve($params, $typeInfo);
61
a7488080 62 if (!empty($typeInfo['image_URL'])) {
6a488035
TO
63 $imageUrl = $typeInfo['image_URL'];
64 $config = CRM_Core_Config::singleton();
65
66 if (!preg_match("/^(\/|(http(s)?:)).+$/i", $imageUrl)) {
67 $imageUrl = $config->resourceBase . $imageUrl;
68 }
69 $imageInfo[$contactType]['image'] = "<div class=\"icon crm-icon {$typeInfo['name']}-icon\" style=\"background: url('{$imageUrl}')\" title=\"{$contactType}\"></div>";
70 $imageInfo[$contactType]['url'] = $imageUrl;
71 }
72 else {
73 $isSubtype = (array_key_exists('parent_id', $typeInfo) &&
74 $typeInfo['parent_id']
75 ) ? TRUE : FALSE;
76
77 if ($isSubtype) {
78 $type = CRM_Contact_BAO_ContactType::getBasicType($typeInfo['name']) . '-subtype';
79 }
80 else {
81 $type = CRM_Utils_Array::value('name', $typeInfo);
82 }
83
84 // do not add title since it hides contact name
85 if ($addProfileOverlay) {
86 $imageInfo[$contactType]['image'] = "<div class=\"icon crm-icon {$type}-icon\"></div>";
87 }
92e4c2a5 88 else {
6a488035
TO
89 $imageInfo[$contactType]['image'] = "<div class=\"icon crm-icon {$type}-icon\" title=\"{$contactType}\"></div>";
90 }
91 $imageInfo[$contactType]['url'] = NULL;
92 }
93 }
94
95 if ($addProfileOverlay) {
96 static $summaryOverlayProfileId = NULL;
97 if (!$summaryOverlayProfileId) {
98 $summaryOverlayProfileId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup', 'summary_overlay', 'id', 'name');
99 }
100
101 $profileURL = CRM_Utils_System::url('civicrm/profile/view',
102 "reset=1&gid={$summaryOverlayProfileId}&id={$contactId}&snippet=4"
103 );
104
105 $imageInfo[$contactType]['summary-link'] = '<a href="' . $profileURL . '" class="crm-summary-link">' . $imageInfo[$contactType]['image'] . '</a>';
106 }
107 else {
108 $imageInfo[$contactType]['summary-link'] = $imageInfo[$contactType]['image'];
109 }
110
111 return $urlOnly ? $imageInfo[$contactType]['url'] : $imageInfo[$contactType]['summary-link'];
112 }
113
114 /**
100fef9d 115 * Function check for mix contact ids(individual+household etc...)
6a488035 116 *
77c5b619
TO
117 * @param array $contactIds
118 * Array of contact ids.
6a488035 119 *
72b3a70c
CW
120 * @return bool
121 * true if mix contact array else false
6a488035 122 *
6a488035
TO
123 */
124 public static function checkContactType(&$contactIds) {
125 if (empty($contactIds)) {
126 return FALSE;
127 }
128
129 $idString = implode(',', $contactIds);
130 $query = "
131SELECT count( DISTINCT contact_type )
132FROM civicrm_contact
133WHERE id IN ( $idString )
134";
e03e1641 135 $count = CRM_Core_DAO::singleValueQuery($query);
6a488035
TO
136 return $count > 1 ? TRUE : FALSE;
137 }
138
139 /**
c57f36a1 140 * Generate a checksum for a $entityId of type $entityType
6a488035 141 *
fd31fa4c 142 * @param int $entityId
77c5b619
TO
143 * @param int $ts
144 * Timestamp that checksum was generated.
145 * @param int $live
146 * Life of this checksum in hours/ 'inf' for infinite.
147 * @param string $hash
148 * Contact hash, if sent, prevents a query in inner loop.
fd31fa4c
EM
149 *
150 * @param string $entityType
151 * @param null $hashSize
6a488035 152 *
a6c01b45
CW
153 * @return array
154 * ( $cs, $ts, $live )
6a488035 155 */
00be9182 156 public static function generateChecksum($entityId, $ts = NULL, $live = NULL, $hash = NULL, $entityType = 'contact', $hashSize = NULL) {
c57f36a1 157 // return a warning message if we dont get a entityId
6a488035
TO
158 // this typically happens when we do a message preview
159 // or an anon mailing view - CRM-8298
c57f36a1 160 if (!$entityId) {
6a488035
TO
161 return 'invalidChecksum';
162 }
163
164 if (!$hash) {
c57f36a1
PJ
165 if ($entityType == 'contact') {
166 $hash = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact',
167 $entityId, 'hash'
168 );
169 }
170 elseif ($entityType == 'mailing') {
171 $hash = CRM_Core_DAO::getFieldValue('CRM_Mailing_DAO_Mailing',
172 $entityId, 'hash'
173 );
174 }
6a488035
TO
175 }
176
177 if (!$hash) {
178 $hash = md5(uniqid(rand(), TRUE));
c57f36a1
PJ
179 if ($hashSize) {
180 $hash = substr($hash, 0, $hashSize);
181 }
182
183 if ($entityType == 'contact') {
184 CRM_Core_DAO::setFieldValue('CRM_Contact_DAO_Contact',
185 $entityId,
186 'hash', $hash
187 );
188 }
189 elseif ($entityType == 'mailing') {
190 CRM_Core_DAO::setFieldValue('CRM_Mailing_DAO_Mailing',
191 $entityId,
192 'hash', $hash
193 );
194 }
6a488035
TO
195 }
196
197 if (!$ts) {
198 $ts = time();
199 }
200
201 if (!$live) {
92a8de72 202 $days = Civi::settings()->get('checksum_timeout');
6a488035
TO
203 $live = 24 * $days;
204 }
205
c57f36a1 206 $cs = md5("{$hash}_{$entityId}_{$ts}_{$live}");
6a488035
TO
207 return "{$cs}_{$ts}_{$live}";
208 }
209
210 /**
fe482240 211 * Make sure the checksum is valid for the passed in contactID.
6a488035 212 *
77c5b619
TO
213 * @param int $contactID
214 * @param string $inputCheck
215 * Checksum to match against.
6a488035 216 *
ae5ffbb7 217 * @return bool
a6c01b45 218 * true if valid, else false
6a488035 219 */
00be9182 220 public static function validChecksum($contactID, $inputCheck) {
6a488035
TO
221
222 $input = CRM_Utils_System::explode('_', $inputCheck, 3);
223
224 $inputCS = CRM_Utils_Array::value(0, $input);
225 $inputTS = CRM_Utils_Array::value(1, $input);
226 $inputLF = CRM_Utils_Array::value(2, $input);
227
228 $check = self::generateChecksum($contactID, $inputTS, $inputLF);
d1b47a06 229 // Joomla_11 - If $inputcheck is null without explicitly casting to a string
230 // you get an error.
231 if (!hash_equals($check, (string) $inputCheck)) {
6a488035
TO
232 return FALSE;
233 }
234
235 // no life limit for checksum
236 if ($inputLF == 'inf') {
237 return TRUE;
238 }
239
240 // checksum matches so now check timestamp
241 $now = time();
242 return ($inputTS + ($inputLF * 60 * 60) >= $now);
243 }
244
6a488035 245 /**
fe482240 246 * Create Current employer relationship for a individual.
6a488035 247 *
77c5b619
TO
248 * @param int $contactID
249 * Contact id of the individual.
250 * @param $organization
251 * (id or name).
c490a46a
CW
252 * @param int $previousEmployerID
253 * @param bool $newContact
6a488035 254 *
6a488035 255 */
00be9182 256 public static function createCurrentEmployerRelationship($contactID, $organization, $previousEmployerID = NULL, $newContact = FALSE) {
157e4ab5 257 //if organization name is passed. CRM-15368,CRM-15547
258 if ($organization && !is_numeric($organization)) {
be2fb01f 259 $dupeIDs = CRM_Contact_BAO_Contact::getDuplicateContacts(['organization_name' => $organization], 'Organization', 'Unsupervised', [], FALSE);
d477e9fa 260
261 if (is_array($dupeIDs) && !empty($dupeIDs)) {
262 // we should create relationship only w/ first org CRM-4193
263 foreach ($dupeIDs as $orgId) {
264 $organization = $orgId;
265 break;
266 }
267 }
268 else {
269 //create new organization
be2fb01f 270 $newOrg = [
d477e9fa 271 'contact_type' => 'Organization',
272 'organization_name' => $organization,
be2fb01f 273 ];
d477e9fa 274 $org = CRM_Contact_BAO_Contact::create($newOrg);
275 $organization = $org->id;
276 }
277 }
278
279 if ($organization && is_numeric($organization)) {
be2fb01f 280 $cid = ['contact' => $contactID];
6a488035
TO
281
282 // get the relationship type id of "Employee of"
283 $relTypeId = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_RelationshipType', 'Employee of', 'id', 'name_a_b');
284 if (!$relTypeId) {
285 CRM_Core_Error::fatal(ts("You seem to have deleted the relationship type 'Employee of'"));
286 }
287
288 // create employee of relationship
be2fb01f 289 $relationshipParams = [
6a488035
TO
290 'is_active' => TRUE,
291 'relationship_type_id' => $relTypeId . '_a_b',
be2fb01f
CW
292 'contact_check' => [$organization => TRUE],
293 ];
ae5ffbb7 294 list($valid, $invalid, $duplicate, $saved, $relationshipIds)
2da59b29 295 = CRM_Contact_BAO_Relationship::legacyCreateMultiple($relationshipParams, $cid);
6a488035 296
6eae3162 297 // In case we change employer, clean previous employer related records.
298 if (!$previousEmployerID && !$newContact) {
90f0b591 299 $previousEmployerID = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $contactID, 'employer_id');
300 }
6a488035 301 if ($previousEmployerID &&
d477e9fa 302 $previousEmployerID != $organization
6a488035
TO
303 ) {
304 self::clearCurrentEmployer($contactID, $previousEmployerID);
305 }
306
307 // set current employer
be2fb01f 308 self::setCurrentEmployer([$contactID => $organization]);
6a488035
TO
309
310 $relationshipParams['relationship_ids'] = $relationshipIds;
2001204e 311 // Handle related memberships. CRM-3792
d477e9fa 312 self::currentEmployerRelatedMembership($contactID, $organization, $relationshipParams, $duplicate, $previousEmployerID);
6a488035
TO
313 }
314 }
315
316 /**
fe482240 317 * Create related memberships for current employer.
6a488035 318 *
77c5b619
TO
319 * @param int $contactID
320 * Contact id of the individual.
321 * @param int $employerID
322 * Contact id of the organization.
323 * @param array $relationshipParams
324 * Relationship params.
325 * @param bool $duplicate
326 * Are we triggered existing relationship.
2a6da8d7 327 *
100fef9d 328 * @param int $previousEmpID
6a488035 329 *
2a6da8d7 330 * @throws CiviCRM_API3_Exception
6a488035 331 */
00be9182 332 public static function currentEmployerRelatedMembership($contactID, $employerID, $relationshipParams, $duplicate = FALSE, $previousEmpID = NULL) {
be2fb01f 333 $ids = [];
6a488035
TO
334 $action = CRM_Core_Action::ADD;
335
336 //we do not know that triggered relationship record is active.
337 if ($duplicate) {
338 $relationship = new CRM_Contact_DAO_Relationship();
339 $relationship->contact_id_a = $contactID;
340 $relationship->contact_id_b = $employerID;
341 $relationship->relationship_type_id = $relationshipParams['relationship_type_id'];
342 if ($relationship->find(TRUE)) {
343 $action = CRM_Core_Action::UPDATE;
344 $ids['contact'] = $contactID;
345 $ids['contactTarget'] = $employerID;
346 $ids['relationship'] = $relationship->id;
347 CRM_Contact_BAO_Relationship::setIsActive($relationship->id, TRUE);
348 }
6a488035
TO
349 }
350
351 //need to handle related meberships. CRM-3792
90f0b591 352 if ($previousEmpID != $employerID) {
353 CRM_Contact_BAO_Relationship::relatedMemberships($contactID, $relationshipParams, $ids, $action);
354 }
6a488035
TO
355 }
356
357 /**
fe482240 358 * Set current employer id and organization name.
6a488035 359 *
77c5b619
TO
360 * @param array $currentEmployerParams
361 * Associated array of contact id and its employer id.
6a488035 362 */
00be9182 363 public static function setCurrentEmployer($currentEmployerParams) {
6a488035
TO
364 foreach ($currentEmployerParams as $contactId => $orgId) {
365 $query = "UPDATE civicrm_contact contact_a,civicrm_contact contact_b
366SET contact_a.employer_id=contact_b.id, contact_a.organization_name=contact_b.organization_name
367WHERE contact_a.id ={$contactId} AND contact_b.id={$orgId}; ";
f42bcfb1 368 CRM_Core_DAO::executeQuery($query);
6a488035
TO
369 }
370 }
371
372 /**
fe482240 373 * Update cached current employer name.
6a488035 374 *
77c5b619
TO
375 * @param int $organizationId
376 * Current employer id.
6a488035 377 */
00be9182 378 public static function updateCurrentEmployer($organizationId) {
6a488035
TO
379 $query = "UPDATE civicrm_contact contact_a,civicrm_contact contact_b
380SET contact_a.organization_name=contact_b.organization_name
381WHERE contact_a.employer_id=contact_b.id AND contact_b.id={$organizationId}; ";
382
383 $dao = CRM_Core_DAO::executeQuery($query);
384 }
385
386 /**
fe482240 387 * Clear cached current employer name.
6a488035 388 *
77c5b619
TO
389 * @param int $contactId
390 * Contact id ( mostly individual contact id).
391 * @param int $employerId
392 * Contact id ( mostly organization contact id).
6a488035 393 */
00be9182 394 public static function clearCurrentEmployer($contactId, $employerId = NULL) {
6a488035
TO
395 $query = "UPDATE civicrm_contact
396SET organization_name=NULL, employer_id = NULL
397WHERE id={$contactId}; ";
398
399 $dao = CRM_Core_DAO::executeQuery($query);
400
401 // need to handle related meberships. CRM-3792
402 if ($employerId) {
403 //1. disable corresponding relationship.
404 //2. delete related membership.
405
406 //get the relationship type id of "Employee of"
407 $relTypeId = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_RelationshipType', 'Employee of', 'id', 'name_a_b');
408 if (!$relTypeId) {
409 CRM_Core_Error::fatal(ts("You seem to have deleted the relationship type 'Employee of'"));
410 }
411 $relMembershipParams['relationship_type_id'] = $relTypeId . '_a_b';
412 $relMembershipParams['contact_check'][$employerId] = 1;
413
414 //get relationship id.
415 if (CRM_Contact_BAO_Relationship::checkDuplicateRelationship($relMembershipParams, $contactId, $employerId)) {
416 $relationship = new CRM_Contact_DAO_Relationship();
417 $relationship->contact_id_a = $contactId;
418 $relationship->contact_id_b = $employerId;
419 $relationship->relationship_type_id = $relTypeId;
420
421 if ($relationship->find(TRUE)) {
422 CRM_Contact_BAO_Relationship::setIsActive($relationship->id, FALSE);
423 CRM_Contact_BAO_Relationship::relatedMemberships($contactId, $relMembershipParams,
be2fb01f 424 $ids = [],
d3e86119 425 CRM_Core_Action::DELETE
6a488035
TO
426 );
427 }
6a488035
TO
428 }
429 }
430 }
431
432 /**
100fef9d 433 * Build form for related contacts / on behalf of organization.
6a488035 434 *
c490a46a 435 * @param CRM_Core_Form $form
5a4f6742
CW
436 * @param string $contactType
437 * contact type.
100fef9d
CW
438 * @param int $countryID
439 * @param int $stateID
5a4f6742
CW
440 * @param string $title
441 * fieldset title.
6a488035 442 *
6a488035 443 */
00be9182 444 public static function buildOnBehalfForm(&$form, $contactType, $countryID, $stateID, $title) {
6a488035
TO
445 $form->assign('contact_type', $contactType);
446 $form->assign('fieldSetTitle', $title);
0949913f 447 $form->assign('contactEditMode', TRUE);
6a488035
TO
448
449 $attributes = CRM_Core_DAO::getAttribute('CRM_Contact_DAO_Contact');
450 if ($form->_contactId) {
451 $form->assign('orgId', $form->_contactId);
452 }
453
454 switch ($contactType) {
455 case 'Organization':
d5f1ee75 456 $form->add('text', 'organization_name', ts('Organization Name'), $attributes['organization_name'], TRUE);
6a488035
TO
457 break;
458
459 case 'Household':
353ffa53 460 $form->add('text', 'household_name', ts('Household Name'), $attributes['household_name']);
6a488035
TO
461 break;
462
463 default:
464 // individual
465 $form->addElement('select', 'prefix_id', ts('Prefix'),
be2fb01f 466 ['' => ts('- prefix -')] + CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'prefix_id')
6a488035
TO
467 );
468 $form->addElement('text', 'first_name', ts('First Name'),
469 $attributes['first_name']
470 );
471 $form->addElement('text', 'middle_name', ts('Middle Name'),
472 $attributes['middle_name']
473 );
474 $form->addElement('text', 'last_name', ts('Last Name'),
475 $attributes['last_name']
476 );
477 $form->addElement('select', 'suffix_id', ts('Suffix'),
be2fb01f 478 ['' => ts('- suffix -')] + CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'suffix_id')
6a488035
TO
479 );
480 }
481
9b118398 482 $addressSequence = CRM_Utils_Address::sequence(\Civi::settings()->get('address_format'));
6a488035
TO
483 $form->assign('addressSequence', array_fill_keys($addressSequence, 1));
484
485 //Primary Phone
486 $form->addElement('text',
487 'phone[1][phone]',
488 ts('Primary Phone'),
489 CRM_Core_DAO::getAttribute('CRM_Core_DAO_Phone',
490 'phone'
491 )
492 );
493 //Primary Email
494 $form->addElement('text',
495 'email[1][email]',
496 ts('Primary Email'),
497 CRM_Core_DAO::getAttribute('CRM_Core_DAO_Email',
498 'email'
499 )
500 );
501 //build the address block
502 CRM_Contact_Form_Edit_Address::buildQuickForm($form);
6a488035
TO
503 }
504
505 /**
100fef9d 506 * Clear cache employer name and employer id
6a488035
TO
507 * of all employee when employer get deleted.
508 *
77c5b619
TO
509 * @param int $employerId
510 * Contact id of employer ( organization id ).
6a488035 511 */
00be9182 512 public static function clearAllEmployee($employerId) {
6a488035
TO
513 $query = "
514UPDATE civicrm_contact
515 SET organization_name=NULL, employer_id = NULL
516 WHERE employer_id={$employerId}; ";
517
33621c4f 518 $dao = CRM_Core_DAO::executeQuery($query);
6a488035
TO
519 }
520
521 /**
fe482240 522 * Given an array of contact ids this function will return array with links to view contact page.
6a488035 523 *
77c5b619
TO
524 * @param array $contactIDs
525 * Associated contact id's.
2a6da8d7
EM
526 * @param bool $addViewLink
527 * @param bool $addEditLink
77c5b619
TO
528 * @param int $originalId
529 * Associated with the contact which is edited.
6a488035
TO
530 *
531 *
a6c01b45
CW
532 * @return array
533 * returns array with links to contact view
6a488035 534 */
00be9182 535 public static function formatContactIDSToLinks($contactIDs, $addViewLink = TRUE, $addEditLink = TRUE, $originalId = NULL) {
be2fb01f 536 $contactLinks = [];
6a488035
TO
537 if (!is_array($contactIDs) || empty($contactIDs)) {
538 return $contactLinks;
539 }
540
541 // does contact has sufficient permissions.
be2fb01f 542 $permissions = [
6a488035
TO
543 'view' => 'view all contacts',
544 'edit' => 'edit all contacts',
545 'merge' => 'merge duplicate contacts',
be2fb01f 546 ];
6a488035 547
be2fb01f 548 $permissionedContactIds = [];
6a488035
TO
549 foreach ($permissions as $task => $permission) {
550 // give permission.
551 if (CRM_Core_Permission::check($permission)) {
552 foreach ($contactIDs as $contactId) {
553 $permissionedContactIds[$contactId][$task] = TRUE;
554 }
555 continue;
556 }
557
558 // check permission on acl basis.
be2fb01f 559 if (in_array($task, [
353ffa53 560 'view',
ae5ffbb7 561 'edit',
be2fb01f 562 ])) {
6a488035
TO
563 $aclPermission = CRM_Core_Permission::VIEW;
564 if ($task == 'edit') {
565 $aclPermission = CRM_Core_Permission::EDIT;
566 }
567 foreach ($contactIDs as $contactId) {
568 if (CRM_Contact_BAO_Contact_Permission::allow($contactId, $aclPermission)) {
569 $permissionedContactIds[$contactId][$task] = TRUE;
570 }
571 }
572 }
573 }
574
575 // retrieve display names for all contacts
576 $query = '
577 SELECT c.id, c.display_name, c.contact_type, ce.email
578 FROM civicrm_contact c
579LEFT JOIN civicrm_email ce ON ( ce.contact_id=c.id AND ce.is_primary = 1 )
580 WHERE c.id IN (' . implode(',', $contactIDs) . ' ) LIMIT 20';
581
582 $dao = CRM_Core_DAO::executeQuery($query);
583
584 $contactLinks['msg'] = NULL;
585 $i = 0;
586 while ($dao->fetch()) {
587
588 $contactLinks['rows'][$i]['display_name'] = $dao->display_name;
589 $contactLinks['rows'][$i]['primary_email'] = $dao->email;
590
591 // get the permission for current contact id.
592 $hasPermissions = CRM_Utils_Array::value($dao->id, $permissionedContactIds);
593 if (!is_array($hasPermissions) || empty($hasPermissions)) {
594 $i++;
595 continue;
596 }
597
598 // do check for view.
599 if (array_key_exists('view', $hasPermissions)) {
a1c7d42f 600 $contactLinks['rows'][$i]['view'] = '<a class="action-item" href="' . CRM_Utils_System::url('civicrm/contact/view', 'reset=1&cid=' . $dao->id) . '" target="_blank">' . ts('View') . '</a>';
6a488035
TO
601 if (!$contactLinks['msg']) {
602 $contactLinks['msg'] = 'view';
603 }
604 }
605 if (array_key_exists('edit', $hasPermissions)) {
606 $contactLinks['rows'][$i]['edit'] = '<a class="action-item" href="' . CRM_Utils_System::url('civicrm/contact/add', 'reset=1&action=update&cid=' . $dao->id) . '" target="_blank">' . ts('Edit') . '</a>';
607 if (!$contactLinks['msg'] || $contactLinks['msg'] != 'merge') {
608 $contactLinks['msg'] = 'edit';
609 }
610 }
611 if (!empty($originalId) && array_key_exists('merge', $hasPermissions)) {
353ffa53 612 $rgBao = new CRM_Dedupe_BAO_RuleGroup();
6a488035 613 $rgBao->contact_type = $dao->contact_type;
353ffa53 614 $rgBao->used = 'Supervised';
6a488035
TO
615 if ($rgBao->find(TRUE)) {
616 $rgid = $rgBao->id;
617 }
618 if ($rgid && isset($dao->id)) {
619 //get an url to merge the contact
620 $contactLinks['rows'][$i]['merge'] = '<a class="action-item" href="' . CRM_Utils_System::url('civicrm/contact/merge', "reset=1&cid=" . $originalId . '&oid=' . $dao->id . '&action=update&rgid=' . $rgid) . '">' . ts('Merge') . '</a>';
621 $contactLinks['msg'] = 'merge';
622 }
623 }
624
625 $i++;
626 }
627
628 return $contactLinks;
629 }
630
631 /**
632 * This function retrieve component related contact information.
633 *
77c5b619
TO
634 * @param array $componentIds
635 * Array of component Ids.
100fef9d 636 * @param string $componentName
77c5b619
TO
637 * @param array $returnProperties
638 * Array of return elements.
6a488035 639 *
a6c01b45
CW
640 * @return array
641 * array of contact info.
6a488035 642 */
be2fb01f
CW
643 public static function contactDetails($componentIds, $componentName, $returnProperties = []) {
644 $contactDetails = [];
6a488035 645 if (empty($componentIds) ||
be2fb01f 646 !in_array($componentName, ['CiviContribute', 'CiviMember', 'CiviEvent', 'Activity', 'CiviCase'])
6a488035
TO
647 ) {
648 return $contactDetails;
649 }
650
651 if (empty($returnProperties)) {
652 $autocompleteContactSearch = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
653 'contact_autocomplete_options'
654 );
be2fb01f 655 $returnProperties = array_fill_keys(array_merge(['sort_name'],
353ffa53
TO
656 array_keys($autocompleteContactSearch)
657 ), 1);
6a488035
TO
658 }
659
660 $compTable = NULL;
661 if ($componentName == 'CiviContribute') {
662 $compTable = 'civicrm_contribution';
663 }
664 elseif ($componentName == 'CiviMember') {
665 $compTable = 'civicrm_membership';
666 }
667 elseif ($componentName == 'Activity') {
668 $compTable = 'civicrm_activity';
44f817d4 669 $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
6a488035 670 }
888da08c
MW
671 elseif ($componentName == 'CiviCase') {
672 $compTable = 'civicrm_case';
673 }
6a488035
TO
674 else {
675 $compTable = 'civicrm_participant';
676 }
677
be2fb01f 678 $select = $from = [];
6a488035 679 foreach ($returnProperties as $property => $ignore) {
be2fb01f 680 $value = (in_array($property, [
353ffa53 681 'city',
ae5ffbb7 682 'street_address',
4959114b 683 'postal_code',
be2fb01f 684 ])) ? 'address' : $property;
6a488035
TO
685 switch ($property) {
686 case 'sort_name':
687 if ($componentName == 'Activity') {
f0385140 688 $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts);
689 $select[] = "contact.$property as $property";
690 $from[$value] = "
691INNER JOIN civicrm_activity_contact acs ON (acs.activity_id = {$compTable}.id AND acs.record_type_id = {$sourceID})
692INNER JOIN civicrm_contact contact ON ( contact.id = acs.contact_id )";
888da08c
MW
693 }
694 elseif ($componentName == 'CiviCase') {
695 $select[] = "contact.$property as $property";
696 $from[$value] = "
697INNER JOIN civicrm_case_contact ccs ON (ccs.case_id = {$compTable}.id)
698INNER JOIN civicrm_contact contact ON ( contact.id = ccs.contact_id )";
6a488035
TO
699 }
700 else {
701 $select[] = "$property as $property";
702 $from[$value] = "INNER JOIN civicrm_contact contact ON ( contact.id = $compTable.contact_id )";
703 }
704 break;
705
706 case 'target_sort_name':
f0385140 707 $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts);
6a488035 708 $select[] = "contact_target.sort_name as $property";
91da6cd5 709 $from[$value] = "
f0385140 710INNER JOIN civicrm_activity_contact act ON (act.activity_id = {$compTable}.id AND act.record_type_id = {$targetID})
711INNER JOIN civicrm_contact contact_target ON ( contact_target.id = act.contact_id )";
6a488035
TO
712 break;
713
714 case 'email':
715 case 'phone':
716 case 'city':
717 case 'street_address':
4959114b 718 case 'postal_code':
6a488035
TO
719 $select[] = "$property as $property";
720 // Grab target contact properties if this is for activity
721 if ($componentName == 'Activity') {
722 $from[$value] = "LEFT JOIN civicrm_{$value} {$value} ON ( contact_target.id = {$value}.contact_id AND {$value}.is_primary = 1 ) ";
723 }
724 else {
725 $from[$value] = "LEFT JOIN civicrm_{$value} {$value} ON ( contact.id = {$value}.contact_id AND {$value}.is_primary = 1 ) ";
726 }
727 break;
728
729 case 'country':
730 case 'state_province':
731 $select[] = "{$property}.name as $property";
732 if (!in_array('address', $from)) {
733 // Grab target contact properties if this is for activity
734 if ($componentName == 'Activity') {
735 $from['address'] = 'LEFT JOIN civicrm_address address ON ( contact_target.id = address.contact_id AND address.is_primary = 1) ';
736 }
737 else {
738 $from['address'] = 'LEFT JOIN civicrm_address address ON ( contact.id = address.contact_id AND address.is_primary = 1) ';
739 }
740 }
741 $from[$value] = " LEFT JOIN civicrm_{$value} {$value} ON ( address.{$value}_id = {$value}.id ) ";
742 break;
743 }
744 }
745
746 //finally retrieve contact details.
747 if (!empty($select) && !empty($from)) {
353ffa53 748 $fromClause = implode(' ', $from);
6a488035 749 $selectClause = implode(', ', $select);
353ffa53 750 $whereClause = "{$compTable}.id IN (" . implode(',', $componentIds) . ')';
be2fb01f 751 $groupBy = CRM_Contact_BAO_Query::getGroupByFromSelectColumns($select, ["{$compTable}.id", 'contact.id']);
6a488035
TO
752
753 $query = "
754 SELECT contact.id as contactId, $compTable.id as componentId, $selectClause
755 FROM $compTable as $compTable $fromClause
756 WHERE $whereClause
dc852c7b 757 {$groupBy}";
6a488035
TO
758
759 $contact = CRM_Core_DAO::executeQuery($query);
760 while ($contact->fetch()) {
761 $contactDetails[$contact->componentId]['contact_id'] = $contact->contactId;
762 foreach ($returnProperties as $property => $ignore) {
763 $contactDetails[$contact->componentId][$property] = $contact->$property;
764 }
765 }
6a488035
TO
766 }
767
768 return $contactDetails;
769 }
770
771 /**
fe482240 772 * Function handles shared contact address processing.
6a488035
TO
773 * In this function we just modify submitted values so that new address created for the user
774 * has same address as shared contact address. We copy the address so that search etc will be
e60b6e52 775 * much more efficient.
6a488035 776 *
77c5b619
TO
777 * @param array $address
778 * This is associated array which contains submitted form values.
6a488035 779 */
00be9182 780 public static function processSharedAddress(&$address) {
6a488035
TO
781 if (!is_array($address)) {
782 return;
783 }
784
e60b6e52
D
785 // In create mode sharing a contact's address is pretty straight forward.
786 // In update mode we should check if the user stops sharing. If yes:
787 // - Set the master_id to an empty value
788 // Normal update process will automatically create new address with submitted values
6a488035 789
e60b6e52 790 // 1. loop through entire submitted address array
be2fb01f 791 $skipFields = ['is_primary', 'location_type_id', 'is_billing', 'master_id', 'update_current_employer'];
6a488035 792 foreach ($address as & $values) {
e60b6e52
D
793 // 2. check if "Use another contact's address" is checked, if not continue
794 // Additionally, if master_id is set (address was shared), set master_id to empty value.
795 if (empty($values['use_shared_address'])) {
796 if (!empty($values['master_id'])) {
797 $values['master_id'] = '';
798 }
6a488035
TO
799 continue;
800 }
801
e0fb9216
CW
802 // Set update_current_employer checkbox value
803 $values['update_current_employer'] = !empty($values['update_current_employer']);
804
6a488035
TO
805 // 3. get the address details for master_id
806 $masterAddress = new CRM_Core_BAO_Address();
807 $masterAddress->id = CRM_Utils_Array::value('master_id', $values);
808 $masterAddress->find(TRUE);
809
810 // 4. modify submitted params and update it with shared contact address
811 // make sure you preserve specific form values like location type, is_primary_ is_billing, master_id
812 // CRM-10336: Also empty any fields from the existing address block if they don't exist in master (otherwise they will persist)
813 foreach ($values as $field => $submittedValue) {
9b873358 814 if (!in_array($field, $skipFields)) {
6a488035 815 if (isset($masterAddress->$field)) {
353ffa53 816 $values[$field] = $masterAddress->$field;
0db6c3e1
TO
817 }
818 else {
6a488035 819 $values[$field] = '';
353ffa53 820 }
6a488035
TO
821 }
822 }
6617747f 823
824 //5. modify the params to include county_id if it exist in master contact.
825 // CRM-16152: This is a hack since QF does not submit disabled field.
826 if (!empty($masterAddress->county_id) && empty($values['county_id'])) {
827 $values['county_id'] = $masterAddress->county_id;
828 }
6a488035
TO
829 }
830 }
6a488035
TO
831
832 /**
fe482240 833 * Get the list of contact name give address associated array.
6a488035 834 *
77c5b619
TO
835 * @param array $addresses
836 * Associated array of.
6a488035 837 *
a6c01b45
CW
838 * @return array
839 * associated array of contact names
6a488035 840 */
00be9182 841 public static function getAddressShareContactNames(&$addresses) {
be2fb01f 842 $contactNames = [];
6a488035 843 // get the list of master id's for address
be2fb01f 844 $masterAddressIds = [];
6a488035 845 foreach ($addresses as $key => $addressValue) {
a7488080 846 if (!empty($addressValue['master_id'])) {
6a488035
TO
847 $masterAddressIds[] = $addressValue['master_id'];
848 }
849 }
850
851 if (!empty($masterAddressIds)) {
852 $query = 'SELECT ca.id, cc.display_name, cc.id as cid, cc.is_deleted
853 FROM civicrm_contact cc
854 INNER JOIN civicrm_address ca ON cc.id = ca.contact_id
855 WHERE ca.id IN ( ' . implode(',', $masterAddressIds) . ')';
856 $dao = CRM_Core_DAO::executeQuery($query);
857
858 while ($dao->fetch()) {
859 $contactViewUrl = CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$dao->cid}");
be2fb01f 860 $contactNames[$dao->id] = [
6a488035
TO
861 'name' => "<a href='{$contactViewUrl}'>{$dao->display_name}</a>",
862 'is_deleted' => $dao->is_deleted,
88820b18 863 'contact_id' => $dao->cid,
be2fb01f 864 ];
6a488035
TO
865 }
866 }
867 return $contactNames;
868 }
869
870 /**
871 * Clear the contact cache so things are kosher. We started off being super aggressive with clearing
872 * caches, but are backing off from this with every release. Compromise between ease of coding versus
873 * performance versus being accurate at that very instant
874 *
0626851e 875 * @param bool $isEmptyPrevNextTable
876 * Should the civicrm_prev_next table be cleared of any contact entries.
877 * This is currently done from import but not other places and would
878 * likely affect user experience in unexpected ways. Existing behaviour retained
879 * ... reluctantly.
6a488035 880 */
0626851e 881 public static function clearContactCaches($isEmptyPrevNextTable = FALSE) {
882 if (!CRM_Core_Config::isPermitCacheFlushMode()) {
883 return;
884 }
885 if ($isEmptyPrevNextTable) {
744b4e34
TO
886 // These two calls are redundant in default deployments, but they're
887 // meaningful if "prevnext" is memory-backed.
888 Civi::service('prevnext')->deleteItem();
6a488035
TO
889 CRM_Core_BAO_PrevNextCache::deleteItem();
890 }
0626851e 891 // clear acl cache if any.
892 CRM_ACL_BAO_Cache::resetCache();
2b68a50c 893 CRM_Contact_BAO_GroupContactCache::opportunisticCacheFlush();
6a488035
TO
894 }
895
86538308 896 /**
c490a46a 897 * @param array $params
86538308
EM
898 *
899 * @throws Exception
900 */
6a488035
TO
901 public static function updateGreeting($params) {
902 $contactType = $params['ct'];
353ffa53
TO
903 $greeting = $params['gt'];
904 $valueID = $id = CRM_Utils_Array::value('id', $params);
905 $force = CRM_Utils_Array::value('force', $params);
906 $limit = CRM_Utils_Array::value('limit', $params);
6a488035
TO
907
908 // if valueID is not passed use default value
909 if (!$valueID) {
910 $valueID = $id = self::defaultGreeting($contactType, $greeting);
911 }
912
be2fb01f 913 $filter = [
6a488035
TO
914 'contact_type' => $contactType,
915 'greeting_type' => $greeting,
be2fb01f 916 ];
6a488035
TO
917
918 $allGreetings = CRM_Core_PseudoConstant::greeting($filter);
919 $originalGreetingString = $greetingString = CRM_Utils_Array::value($valueID, $allGreetings);
920 if (!$greetingString) {
be2fb01f 921 CRM_Core_Error::fatal(ts('Incorrect greeting value id %1, or no default greeting for this contact type and greeting type.', [1 => $valueID]));
6a488035
TO
922 }
923
924 // build return properties based on tokens
925 $greetingTokens = CRM_Utils_Token::getTokens($greetingString);
926 $tokens = CRM_Utils_Array::value('contact', $greetingTokens);
be2fb01f 927 $greetingsReturnProperties = [];
6a488035
TO
928 if (is_array($tokens)) {
929 $greetingsReturnProperties = array_fill_keys(array_values($tokens), 1);
930 }
931
932 // Process ALL contacts only when force=1 or force=2 is passed. Else only contacts with NULL greeting or addressee value are updated.
933 $processAll = $processOnlyIdSet = FALSE;
934 if ($force == 1) {
935 $processAll = TRUE;
936 }
937 elseif ($force == 2) {
938 $processOnlyIdSet = TRUE;
939 }
940
941 //FIXME : apiQuery should handle these clause.
be2fb01f 942 $filterContactFldIds = $filterIds = [];
6a488035
TO
943 $idFldName = $displayFldName = NULL;
944 if (in_array($greeting, CRM_Contact_BAO_Contact::$_greetingTypes)) {
945 $idFldName = $greeting . '_id';
946 $displayFldName = $greeting . '_display';
947 }
948
949 if ($idFldName) {
be2fb01f 950 $queryParams = [1 => [$contactType, 'String']];
30b0cb91 951
6a488035
TO
952 // if $force == 1 then update all contacts else only
953 // those with NULL greeting or addressee value CRM-9476
954 if ($processAll) {
955 $sql = "SELECT DISTINCT id, $idFldName FROM civicrm_contact WHERE contact_type = %1 ";
956 }
957 else {
a39ef8af
E
958 $sql = "
959 SELECT DISTINCT id, $idFldName
960 FROM civicrm_contact
961 WHERE contact_type = %1
962 AND ({$idFldName} IS NULL
963 OR ( {$idFldName} IS NOT NULL AND ({$displayFldName} IS NULL OR {$displayFldName} = '')) )";
6a488035
TO
964 }
965
24f77640 966 if ($limit) {
30b0cb91 967 $sql .= " LIMIT 0, %2";
be2fb01f 968 $queryParams += [2 => [$limit, 'Integer']];
24f77640 969 }
970
30b0cb91 971 $dao = CRM_Core_DAO::executeQuery($sql, $queryParams);
6a488035
TO
972 while ($dao->fetch()) {
973 $filterContactFldIds[$dao->id] = $dao->$idFldName;
974
975 if (!CRM_Utils_System::isNull($dao->$idFldName)) {
976 $filterIds[$dao->id] = $dao->$idFldName;
977 }
978 }
979 }
980
981 if (empty($filterContactFldIds)) {
982 $filterContactFldIds[] = 0;
983 }
984
985 // retrieve only required contact information
be2fb01f 986 $extraParams[] = ['contact_type', '=', $contactType, 0, 0];
6a488035
TO
987 // we do token replacement in the replaceGreetingTokens hook
988 list($greetingDetails) = CRM_Utils_Token::getTokenDetails(array_keys($filterContactFldIds),
989 $greetingsReturnProperties,
990 FALSE, FALSE, $extraParams
991 );
992 // perform token replacement and build update SQL
be2fb01f 993 $contactIds = [];
6a488035
TO
994 $cacheFieldQuery = "UPDATE civicrm_contact SET {$greeting}_display = CASE id ";
995 foreach ($greetingDetails as $contactID => $contactDetails) {
996 if (!$processAll &&
997 !array_key_exists($contactID, $filterContactFldIds)
998 ) {
999 continue;
1000 }
1001
1002 if ($processOnlyIdSet && !array_key_exists($contactID, $filterIds)) {
1003 continue;
1004 }
1005
1006 if ($id) {
1007 $greetingString = $originalGreetingString;
1008 $contactIds[] = $contactID;
1009 }
1010 else {
1011 if ($greetingBuffer = CRM_Utils_Array::value($filterContactFldIds[$contactID], $allGreetings)) {
1012 $greetingString = $greetingBuffer;
1013 }
1014 }
1015
73d64eb6 1016 self::processGreetingTemplate($greetingString, $contactDetails, $contactID, 'CRM_UpdateGreeting');
6a488035
TO
1017 $greetingString = CRM_Core_DAO::escapeString($greetingString);
1018 $cacheFieldQuery .= " WHEN {$contactID} THEN '{$greetingString}' ";
1019
1020 $allContactIds[] = $contactID;
1021 }
1022
1023 if (!empty($allContactIds)) {
1024 $cacheFieldQuery .= " ELSE {$greeting}_display
1025 END;";
1026 if (!empty($contactIds)) {
1027 // need to update greeting _id field.
1028 // reset greeting _custom
1029 $resetCustomGreeting = '';
1030 if ($valueID != 4) {
1031 $resetCustomGreeting = ", {$greeting}_custom = NULL ";
1032 }
1033
1034 $queryString = "
1035UPDATE civicrm_contact
1036SET {$greeting}_id = {$valueID}
1037 {$resetCustomGreeting}
1038WHERE id IN (" . implode(',', $contactIds) . ")";
1039 CRM_Core_DAO::executeQuery($queryString);
1040 }
1041
1042 // now update cache field
1043 CRM_Core_DAO::executeQuery($cacheFieldQuery);
1044 }
1045 }
1046
1047 /**
fe482240 1048 * Fetch the default greeting for a given contact type.
6a488035 1049 *
77c5b619
TO
1050 * @param string $contactType
1051 * Contact type.
1052 * @param string $greetingType
1053 * Greeting type.
6a488035 1054 *
e97c66ff 1055 * @return int|null
6a488035 1056 */
00be9182 1057 public static function defaultGreeting($contactType, $greetingType) {
be2fb01f 1058 $contactTypeFilters = [
ae5ffbb7
TO
1059 'Individual' => 1,
1060 'Household' => 2,
1061 'Organization' => 3,
be2fb01f 1062 ];
6a488035 1063 if (!isset($contactTypeFilters[$contactType])) {
ae5ffbb7 1064 return NULL;
6a488035
TO
1065 }
1066 $filter = $contactTypeFilters[$contactType];
1067
1068 $id = CRM_Core_OptionGroup::values($greetingType, NULL, NULL, NULL,
1069 " AND is_default = 1 AND (filter = {$filter} OR filter = 0)",
1070 'value'
1071 );
1072 if (!empty($id)) {
1073 return current($id);
1074 }
1075 }
73d64eb6 1076
63b7d442
AS
1077 /**
1078 * Get the tokens that will need to be resolved to populate the contact's greetings.
1079 *
1080 * @param array $contactParams
1081 *
1082 * @return array
1083 * Array of tokens. The ALL ke
1084 */
1085 public static function getTokensRequiredForContactGreetings($contactParams) {
be2fb01f
CW
1086 $tokens = [];
1087 foreach (['addressee', 'email_greeting', 'postal_greeting'] as $greeting) {
63b7d442
AS
1088 $string = '';
1089 if (!empty($contactParams[$greeting . '_id'])) {
1090 $string = CRM_Core_PseudoConstant::getLabel('CRM_Contact_BAO_Contact', $greeting . '_id', $contactParams[$greeting . '_id']);
1091 }
1092 $string = isset($contactParams[$greeting . '_custom']) ? $contactParams[$greeting . '_custom'] : $string;
1093 if (empty($string)) {
be2fb01f 1094 $tokens[$greeting] = [];
63b7d442
AS
1095 }
1096 else {
1097 $tokens[$greeting] = CRM_Utils_Token::getTokens($string);
1098 }
1099 }
1100 $allTokens = array_merge_recursive($tokens['addressee'], $tokens['email_greeting'], $tokens['postal_greeting']);
1101 $tokens['all'] = $allTokens;
1102 return $tokens;
1103 }
1104
73d64eb6
OB
1105 /**
1106 * Process a greeting template string to produce the individualised greeting text.
1107 *
1108 * This works just like message templates for mailings:
1109 * the template is processed with the token substitution mechanism,
1110 * to supply the individual contact data;
1111 * and it is also processed with Smarty,
1112 * to allow for conditionals etc. based on the contact data.
1113 *
1114 * Note: We don't pass any variables to Smarty --
1115 * all variable data is inserted into the input string
1116 * by the token substitution mechanism,
1117 * before Smarty is invoked.
1118 *
77c5b619
TO
1119 * @param string $templateString
1120 * The greeting template string with contact tokens + Smarty syntax.
73d64eb6 1121 *
67d19299 1122 * @param array $contactDetails
100fef9d
CW
1123 * @param int $contactID
1124 * @param string $className
73d64eb6 1125 */
00be9182 1126 public static function processGreetingTemplate(&$templateString, $contactDetails, $contactID, $className) {
73d64eb6
OB
1127 CRM_Utils_Token::replaceGreetingTokens($templateString, $contactDetails, $contactID, $className, TRUE);
1128
1129 $smarty = CRM_Core_Smarty::singleton();
1130 $templateString = $smarty->fetch("string:$templateString");
1131 }
96025800 1132
055f6c27
TO
1133 /**
1134 * Determine if a contact ID is real/valid.
1135 *
1136 * @param int $contactId
1137 * The hypothetical contact ID
1138 * @return bool
1139 */
1140 public static function isContactId($contactId) {
1141 if ($contactId) {
1142 // ensure that this is a valid contact id (for session inconsistency rules)
1143 $cid = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact',
1144 $contactId,
1145 'id',
1146 'id'
1147 );
1148 if ($cid) {
1149 return TRUE;
1150 }
1151 }
1152 return FALSE;
1153 }
1154
6a488035 1155}