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