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