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