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