3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
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 +--------------------------------------------------------------------+
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
17 class CRM_Contact_BAO_Contact_Utils
{
20 * Given a contact type, get the contact image.
22 * @param string $contactType
24 * @param bool $urlOnly
25 * If we need to return only image url.
26 * @param int $contactId
28 * @param bool $addProfileOverlay
29 * If profile overlay class should be added.
32 * @throws \CRM_Core_Exception
34 public static function getImage($contactType, $urlOnly = FALSE, $contactId = NULL, $addProfileOverlay = TRUE) {
35 static $imageInfo = [];
37 $contactType = CRM_Utils_Array
::explodePadded($contactType);
38 $contactType = $contactType[0];
40 if (!array_key_exists($contactType, $imageInfo)) {
41 $imageInfo[$contactType] = [];
44 $params = ['name' => $contactType];
45 CRM_Contact_BAO_ContactType
::retrieve($params, $typeInfo);
47 if (!empty($typeInfo['image_URL'])) {
48 $imageUrl = $typeInfo['image_URL'];
49 $config = CRM_Core_Config
::singleton();
51 if (!preg_match("/^(\/|(http(s)?:)).+$/i", $imageUrl)) {
52 $imageUrl = $config->resourceBase
. $imageUrl;
54 $imageInfo[$contactType]['image'] = "<div class=\"icon crm-icon {$typeInfo['name']}-icon\" style=\"background: url('{$imageUrl}')\" title=\"{$contactType}\"></div>";
55 $imageInfo[$contactType]['url'] = $imageUrl;
58 $isSubtype = (array_key_exists('parent_id', $typeInfo) &&
59 $typeInfo['parent_id']
63 $type = CRM_Contact_BAO_ContactType
::getBasicType($typeInfo['name']) . '-subtype';
66 $type = CRM_Utils_Array
::value('name', $typeInfo);
69 // do not add title since it hides contact name
70 if ($addProfileOverlay) {
71 $imageInfo[$contactType]['image'] = "<div class=\"icon crm-icon {$type}-icon\"></div>";
74 $imageInfo[$contactType]['image'] = "<div class=\"icon crm-icon {$type}-icon\" title=\"{$contactType}\"></div>";
76 $imageInfo[$contactType]['url'] = NULL;
80 if ($addProfileOverlay) {
81 static $summaryOverlayProfileId = NULL;
82 if (!$summaryOverlayProfileId) {
83 $summaryOverlayProfileId = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_UFGroup', 'summary_overlay', 'id', 'name');
86 $profileURL = CRM_Utils_System
::url('civicrm/profile/view',
87 "reset=1&gid={$summaryOverlayProfileId}&id={$contactId}&snippet=4"
90 $imageInfo[$contactType]['summary-link'] = '<a href="' . $profileURL . '" class="crm-summary-link">' . $imageInfo[$contactType]['image'] . '</a>';
93 $imageInfo[$contactType]['summary-link'] = $imageInfo[$contactType]['image'];
96 return $urlOnly ?
$imageInfo[$contactType]['url'] : $imageInfo[$contactType]['summary-link'];
100 * Function check for mix contact ids(individual+household etc...)
102 * @param array $contactIds
103 * Array of contact ids.
106 * true if mix contact array else false
109 public static function checkContactType(&$contactIds) {
110 if (empty($contactIds)) {
114 $idString = implode(',', $contactIds);
116 SELECT count( DISTINCT contact_type )
118 WHERE id IN ( $idString )
120 $count = CRM_Core_DAO
::singleValueQuery($query);
121 return $count > 1 ?
TRUE : FALSE;
125 * Generate a checksum for a $entityId of type $entityType
127 * @param int $entityId
129 * Timestamp that checksum was generated.
131 * Life of this checksum in hours/ 'inf' for infinite.
132 * @param string $hash
133 * Contact hash, if sent, prevents a query in inner loop.
135 * @param string $entityType
136 * @param null $hashSize
139 * ( $cs, $ts, $live )
140 * @throws \CRM_Core_Exception
142 public static function generateChecksum($entityId, $ts = NULL, $live = NULL, $hash = NULL, $entityType = 'contact', $hashSize = NULL) {
143 // return a warning message if we dont get a entityId
144 // this typically happens when we do a message preview
145 // or an anon mailing view - CRM-8298
147 return 'invalidChecksum';
151 if ($entityType == 'contact') {
152 $hash = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact',
156 elseif ($entityType == 'mailing') {
157 $hash = CRM_Core_DAO
::getFieldValue('CRM_Mailing_DAO_Mailing',
164 $hash = md5(uniqid(rand(), TRUE));
166 $hash = substr($hash, 0, $hashSize);
169 if ($entityType == 'contact') {
170 CRM_Core_DAO
::setFieldValue('CRM_Contact_DAO_Contact',
175 elseif ($entityType == 'mailing') {
176 CRM_Core_DAO
::setFieldValue('CRM_Mailing_DAO_Mailing',
188 $days = Civi
::settings()->get('checksum_timeout');
192 $cs = md5("{$hash}_{$entityId}_{$ts}_{$live}");
193 return "{$cs}_{$ts}_{$live}";
197 * Make sure the checksum is valid for the passed in contactID.
199 * @param int $contactID
200 * @param string $inputCheck
201 * Checksum to match against.
204 * true if valid, else false
206 * @throws \CRM_Core_Exception
208 public static function validChecksum($contactID, $inputCheck) {
210 $input = CRM_Utils_System
::explode('_', $inputCheck, 3);
212 $inputCS = CRM_Utils_Array
::value(0, $input);
213 $inputTS = CRM_Utils_Array
::value(1, $input);
214 $inputLF = CRM_Utils_Array
::value(2, $input);
216 $check = self
::generateChecksum($contactID, $inputTS, $inputLF);
217 // Joomla_11 - If $inputcheck is null without explicitly casting to a string
219 if (!hash_equals($check, (string) $inputCheck)) {
223 // no life limit for checksum
224 if ($inputLF == 'inf') {
228 // checksum matches so now check timestamp
230 return ($inputTS +
($inputLF * 60 * 60) >= $now);
234 * Create Current employer relationship for a individual.
236 * @param int $contactID
237 * Contact id of the individual.
238 * @param $organization
240 * @param int $previousEmployerID
241 * @param bool $newContact
243 * @throws \CRM_Core_Exception
244 * @throws \CiviCRM_API3_Exception
246 public static function createCurrentEmployerRelationship($contactID, $organization, $previousEmployerID = NULL, $newContact = FALSE) {
247 //if organization name is passed. CRM-15368,CRM-15547
248 if ($organization && !is_numeric($organization)) {
249 $dupeIDs = CRM_Contact_BAO_Contact
::getDuplicateContacts(['organization_name' => $organization], 'Organization', 'Unsupervised', [], FALSE);
251 if (is_array($dupeIDs) && !empty($dupeIDs)) {
252 // we should create relationship only w/ first org CRM-4193
253 foreach ($dupeIDs as $orgId) {
254 $organization = $orgId;
259 //create new organization
261 'contact_type' => 'Organization',
262 'organization_name' => $organization,
264 $org = CRM_Contact_BAO_Contact
::create($newOrg);
265 $organization = $org->id
;
269 if ($organization && is_numeric($organization)) {
270 $cid = ['contact' => $contactID];
272 // get the relationship type id of "Employee of"
273 $relTypeId = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_RelationshipType', 'Employee of', 'id', 'name_a_b');
275 throw new CRM_Core_Exception(ts("You seem to have deleted the relationship type 'Employee of'"));
278 // create employee of relationship
279 $relationshipParams = [
281 'relationship_type_id' => $relTypeId . '_a_b',
282 'contact_check' => [$organization => TRUE],
284 list($valid, $invalid, $duplicate, $saved, $relationshipIds)
285 = CRM_Contact_BAO_Relationship
::legacyCreateMultiple($relationshipParams, $cid);
287 // In case we change employer, clean previous employer related records.
288 if (!$previousEmployerID && !$newContact) {
289 $previousEmployerID = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact', $contactID, 'employer_id');
291 if ($previousEmployerID &&
292 $previousEmployerID != $organization
294 self
::clearCurrentEmployer($contactID, $previousEmployerID);
297 // set current employer
298 self
::setCurrentEmployer([$contactID => $organization]);
300 $relationshipParams['relationship_ids'] = $relationshipIds;
301 // Handle related memberships. CRM-3792
302 self
::currentEmployerRelatedMembership($contactID, $organization, $relationshipParams, $duplicate, $previousEmployerID);
307 * Create related memberships for current employer.
309 * @param int $contactID
310 * Contact id of the individual.
311 * @param int $employerID
312 * Contact id of the organization.
313 * @param array $relationshipParams
314 * Relationship params.
315 * @param bool $duplicate
316 * Are we triggered existing relationship.
318 * @param int $previousEmpID
320 * @throws CiviCRM_API3_Exception
321 * @throws \CRM_Core_Exception
323 public static function currentEmployerRelatedMembership($contactID, $employerID, $relationshipParams, $duplicate = FALSE, $previousEmpID = NULL) {
325 $action = CRM_Core_Action
::ADD
;
327 //we do not know that triggered relationship record is active.
329 $relationship = new CRM_Contact_DAO_Relationship();
330 $relationship->contact_id_a
= $contactID;
331 $relationship->contact_id_b
= $employerID;
332 $relationship->relationship_type_id
= $relationshipParams['relationship_type_id'];
333 if ($relationship->find(TRUE)) {
334 $action = CRM_Core_Action
::UPDATE
;
335 $ids['contact'] = $contactID;
336 $ids['contactTarget'] = $employerID;
337 $ids['relationship'] = $relationship->id
;
338 CRM_Contact_BAO_Relationship
::setIsActive($relationship->id
, TRUE);
342 //need to handle related meberships. CRM-3792
343 if ($previousEmpID != $employerID) {
344 CRM_Contact_BAO_Relationship
::relatedMemberships($contactID, $relationshipParams, $ids, $action);
349 * Set current employer id and organization name.
351 * @param array $currentEmployerParams
352 * Associated array of contact id and its employer id.
354 public static function setCurrentEmployer($currentEmployerParams) {
355 foreach ($currentEmployerParams as $contactId => $orgId) {
356 $query = "UPDATE civicrm_contact contact_a,civicrm_contact contact_b
357 SET contact_a.employer_id=contact_b.id, contact_a.organization_name=contact_b.organization_name
358 WHERE contact_a.id ={$contactId} AND contact_b.id={$orgId}; ";
359 CRM_Core_DAO
::executeQuery($query);
364 * Update cached current employer name.
366 * @param int $organizationId
367 * Current employer id.
369 public static function updateCurrentEmployer($organizationId) {
370 $query = "UPDATE civicrm_contact contact_a,civicrm_contact contact_b
371 SET contact_a.organization_name=contact_b.organization_name
372 WHERE contact_a.employer_id=contact_b.id AND contact_b.id={$organizationId}; ";
374 CRM_Core_DAO
::executeQuery($query);
378 * Clear cached current employer name.
380 * @param int $contactId
381 * Contact id ( mostly individual contact id).
382 * @param int $employerId
383 * Contact id ( mostly organization contact id).
385 * @throws \CRM_Core_Exception
386 * @throws \CiviCRM_API3_Exception
388 public static function clearCurrentEmployer($contactId, $employerId = NULL) {
389 $query = "UPDATE civicrm_contact
390 SET organization_name=NULL, employer_id = NULL
391 WHERE id={$contactId}; ";
393 $dao = CRM_Core_DAO
::executeQuery($query);
395 // need to handle related meberships. CRM-3792
397 //1. disable corresponding relationship.
398 //2. delete related membership.
400 //get the relationship type id of "Employee of"
401 $relTypeId = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_RelationshipType', 'Employee of', 'id', 'name_a_b');
403 throw new CRM_Core_Exception(ts("You seem to have deleted the relationship type 'Employee of'"));
405 $relMembershipParams['relationship_type_id'] = $relTypeId . '_a_b';
406 $relMembershipParams['contact_check'][$employerId] = 1;
408 //get relationship id.
409 if (CRM_Contact_BAO_Relationship
::checkDuplicateRelationship($relMembershipParams, $contactId, $employerId)) {
410 $relationship = new CRM_Contact_DAO_Relationship();
411 $relationship->contact_id_a
= $contactId;
412 $relationship->contact_id_b
= $employerId;
413 $relationship->relationship_type_id
= $relTypeId;
415 if ($relationship->find(TRUE)) {
416 CRM_Contact_BAO_Relationship
::setIsActive($relationship->id
, FALSE);
417 CRM_Contact_BAO_Relationship
::relatedMemberships($contactId, $relMembershipParams,
419 CRM_Core_Action
::DELETE
427 * Build form for related contacts / on behalf of organization.
429 * @param CRM_Core_Form $form
430 * @param string $contactType
432 * @param int $countryID
433 * @param int $stateID
434 * @param string $title
437 * @throws \CiviCRM_API3_Exception
439 public static function buildOnBehalfForm(&$form, $contactType, $countryID, $stateID, $title) {
440 $form->assign('contact_type', $contactType);
441 $form->assign('fieldSetTitle', $title);
442 $form->assign('contactEditMode', TRUE);
444 $attributes = CRM_Core_DAO
::getAttribute('CRM_Contact_DAO_Contact');
445 if ($form->_contactId
) {
446 $form->assign('orgId', $form->_contactId
);
449 switch ($contactType) {
451 $form->add('text', 'organization_name', ts('Organization Name'), $attributes['organization_name'], TRUE);
455 $form->add('text', 'household_name', ts('Household Name'), $attributes['household_name']);
460 $form->addElement('select', 'prefix_id', ts('Prefix'),
461 ['' => ts('- prefix -')] + CRM_Core_PseudoConstant
::get('CRM_Contact_DAO_Contact', 'prefix_id')
463 $form->addElement('text', 'first_name', ts('First Name'),
464 $attributes['first_name']
466 $form->addElement('text', 'middle_name', ts('Middle Name'),
467 $attributes['middle_name']
469 $form->addElement('text', 'last_name', ts('Last Name'),
470 $attributes['last_name']
472 $form->addElement('select', 'suffix_id', ts('Suffix'),
473 ['' => ts('- suffix -')] + CRM_Core_PseudoConstant
::get('CRM_Contact_DAO_Contact', 'suffix_id')
477 $addressSequence = CRM_Utils_Address
::sequence(\Civi
::settings()->get('address_format'));
478 $form->assign('addressSequence', array_fill_keys($addressSequence, 1));
481 $form->addElement('text',
484 CRM_Core_DAO
::getAttribute('CRM_Core_DAO_Phone',
489 $form->addElement('text',
492 CRM_Core_DAO
::getAttribute('CRM_Core_DAO_Email',
496 //build the address block
497 CRM_Contact_Form_Edit_Address
::buildQuickForm($form);
501 * Clear cache employer name and employer id
502 * of all employee when employer get deleted.
504 * @param int $employerId
505 * Contact id of employer ( organization id ).
507 public static function clearAllEmployee($employerId) {
509 UPDATE civicrm_contact
510 SET organization_name=NULL, employer_id = NULL
511 WHERE employer_id={$employerId}; ";
513 $dao = CRM_Core_DAO
::executeQuery($query);
517 * Given an array of contact ids this function will return array with links to view contact page.
519 * @param array $contactIDs
520 * Associated contact id's.
521 * @param bool $addViewLink
522 * @param bool $addEditLink
523 * @param int $originalId
524 * Associated with the contact which is edited.
528 * returns array with links to contact view
530 public static function formatContactIDSToLinks($contactIDs, $addViewLink = TRUE, $addEditLink = TRUE, $originalId = NULL) {
532 if (!is_array($contactIDs) ||
empty($contactIDs)) {
533 return $contactLinks;
536 // does contact has sufficient permissions.
538 'view' => 'view all contacts',
539 'edit' => 'edit all contacts',
540 'merge' => 'merge duplicate contacts',
543 $permissionedContactIds = [];
544 foreach ($permissions as $task => $permission) {
546 if (CRM_Core_Permission
::check($permission)) {
547 foreach ($contactIDs as $contactId) {
548 $permissionedContactIds[$contactId][$task] = TRUE;
553 // check permission on acl basis.
554 if (in_array($task, [
558 $aclPermission = CRM_Core_Permission
::VIEW
;
559 if ($task == 'edit') {
560 $aclPermission = CRM_Core_Permission
::EDIT
;
562 foreach ($contactIDs as $contactId) {
563 if (CRM_Contact_BAO_Contact_Permission
::allow($contactId, $aclPermission)) {
564 $permissionedContactIds[$contactId][$task] = TRUE;
570 // retrieve display names for all contacts
572 SELECT c.id, c.display_name, c.contact_type, ce.email
573 FROM civicrm_contact c
574 LEFT JOIN civicrm_email ce ON ( ce.contact_id=c.id AND ce.is_primary = 1 )
575 WHERE c.id IN (' . implode(',', $contactIDs) . ' ) LIMIT 20';
577 $dao = CRM_Core_DAO
::executeQuery($query);
579 $contactLinks['msg'] = NULL;
581 while ($dao->fetch()) {
583 $contactLinks['rows'][$i]['display_name'] = $dao->display_name
;
584 $contactLinks['rows'][$i]['primary_email'] = $dao->email
;
586 // get the permission for current contact id.
587 $hasPermissions = CRM_Utils_Array
::value($dao->id
, $permissionedContactIds);
588 if (!is_array($hasPermissions) ||
empty($hasPermissions)) {
593 // do check for view.
594 if (array_key_exists('view', $hasPermissions)) {
595 $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>';
596 if (!$contactLinks['msg']) {
597 $contactLinks['msg'] = 'view';
600 if (array_key_exists('edit', $hasPermissions)) {
601 $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>';
602 if (!$contactLinks['msg'] ||
$contactLinks['msg'] != 'merge') {
603 $contactLinks['msg'] = 'edit';
606 if (!empty($originalId) && array_key_exists('merge', $hasPermissions)) {
607 $rgBao = new CRM_Dedupe_BAO_RuleGroup();
608 $rgBao->contact_type
= $dao->contact_type
;
609 $rgBao->used
= 'Supervised';
610 if ($rgBao->find(TRUE)) {
613 if ($rgid && isset($dao->id
)) {
614 //get an url to merge the contact
615 $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>';
616 $contactLinks['msg'] = 'merge';
623 return $contactLinks;
627 * This function retrieve component related contact information.
629 * @param array $componentIds
630 * Array of component Ids.
631 * @param string $componentName
632 * @param array $returnProperties
633 * Array of return elements.
636 * array of contact info.
638 public static function contactDetails($componentIds, $componentName, $returnProperties = []) {
639 $contactDetails = [];
640 if (empty($componentIds) ||
641 !in_array($componentName, ['CiviContribute', 'CiviMember', 'CiviEvent', 'Activity', 'CiviCase'])
643 return $contactDetails;
646 if (empty($returnProperties)) {
647 $autocompleteContactSearch = CRM_Core_BAO_Setting
::valueOptions(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
648 'contact_autocomplete_options'
650 $returnProperties = array_fill_keys(array_merge(['sort_name'],
651 array_keys($autocompleteContactSearch)
656 if ($componentName == 'CiviContribute') {
657 $compTable = 'civicrm_contribution';
659 elseif ($componentName == 'CiviMember') {
660 $compTable = 'civicrm_membership';
662 elseif ($componentName == 'Activity') {
663 $compTable = 'civicrm_activity';
664 $activityContacts = CRM_Activity_BAO_ActivityContact
::buildOptions('record_type_id', 'validate');
666 elseif ($componentName == 'CiviCase') {
667 $compTable = 'civicrm_case';
670 $compTable = 'civicrm_participant';
673 $select = $from = [];
674 foreach ($returnProperties as $property => $ignore) {
675 $value = (in_array($property, [
679 ])) ?
'address' : $property;
682 if ($componentName == 'Activity') {
683 $sourceID = CRM_Utils_Array
::key('Activity Source', $activityContacts);
684 $select[] = "contact.$property as $property";
686 INNER JOIN civicrm_activity_contact acs ON (acs.activity_id = {$compTable}.id AND acs.record_type_id = {$sourceID})
687 INNER JOIN civicrm_contact contact ON ( contact.id = acs.contact_id )";
689 elseif ($componentName == 'CiviCase') {
690 $select[] = "contact.$property as $property";
692 INNER JOIN civicrm_case_contact ccs ON (ccs.case_id = {$compTable}.id)
693 INNER JOIN civicrm_contact contact ON ( contact.id = ccs.contact_id )";
696 $select[] = "$property as $property";
697 $from[$value] = "INNER JOIN civicrm_contact contact ON ( contact.id = $compTable.contact_id )";
701 case 'target_sort_name':
702 $targetID = CRM_Utils_Array
::key('Activity Targets', $activityContacts);
703 $select[] = "contact_target.sort_name as $property";
705 INNER JOIN civicrm_activity_contact act ON (act.activity_id = {$compTable}.id AND act.record_type_id = {$targetID})
706 INNER JOIN civicrm_contact contact_target ON ( contact_target.id = act.contact_id )";
712 case 'street_address':
714 $select[] = "$property as $property";
715 // Grab target contact properties if this is for activity
716 if ($componentName == 'Activity') {
717 $from[$value] = "LEFT JOIN civicrm_{$value} {$value} ON ( contact_target.id = {$value}.contact_id AND {$value}.is_primary = 1 ) ";
720 $from[$value] = "LEFT JOIN civicrm_{$value} {$value} ON ( contact.id = {$value}.contact_id AND {$value}.is_primary = 1 ) ";
725 case 'state_province':
726 $select[] = "{$property}.name as $property";
727 if (!in_array('address', $from)) {
728 // Grab target contact properties if this is for activity
729 if ($componentName == 'Activity') {
730 $from['address'] = 'LEFT JOIN civicrm_address address ON ( contact_target.id = address.contact_id AND address.is_primary = 1) ';
733 $from['address'] = 'LEFT JOIN civicrm_address address ON ( contact.id = address.contact_id AND address.is_primary = 1) ';
736 $from[$value] = " LEFT JOIN civicrm_{$value} {$value} ON ( address.{$value}_id = {$value}.id ) ";
741 //finally retrieve contact details.
742 if (!empty($select) && !empty($from)) {
743 $fromClause = implode(' ', $from);
744 $selectClause = implode(', ', $select);
745 $whereClause = "{$compTable}.id IN (" . implode(',', $componentIds) . ')';
746 $groupBy = CRM_Contact_BAO_Query
::getGroupByFromSelectColumns($select, ["{$compTable}.id", 'contact.id']);
749 SELECT contact.id as contactId, $compTable.id as componentId, $selectClause
750 FROM $compTable as $compTable $fromClause
754 $contact = CRM_Core_DAO
::executeQuery($query);
755 while ($contact->fetch()) {
756 $contactDetails[$contact->componentId
]['contact_id'] = $contact->contactId
;
757 foreach ($returnProperties as $property => $ignore) {
758 $contactDetails[$contact->componentId
][$property] = $contact->$property;
763 return $contactDetails;
767 * Function handles shared contact address processing.
768 * In this function we just modify submitted values so that new address created for the user
769 * has same address as shared contact address. We copy the address so that search etc will be
770 * much more efficient.
772 * @param array $address
773 * This is associated array which contains submitted form values.
775 public static function processSharedAddress(&$address) {
776 if (!is_array($address)) {
780 // In create mode sharing a contact's address is pretty straight forward.
781 // In update mode we should check if the user stops sharing. If yes:
782 // - Set the master_id to an empty value
783 // Normal update process will automatically create new address with submitted values
785 // 1. loop through entire submitted address array
786 $skipFields = ['is_primary', 'location_type_id', 'is_billing', 'master_id', 'add_relationship'];
787 foreach ($address as & $values) {
788 // 2. check if "Use another contact's address" is checked, if not continue
789 // Additionally, if master_id is set (address was shared), set master_id to empty value.
790 if (empty($values['use_shared_address'])) {
791 if (!empty($values['master_id'])) {
792 $values['master_id'] = '';
797 // Set add_relationship checkbox value
798 $values['add_relationship'] = !empty($values['add_relationship']);
800 // 3. get the address details for master_id
801 $masterAddress = new CRM_Core_BAO_Address();
802 $masterAddress->id
= CRM_Utils_Array
::value('master_id', $values);
803 $masterAddress->find(TRUE);
805 // 4. modify submitted params and update it with shared contact address
806 // make sure you preserve specific form values like location type, is_primary_ is_billing, master_id
807 // CRM-10336: Also empty any fields from the existing address block if they don't exist in master (otherwise they will persist)
808 foreach ($values as $field => $submittedValue) {
809 if (!in_array($field, $skipFields)) {
810 if (isset($masterAddress->$field)) {
811 $values[$field] = $masterAddress->$field;
814 $values[$field] = '';
819 //5. modify the params to include county_id if it exist in master contact.
820 // CRM-16152: This is a hack since QF does not submit disabled field.
821 if (!empty($masterAddress->county_id
) && empty($values['county_id'])) {
822 $values['county_id'] = $masterAddress->county_id
;
828 * Get the list of contact name give address associated array.
830 * @param array $addresses
831 * Associated array of.
834 * associated array of contact names
836 public static function getAddressShareContactNames(&$addresses) {
838 // get the list of master id's for address
839 $masterAddressIds = [];
840 foreach ($addresses as $key => $addressValue) {
841 if (!empty($addressValue['master_id'])) {
842 $masterAddressIds[] = $addressValue['master_id'];
846 if (!empty($masterAddressIds)) {
847 $query = 'SELECT ca.id, cc.display_name, cc.id as cid, cc.is_deleted
848 FROM civicrm_contact cc
849 INNER JOIN civicrm_address ca ON cc.id = ca.contact_id
850 WHERE ca.id IN ( ' . implode(',', $masterAddressIds) . ')';
851 $dao = CRM_Core_DAO
::executeQuery($query);
853 while ($dao->fetch()) {
854 $contactViewUrl = CRM_Utils_System
::url('civicrm/contact/view', "reset=1&cid={$dao->cid}");
855 $contactNames[$dao->id
] = [
856 'name' => "<a href='{$contactViewUrl}'>{$dao->display_name}</a>",
857 'is_deleted' => $dao->is_deleted
,
858 'contact_id' => $dao->cid
,
862 return $contactNames;
866 * Clear the contact cache so things are kosher. We started off being super aggressive with clearing
867 * caches, but are backing off from this with every release. Compromise between ease of coding versus
868 * performance versus being accurate at that very instant
870 * @param bool $isEmptyPrevNextTable
871 * Should the civicrm_prev_next table be cleared of any contact entries.
872 * This is currently done from import but not other places and would
873 * likely affect user experience in unexpected ways. Existing behaviour retained
876 public static function clearContactCaches($isEmptyPrevNextTable = FALSE) {
877 if (!CRM_Core_Config
::isPermitCacheFlushMode()) {
880 if ($isEmptyPrevNextTable) {
881 // These two calls are redundant in default deployments, but they're
882 // meaningful if "prevnext" is memory-backed.
883 Civi
::service('prevnext')->deleteItem();
884 CRM_Core_BAO_PrevNextCache
::deleteItem();
886 // clear acl cache if any.
887 CRM_ACL_BAO_Cache
::resetCache();
888 CRM_Contact_BAO_GroupContactCache
::opportunisticCacheFlush();
892 * @param array $params
896 public static function updateGreeting($params) {
897 $contactType = $params['ct'];
898 $greeting = $params['gt'];
899 $valueID = $id = CRM_Utils_Array
::value('id', $params);
900 $force = CRM_Utils_Array
::value('force', $params);
901 $limit = CRM_Utils_Array
::value('limit', $params);
903 // if valueID is not passed use default value
905 $valueID = $id = self
::defaultGreeting($contactType, $greeting);
909 'contact_type' => $contactType,
910 'greeting_type' => $greeting,
913 $allGreetings = CRM_Core_PseudoConstant
::greeting($filter);
914 $originalGreetingString = $greetingString = CRM_Utils_Array
::value($valueID, $allGreetings);
915 if (!$greetingString) {
916 throw new CRM_Core_Exception(ts('Incorrect greeting value id %1, or no default greeting for this contact type and greeting type.', [1 => $valueID]));
919 // build return properties based on tokens
920 $greetingTokens = CRM_Utils_Token
::getTokens($greetingString);
921 $tokens = CRM_Utils_Array
::value('contact', $greetingTokens);
922 $greetingsReturnProperties = [];
923 if (is_array($tokens)) {
924 $greetingsReturnProperties = array_fill_keys(array_values($tokens), 1);
927 // Process ALL contacts only when force=1 or force=2 is passed. Else only contacts with NULL greeting or addressee value are updated.
928 $processAll = $processOnlyIdSet = FALSE;
932 elseif ($force == 2) {
933 $processOnlyIdSet = TRUE;
936 //FIXME : apiQuery should handle these clause.
937 $filterContactFldIds = $filterIds = [];
938 $idFldName = $displayFldName = NULL;
939 if (in_array($greeting, CRM_Contact_BAO_Contact
::$_greetingTypes)) {
940 $idFldName = $greeting . '_id';
941 $displayFldName = $greeting . '_display';
945 $queryParams = [1 => [$contactType, 'String']];
947 // if $force == 1 then update all contacts else only
948 // those with NULL greeting or addressee value CRM-9476
950 $sql = "SELECT DISTINCT id, $idFldName FROM civicrm_contact WHERE contact_type = %1 ";
954 SELECT DISTINCT id, $idFldName
956 WHERE contact_type = %1
957 AND ({$idFldName} IS NULL
958 OR ( {$idFldName} IS NOT NULL AND ({$displayFldName} IS NULL OR {$displayFldName} = '')) )";
962 $sql .= " LIMIT 0, %2";
963 $queryParams +
= [2 => [$limit, 'Integer']];
966 $dao = CRM_Core_DAO
::executeQuery($sql, $queryParams);
967 while ($dao->fetch()) {
968 $filterContactFldIds[$dao->id
] = $dao->$idFldName;
970 if (!CRM_Utils_System
::isNull($dao->$idFldName)) {
971 $filterIds[$dao->id
] = $dao->$idFldName;
976 if (empty($filterContactFldIds)) {
977 $filterContactFldIds[] = 0;
980 // retrieve only required contact information
981 $extraParams[] = ['contact_type', '=', $contactType, 0, 0];
982 // we do token replacement in the replaceGreetingTokens hook
983 list($greetingDetails) = CRM_Utils_Token
::getTokenDetails(array_keys($filterContactFldIds),
984 $greetingsReturnProperties,
985 FALSE, FALSE, $extraParams
987 // perform token replacement and build update SQL
989 $cacheFieldQuery = "UPDATE civicrm_contact SET {$greeting}_display = CASE id ";
990 foreach ($greetingDetails as $contactID => $contactDetails) {
992 !array_key_exists($contactID, $filterContactFldIds)
997 if ($processOnlyIdSet && !array_key_exists($contactID, $filterIds)) {
1002 $greetingString = $originalGreetingString;
1003 $contactIds[] = $contactID;
1006 if ($greetingBuffer = CRM_Utils_Array
::value($filterContactFldIds[$contactID], $allGreetings)) {
1007 $greetingString = $greetingBuffer;
1011 self
::processGreetingTemplate($greetingString, $contactDetails, $contactID, 'CRM_UpdateGreeting');
1012 $greetingString = CRM_Core_DAO
::escapeString($greetingString);
1013 $cacheFieldQuery .= " WHEN {$contactID} THEN '{$greetingString}' ";
1015 $allContactIds[] = $contactID;
1018 if (!empty($allContactIds)) {
1019 $cacheFieldQuery .= " ELSE {$greeting}_display
1021 if (!empty($contactIds)) {
1022 // need to update greeting _id field.
1023 // reset greeting _custom
1024 $resetCustomGreeting = '';
1025 if ($valueID != 4) {
1026 $resetCustomGreeting = ", {$greeting}_custom = NULL ";
1030 UPDATE civicrm_contact
1031 SET {$greeting}_id = {$valueID}
1032 {$resetCustomGreeting}
1033 WHERE id IN (" . implode(',', $contactIds) . ")";
1034 CRM_Core_DAO
::executeQuery($queryString);
1037 // now update cache field
1038 CRM_Core_DAO
::executeQuery($cacheFieldQuery);
1043 * Fetch the default greeting for a given contact type.
1045 * @param string $contactType
1047 * @param string $greetingType
1052 public static function defaultGreeting($contactType, $greetingType) {
1053 $contactTypeFilters = [
1056 'Organization' => 3,
1058 if (!isset($contactTypeFilters[$contactType])) {
1061 $filter = $contactTypeFilters[$contactType];
1063 $id = CRM_Core_OptionGroup
::values($greetingType, NULL, NULL, NULL,
1064 " AND is_default = 1 AND (filter = {$filter} OR filter = 0)",
1068 return current($id);
1073 * Get the tokens that will need to be resolved to populate the contact's greetings.
1075 * @param array $contactParams
1078 * Array of tokens. The ALL ke
1080 public static function getTokensRequiredForContactGreetings($contactParams) {
1082 foreach (['addressee', 'email_greeting', 'postal_greeting'] as $greeting) {
1084 if (!empty($contactParams[$greeting . '_id'])) {
1085 $string = CRM_Core_PseudoConstant
::getLabel('CRM_Contact_BAO_Contact', $greeting . '_id', $contactParams[$greeting . '_id']);
1087 $string = isset($contactParams[$greeting . '_custom']) ?
$contactParams[$greeting . '_custom'] : $string;
1088 if (empty($string)) {
1089 $tokens[$greeting] = [];
1092 $tokens[$greeting] = CRM_Utils_Token
::getTokens($string);
1095 $allTokens = array_merge_recursive($tokens['addressee'], $tokens['email_greeting'], $tokens['postal_greeting']);
1096 $tokens['all'] = $allTokens;
1101 * Process a greeting template string to produce the individualised greeting text.
1103 * This works just like message templates for mailings:
1104 * the template is processed with the token substitution mechanism,
1105 * to supply the individual contact data;
1106 * and it is also processed with Smarty,
1107 * to allow for conditionals etc. based on the contact data.
1109 * Note: We don't pass any variables to Smarty --
1110 * all variable data is inserted into the input string
1111 * by the token substitution mechanism,
1112 * before Smarty is invoked.
1114 * @param string $templateString
1115 * The greeting template string with contact tokens + Smarty syntax.
1117 * @param array $contactDetails
1118 * @param int $contactID
1119 * @param string $className
1121 public static function processGreetingTemplate(&$templateString, $contactDetails, $contactID, $className) {
1122 CRM_Utils_Token
::replaceGreetingTokens($templateString, $contactDetails, $contactID, $className, TRUE);
1124 $smarty = CRM_Core_Smarty
::singleton();
1125 $templateString = $smarty->fetch("string:$templateString");
1129 * Determine if a contact ID is real/valid.
1131 * @param int $contactId
1132 * The hypothetical contact ID
1135 * @throws \CRM_Core_Exception
1137 public static function isContactId($contactId) {
1139 // ensure that this is a valid contact id (for session inconsistency rules)
1140 $cid = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact',