Merge pull request #4857 from atif-shaikh/CRM-15203
[civicrm-core.git] / CRM / Contact / BAO / Contact / Utils.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
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 +--------------------------------------------------------------------+
26 */
27
28 /**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2014
32 * $Id$
33 *
34 */
35 class CRM_Contact_BAO_Contact_Utils {
36
37 /**
38 * Given a contact type, get the contact image
39 *
40 * @param string $contactType contact type
41 * @param boolean $urlOnly if we need to return only image url
42 * @param int $contactId contact id
43 * @param boolean $addProfileOverlay if profile overlay class should be added
44 *
45 * @return string
46 * @static
47 */
48 public static function getImage($contactType, $urlOnly = FALSE, $contactId = NULL, $addProfileOverlay = TRUE) {
49 static $imageInfo = array();
50
51 $contactType = explode(CRM_Core_DAO::VALUE_SEPARATOR, trim($contactType, CRM_Core_DAO::VALUE_SEPARATOR));
52 $contactType = $contactType[0];
53
54 if (!array_key_exists($contactType, $imageInfo)) {
55 $imageInfo[$contactType] = array();
56
57 $typeInfo = array();
58 $params = array('name' => $contactType);
59 CRM_Contact_BAO_ContactType::retrieve($params, $typeInfo);
60
61 if (!empty($typeInfo['image_URL'])) {
62 $imageUrl = $typeInfo['image_URL'];
63 $config = CRM_Core_Config::singleton();
64
65 if (!preg_match("/^(\/|(http(s)?:)).+$/i", $imageUrl)) {
66 $imageUrl = $config->resourceBase . $imageUrl;
67 }
68 $imageInfo[$contactType]['image'] = "<div class=\"icon crm-icon {$typeInfo['name']}-icon\" style=\"background: url('{$imageUrl}')\" title=\"{$contactType}\"></div>";
69 $imageInfo[$contactType]['url'] = $imageUrl;
70 }
71 else {
72 $isSubtype = (array_key_exists('parent_id', $typeInfo) &&
73 $typeInfo['parent_id']
74 ) ? TRUE : FALSE;
75
76 if ($isSubtype) {
77 $type = CRM_Contact_BAO_ContactType::getBasicType($typeInfo['name']) . '-subtype';
78 }
79 else {
80 $type = CRM_Utils_Array::value('name', $typeInfo);
81 }
82
83 // do not add title since it hides contact name
84 if ($addProfileOverlay) {
85 $imageInfo[$contactType]['image'] = "<div class=\"icon crm-icon {$type}-icon\"></div>";
86 }
87 else{
88 $imageInfo[$contactType]['image'] = "<div class=\"icon crm-icon {$type}-icon\" title=\"{$contactType}\"></div>";
89 }
90 $imageInfo[$contactType]['url'] = NULL;
91 }
92 }
93
94 if ($addProfileOverlay) {
95 static $summaryOverlayProfileId = NULL;
96 if (!$summaryOverlayProfileId) {
97 $summaryOverlayProfileId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup', 'summary_overlay', 'id', 'name');
98 }
99
100 $profileURL = CRM_Utils_System::url('civicrm/profile/view',
101 "reset=1&gid={$summaryOverlayProfileId}&id={$contactId}&snippet=4"
102 );
103
104 $imageInfo[$contactType]['summary-link'] = '<a href="' . $profileURL . '" class="crm-summary-link">' . $imageInfo[$contactType]['image'] . '</a>';
105 }
106 else {
107 $imageInfo[$contactType]['summary-link'] = $imageInfo[$contactType]['image'];
108 }
109
110 return $urlOnly ? $imageInfo[$contactType]['url'] : $imageInfo[$contactType]['summary-link'];
111 }
112
113 /**
114 * Function check for mix contact ids(individual+household etc...)
115 *
116 * @param array $contactIds array of contact ids
117 *
118 * @return boolen true or false true if mix contact array else fale
119 *
120 * @static
121 */
122 public static function checkContactType(&$contactIds) {
123 if (empty($contactIds)) {
124 return FALSE;
125 }
126
127 $idString = implode(',', $contactIds);
128 $query = "
129 SELECT count( DISTINCT contact_type )
130 FROM civicrm_contact
131 WHERE id IN ( $idString )
132 ";
133 $count = CRM_Core_DAO::singleValueQuery($query,
134 CRM_Core_DAO::$_nullArray
135 );
136 return $count > 1 ? TRUE : FALSE;
137 }
138
139 /**
140 * Generate a checksum for a $entityId of type $entityType
141 *
142 * @param int $entityId
143 * @param int $ts timestamp that checksum was generated
144 * @param int $live life of this checksum in hours/ 'inf' for infinite
145 * @param string $hash contact hash, if sent, prevents a query in inner loop
146 *
147 * @param string $entityType
148 * @param null $hashSize
149 *
150 * @return array ( $cs, $ts, $live )
151 * @static
152 */
153 public static function generateChecksum($entityId, $ts = NULL, $live = NULL, $hash = NULL, $entityType = 'contact', $hashSize = NULL) {
154 // return a warning message if we dont get a entityId
155 // this typically happens when we do a message preview
156 // or an anon mailing view - CRM-8298
157 if (!$entityId) {
158 return 'invalidChecksum';
159 }
160
161 if (!$hash) {
162 if ($entityType == 'contact') {
163 $hash = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact',
164 $entityId, 'hash'
165 );
166 }
167 elseif ($entityType == 'mailing') {
168 $hash = CRM_Core_DAO::getFieldValue('CRM_Mailing_DAO_Mailing',
169 $entityId, 'hash'
170 );
171 }
172 }
173
174 if (!$hash) {
175 $hash = md5(uniqid(rand(), TRUE));
176 if ($hashSize) {
177 $hash = substr($hash, 0, $hashSize);
178 }
179
180 if ($entityType == 'contact') {
181 CRM_Core_DAO::setFieldValue('CRM_Contact_DAO_Contact',
182 $entityId,
183 'hash', $hash
184 );
185 }
186 elseif ($entityType == 'mailing') {
187 CRM_Core_DAO::setFieldValue('CRM_Mailing_DAO_Mailing',
188 $entityId,
189 'hash', $hash
190 );
191 }
192 }
193
194 if (!$ts) {
195 $ts = time();
196 }
197
198 if (!$live) {
199 $days = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
200 'checksum_timeout',
201 NULL,
202 7
203 );
204 $live = 24 * $days;
205 }
206
207 $cs = md5("{$hash}_{$entityId}_{$ts}_{$live}");
208 return "{$cs}_{$ts}_{$live}";
209 }
210
211 /**
212 * Make sure the checksum is valid for the passed in contactID
213 *
214 * @param int $contactID
215 * @param string $inputCheck checksum to match against
216 *
217 * @return boolean true if valid, else false
218 * @static
219 */
220 public static function validChecksum($contactID, $inputCheck) {
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);
229
230 if ($check != $inputCheck) {
231 return FALSE;
232 }
233
234 // no life limit for checksum
235 if ($inputLF == 'inf') {
236 return TRUE;
237 }
238
239 // checksum matches so now check timestamp
240 $now = time();
241 return ($inputTS + ($inputLF * 60 * 60) >= $now);
242 }
243
244 /**
245 * Get the count of contact loctions
246 *
247 * @param int $contactId contact id
248 *
249 * @return int $locationCount max locations for the contact
250 * @static
251 */
252 public static function maxLocations($contactId) {
253 $contactLocations = array();
254
255 // find number of location blocks for this contact and adjust value accordinly
256 // get location type from email
257 $query = "
258 ( SELECT location_type_id FROM civicrm_email WHERE contact_id = {$contactId} )
259 UNION
260 ( SELECT location_type_id FROM civicrm_phone WHERE contact_id = {$contactId} )
261 UNION
262 ( SELECT location_type_id FROM civicrm_im WHERE contact_id = {$contactId} )
263 UNION
264 ( SELECT location_type_id FROM civicrm_address WHERE contact_id = {$contactId} )
265 ";
266 $dao = CRM_Core_DAO::executeQuery($query, CRM_Core_DAO::$_nullArray);
267 return $dao->N;
268 }
269
270 /**
271 * Create Current employer relationship for a individual
272 *
273 * @param int $contactID contact id of the individual
274 * @param $organization (id or name)
275 * @param int $previousEmployerID
276 * @param bool $newContact
277 *
278 * @static
279 */
280 public static function createCurrentEmployerRelationship($contactID, $organization, $previousEmployerID = NULL, $newContact = FALSE) {
281 //if organization name is passed. CRM-15368,CRM-15547
282 if ($organization && !is_numeric($organization)) {
283 $organizationParams['organization_name'] = $organization;
284 $dedupeParams = CRM_Dedupe_Finder::formatParams($organizationParams, 'Organization');
285
286 $dedupeParams['check_permission'] = FALSE;
287 $dupeIDs = CRM_Dedupe_Finder::dupesByParams($dedupeParams, 'Organization', 'Unsupervised');
288
289 if (is_array($dupeIDs) && !empty($dupeIDs)) {
290 // we should create relationship only w/ first org CRM-4193
291 foreach ($dupeIDs as $orgId) {
292 $organization = $orgId;
293 break;
294 }
295 }
296 else {
297 //create new organization
298 $newOrg = array(
299 'contact_type' => 'Organization',
300 'organization_name' => $organization,
301 );
302 $org = CRM_Contact_BAO_Contact::create($newOrg);
303 $organization = $org->id;
304 }
305 }
306
307 if ($organization && is_numeric($organization)) {
308 $cid = array('contact' => $contactID);
309
310 // get the relationship type id of "Employee of"
311 $relTypeId = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_RelationshipType', 'Employee of', 'id', 'name_a_b');
312 if (!$relTypeId) {
313 CRM_Core_Error::fatal(ts("You seem to have deleted the relationship type 'Employee of'"));
314 }
315
316 // create employee of relationship
317 $relationshipParams = array(
318 'is_active' => TRUE,
319 'relationship_type_id' => $relTypeId . '_a_b',
320 'contact_check' => array($organization => TRUE),
321 );
322 list($valid, $invalid, $duplicate,
323 $saved, $relationshipIds
324 ) = CRM_Contact_BAO_Relationship::createMultiple($relationshipParams, $cid);
325
326
327 // In case we change employer, clean previous employer related records.
328 if (!$previousEmployerID && !$newContact) {
329 $previousEmployerID = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $contactID, 'employer_id');
330 }
331 if ($previousEmployerID &&
332 $previousEmployerID != $organization
333 ) {
334 self::clearCurrentEmployer($contactID, $previousEmployerID);
335 }
336
337 // set current employer
338 self::setCurrentEmployer(array($contactID => $organization));
339
340 $relationshipParams['relationship_ids'] = $relationshipIds;
341 // handle related meberships. CRM-3792
342 self::currentEmployerRelatedMembership($contactID, $organization, $relationshipParams, $duplicate, $previousEmployerID);
343 }
344 }
345
346 /**
347 * Create related memberships for current employer
348 *
349 * @param int $contactID contact id of the individual
350 * @param int $employerID contact id of the organization.
351 * @param array $relationshipParams relationship params.
352 * @param boolean $duplicate are we triggered existing relationship.
353 *
354 * @param int $previousEmpID
355 *
356 * @throws CiviCRM_API3_Exception
357 * @static
358 */
359 public static function currentEmployerRelatedMembership($contactID, $employerID, $relationshipParams, $duplicate = FALSE, $previousEmpID = NULL) {
360 $ids = array();
361 $action = CRM_Core_Action::ADD;
362
363 //we do not know that triggered relationship record is active.
364 if ($duplicate) {
365 $relationship = new CRM_Contact_DAO_Relationship();
366 $relationship->contact_id_a = $contactID;
367 $relationship->contact_id_b = $employerID;
368 $relationship->relationship_type_id = $relationshipParams['relationship_type_id'];
369 if ($relationship->find(TRUE)) {
370 $action = CRM_Core_Action::UPDATE;
371 $ids['contact'] = $contactID;
372 $ids['contactTarget'] = $employerID;
373 $ids['relationship'] = $relationship->id;
374 CRM_Contact_BAO_Relationship::setIsActive($relationship->id, TRUE);
375 }
376 $relationship->free();
377 }
378
379 //need to handle related meberships. CRM-3792
380 if ($previousEmpID != $employerID) {
381 CRM_Contact_BAO_Relationship::relatedMemberships($contactID, $relationshipParams, $ids, $action);
382 }
383 }
384
385 /**
386 * Set current employer id and organization name
387 *
388 * @param array $currentEmployerParams associated array of contact id and its employer id
389 *
390 */
391 public static function setCurrentEmployer($currentEmployerParams) {
392 foreach ($currentEmployerParams as $contactId => $orgId) {
393 $query = "UPDATE civicrm_contact contact_a,civicrm_contact contact_b
394 SET contact_a.employer_id=contact_b.id, contact_a.organization_name=contact_b.organization_name
395 WHERE contact_a.id ={$contactId} AND contact_b.id={$orgId}; ";
396
397 //FIXME : currently civicrm mysql_query support only single statement
398 //execution, though mysql 5.0 support multiple statement execution.
399 $dao = CRM_Core_DAO::executeQuery($query);
400 }
401 }
402
403 /**
404 * Update cached current employer name
405 *
406 * @param int $organizationId current employer id
407 *
408 */
409 public static function updateCurrentEmployer($organizationId) {
410 $query = "UPDATE civicrm_contact contact_a,civicrm_contact contact_b
411 SET contact_a.organization_name=contact_b.organization_name
412 WHERE contact_a.employer_id=contact_b.id AND contact_b.id={$organizationId}; ";
413
414 $dao = CRM_Core_DAO::executeQuery($query);
415 }
416
417 /**
418 * Clear cached current employer name
419 *
420 * @param int $contactId contact id ( mostly individual contact id)
421 * @param int $employerId contact id ( mostly organization contact id)
422 *
423 */
424 public static function clearCurrentEmployer($contactId, $employerId = NULL) {
425 $query = "UPDATE civicrm_contact
426 SET organization_name=NULL, employer_id = NULL
427 WHERE id={$contactId}; ";
428
429 $dao = CRM_Core_DAO::executeQuery($query);
430
431 // need to handle related meberships. CRM-3792
432 if ($employerId) {
433 //1. disable corresponding relationship.
434 //2. delete related membership.
435
436 //get the relationship type id of "Employee of"
437 $relTypeId = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_RelationshipType', 'Employee of', 'id', 'name_a_b');
438 if (!$relTypeId) {
439 CRM_Core_Error::fatal(ts("You seem to have deleted the relationship type 'Employee of'"));
440 }
441 $relMembershipParams['relationship_type_id'] = $relTypeId . '_a_b';
442 $relMembershipParams['contact_check'][$employerId] = 1;
443
444 //get relationship id.
445 if (CRM_Contact_BAO_Relationship::checkDuplicateRelationship($relMembershipParams, $contactId, $employerId)) {
446 $relationship = new CRM_Contact_DAO_Relationship();
447 $relationship->contact_id_a = $contactId;
448 $relationship->contact_id_b = $employerId;
449 $relationship->relationship_type_id = $relTypeId;
450
451 if ($relationship->find(TRUE)) {
452 CRM_Contact_BAO_Relationship::setIsActive($relationship->id, FALSE);
453 CRM_Contact_BAO_Relationship::relatedMemberships($contactId, $relMembershipParams,
454 $ids = array(
455 ), CRM_Core_Action::DELETE
456 );
457 }
458 $relationship->free();
459 }
460 }
461 }
462
463 /**
464 * Build form for related contacts / on behalf of organization.
465 *
466 * @param CRM_Core_Form $form
467 * @param $contactType string contact type
468 * @param int $countryID
469 * @param int $stateID
470 * @param $title string fieldset title
471 *
472 * @static
473 */
474 public static function buildOnBehalfForm(&$form, $contactType, $countryID, $stateID, $title) {
475
476 $config = CRM_Core_Config::singleton();
477
478 $form->assign('contact_type', $contactType);
479 $form->assign('fieldSetTitle', $title);
480 $form->assign('contactEditMode', TRUE);
481
482 $attributes = CRM_Core_DAO::getAttribute('CRM_Contact_DAO_Contact');
483 if ($form->_contactId) {
484 $form->assign('orgId', $form->_contactId);
485 }
486
487 switch ($contactType) {
488 case 'Organization':
489 $form->add('text', 'organization_name', ts('Organization Name'), $attributes['organization_name'], TRUE);
490 break;
491
492 case 'Household':
493 $form->add('text', 'household_name', ts('Household Name'), $attributes['household_name']);
494 break;
495
496 default:
497 // individual
498 $form->addElement('select', 'prefix_id', ts('Prefix'),
499 array('' => ts('- prefix -')) + CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'prefix_id')
500 );
501 $form->addElement('text', 'first_name', ts('First Name'),
502 $attributes['first_name']
503 );
504 $form->addElement('text', 'middle_name', ts('Middle Name'),
505 $attributes['middle_name']
506 );
507 $form->addElement('text', 'last_name', ts('Last Name'),
508 $attributes['last_name']
509 );
510 $form->addElement('select', 'suffix_id', ts('Suffix'),
511 array('' => ts('- suffix -')) + CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'suffix_id')
512 );
513 }
514
515 $addressSequence = $config->addressSequence();
516 $form->assign('addressSequence', array_fill_keys($addressSequence, 1));
517
518 //Primary Phone
519 $form->addElement('text',
520 'phone[1][phone]',
521 ts('Primary Phone'),
522 CRM_Core_DAO::getAttribute('CRM_Core_DAO_Phone',
523 'phone'
524 )
525 );
526 //Primary Email
527 $form->addElement('text',
528 'email[1][email]',
529 ts('Primary Email'),
530 CRM_Core_DAO::getAttribute('CRM_Core_DAO_Email',
531 'email'
532 )
533 );
534 //build the address block
535 CRM_Contact_Form_Edit_Address::buildQuickForm($form);
536 }
537
538 /**
539 * Clear cache employer name and employer id
540 * of all employee when employer get deleted.
541 *
542 * @param int $employerId contact id of employer ( organization id )
543 *
544 */
545 public static function clearAllEmployee($employerId) {
546 $query = "
547 UPDATE civicrm_contact
548 SET organization_name=NULL, employer_id = NULL
549 WHERE employer_id={$employerId}; ";
550
551 $dao = CRM_Core_DAO::executeQuery($query, CRM_Core_DAO::$_nullArray);
552 }
553
554 /**
555 * Given an array of contact ids this function will return array with links to view contact page
556 *
557 * @param array $contactIDs associated contact id's
558 * @param bool $addViewLink
559 * @param bool $addEditLink
560 * @param int $originalId associated with the contact which is edited
561 *
562 *
563 * @return array $contactViewLinks returns array with links to contact view
564 * @static
565 */
566 public static function formatContactIDSToLinks($contactIDs, $addViewLink = TRUE, $addEditLink = TRUE, $originalId = NULL) {
567 $contactLinks = array();
568 if (!is_array($contactIDs) || empty($contactIDs)) {
569 return $contactLinks;
570 }
571
572 // does contact has sufficient permissions.
573 $permissions = array(
574 'view' => 'view all contacts',
575 'edit' => 'edit all contacts',
576 'merge' => 'merge duplicate contacts',
577 );
578
579 $permissionedContactIds = array();
580 foreach ($permissions as $task => $permission) {
581 // give permission.
582 if (CRM_Core_Permission::check($permission)) {
583 foreach ($contactIDs as $contactId) {
584 $permissionedContactIds[$contactId][$task] = TRUE;
585 }
586 continue;
587 }
588
589 // check permission on acl basis.
590 if (in_array($task, array(
591 'view', 'edit'))) {
592 $aclPermission = CRM_Core_Permission::VIEW;
593 if ($task == 'edit') {
594 $aclPermission = CRM_Core_Permission::EDIT;
595 }
596 foreach ($contactIDs as $contactId) {
597 if (CRM_Contact_BAO_Contact_Permission::allow($contactId, $aclPermission)) {
598 $permissionedContactIds[$contactId][$task] = TRUE;
599 }
600 }
601 }
602 }
603
604 // retrieve display names for all contacts
605 $query = '
606 SELECT c.id, c.display_name, c.contact_type, ce.email
607 FROM civicrm_contact c
608 LEFT JOIN civicrm_email ce ON ( ce.contact_id=c.id AND ce.is_primary = 1 )
609 WHERE c.id IN (' . implode(',', $contactIDs) . ' ) LIMIT 20';
610
611 $dao = CRM_Core_DAO::executeQuery($query);
612
613 $contactLinks['msg'] = NULL;
614 $i = 0;
615 while ($dao->fetch()) {
616
617 $contactLinks['rows'][$i]['display_name'] = $dao->display_name;
618 $contactLinks['rows'][$i]['primary_email'] = $dao->email;
619
620 // get the permission for current contact id.
621 $hasPermissions = CRM_Utils_Array::value($dao->id, $permissionedContactIds);
622 if (!is_array($hasPermissions) || empty($hasPermissions)) {
623 $i++;
624 continue;
625 }
626
627 // do check for view.
628 if (array_key_exists('view', $hasPermissions)) {
629 $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>';
630 if (!$contactLinks['msg']) {
631 $contactLinks['msg'] = 'view';
632 }
633 }
634 if (array_key_exists('edit', $hasPermissions)) {
635 $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>';
636 if (!$contactLinks['msg'] || $contactLinks['msg'] != 'merge') {
637 $contactLinks['msg'] = 'edit';
638 }
639 }
640 if (!empty($originalId) && array_key_exists('merge', $hasPermissions)) {
641 $rgBao = new CRM_Dedupe_BAO_RuleGroup();
642 $rgBao->contact_type = $dao->contact_type;
643 $rgBao->used = 'Supervised';
644 if ($rgBao->find(TRUE)) {
645 $rgid = $rgBao->id;
646 }
647 if ($rgid && isset($dao->id)) {
648 //get an url to merge the contact
649 $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>';
650 $contactLinks['msg'] = 'merge';
651 }
652 }
653
654 $i++;
655 }
656
657 return $contactLinks;
658 }
659
660 /**
661 * This function retrieve component related contact information.
662 *
663 * @param array $componentIds array of component Ids.
664 * @param string $componentName
665 * @param array $returnProperties array of return elements.
666 *
667 * @return array $contactDetails array of contact info.@static
668 */
669 public static function contactDetails($componentIds, $componentName, $returnProperties = array()) {
670 $contactDetails = array();
671 if (empty($componentIds) ||
672 !in_array($componentName, array('CiviContribute', 'CiviMember', 'CiviEvent', 'Activity'))
673 ) {
674 return $contactDetails;
675 }
676
677 if (empty($returnProperties)) {
678 $autocompleteContactSearch = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
679 'contact_autocomplete_options'
680 );
681 $returnProperties = array_fill_keys(array_merge(array('sort_name'),
682 array_keys($autocompleteContactSearch)
683 ), 1);
684 }
685
686 $compTable = NULL;
687 if ($componentName == 'CiviContribute') {
688 $compTable = 'civicrm_contribution';
689 }
690 elseif ($componentName == 'CiviMember') {
691 $compTable = 'civicrm_membership';
692 }
693 elseif ($componentName == 'Activity') {
694 $compTable = 'civicrm_activity';
695 $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name');
696 }
697 else {
698 $compTable = 'civicrm_participant';
699 }
700
701 $select = $from = array();
702 foreach ($returnProperties as $property => $ignore) {
703 $value = (in_array($property, array(
704 'city', 'street_address'))) ? 'address' : $property;
705 switch ($property) {
706 case 'sort_name':
707 if ($componentName == 'Activity') {
708 $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts);
709 $select[] = "contact.$property as $property";
710 $from[$value] = "
711 INNER JOIN civicrm_activity_contact acs ON (acs.activity_id = {$compTable}.id AND acs.record_type_id = {$sourceID})
712 INNER JOIN civicrm_contact contact ON ( contact.id = acs.contact_id )";
713 }
714 else {
715 $select[] = "$property as $property";
716 $from[$value] = "INNER JOIN civicrm_contact contact ON ( contact.id = $compTable.contact_id )";
717 }
718 break;
719
720 case 'target_sort_name':
721 $targetID = CRM_Utils_Array::key('Activity Targets', $activityContacts);
722 $select[] = "contact_target.sort_name as $property";
723 $from[$value] = "
724 INNER JOIN civicrm_activity_contact act ON (act.activity_id = {$compTable}.id AND act.record_type_id = {$targetID})
725 INNER JOIN civicrm_contact contact_target ON ( contact_target.id = act.contact_id )";
726 break;
727
728 case 'email':
729 case 'phone':
730 case 'city':
731 case 'street_address':
732 $select[] = "$property as $property";
733 // Grab target contact properties if this is for activity
734 if ($componentName == 'Activity') {
735 $from[$value] = "LEFT JOIN civicrm_{$value} {$value} ON ( contact_target.id = {$value}.contact_id AND {$value}.is_primary = 1 ) ";
736 }
737 else {
738 $from[$value] = "LEFT JOIN civicrm_{$value} {$value} ON ( contact.id = {$value}.contact_id AND {$value}.is_primary = 1 ) ";
739 }
740 break;
741
742 case 'country':
743 case 'state_province':
744 $select[] = "{$property}.name as $property";
745 if (!in_array('address', $from)) {
746 // Grab target contact properties if this is for activity
747 if ($componentName == 'Activity') {
748 $from['address'] = 'LEFT JOIN civicrm_address address ON ( contact_target.id = address.contact_id AND address.is_primary = 1) ';
749 }
750 else {
751 $from['address'] = 'LEFT JOIN civicrm_address address ON ( contact.id = address.contact_id AND address.is_primary = 1) ';
752 }
753 }
754 $from[$value] = " LEFT JOIN civicrm_{$value} {$value} ON ( address.{$value}_id = {$value}.id ) ";
755 break;
756 }
757 }
758
759 //finally retrieve contact details.
760 if (!empty($select) && !empty($from)) {
761 $fromClause = implode(' ', $from);
762 $selectClause = implode(', ', $select);
763 $whereClause = "{$compTable}.id IN (" . implode(',', $componentIds) . ')';
764
765 $query = "
766 SELECT contact.id as contactId, $compTable.id as componentId, $selectClause
767 FROM $compTable as $compTable $fromClause
768 WHERE $whereClause
769 Group By componentId";
770
771 $contact = CRM_Core_DAO::executeQuery($query);
772 while ($contact->fetch()) {
773 $contactDetails[$contact->componentId]['contact_id'] = $contact->contactId;
774 foreach ($returnProperties as $property => $ignore) {
775 $contactDetails[$contact->componentId][$property] = $contact->$property;
776 }
777 }
778 $contact->free();
779 }
780
781 return $contactDetails;
782 }
783
784 /**
785 * Function handles shared contact address processing
786 * In this function we just modify submitted values so that new address created for the user
787 * has same address as shared contact address. We copy the address so that search etc will be
788 * much efficient.
789 *
790 * @param array $address this is associated array which contains submitted form values
791 *
792 * @return void
793 * @static
794 */
795 public static function processSharedAddress(&$address) {
796 if (!is_array($address)) {
797 return;
798 }
799
800 // Sharing contact address during create mode is pretty straight forward.
801 // In update mode we should check following:
802 // - We should check if user has uncheck shared contact address
803 // - If yes then unset the master_id or may be just delete the address that copied master
804 // Normal update process will automatically create new address with submitted values
805
806 // 1. loop through entire subnitted address array
807 $masterAddress = array();
808 $skipFields = array('is_primary', 'location_type_id', 'is_billing', 'master_id');
809 foreach ($address as & $values) {
810 // 2. check if master id exists, if not continue
811 if (empty($values['master_id']) || empty($values['use_shared_address'])) {
812 // we should unset master id when use uncheck share address for existing address
813 $values['master_id'] = 'null';
814 continue;
815 }
816
817 // 3. get the address details for master_id
818 $masterAddress = new CRM_Core_BAO_Address();
819 $masterAddress->id = CRM_Utils_Array::value('master_id', $values);
820 $masterAddress->find(TRUE);
821
822 // 4. modify submitted params and update it with shared contact address
823 // make sure you preserve specific form values like location type, is_primary_ is_billing, master_id
824 // CRM-10336: Also empty any fields from the existing address block if they don't exist in master (otherwise they will persist)
825 foreach ($values as $field => $submittedValue) {
826 if (!in_array($field, $skipFields)){
827 if (isset($masterAddress->$field)) {
828 $values[$field] = $masterAddress->$field;
829 } else {
830 $values[$field] = '';
831 }
832 }
833 }
834 }
835 }
836
837 /**
838 * Get the list of contact name give address associated array
839 *
840 * @param array $addresses associated array of
841 *
842 * @return array $contactNames associated array of contact names@static
843 */
844 public static function getAddressShareContactNames(&$addresses) {
845 $contactNames = array();
846 // get the list of master id's for address
847 $masterAddressIds = array();
848 foreach ($addresses as $key => $addressValue) {
849 if (!empty($addressValue['master_id'])) {
850 $masterAddressIds[] = $addressValue['master_id'];
851 }
852 }
853
854 if (!empty($masterAddressIds)) {
855 $query = 'SELECT ca.id, cc.display_name, cc.id as cid, cc.is_deleted
856 FROM civicrm_contact cc
857 INNER JOIN civicrm_address ca ON cc.id = ca.contact_id
858 WHERE ca.id IN ( ' . implode(',', $masterAddressIds) . ')';
859 $dao = CRM_Core_DAO::executeQuery($query);
860
861 while ($dao->fetch()) {
862 $contactViewUrl = CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$dao->cid}");
863 $contactNames[$dao->id] = array(
864 'name' => "<a href='{$contactViewUrl}'>{$dao->display_name}</a>",
865 'is_deleted' => $dao->is_deleted,
866 'contact_id' => $dao->cid,
867 );
868 }
869 }
870 return $contactNames;
871 }
872
873 /**
874 * Clear the contact cache so things are kosher. We started off being super aggressive with clearing
875 * caches, but are backing off from this with every release. Compromise between ease of coding versus
876 * performance versus being accurate at that very instant
877 *
878 * @param $contactID - the contactID that was edited / deleted
879 *
880 * @return void
881 * @static
882 */
883 public static function clearContactCaches($contactID = NULL) {
884 // clear acl cache if any.
885 CRM_ACL_BAO_Cache::resetCache();
886
887 if (empty($contactID)) {
888 // also clear prev/next dedupe cache - if no contactID passed in
889 CRM_Core_BAO_PrevNextCache::deleteItem();
890 }
891
892 // reset the group contact cache for this group
893 CRM_Contact_BAO_GroupContactCache::remove();
894 }
895
896 /**
897 * @param array $params
898 *
899 * @throws Exception
900 */
901 public static function updateGreeting($params) {
902 $contactType = $params['ct'];
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);
907
908 // if valueID is not passed use default value
909 if (!$valueID) {
910 $valueID = $id = self::defaultGreeting($contactType, $greeting);
911 }
912
913 $filter = array(
914 'contact_type' => $contactType,
915 'greeting_type' => $greeting,
916 );
917
918 $allGreetings = CRM_Core_PseudoConstant::greeting($filter);
919 $originalGreetingString = $greetingString = CRM_Utils_Array::value($valueID, $allGreetings);
920 if (!$greetingString) {
921 CRM_Core_Error::fatal(ts('Incorrect greeting value id %1, or no default greeting for this contact type and greeting type.', array(1 => $valueID)));
922 }
923
924 // build return properties based on tokens
925 $greetingTokens = CRM_Utils_Token::getTokens($greetingString);
926 $tokens = CRM_Utils_Array::value('contact', $greetingTokens);
927 $greetingsReturnProperties = array();
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.
942 $filterContactFldIds = $filterIds = array();
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) {
950 $queryParams = array(1 => array($contactType, 'String'));
951
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 {
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} = '')) )";
964 }
965
966 if ($limit) {
967 $sql .= " LIMIT 0, %2";
968 $queryParams += array(2 => array($limit, 'Integer'));
969 }
970
971 $dao = CRM_Core_DAO::executeQuery($sql, $queryParams);
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
986 $extraParams[] = array('contact_type', '=', $contactType, 0, 0);
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
993 $contactIds = array();
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
1016 self::processGreetingTemplate($greetingString, $contactDetails, $contactID, 'CRM_UpdateGreeting');
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 = "
1035 UPDATE civicrm_contact
1036 SET {$greeting}_id = {$valueID}
1037 {$resetCustomGreeting}
1038 WHERE 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 /**
1048 * Fetch the default greeting for a given contact type
1049 *
1050 * @param string $contactType contact type
1051 * @param string $greetingType greeting type
1052 *
1053 * @return int or null
1054 */
1055 public static function defaultGreeting($contactType, $greetingType) {
1056 $contactTypeFilters = array('Individual' => 1, 'Household' => 2, 'Organization' => 3);
1057 if (!isset($contactTypeFilters[$contactType])) {
1058 return;
1059 }
1060 $filter = $contactTypeFilters[$contactType];
1061
1062 $id = CRM_Core_OptionGroup::values($greetingType, NULL, NULL, NULL,
1063 " AND is_default = 1 AND (filter = {$filter} OR filter = 0)",
1064 'value'
1065 );
1066 if (!empty($id)) {
1067 return current($id);
1068 }
1069 }
1070
1071 /**
1072 * Process a greeting template string to produce the individualised greeting text.
1073 *
1074 * This works just like message templates for mailings:
1075 * the template is processed with the token substitution mechanism,
1076 * to supply the individual contact data;
1077 * and it is also processed with Smarty,
1078 * to allow for conditionals etc. based on the contact data.
1079 *
1080 * Note: We don't pass any variables to Smarty --
1081 * all variable data is inserted into the input string
1082 * by the token substitution mechanism,
1083 * before Smarty is invoked.
1084 *
1085 * @param string $templateString the greeting template string with contact tokens + Smarty syntax
1086 *
1087 * @param $contactDetails
1088 * @param int $contactID
1089 * @param string $className
1090 *
1091 * @return void
1092 * @static
1093 */
1094 public static function processGreetingTemplate(&$templateString, $contactDetails, $contactID, $className) {
1095 CRM_Utils_Token::replaceGreetingTokens($templateString, $contactDetails, $contactID, $className, TRUE);
1096
1097 $smarty = CRM_Core_Smarty::singleton();
1098 $templateString = $smarty->fetch("string:$templateString");
1099 }
1100 }