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