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