Merge pull request #11037 from totten/master-daotest
[civicrm-core.git] / CRM / Contact / BAO / Contact.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2017 |
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-2017
32 */
33 class CRM_Contact_BAO_Contact extends CRM_Contact_DAO_Contact {
34
35 /**
36 * SQL function used to format the phone_numeric field via trigger.
37 * @see self::triggerInfo()
38 *
39 * Note that this is also used by the 4.3 upgrade script.
40 * @see CRM_Upgrade_Incremental_php_FourThree
41 */
42 const DROP_STRIP_FUNCTION_43 = "DROP FUNCTION IF EXISTS civicrm_strip_non_numeric";
43 const CREATE_STRIP_FUNCTION_43 = "
44 CREATE FUNCTION civicrm_strip_non_numeric(input VARCHAR(255) CHARACTER SET utf8)
45 RETURNS VARCHAR(255) CHARACTER SET utf8
46 DETERMINISTIC
47 NO SQL
48 BEGIN
49 DECLARE output VARCHAR(255) CHARACTER SET utf8 DEFAULT '';
50 DECLARE iterator INT DEFAULT 1;
51 WHILE iterator < (LENGTH(input) + 1) DO
52 IF SUBSTRING(input, iterator, 1) IN ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9') THEN
53 SET output = CONCAT(output, SUBSTRING(input, iterator, 1));
54 END IF;
55 SET iterator = iterator + 1;
56 END WHILE;
57 RETURN output;
58 END";
59
60 /**
61 * The types of communication preferences.
62 *
63 * @var array
64 */
65 static $_commPrefs = array(
66 'do_not_phone',
67 'do_not_email',
68 'do_not_mail',
69 'do_not_sms',
70 'do_not_trade',
71 );
72
73 /**
74 * Types of greetings.
75 *
76 * @var array
77 */
78 static $_greetingTypes = array(
79 'addressee',
80 'email_greeting',
81 'postal_greeting',
82 );
83
84 /**
85 * Static field for all the contact information that we can potentially import.
86 *
87 * @var array
88 */
89 static $_importableFields = array();
90
91 /**
92 * Static field for all the contact information that we can potentially export.
93 *
94 * @var array
95 */
96 static $_exportableFields = NULL;
97
98 /**
99 * Class constructor.
100 */
101 public function __construct() {
102 parent::__construct();
103 }
104
105 /**
106 * Takes an associative array and creates a contact object.
107 *
108 * The function extracts all the params it needs to initialize the create a
109 * contact object. the params array could contain additional unused name/value
110 * pairs
111 *
112 * @param array $params
113 * (reference) an assoc array of name/value pairs.
114 *
115 * @return CRM_Contact_BAO_Contact|CRM_Core_Error|NULL
116 * Created or updated contact object or error object.
117 * (error objects are being phased out in favour of exceptions)
118 */
119 public static function add(&$params) {
120 $contact = new CRM_Contact_DAO_Contact();
121
122 if (empty($params)) {
123 return NULL;
124 }
125
126 // Fix for validate contact sub type CRM-5143.
127 if (isset($params['contact_sub_type'])) {
128 if (empty($params['contact_sub_type'])) {
129 $params['contact_sub_type'] = 'null';
130 }
131 else {
132 if (!CRM_Contact_BAO_ContactType::isExtendsContactType($params['contact_sub_type'],
133 $params['contact_type'], TRUE
134 )
135 ) {
136 // we'll need to fix tests to handle this
137 // CRM-7925
138 CRM_Core_Error::fatal(ts('The Contact Sub Type does not match the Contact type for this record'));
139 }
140 $params['contact_sub_type'] = CRM_Utils_Array::implodePadded($params['contact_sub_type']);
141 }
142 }
143 else {
144 // Reset the value.
145 // CRM-101XX.
146 $params['contact_sub_type'] = 'null';
147 }
148
149 // Fixed contact source.
150 if (isset($params['contact_source'])) {
151 $params['source'] = $params['contact_source'];
152 }
153
154 if (isset($params['preferred_communication_method']) && is_array($params['preferred_communication_method'])) {
155 CRM_Utils_Array::formatArrayKeys($params['preferred_communication_method']);
156 $contact->preferred_communication_method = CRM_Utils_Array::implodePadded($params['preferred_communication_method']);
157 unset($params['preferred_communication_method']);
158 }
159
160 $allNull = $contact->copyValues($params);
161
162 $contact->id = CRM_Utils_Array::value('contact_id', $params);
163
164 if ($contact->contact_type == 'Individual') {
165 $allNull = FALSE;
166
167 // Format individual fields.
168 CRM_Contact_BAO_Individual::format($params, $contact);
169 }
170 elseif ($contact->contact_type == 'Household') {
171 if (isset($params['household_name'])) {
172 $allNull = FALSE;
173 $contact->display_name = $contact->sort_name = CRM_Utils_Array::value('household_name', $params, '');
174 }
175 }
176 elseif ($contact->contact_type == 'Organization') {
177 if (isset($params['organization_name'])) {
178 $allNull = FALSE;
179 $contact->display_name = $contact->sort_name = CRM_Utils_Array::value('organization_name', $params, '');
180 }
181 }
182
183 $privacy = CRM_Utils_Array::value('privacy', $params);
184 if ($privacy &&
185 is_array($privacy) &&
186 !empty($privacy)
187 ) {
188 $allNull = FALSE;
189 foreach (self::$_commPrefs as $name) {
190 $contact->$name = CRM_Utils_Array::value($name, $privacy, FALSE);
191 }
192 }
193
194 // Since hash was required, make sure we have a 0 value for it (CRM-1063).
195 // @todo - does this mean we can remove this block?
196 // Fixed in 1.5 by making hash optional, only do this in create mode, not update.
197 if ((!array_key_exists('hash', $contact) || !$contact->hash) && !$contact->id) {
198 $allNull = FALSE;
199 $contact->hash = md5(uniqid(rand(), TRUE));
200 }
201
202 // Even if we don't need $employerId, it's important to call getFieldValue() before
203 // the contact is saved because we want the existing value to be cached.
204 // createCurrentEmployerRelationship() needs the old value not the updated one. CRM-10788
205 $employerId = empty($contact->id) ? NULL : CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $contact->id, 'employer_id');
206
207 if (!$allNull) {
208 $contact->save();
209
210 CRM_Core_BAO_Log::register($contact->id,
211 'civicrm_contact',
212 $contact->id
213 );
214 }
215
216 if ($contact->contact_type == 'Individual' && (isset($params['current_employer']) || isset($params['employer_id']))) {
217 // Create current employer.
218 $newEmployer = !empty($params['employer_id']) ? $params['employer_id'] : CRM_Utils_Array::value('current_employer', $params);
219
220 $newContact = FALSE;
221 if (empty($params['contact_id'])) {
222 $newContact = TRUE;
223 }
224 if ($newEmployer) {
225 CRM_Contact_BAO_Contact_Utils::createCurrentEmployerRelationship($contact->id, $newEmployer, $employerId, $newContact);
226 }
227 else {
228 if ($employerId) {
229 CRM_Contact_BAO_Contact_Utils::clearCurrentEmployer($contact->id, $employerId);
230 }
231 }
232 }
233
234 // Update cached employer name.
235 if ($contact->contact_type == 'Organization') {
236 CRM_Contact_BAO_Contact_Utils::updateCurrentEmployer($contact->id);
237 }
238
239 return $contact;
240 }
241
242 /**
243 * Create contact.
244 *
245 * takes an associative array and creates a contact object and all the associated
246 * derived objects (i.e. individual, location, email, phone etc)
247 *
248 * This function is invoked from within the web form layer and also from the api layer
249 *
250 * @param array $params
251 * (reference ) an assoc array of name/value pairs.
252 * @param bool $fixAddress
253 * If we need to fix address.
254 * @param bool $invokeHooks
255 * If we need to invoke hooks.
256 *
257 * @param bool $skipDelete
258 * Unclear parameter, passed to website create
259 *
260 * @todo explain this parameter
261 *
262 * @throws Exception
263 * @return CRM_Contact_BAO_Contact|CRM_Core_Error
264 * Created or updated contribution object. We are deprecating returning an error in
265 * favour of exceptions
266 */
267 public static function &create(&$params, $fixAddress = TRUE, $invokeHooks = TRUE, $skipDelete = FALSE) {
268 $contact = NULL;
269 if (empty($params['contact_type']) && empty($params['contact_id'])) {
270 return $contact;
271 }
272
273 $isEdit = TRUE;
274 if ($invokeHooks) {
275 if (!empty($params['contact_id'])) {
276 CRM_Utils_Hook::pre('edit', $params['contact_type'], $params['contact_id'], $params);
277 }
278 else {
279 CRM_Utils_Hook::pre('create', $params['contact_type'], NULL, $params);
280 $isEdit = FALSE;
281 }
282 }
283
284 $config = CRM_Core_Config::singleton();
285
286 // CRM-6942: set preferred language to the current language if it’s unset (and we’re creating a contact).
287 if (empty($params['contact_id'])) {
288 // A case could be made for checking isset rather than empty but this is more consistent with previous behaviour.
289 if (empty($params['preferred_language']) && ($language = CRM_Core_I18n::getContactDefaultLanguage()) != FALSE) {
290 $params['preferred_language'] = $language;
291 }
292
293 // CRM-9739: set greeting & addressee if unset and we’re creating a contact.
294 foreach (self::$_greetingTypes as $greeting) {
295 if (empty($params[$greeting . '_id'])) {
296 if ($defaultGreetingTypeId
297 = CRM_Contact_BAO_Contact_Utils::defaultGreeting($params['contact_type'], $greeting)
298 ) {
299 $params[$greeting . '_id'] = $defaultGreetingTypeId;
300 }
301 }
302 }
303 }
304
305 $transaction = new CRM_Core_Transaction();
306
307 $contact = self::add($params);
308 if (!$contact) {
309 // Not dying here is stupid, since we get into weird situation and into a bug that
310 // is impossible to figure out for the user or for us
311 // CRM-7925
312 CRM_Core_Error::fatal();
313 }
314
315 $params['contact_id'] = $contact->id;
316
317 if (Civi::settings()->get('is_enabled')) {
318 // Enabling multisite causes the contact to be added to the domain group.
319 $domainGroupID = CRM_Core_BAO_Domain::getGroupId();
320 if (!empty($domainGroupID)) {
321 if (!empty($params['group']) && is_array($params['group'])) {
322 $params['group'][$domainGroupID] = 1;
323 }
324 else {
325 $params['group'] = array($domainGroupID => 1);
326 }
327 }
328 }
329
330 if (array_key_exists('group', $params)) {
331 $contactIds = array($params['contact_id']);
332 foreach ($params['group'] as $groupId => $flag) {
333 if ($flag == 1) {
334 CRM_Contact_BAO_GroupContact::addContactsToGroup($contactIds, $groupId);
335 }
336 elseif ($flag == -1) {
337 CRM_Contact_BAO_GroupContact::removeContactsFromGroup($contactIds, $groupId);
338 }
339 }
340 }
341
342 // Add location Block data.
343 $blocks = CRM_Core_BAO_Location::create($params, $fixAddress);
344 foreach ($blocks as $name => $value) {
345 $contact->$name = $value;
346 }
347 if (!empty($params['updateBlankLocInfo'])) {
348 $skipDelete = TRUE;
349 }
350
351 //add website
352 CRM_Core_BAO_Website::create($params['website'], $contact->id, $skipDelete);
353
354 $userID = CRM_Core_Session::singleton()->get('userID');
355 // add notes
356 if (!empty($params['note'])) {
357 if (is_array($params['note'])) {
358 foreach ($params['note'] as $note) {
359 $contactId = $contact->id;
360 if (isset($note['contact_id'])) {
361 $contactId = $note['contact_id'];
362 }
363 //if logged in user, overwrite contactId
364 if ($userID) {
365 $contactId = $userID;
366 }
367
368 $noteParams = array(
369 'entity_id' => $contact->id,
370 'entity_table' => 'civicrm_contact',
371 'note' => $note['note'],
372 'subject' => CRM_Utils_Array::value('subject', $note),
373 'contact_id' => $contactId,
374 );
375 CRM_Core_BAO_Note::add($noteParams, CRM_Core_DAO::$_nullArray);
376 }
377 }
378 else {
379 $contactId = $contact->id;
380 if (isset($note['contact_id'])) {
381 $contactId = $note['contact_id'];
382 }
383 //if logged in user, overwrite contactId
384 if ($userID) {
385 $contactId = $userID;
386 }
387
388 $noteParams = array(
389 'entity_id' => $contact->id,
390 'entity_table' => 'civicrm_contact',
391 'note' => $params['note'],
392 'subject' => CRM_Utils_Array::value('subject', $params),
393 'contact_id' => $contactId,
394 );
395 CRM_Core_BAO_Note::add($noteParams, CRM_Core_DAO::$_nullArray);
396 }
397 }
398
399 // update the UF user_unique_id if that has changed
400 CRM_Core_BAO_UFMatch::updateUFName($contact->id);
401
402 if (!empty($params['custom']) &&
403 is_array($params['custom'])
404 ) {
405 CRM_Core_BAO_CustomValueTable::store($params['custom'], 'civicrm_contact', $contact->id);
406 }
407
408 // make a civicrm_subscription_history entry only on contact create (CRM-777)
409 if (empty($params['contact_id'])) {
410 $subscriptionParams = array(
411 'contact_id' => $contact->id,
412 'status' => 'Added',
413 'method' => 'Admin',
414 );
415 CRM_Contact_BAO_SubscriptionHistory::create($subscriptionParams);
416 }
417
418 $transaction->commit();
419
420 // CRM-6367: fetch the right label for contact type’s display
421 $contact->contact_type_display = CRM_Core_DAO::getFieldValue(
422 'CRM_Contact_DAO_ContactType',
423 $contact->contact_type,
424 'label',
425 'name'
426 );
427
428 CRM_Contact_BAO_Contact_Utils::clearContactCaches();
429
430 if ($invokeHooks) {
431 if ($isEdit) {
432 CRM_Utils_Hook::post('edit', $params['contact_type'], $contact->id, $contact);
433 }
434 else {
435 CRM_Utils_Hook::post('create', $params['contact_type'], $contact->id, $contact);
436 }
437 }
438
439 // process greetings CRM-4575, cache greetings
440 self::processGreetings($contact);
441
442 return $contact;
443 }
444
445 /**
446 * Get the display name and image of a contact.
447 *
448 * @param int $id
449 * The contactId.
450 *
451 * @param bool $includeTypeInReturnParameters
452 * Should type be part of the returned array?
453 *
454 * @return array
455 * the displayName and contactImage for this contact
456 */
457 public static function getDisplayAndImage($id, $includeTypeInReturnParameters = FALSE) {
458 //CRM-14276 added the * on the civicrm_contact table so that we have all the contact info available
459 $sql = "
460 SELECT civicrm_contact.*,
461 civicrm_email.email as email
462 FROM civicrm_contact
463 LEFT JOIN civicrm_email ON civicrm_email.contact_id = civicrm_contact.id
464 AND civicrm_email.is_primary = 1
465 WHERE civicrm_contact.id = " . CRM_Utils_Type::escape($id, 'Integer');
466 $dao = new CRM_Core_DAO();
467 $dao->query($sql);
468 if ($dao->fetch()) {
469 $image = CRM_Contact_BAO_Contact_Utils::getImage($dao->contact_sub_type ?
470 $dao->contact_sub_type : $dao->contact_type, FALSE, $id
471 );
472 $imageUrl = CRM_Contact_BAO_Contact_Utils::getImage($dao->contact_sub_type ?
473 $dao->contact_sub_type : $dao->contact_type, TRUE, $id
474 );
475
476 // use email if display_name is empty
477 if (empty($dao->display_name)) {
478 $displayName = $dao->email;
479 }
480 else {
481 $displayName = $dao->display_name;
482 }
483
484 CRM_Utils_Hook::alterDisplayName($displayName, $id, $dao);
485
486 return $includeTypeInReturnParameters ? array(
487 $displayName,
488 $image,
489 $dao->contact_type,
490 $dao->contact_sub_type,
491 $imageUrl,
492 ) : array($displayName, $image, $imageUrl);
493 }
494 return NULL;
495 }
496
497 /**
498 * Add billing fields to the params if appropriate.
499 *
500 * If we have ANY name fields then we want to ignore all the billing name fields. However, if we
501 * don't then we should set the name fields to the filling fields AND add the preserveDBName
502 * parameter (which will tell the BAO only to set those fields if none already exist.
503 *
504 * We specifically don't want to set first name from billing and last name form an on-page field. Mixing &
505 * matching is best done by hipsters.
506 *
507 * @param array $params
508 */
509 public static function addBillingNameFieldsIfOtherwiseNotSet(&$params) {
510 $nameFields = array('first_name', 'middle_name', 'last_name', 'nick_name', 'prefix_id', 'suffix_id');
511 foreach ($nameFields as $field) {
512 if (!empty($params[$field])) {
513 return;
514 }
515 }
516 // There are only 3 - we can iterate through them twice :-)
517 foreach ($nameFields as $field) {
518 if (!empty($params['billing_' . $field])) {
519 $params[$field] = $params['billing_' . $field];
520 }
521 $params['preserveDBName'] = TRUE;
522 }
523
524 }
525
526 /**
527 * Create last viewed link to recently updated contact.
528 *
529 * @param array $crudLinkSpec
530 * - action: int, CRM_Core_Action::UPDATE or CRM_Core_Action::VIEW [default: VIEW]
531 * - entity_table: string, eg "civicrm_contact"
532 * - entity_id: int
533 *
534 * @return array|NULL
535 * NULL if unavailable, or
536 * [path: string, query: string, title: string]
537 * @see CRM_Utils_System::createDefaultCrudLink
538 */
539 public function createDefaultCrudLink($crudLinkSpec) {
540 switch ($crudLinkSpec['action']) {
541 case CRM_Core_Action::VIEW:
542 $result = array(
543 'title' => $this->display_name,
544 'path' => 'civicrm/contact/view',
545 'query' => array(
546 'reset' => 1,
547 'cid' => $this->id,
548 ),
549 );
550 return $result;
551
552 case CRM_Core_Action::UPDATE:
553 $result = array(
554 'title' => $this->display_name,
555 'path' => 'civicrm/contact/add',
556 'query' => array(
557 'reset' => 1,
558 'action' => 'update',
559 'cid' => $this->id,
560 ),
561 );
562 return $result;
563 }
564 return NULL;
565 }
566
567 /**
568 * Get the values for pseudoconstants for name->value and reverse.
569 *
570 * @param array $defaults
571 * (reference) the default values, some of which need to be resolved.
572 * @param bool $reverse
573 * True if we want to resolve the values in the reverse direction (value -> name).
574 */
575 public static function resolveDefaults(&$defaults, $reverse = FALSE) {
576 // Hack for birth_date.
577 if (!empty($defaults['birth_date'])) {
578 if (is_array($defaults['birth_date'])) {
579 $defaults['birth_date'] = CRM_Utils_Date::format($defaults['birth_date'], '-');
580 }
581 }
582
583 CRM_Utils_Array::lookupValue($defaults, 'prefix', CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'prefix_id'), $reverse);
584 CRM_Utils_Array::lookupValue($defaults, 'suffix', CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'suffix_id'), $reverse);
585 CRM_Utils_Array::lookupValue($defaults, 'gender', CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'gender_id'), $reverse);
586 CRM_Utils_Array::lookupValue($defaults, 'communication_style', CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'communication_style_id'), $reverse);
587
588 //lookup value of email/postal greeting, addressee, CRM-4575
589 foreach (self::$_greetingTypes as $greeting) {
590 $filterCondition = array(
591 'contact_type' => CRM_Utils_Array::value('contact_type', $defaults),
592 'greeting_type' => $greeting,
593 );
594 CRM_Utils_Array::lookupValue($defaults, $greeting,
595 CRM_Core_PseudoConstant::greeting($filterCondition), $reverse
596 );
597 }
598
599 $blocks = array('address', 'im', 'phone');
600 foreach ($blocks as $name) {
601 if (!array_key_exists($name, $defaults) || !is_array($defaults[$name])) {
602 continue;
603 }
604 foreach ($defaults[$name] as $count => & $values) {
605
606 //get location type id.
607 CRM_Utils_Array::lookupValue($values, 'location_type', CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id'), $reverse);
608
609 if ($name == 'address') {
610 // FIXME: lookupValue doesn't work for vcard_name
611 if (!empty($values['location_type_id'])) {
612 $vcardNames = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id', array('labelColumn' => 'vcard_name'));
613 $values['vcard_name'] = $vcardNames[$values['location_type_id']];
614 }
615
616 if (!CRM_Utils_Array::lookupValue($values,
617 'country',
618 CRM_Core_PseudoConstant::country(),
619 $reverse
620 ) &&
621 $reverse
622 ) {
623 CRM_Utils_Array::lookupValue($values,
624 'country',
625 CRM_Core_PseudoConstant::countryIsoCode(),
626 $reverse
627 );
628 }
629
630 // CRM-7597
631 // if we find a country id above, we need to restrict it to that country
632 // rather than the list of all countries
633
634 if (!empty($values['country_id'])) {
635 $stateProvinceList = CRM_Core_PseudoConstant::stateProvinceForCountry($values['country_id']);
636 }
637 else {
638 $stateProvinceList = CRM_Core_PseudoConstant::stateProvince();
639 }
640 if (!CRM_Utils_Array::lookupValue($values,
641 'state_province',
642 $stateProvinceList,
643 $reverse
644 ) &&
645 $reverse
646 ) {
647
648 if (!empty($values['country_id'])) {
649 $stateProvinceList = CRM_Core_PseudoConstant::stateProvinceForCountry($values['country_id'], 'abbreviation');
650 }
651 else {
652 $stateProvinceList = CRM_Core_PseudoConstant::stateProvinceAbbreviation();
653 }
654 CRM_Utils_Array::lookupValue($values,
655 'state_province',
656 $stateProvinceList,
657 $reverse
658 );
659 }
660
661 if (!empty($values['state_province_id'])) {
662 $countyList = CRM_Core_PseudoConstant::countyForState($values['state_province_id']);
663 }
664 else {
665 $countyList = CRM_Core_PseudoConstant::county();
666 }
667 CRM_Utils_Array::lookupValue($values,
668 'county',
669 $countyList,
670 $reverse
671 );
672 }
673
674 if ($name == 'im') {
675 CRM_Utils_Array::lookupValue($values,
676 'provider',
677 CRM_Core_PseudoConstant::get('CRM_Core_DAO_IM', 'provider_id'),
678 $reverse
679 );
680 }
681
682 if ($name == 'phone') {
683 CRM_Utils_Array::lookupValue($values,
684 'phone_type',
685 CRM_Core_PseudoConstant::get('CRM_Core_DAO_Phone', 'phone_type_id'),
686 $reverse
687 );
688 }
689
690 // Kill the reference.
691 unset($values);
692 }
693 }
694 }
695
696 /**
697 * Fetch object based on array of properties.
698 *
699 * @param array $params
700 * (reference ) an assoc array of name/value pairs.
701 * @param array $defaults
702 * (reference ) an assoc array to hold the name / value pairs.
703 * in a hierarchical manner
704 * @param bool $microformat
705 * For location in microformat.
706 *
707 * @return CRM_Contact_BAO_Contact
708 */
709 public static function &retrieve(&$params, &$defaults, $microformat = FALSE) {
710 if (array_key_exists('contact_id', $params)) {
711 $params['id'] = $params['contact_id'];
712 }
713 elseif (array_key_exists('id', $params)) {
714 $params['contact_id'] = $params['id'];
715 }
716
717 $contact = self::getValues($params, $defaults);
718
719 unset($params['id']);
720
721 //get the block information for this contact
722 $entityBlock = array('contact_id' => $params['contact_id']);
723 $blocks = CRM_Core_BAO_Location::getValues($entityBlock, $microformat);
724 $defaults = array_merge($defaults, $blocks);
725 foreach ($blocks as $block => $value) {
726 $contact->$block = $value;
727 }
728
729 if (!isset($params['noNotes'])) {
730 $contact->notes = CRM_Core_BAO_Note::getValues($params, $defaults);
731 }
732
733 if (!isset($params['noRelationships'])) {
734 $contact->relationship = CRM_Contact_BAO_Relationship::getValues($params, $defaults);
735 }
736
737 if (!isset($params['noGroups'])) {
738 $contact->groupContact = CRM_Contact_BAO_GroupContact::getValues($params, $defaults);
739 }
740
741 if (!isset($params['noWebsite'])) {
742 $contact->website = CRM_Core_BAO_Website::getValues($params, $defaults);
743 }
744
745 return $contact;
746 }
747
748 /**
749 * Get the display name of a contact.
750 *
751 * @param int $id
752 * Id of the contact.
753 *
754 * @return null|string
755 * display name of the contact if found
756 */
757 public static function displayName($id) {
758 $displayName = NULL;
759 if ($id) {
760 $displayName = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $id, 'display_name');
761 }
762
763 return $displayName;
764 }
765
766 /**
767 * Delete a contact and all its associated records.
768 *
769 * @param int $id
770 * Id of the contact to delete.
771 * @param bool $restore
772 * Whether to actually restore, not delete.
773 * @param bool $skipUndelete
774 * Whether to force contact delete or not.
775 * @param bool $checkPermissions
776 *
777 * @return bool
778 * Was contact deleted?
779 */
780 public static function deleteContact($id, $restore = FALSE, $skipUndelete = FALSE, $checkPermissions = TRUE) {
781
782 if (!$id) {
783 return FALSE;
784 }
785 // If trash is disabled in system settings then we always skip
786 if (!Civi::settings()->get('contact_undelete')) {
787 $skipUndelete = TRUE;
788 }
789
790 // make sure we have edit permission for this contact
791 // before we delete
792 if ($checkPermissions && (($skipUndelete && !CRM_Core_Permission::check('delete contacts')) ||
793 ($restore && !CRM_Core_Permission::check('access deleted contacts')))
794 ) {
795 return FALSE;
796 }
797
798 // CRM-12929
799 // Restrict contact to be delete if contact has financial trxns
800 $error = NULL;
801 if ($skipUndelete && CRM_Financial_BAO_FinancialItem::checkContactPresent(array($id), $error)) {
802 return FALSE;
803 }
804
805 // make sure this contact_id does not have any membership types
806 $membershipTypeID = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipType',
807 $id,
808 'id',
809 'member_of_contact_id'
810 );
811 if ($membershipTypeID) {
812 return FALSE;
813 }
814
815 $contact = new CRM_Contact_DAO_Contact();
816 $contact->id = $id;
817 if (!$contact->find(TRUE)) {
818 return FALSE;
819 }
820
821 $contactType = $contact->contact_type;
822 // currently we only clear employer cache.
823 // we are now deleting inherited membership if any.
824 if ($contact->contact_type == 'Organization') {
825 $action = $restore ? CRM_Core_Action::ENABLE : CRM_Core_Action::DISABLE;
826 $relationshipDtls = CRM_Contact_BAO_Relationship::getRelationship($id);
827 if (!empty($relationshipDtls)) {
828 foreach ($relationshipDtls as $rId => $details) {
829 CRM_Contact_BAO_Relationship::disableEnableRelationship($rId, $action);
830 }
831 }
832 CRM_Contact_BAO_Contact_Utils::clearAllEmployee($id);
833 }
834
835 if ($restore) {
836 return self::contactTrashRestore($contact, TRUE);
837 }
838
839 // start a new transaction
840 $transaction = new CRM_Core_Transaction();
841
842 if ($skipUndelete) {
843 CRM_Utils_Hook::pre('delete', $contactType, $id, CRM_Core_DAO::$_nullArray);
844
845 //delete billing address if exists.
846 CRM_Contribute_BAO_Contribution::deleteAddress(NULL, $id);
847
848 // delete the log entries since we dont have triggers enabled as yet
849 $logDAO = new CRM_Core_DAO_Log();
850 $logDAO->entity_table = 'civicrm_contact';
851 $logDAO->entity_id = $id;
852 $logDAO->delete();
853
854 // delete contact participants CRM-12155
855 CRM_Event_BAO_Participant::deleteContactParticipant($id);
856
857 // delete contact contributions CRM-12155
858 CRM_Contribute_BAO_Contribution::deleteContactContribution($id);
859
860 // do activity cleanup, CRM-5604
861 CRM_Activity_BAO_Activity::cleanupActivity($id);
862
863 // delete all notes related to contact
864 CRM_Core_BAO_Note::cleanContactNotes($id);
865
866 // delete cases related to contact
867 $contactCases = CRM_Case_BAO_Case::retrieveCaseIdsByContactId($id);
868 if (!empty($contactCases)) {
869 foreach ($contactCases as $caseId) {
870 //check if case is associate with other contact or not.
871 $caseContactId = CRM_Case_BAO_Case::getCaseClients($caseId);
872 if (count($caseContactId) <= 1) {
873 CRM_Case_BAO_Case::deleteCase($caseId);
874 }
875 }
876 }
877
878 $contact->delete();
879 }
880 else {
881 self::contactTrashRestore($contact);
882 }
883
884 //delete the contact id from recently view
885 CRM_Utils_Recent::delContact($id);
886 self::updateContactCache($id, empty($restore));
887
888 // delete any dupe cache entry
889 CRM_Core_BAO_PrevNextCache::deleteItem($id);
890
891 $transaction->commit();
892
893 if ($skipUndelete) {
894 CRM_Utils_Hook::post('delete', $contactType, $contact->id, $contact);
895 }
896
897 // also reset the DB_DO global array so we can reuse the memory
898 // http://issues.civicrm.org/jira/browse/CRM-4387
899 CRM_Core_DAO::freeResult();
900
901 return TRUE;
902 }
903
904 /**
905 * Action to update any caches relating to a recently update contact.
906 *
907 * I was going to call this from delete as well as from create to ensure the delete is being
908 * done whenever a contact is set to is_deleted=1 BUT I found create is already over-aggressive in
909 * that regard so adding it to delete seems to be enough to remove it from CRM_Contact_BAO_Contact_Permission
910 * where the call involved a subquery that was locking the table.
911 *
912 * @param int $contactID
913 * @param bool $isTrashed
914 */
915 public static function updateContactCache($contactID, $isTrashed = FALSE) {
916
917 if ($isTrashed) {
918 CRM_Contact_BAO_GroupContactCache::removeContact($contactID);
919 // This has been moved to here from CRM_Contact_BAO_Contact_Permission as that was causing
920 // a table-locking query. It still seems a bit inadequate as it assumes the acl users can't see deleted
921 // but this should not cause any change as long as contacts are not being trashed outside the
922 // main functions for that.
923 CRM_Core_DAO::executeQuery('DELETE FROM civicrm_acl_contact_cache WHERE contact_id = %1', array(1 => array($contactID, 'Integer')));
924 }
925 else {
926 CRM_Contact_BAO_GroupContactCache::opportunisticCacheFlush();
927 }
928 }
929
930 /**
931 * Delete the image of a contact.
932 *
933 * @param int $id
934 * Id of the contact.
935 *
936 * @return bool
937 * Was contact image deleted?
938 */
939 public static function deleteContactImage($id) {
940 if (!$id) {
941 return FALSE;
942 }
943 $query = "
944 UPDATE civicrm_contact
945 SET image_URL=NULL
946 WHERE id={$id}; ";
947 CRM_Core_DAO::executeQuery($query);
948 return TRUE;
949 }
950
951 /**
952 * Return proportional height and width of the image.
953 *
954 * @param int $imageWidth
955 * Width of image.
956 *
957 * @param int $imageHeight
958 * Height of image.
959 *
960 * @return array
961 * Thumb dimension of image
962 */
963 public static function getThumbSize($imageWidth, $imageHeight) {
964 $thumbWidth = 100;
965 if ($imageWidth && $imageHeight) {
966 $imageRatio = $imageWidth / $imageHeight;
967 }
968 else {
969 $imageRatio = 1;
970 }
971 if ($imageRatio > 1) {
972 $imageThumbWidth = $thumbWidth;
973 $imageThumbHeight = round($thumbWidth / $imageRatio);
974 }
975 else {
976 $imageThumbHeight = $thumbWidth;
977 $imageThumbWidth = round($thumbWidth * $imageRatio);
978 }
979
980 return array($imageThumbWidth, $imageThumbHeight);
981 }
982
983 /**
984 * Validate type of contact image.
985 *
986 * @param array $params
987 * @param string $imageIndex
988 * Index of image field.
989 * @param string $statusMsg
990 * Status message to be set after operation.
991 * @param string $opType
992 * Type of operation like fatal, bounce etc.
993 *
994 * @return bool
995 * true if valid image extension
996 */
997 public static function processImageParams(
998 &$params,
999 $imageIndex = 'image_URL',
1000 $statusMsg = NULL,
1001 $opType = 'status'
1002 ) {
1003 $mimeType = array(
1004 'image/jpeg',
1005 'image/jpg',
1006 'image/png',
1007 'image/bmp',
1008 'image/p-jpeg',
1009 'image/gif',
1010 'image/x-png',
1011 );
1012
1013 if (in_array($params[$imageIndex]['type'], $mimeType)) {
1014 $photo = basename($params[$imageIndex]['name']);
1015 $params[$imageIndex] = CRM_Utils_System::url('civicrm/contact/imagefile', 'photo=' . $photo, TRUE, NULL, TRUE, TRUE);
1016 return TRUE;
1017 }
1018 else {
1019 unset($params[$imageIndex]);
1020 if (!$statusMsg) {
1021 $statusMsg = ts('Image could not be uploaded due to invalid type extension.');
1022 }
1023 if ($opType == 'status') {
1024 CRM_Core_Session::setStatus($statusMsg, 'Sorry', 'error');
1025 }
1026 // FIXME: additional support for fatal, bounce etc could be added.
1027 return FALSE;
1028 }
1029 }
1030
1031 /**
1032 * Extract contact id from url for deleting contact image.
1033 */
1034 public static function processImage() {
1035
1036 $action = CRM_Utils_Request::retrieve('action', 'String');
1037 $cid = CRM_Utils_Request::retrieve('cid', 'Positive');
1038 // retrieve contact id in case of Profile context
1039 $id = CRM_Utils_Request::retrieve('id', 'Positive');
1040 $cid = $cid ? $cid : $id;
1041 if ($action & CRM_Core_Action::DELETE) {
1042 if (CRM_Utils_Request::retrieve('confirmed', 'Boolean')) {
1043 CRM_Contact_BAO_Contact::deleteContactImage($cid);
1044 CRM_Core_Session::setStatus(ts('Contact image deleted successfully'), ts('Image Deleted'), 'success');
1045 $session = CRM_Core_Session::singleton();
1046 $toUrl = $session->popUserContext();
1047 CRM_Utils_System::redirect($toUrl);
1048 }
1049 }
1050 }
1051
1052 /**
1053 * Function to set is_delete true or restore deleted contact.
1054 *
1055 * @param CRM_Contact_DAO_Contact $contact
1056 * Contact DAO object.
1057 * @param bool $restore
1058 * True to set the is_delete = 1 else false to restore deleted contact,
1059 * i.e. is_delete = 0
1060 *
1061 * @return bool
1062 */
1063 public static function contactTrashRestore($contact, $restore = FALSE) {
1064 $updateParams = array(
1065 'id' => $contact->id,
1066 'is_deleted' => $restore ? 0 : 1,
1067 );
1068
1069 CRM_Utils_Hook::pre('update', $contact->contact_type, $contact->id, $updateParams);
1070
1071 $params = array(1 => array($contact->id, 'Integer'));
1072 if (!$restore) {
1073 $query = "DELETE FROM civicrm_uf_match WHERE contact_id = %1";
1074 CRM_Core_DAO::executeQuery($query, $params);
1075 }
1076
1077 $contact->copyValues($updateParams);
1078 $contact->save();
1079
1080 CRM_Utils_Hook::post('update', $contact->contact_type, $contact->id, $contact);
1081
1082 return TRUE;
1083 }
1084
1085 /**
1086 * Get contact type for a contact.
1087 *
1088 * @param int $id
1089 * Id of the contact whose contact type is needed.
1090 *
1091 * @return string
1092 * contact_type if $id found else null ""
1093 */
1094 public static function getContactType($id) {
1095 return CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $id, 'contact_type');
1096 }
1097
1098 /**
1099 * Get contact sub type for a contact.
1100 *
1101 * @param int $id
1102 * Id of the contact whose contact sub type is needed.
1103 *
1104 * @param string $implodeDelimiter
1105 *
1106 * @return string
1107 * contact_sub_type if $id found else null ""
1108 */
1109 public static function getContactSubType($id, $implodeDelimiter = NULL) {
1110 $subtype = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $id, 'contact_sub_type');
1111 if (!$subtype) {
1112 return $implodeDelimiter ? NULL : array();
1113 }
1114
1115 $subtype = CRM_Utils_Array::explodePadded($subtype);
1116
1117 if ($implodeDelimiter) {
1118 $subtype = implode($implodeDelimiter, $subtype);
1119 }
1120 return $subtype;
1121 }
1122
1123 /**
1124 * Get pair of contact-type and sub-type for a contact.
1125 *
1126 * @param int $id
1127 * Id of the contact whose contact sub/contact type is needed.
1128 *
1129 * @return array
1130 */
1131 public static function getContactTypes($id) {
1132 $params = array('id' => $id);
1133 $details = array();
1134 $contact = CRM_Core_DAO::commonRetrieve('CRM_Contact_DAO_Contact',
1135 $params,
1136 $details,
1137 array('contact_type', 'contact_sub_type')
1138 );
1139
1140 if ($contact) {
1141 $contactTypes = array();
1142 if ($contact->contact_sub_type) {
1143 $contactTypes = CRM_Utils_Array::explodePadded($contact->contact_sub_type);
1144 }
1145 array_unshift($contactTypes, $contact->contact_type);
1146
1147 return $contactTypes;
1148 }
1149 else {
1150 CRM_Core_Error::fatal();
1151 }
1152 }
1153
1154 /**
1155 * Combine all the importable fields from the lower levels object.
1156 *
1157 * The ordering is important, since currently we do not have a weight
1158 * scheme. Adding weight is super important
1159 *
1160 * @param int|string $contactType contact Type
1161 * @param bool $status
1162 * Status is used to manipulate first title.
1163 * @param bool $showAll
1164 * If true returns all fields (includes disabled fields).
1165 * @param bool $isProfile
1166 * If its profile mode.
1167 * @param bool $checkPermission
1168 * If false, do not include permissioning clause (for custom data).
1169 *
1170 * @param bool $withMultiCustomFields
1171 *
1172 * @return array
1173 * array of importable Fields
1174 */
1175 public static function importableFields(
1176 $contactType = 'Individual',
1177 $status = FALSE,
1178 $showAll = FALSE,
1179 $isProfile = FALSE,
1180 $checkPermission = TRUE,
1181 $withMultiCustomFields = FALSE
1182 ) {
1183 if (empty($contactType)) {
1184 $contactType = 'All';
1185 }
1186
1187 $cacheKeyString = "importableFields $contactType";
1188 $cacheKeyString .= $status ? '_1' : '_0';
1189 $cacheKeyString .= $showAll ? '_1' : '_0';
1190 $cacheKeyString .= $isProfile ? '_1' : '_0';
1191 $cacheKeyString .= $checkPermission ? '_1' : '_0';
1192
1193 $fields = CRM_Utils_Array::value($cacheKeyString, self::$_importableFields);
1194
1195 if (!$fields) {
1196 // check if we can retrieve from database cache
1197 $fields = CRM_Core_BAO_Cache::getItem('contact fields', $cacheKeyString);
1198 }
1199
1200 if (!$fields) {
1201 $fields = CRM_Contact_DAO_Contact::import();
1202
1203 // get the fields thar are meant for contact types
1204 if (in_array($contactType, array(
1205 'Individual',
1206 'Household',
1207 'Organization',
1208 'All',
1209 ))) {
1210 $fields = array_merge($fields, CRM_Core_OptionValue::getFields('', $contactType));
1211 }
1212
1213 $locationFields = array_merge(CRM_Core_DAO_Address::import(),
1214 CRM_Core_DAO_Phone::import(),
1215 CRM_Core_DAO_Email::import(),
1216 CRM_Core_DAO_IM::import(TRUE),
1217 CRM_Core_DAO_OpenID::import()
1218 );
1219
1220 $locationFields = array_merge($locationFields,
1221 CRM_Core_BAO_CustomField::getFieldsForImport('Address',
1222 FALSE,
1223 FALSE,
1224 FALSE,
1225 FALSE
1226 )
1227 );
1228
1229 foreach ($locationFields as $key => $field) {
1230 $locationFields[$key]['hasLocationType'] = TRUE;
1231 }
1232
1233 $fields = array_merge($fields, $locationFields);
1234
1235 $fields = array_merge($fields, CRM_Contact_DAO_Contact::import());
1236 $fields = array_merge($fields, CRM_Core_DAO_Note::import());
1237
1238 //website fields
1239 $fields = array_merge($fields, CRM_Core_DAO_Website::import());
1240 $fields['url']['hasWebsiteType'] = TRUE;
1241
1242 if ($contactType != 'All') {
1243 $fields = array_merge($fields,
1244 CRM_Core_BAO_CustomField::getFieldsForImport($contactType,
1245 $showAll,
1246 TRUE,
1247 FALSE,
1248 FALSE,
1249 $withMultiCustomFields
1250 )
1251 );
1252 //unset the fields, which are not related to their
1253 //contact type.
1254 $commonValues = array(
1255 'Individual' => array(
1256 'household_name',
1257 'legal_name',
1258 'sic_code',
1259 'organization_name',
1260 ),
1261 'Household' => array(
1262 'first_name',
1263 'middle_name',
1264 'last_name',
1265 'formal_title',
1266 'job_title',
1267 'gender_id',
1268 'prefix_id',
1269 'suffix_id',
1270 'birth_date',
1271 'organization_name',
1272 'legal_name',
1273 'legal_identifier',
1274 'sic_code',
1275 'home_URL',
1276 'is_deceased',
1277 'deceased_date',
1278 ),
1279 'Organization' => array(
1280 'first_name',
1281 'middle_name',
1282 'last_name',
1283 'formal_title',
1284 'job_title',
1285 'gender_id',
1286 'prefix_id',
1287 'suffix_id',
1288 'birth_date',
1289 'household_name',
1290 'is_deceased',
1291 'deceased_date',
1292 ),
1293 );
1294 foreach ($commonValues[$contactType] as $value) {
1295 unset($fields[$value]);
1296 }
1297 }
1298 else {
1299 foreach (array('Individual', 'Household', 'Organization') as $type) {
1300 $fields = array_merge($fields,
1301 CRM_Core_BAO_CustomField::getFieldsForImport($type,
1302 $showAll,
1303 FALSE,
1304 FALSE,
1305 FALSE,
1306 $withMultiCustomFields
1307 )
1308 );
1309 }
1310 }
1311
1312 if ($isProfile) {
1313 $fields = array_merge($fields, array(
1314 'group' => array(
1315 'title' => ts('Group(s)'),
1316 'name' => 'group',
1317 ),
1318 'tag' => array(
1319 'title' => ts('Tag(s)'),
1320 'name' => 'tag',
1321 ),
1322 'note' => array(
1323 'title' => ts('Note(s)'),
1324 'name' => 'note',
1325 ),
1326 'communication_style_id' => array(
1327 'title' => ts('Communication Style'),
1328 'name' => 'communication_style_id',
1329 ),
1330 ));
1331 }
1332
1333 //Sorting fields in alphabetical order(CRM-1507)
1334 $fields = CRM_Utils_Array::crmArraySortByField($fields, 'title');
1335
1336 CRM_Core_BAO_Cache::setItem($fields, 'contact fields', $cacheKeyString);
1337 }
1338
1339 self::$_importableFields[$cacheKeyString] = $fields;
1340
1341 if (!$isProfile) {
1342 if (!$status) {
1343 $fields = array_merge(array('do_not_import' => array('title' => ts('- do not import -'))),
1344 self::$_importableFields[$cacheKeyString]
1345 );
1346 }
1347 else {
1348 $fields = array_merge(array('' => array('title' => ts('- Contact Fields -'))),
1349 self::$_importableFields[$cacheKeyString]
1350 );
1351 }
1352 }
1353 return $fields;
1354 }
1355
1356 /**
1357 * Combine all the exportable fields from the lower levels object.
1358 *
1359 * Currently we are using importable fields as exportable fields
1360 *
1361 * @param int|string $contactType contact Type
1362 * @param bool $status
1363 * True while exporting primary contacts.
1364 * @param bool $export
1365 * True when used during export.
1366 * @param bool $search
1367 * True when used during search, might conflict with export param?.
1368 *
1369 * @param bool $withMultiRecord
1370 *
1371 * @return array
1372 * array of exportable Fields
1373 */
1374 public static function &exportableFields($contactType = 'Individual', $status = FALSE, $export = FALSE, $search = FALSE, $withMultiRecord = FALSE, $checkPermissions = TRUE) {
1375 if (empty($contactType)) {
1376 $contactType = 'All';
1377 }
1378
1379 $cacheKeyString = "exportableFields $contactType";
1380 $cacheKeyString .= $export ? '_1' : '_0';
1381 $cacheKeyString .= $status ? '_1' : '_0';
1382 $cacheKeyString .= $search ? '_1' : '_0';
1383 //CRM-14501 it turns out that the impact of permissioning here is sometimes inconsistent. The field that
1384 //calculates custom fields takes into account the logged in user & caches that for all users
1385 //as an interim fix we will cache the fields by contact
1386 $cacheKeyString .= '_' . CRM_Core_Session::getLoggedInContactID();
1387
1388 if (!self::$_exportableFields || !CRM_Utils_Array::value($cacheKeyString, self::$_exportableFields)) {
1389 if (!self::$_exportableFields) {
1390 self::$_exportableFields = array();
1391 }
1392
1393 // check if we can retrieve from database cache
1394 $fields = CRM_Core_BAO_Cache::getItem('contact fields', $cacheKeyString);
1395
1396 if (!$fields) {
1397 $fields = CRM_Contact_DAO_Contact::export();
1398
1399 // The fields are meant for contact types.
1400 if (in_array($contactType, array(
1401 'Individual',
1402 'Household',
1403 'Organization',
1404 'All',
1405 )
1406 )) {
1407 $fields = array_merge($fields, CRM_Core_OptionValue::getFields('', $contactType));
1408 }
1409 // add current employer for individuals
1410 $fields = array_merge($fields, array(
1411 'current_employer' =>
1412 array(
1413 'name' => 'organization_name',
1414 'title' => ts('Current Employer'),
1415 ),
1416 ));
1417
1418 $locationType = array(
1419 'location_type' => array(
1420 'name' => 'location_type',
1421 'where' => 'civicrm_location_type.name',
1422 'title' => ts('Location Type'),
1423 ),
1424 );
1425
1426 $IMProvider = array(
1427 'im_provider' => array(
1428 'name' => 'im_provider',
1429 'where' => 'civicrm_im.provider_id',
1430 'title' => ts('IM Provider'),
1431 ),
1432 );
1433
1434 $locationFields = array_merge($locationType,
1435 CRM_Core_DAO_Address::export(),
1436 CRM_Core_DAO_Phone::export(),
1437 CRM_Core_DAO_Email::export(),
1438 $IMProvider,
1439 CRM_Core_DAO_IM::export(TRUE),
1440 CRM_Core_DAO_OpenID::export()
1441 );
1442
1443 $locationFields = array_merge($locationFields,
1444 CRM_Core_BAO_CustomField::getFieldsForImport('Address')
1445 );
1446
1447 foreach ($locationFields as $key => $field) {
1448 $locationFields[$key]['hasLocationType'] = TRUE;
1449 }
1450
1451 $fields = array_merge($fields, $locationFields);
1452
1453 //add world region
1454 $fields = array_merge($fields,
1455 CRM_Core_DAO_Worldregion::export()
1456 );
1457
1458 $fields = array_merge($fields,
1459 CRM_Contact_DAO_Contact::export()
1460 );
1461
1462 //website fields
1463 $fields = array_merge($fields, CRM_Core_DAO_Website::export());
1464
1465 if ($contactType != 'All') {
1466 $fields = array_merge($fields,
1467 CRM_Core_BAO_CustomField::getFieldsForImport($contactType, $status, FALSE, $search, $checkPermissions, $withMultiRecord)
1468 );
1469 }
1470 else {
1471 foreach (array(
1472 'Individual',
1473 'Household',
1474 'Organization',
1475 ) as $type) {
1476 $fields = array_merge($fields,
1477 CRM_Core_BAO_CustomField::getFieldsForImport($type, FALSE, FALSE, $search, $checkPermissions, $withMultiRecord)
1478 );
1479 }
1480 }
1481 $fields['current_employer_id']['title'] = ts('Current Employer ID');
1482 //fix for CRM-791
1483 if ($export) {
1484 $fields = array_merge($fields, array(
1485 'groups' => array(
1486 'title' => ts('Group(s)'),
1487 'name' => 'groups',
1488 ),
1489 'tags' => array(
1490 'title' => ts('Tag(s)'),
1491 'name' => 'tags',
1492 ),
1493 'notes' => array(
1494 'title' => ts('Note(s)'),
1495 'name' => 'notes',
1496 ),
1497 ));
1498 }
1499 else {
1500 $fields = array_merge($fields, array(
1501 'group' => array(
1502 'title' => ts('Group(s)'),
1503 'name' => 'group',
1504 ),
1505 'tag' => array(
1506 'title' => ts('Tag(s)'),
1507 'name' => 'tag',
1508 ),
1509 'note' => array(
1510 'title' => ts('Note(s)'),
1511 'name' => 'note',
1512 ),
1513 ));
1514 }
1515
1516 //Sorting fields in alphabetical order(CRM-1507)
1517 foreach ($fields as $k => $v) {
1518 $sortArray[$k] = CRM_Utils_Array::value('title', $v);
1519 }
1520
1521 $fields = array_merge($sortArray, $fields);
1522 //unset the field which are not related to their contact type.
1523 if ($contactType != 'All') {
1524 $commonValues = array(
1525 'Individual' => array(
1526 'household_name',
1527 'legal_name',
1528 'sic_code',
1529 'organization_name',
1530 'email_greeting_custom',
1531 'postal_greeting_custom',
1532 'addressee_custom',
1533 ),
1534 'Household' => array(
1535 'first_name',
1536 'middle_name',
1537 'last_name',
1538 'formal_title',
1539 'job_title',
1540 'gender_id',
1541 'prefix_id',
1542 'suffix_id',
1543 'birth_date',
1544 'organization_name',
1545 'legal_name',
1546 'legal_identifier',
1547 'sic_code',
1548 'home_URL',
1549 'is_deceased',
1550 'deceased_date',
1551 'current_employer',
1552 'email_greeting_custom',
1553 'postal_greeting_custom',
1554 'addressee_custom',
1555 'prefix_id',
1556 'suffix_id',
1557 ),
1558 'Organization' => array(
1559 'first_name',
1560 'middle_name',
1561 'last_name',
1562 'formal_title',
1563 'job_title',
1564 'gender_id',
1565 'prefix_id',
1566 'suffix_id',
1567 'birth_date',
1568 'household_name',
1569 'email_greeting_custom',
1570 'postal_greeting_custom',
1571 'prefix_id',
1572 'suffix_id',
1573 'gender_id',
1574 'addressee_custom',
1575 'is_deceased',
1576 'deceased_date',
1577 'current_employer',
1578 ),
1579 );
1580 foreach ($commonValues[$contactType] as $value) {
1581 unset($fields[$value]);
1582 }
1583 }
1584
1585 CRM_Core_BAO_Cache::setItem($fields, 'contact fields', $cacheKeyString);
1586 }
1587 self::$_exportableFields[$cacheKeyString] = $fields;
1588 }
1589
1590 if (!$status) {
1591 $fields = self::$_exportableFields[$cacheKeyString];
1592 }
1593 else {
1594 $fields = array_merge(array('' => array('title' => ts('- Contact Fields -'))),
1595 self::$_exportableFields[$cacheKeyString]
1596 );
1597 }
1598
1599 return $fields;
1600 }
1601
1602 /**
1603 * Get the all contact details (Hierarchical).
1604 *
1605 * @param int $contactId
1606 * Contact id.
1607 * @param array $fields
1608 * Fields array.
1609 *
1610 * @return array
1611 * Contact details
1612 */
1613 public static function getHierContactDetails($contactId, &$fields) {
1614 $params = array(array('contact_id', '=', $contactId, 0, 0));
1615 $options = array();
1616
1617 $returnProperties = self::makeHierReturnProperties($fields, $contactId);
1618
1619 // We don't know the contents of return properties, but we need the lower
1620 // level ids of the contact so add a few fields.
1621 $returnProperties['first_name'] = 1;
1622 $returnProperties['organization_name'] = 1;
1623 $returnProperties['household_name'] = 1;
1624 $returnProperties['contact_type'] = 1;
1625 $returnProperties['contact_sub_type'] = 1;
1626 return list($query, $options) = CRM_Contact_BAO_Query::apiQuery($params, $returnProperties, $options);
1627 }
1628
1629 /**
1630 * Given a set of flat profile style field names, create a hierarchy.
1631 *
1632 * This is for the query to use, create the right sql.
1633 *
1634 * @param $fields
1635 * @param int $contactId
1636 * Contact id.
1637 *
1638 * @return array
1639 * A hierarchical property tree if appropriate
1640 */
1641 public static function &makeHierReturnProperties($fields, $contactId = NULL) {
1642 $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id');
1643
1644 $returnProperties = array();
1645
1646 $multipleFields = array('website' => 'url');
1647 foreach ($fields as $name => $dontCare) {
1648 if (strpos($name, '-') !== FALSE) {
1649 list($fieldName, $id, $type) = CRM_Utils_System::explode('-', $name, 3);
1650
1651 if (!in_array($fieldName, $multipleFields)) {
1652 if ($id == 'Primary') {
1653 $locationTypeName = 1;
1654 }
1655 else {
1656 $locationTypeName = CRM_Utils_Array::value($id, $locationTypes);
1657 if (!$locationTypeName) {
1658 continue;
1659 }
1660 }
1661
1662 if (empty($returnProperties['location'])) {
1663 $returnProperties['location'] = array();
1664 }
1665 if (empty($returnProperties['location'][$locationTypeName])) {
1666 $returnProperties['location'][$locationTypeName] = array();
1667 $returnProperties['location'][$locationTypeName]['location_type'] = $id;
1668 }
1669 if (in_array($fieldName, array(
1670 'phone',
1671 'im',
1672 'email',
1673 'openid',
1674 'phone_ext',
1675 ))) {
1676 if ($type) {
1677 $returnProperties['location'][$locationTypeName][$fieldName . '-' . $type] = 1;
1678 }
1679 else {
1680 $returnProperties['location'][$locationTypeName][$fieldName] = 1;
1681 }
1682 }
1683 elseif (substr($fieldName, 0, 14) === 'address_custom') {
1684 $returnProperties['location'][$locationTypeName][substr($fieldName, 8)] = 1;
1685 }
1686 else {
1687 $returnProperties['location'][$locationTypeName][$fieldName] = 1;
1688 }
1689 }
1690 else {
1691 $returnProperties['website'][$id][$fieldName] = 1;
1692 }
1693 }
1694 else {
1695 $returnProperties[$name] = 1;
1696 }
1697 }
1698
1699 return $returnProperties;
1700 }
1701
1702 /**
1703 * Return the primary location type of a contact.
1704 *
1705 * $params int $contactId contact_id
1706 * $params boolean $isPrimaryExist if true, return primary contact location type otherwise null
1707 * $params boolean $skipDefaultPriamry if true, return primary contact location type otherwise null
1708 *
1709 * @param int $contactId
1710 * @param bool $skipDefaultPriamry
1711 * @param null $block
1712 *
1713 * @return int
1714 * $locationType location_type_id
1715 */
1716 public static function getPrimaryLocationType($contactId, $skipDefaultPriamry = FALSE, $block = NULL) {
1717 if ($block) {
1718 $entityBlock = array('contact_id' => $contactId);
1719 $blocks = CRM_Core_BAO_Location::getValues($entityBlock);
1720 foreach ($blocks[$block] as $key => $value) {
1721 if (!empty($value['is_primary'])) {
1722 $locationType = CRM_Utils_Array::value('location_type_id', $value);
1723 }
1724 }
1725 }
1726 else {
1727 $query = "
1728 SELECT
1729 IF ( civicrm_email.location_type_id IS NULL,
1730 IF ( civicrm_address.location_type_id IS NULL,
1731 IF ( civicrm_phone.location_type_id IS NULL,
1732 IF ( civicrm_im.location_type_id IS NULL,
1733 IF ( civicrm_openid.location_type_id IS NULL, null, civicrm_openid.location_type_id)
1734 ,civicrm_im.location_type_id)
1735 ,civicrm_phone.location_type_id)
1736 ,civicrm_address.location_type_id)
1737 ,civicrm_email.location_type_id) as locationType
1738 FROM civicrm_contact
1739 LEFT JOIN civicrm_email ON ( civicrm_email.is_primary = 1 AND civicrm_email.contact_id = civicrm_contact.id )
1740 LEFT JOIN civicrm_address ON ( civicrm_address.is_primary = 1 AND civicrm_address.contact_id = civicrm_contact.id)
1741 LEFT JOIN civicrm_phone ON ( civicrm_phone.is_primary = 1 AND civicrm_phone.contact_id = civicrm_contact.id)
1742 LEFT JOIN civicrm_im ON ( civicrm_im.is_primary = 1 AND civicrm_im.contact_id = civicrm_contact.id)
1743 LEFT JOIN civicrm_openid ON ( civicrm_openid.is_primary = 1 AND civicrm_openid.contact_id = civicrm_contact.id)
1744 WHERE civicrm_contact.id = %1 ";
1745
1746 $params = array(1 => array($contactId, 'Integer'));
1747
1748 $dao = CRM_Core_DAO::executeQuery($query, $params);
1749
1750 $locationType = NULL;
1751 if ($dao->fetch()) {
1752 $locationType = $dao->locationType;
1753 }
1754 }
1755 if (isset($locationType)) {
1756 return $locationType;
1757 }
1758 elseif ($skipDefaultPriamry) {
1759 // if there is no primary contact location then return null
1760 return NULL;
1761 }
1762 else {
1763 // if there is no primart contact location, then return default
1764 // location type of the system
1765 $defaultLocationType = CRM_Core_BAO_LocationType::getDefault();
1766 return $defaultLocationType->id;
1767 }
1768 }
1769
1770 /**
1771 * Get the display name, primary email and location type of a contact.
1772 *
1773 * @param int $id
1774 * Id of the contact.
1775 *
1776 * @return array
1777 * Array of display_name, email if found, do_not_email or (null,null,null)
1778 */
1779 public static function getContactDetails($id) {
1780 // check if the contact type
1781 $contactType = self::getContactType($id);
1782
1783 $nameFields = ($contactType == 'Individual') ? "civicrm_contact.first_name, civicrm_contact.last_name, civicrm_contact.display_name" : "civicrm_contact.display_name";
1784
1785 $sql = "
1786 SELECT $nameFields, civicrm_email.email, civicrm_contact.do_not_email, civicrm_email.on_hold, civicrm_contact.is_deceased
1787 FROM civicrm_contact LEFT JOIN civicrm_email ON (civicrm_contact.id = civicrm_email.contact_id)
1788 WHERE civicrm_contact.id = %1
1789 ORDER BY civicrm_email.is_primary DESC";
1790 $params = array(1 => array($id, 'Integer'));
1791 $dao = CRM_Core_DAO::executeQuery($sql, $params);
1792
1793 if ($dao->fetch()) {
1794 if ($contactType == 'Individual') {
1795 if ($dao->first_name || $dao->last_name) {
1796 $name = "{$dao->first_name} {$dao->last_name}";
1797 }
1798 else {
1799 $name = $dao->display_name;
1800 }
1801 }
1802 else {
1803 $name = $dao->display_name;
1804 }
1805 $email = $dao->email;
1806 $doNotEmail = $dao->do_not_email ? TRUE : FALSE;
1807 $onHold = $dao->on_hold ? TRUE : FALSE;
1808 $isDeceased = $dao->is_deceased ? TRUE : FALSE;
1809 return array($name, $email, $doNotEmail, $onHold, $isDeceased);
1810 }
1811 return array(NULL, NULL, NULL, NULL, NULL);
1812 }
1813
1814 /**
1815 * Add/edit/register contacts through profile.
1816 *
1817 * @param array $params
1818 * Array of profile fields to be edited/added.
1819 * @param array $fields
1820 * Array of fields from UFGroup.
1821 * @param int $contactID
1822 * Id of the contact to be edited/added.
1823 * @param int $addToGroupID
1824 * Specifies the default group to which contact is added.
1825 * @param int $ufGroupId
1826 * Uf group id (profile id).
1827 * @param string $ctype
1828 * @param bool $visibility
1829 * Basically lets us know where this request is coming from.
1830 * if via a profile from web, we restrict what groups are changed
1831 *
1832 * @return int
1833 * contact id created/edited
1834 */
1835 public static function createProfileContact(
1836 &$params,
1837 &$fields,
1838 $contactID = NULL,
1839 $addToGroupID = NULL,
1840 $ufGroupId = NULL,
1841 $ctype = NULL,
1842 $visibility = FALSE
1843 ) {
1844 // add ufGroupID to params array ( CRM-2012 )
1845 if ($ufGroupId) {
1846 $params['uf_group_id'] = $ufGroupId;
1847 }
1848 self::addBillingNameFieldsIfOtherwiseNotSet($params);
1849
1850 // If a user has logged in, or accessed via a checksum
1851 // Then deliberately 'blanking' a value in the profile should remove it from their record
1852 $session = CRM_Core_Session::singleton();
1853 $params['updateBlankLocInfo'] = TRUE;
1854 if (($session->get('authSrc') & (CRM_Core_Permission::AUTH_SRC_CHECKSUM + CRM_Core_Permission::AUTH_SRC_LOGIN)) == 0) {
1855 $params['updateBlankLocInfo'] = FALSE;
1856 }
1857
1858 if ($contactID) {
1859 $editHook = TRUE;
1860 CRM_Utils_Hook::pre('edit', 'Profile', $contactID, $params);
1861 }
1862 else {
1863 $editHook = FALSE;
1864 CRM_Utils_Hook::pre('create', 'Profile', NULL, $params);
1865 }
1866
1867 list($data, $contactDetails) = self::formatProfileContactParams($params, $fields, $contactID, $ufGroupId, $ctype);
1868
1869 // manage is_opt_out
1870 if (array_key_exists('is_opt_out', $fields) && array_key_exists('is_opt_out', $params)) {
1871 $wasOptOut = CRM_Utils_Array::value('is_opt_out', $contactDetails, FALSE);
1872 $isOptOut = CRM_Utils_Array::value('is_opt_out', $params, FALSE);
1873 $data['is_opt_out'] = $isOptOut;
1874 // on change, create new civicrm_subscription_history entry
1875 if (($wasOptOut != $isOptOut) && !empty($contactDetails['contact_id'])) {
1876 $shParams = array(
1877 'contact_id' => $contactDetails['contact_id'],
1878 'status' => $isOptOut ? 'Removed' : 'Added',
1879 'method' => 'Web',
1880 );
1881 CRM_Contact_BAO_SubscriptionHistory::create($shParams);
1882 }
1883 }
1884
1885 $contact = self::create($data);
1886
1887 // contact is null if the profile does not have any contact fields
1888 if ($contact) {
1889 $contactID = $contact->id;
1890 }
1891
1892 if (empty($contactID)) {
1893 CRM_Core_Error::fatal('Cannot proceed without a valid contact id');
1894 }
1895
1896 // Process group and tag
1897 if (!empty($fields['group'])) {
1898 $method = 'Admin';
1899 // this for sure means we are coming in via profile since i added it to fix
1900 // removing contacts from user groups -- lobo
1901 if ($visibility) {
1902 $method = 'Web';
1903 }
1904 CRM_Contact_BAO_GroupContact::create($params['group'], $contactID, $visibility, $method);
1905 }
1906
1907 if (!empty($fields['tag'])) {
1908 CRM_Core_BAO_EntityTag::create($params['tag'], 'civicrm_contact', $contactID);
1909 }
1910
1911 //to add profile in default group
1912 if (is_array($addToGroupID)) {
1913 $contactIds = array($contactID);
1914 foreach ($addToGroupID as $groupId) {
1915 CRM_Contact_BAO_GroupContact::addContactsToGroup($contactIds, $groupId);
1916 }
1917 }
1918 elseif ($addToGroupID) {
1919 $contactIds = array($contactID);
1920 CRM_Contact_BAO_GroupContact::addContactsToGroup($contactIds, $addToGroupID);
1921 }
1922
1923 CRM_Contact_BAO_GroupContactCache::opportunisticCacheFlush();
1924
1925 if ($editHook) {
1926 CRM_Utils_Hook::post('edit', 'Profile', $contactID, $params);
1927 }
1928 else {
1929 CRM_Utils_Hook::post('create', 'Profile', $contactID, $params);
1930 }
1931 return $contactID;
1932 }
1933
1934 /**
1935 * Format profile contact parameters.
1936 *
1937 * @param array $params
1938 * @param $fields
1939 * @param int $contactID
1940 * @param int $ufGroupId
1941 * @param null $ctype
1942 * @param bool $skipCustom
1943 *
1944 * @return array
1945 */
1946 public static function formatProfileContactParams(
1947 &$params,
1948 &$fields,
1949 $contactID = NULL,
1950 $ufGroupId = NULL,
1951 $ctype = NULL,
1952 $skipCustom = FALSE
1953 ) {
1954
1955 $data = $contactDetails = array();
1956
1957 // get the contact details (hier)
1958 if ($contactID) {
1959 list($details, $options) = self::getHierContactDetails($contactID, $fields);
1960
1961 $contactDetails = $details[$contactID];
1962 $data['contact_type'] = CRM_Utils_Array::value('contact_type', $contactDetails);
1963 $data['contact_sub_type'] = CRM_Utils_Array::value('contact_sub_type', $contactDetails);
1964 }
1965 else {
1966 //we should get contact type only if contact
1967 if ($ufGroupId) {
1968 $data['contact_type'] = CRM_Core_BAO_UFField::getProfileType($ufGroupId);
1969
1970 //special case to handle profile with only contact fields
1971 if ($data['contact_type'] == 'Contact') {
1972 $data['contact_type'] = 'Individual';
1973 }
1974 elseif (CRM_Contact_BAO_ContactType::isaSubType($data['contact_type'])) {
1975 $data['contact_type'] = CRM_Contact_BAO_ContactType::getBasicType($data['contact_type']);
1976 }
1977 }
1978 elseif ($ctype) {
1979 $data['contact_type'] = $ctype;
1980 }
1981 else {
1982 $data['contact_type'] = 'Individual';
1983 }
1984 }
1985
1986 //fix contact sub type CRM-5125
1987 if (array_key_exists('contact_sub_type', $params) &&
1988 !empty($params['contact_sub_type'])
1989 ) {
1990 $data['contact_sub_type'] = CRM_Utils_Array::implodePadded($params['contact_sub_type']);
1991 }
1992 elseif (array_key_exists('contact_sub_type_hidden', $params) &&
1993 !empty($params['contact_sub_type_hidden'])
1994 ) {
1995 // if profile was used, and had any subtype, we obtain it from there
1996 //CRM-13596 - add to existing contact types, rather than overwriting
1997 $data_contact_sub_type_arr = CRM_Utils_Array::explodePadded($data['contact_sub_type']);
1998 if (!in_array($params['contact_sub_type_hidden'], $data_contact_sub_type_arr)) {
1999 //CRM-20517 - make sure contact_sub_type gets the correct delimiters
2000 $data['contact_sub_type'] = trim($data['contact_sub_type'], CRM_Core_DAO::VALUE_SEPARATOR);
2001 $data['contact_sub_type'] = CRM_Core_DAO::VALUE_SEPARATOR . $data['contact_sub_type'] . CRM_Utils_Array::implodePadded($params['contact_sub_type_hidden']);
2002 }
2003 }
2004
2005 if ($ctype == 'Organization') {
2006 $data['organization_name'] = CRM_Utils_Array::value('organization_name', $contactDetails);
2007 }
2008 elseif ($ctype == 'Household') {
2009 $data['household_name'] = CRM_Utils_Array::value('household_name', $contactDetails);
2010 }
2011
2012 $locationType = array();
2013 $count = 1;
2014
2015 if ($contactID) {
2016 //add contact id
2017 $data['contact_id'] = $contactID;
2018 $primaryLocationType = self::getPrimaryLocationType($contactID);
2019 }
2020 else {
2021 $defaultLocation = CRM_Core_BAO_LocationType::getDefault();
2022 $defaultLocationId = $defaultLocation->id;
2023 }
2024
2025 $billingLocationTypeId = CRM_Core_BAO_LocationType::getBilling();
2026
2027 $blocks = array('email', 'phone', 'im', 'openid');
2028
2029 $multiplFields = array('url');
2030 // prevent overwritten of formatted array, reset all block from
2031 // params if it is not in valid format (since import pass valid format)
2032 foreach ($blocks as $blk) {
2033 if (array_key_exists($blk, $params) &&
2034 !is_array($params[$blk])
2035 ) {
2036 unset($params[$blk]);
2037 }
2038 }
2039
2040 $primaryPhoneLoc = NULL;
2041 $session = CRM_Core_Session::singleton();
2042 foreach ($params as $key => $value) {
2043 list($fieldName, $locTypeId, $typeId) = CRM_Utils_System::explode('-', $key, 3);
2044
2045 if ($locTypeId == 'Primary') {
2046 if ($contactID) {
2047 if (in_array($fieldName, $blocks)) {
2048 $locTypeId = self::getPrimaryLocationType($contactID, FALSE, $fieldName);
2049 }
2050 else {
2051 $locTypeId = self::getPrimaryLocationType($contactID, FALSE, 'address');
2052 }
2053 $primaryLocationType = $locTypeId;
2054 }
2055 else {
2056 $locTypeId = $defaultLocationId;
2057 }
2058 }
2059
2060 if (is_numeric($locTypeId) &&
2061 !in_array($fieldName, $multiplFields) &&
2062 substr($fieldName, 0, 7) != 'custom_'
2063 ) {
2064 $index = $locTypeId;
2065
2066 if (is_numeric($typeId)) {
2067 $index .= '-' . $typeId;
2068 }
2069 if (!in_array($index, $locationType)) {
2070 $locationType[$count] = $index;
2071 $count++;
2072 }
2073
2074 $loc = CRM_Utils_Array::key($index, $locationType);
2075
2076 $blockName = in_array($fieldName, $blocks) ? $fieldName : 'address';
2077
2078 $data[$blockName][$loc]['location_type_id'] = $locTypeId;
2079
2080 //set is_billing true, for location type "Billing"
2081 if ($locTypeId == $billingLocationTypeId) {
2082 $data[$blockName][$loc]['is_billing'] = 1;
2083 }
2084
2085 if ($contactID) {
2086 //get the primary location type
2087 if ($locTypeId == $primaryLocationType) {
2088 $data[$blockName][$loc]['is_primary'] = 1;
2089 }
2090 }
2091 elseif ($locTypeId == $defaultLocationId) {
2092 $data[$blockName][$loc]['is_primary'] = 1;
2093 }
2094
2095 if (in_array($fieldName, array('phone'))) {
2096 if ($typeId) {
2097 $data['phone'][$loc]['phone_type_id'] = $typeId;
2098 }
2099 else {
2100 $data['phone'][$loc]['phone_type_id'] = '';
2101 }
2102 $data['phone'][$loc]['phone'] = $value;
2103
2104 //special case to handle primary phone with different phone types
2105 // in this case we make first phone type as primary
2106 if (isset($data['phone'][$loc]['is_primary']) && !$primaryPhoneLoc) {
2107 $primaryPhoneLoc = $loc;
2108 }
2109
2110 if ($loc != $primaryPhoneLoc) {
2111 unset($data['phone'][$loc]['is_primary']);
2112 }
2113 }
2114 elseif ($fieldName == 'phone_ext') {
2115 $data['phone'][$loc]['phone_ext'] = $value;
2116 }
2117 elseif ($fieldName == 'email') {
2118 $data['email'][$loc]['email'] = $value;
2119 if (empty($contactID)) {
2120 $data['email'][$loc]['is_primary'] = 1;
2121 }
2122 }
2123 elseif ($fieldName == 'im') {
2124 if (isset($params[$key . '-provider_id'])) {
2125 $data['im'][$loc]['provider_id'] = $params[$key . '-provider_id'];
2126 }
2127 if (strpos($key, '-provider_id') !== FALSE) {
2128 $data['im'][$loc]['provider_id'] = $params[$key];
2129 }
2130 else {
2131 $data['im'][$loc]['name'] = $value;
2132 }
2133 }
2134 elseif ($fieldName == 'openid') {
2135 $data['openid'][$loc]['openid'] = $value;
2136 }
2137 else {
2138 if ($fieldName === 'state_province') {
2139 // CRM-3393
2140 if (is_numeric($value) && ((int ) $value) >= 1000) {
2141 $data['address'][$loc]['state_province_id'] = $value;
2142 }
2143 elseif (empty($value)) {
2144 $data['address'][$loc]['state_province_id'] = '';
2145 }
2146 else {
2147 $data['address'][$loc]['state_province'] = $value;
2148 }
2149 }
2150 elseif ($fieldName === 'country') {
2151 // CRM-3393
2152 if (is_numeric($value) && ((int ) $value) >= 1000
2153 ) {
2154 $data['address'][$loc]['country_id'] = $value;
2155 }
2156 elseif (empty($value)) {
2157 $data['address'][$loc]['country_id'] = '';
2158 }
2159 else {
2160 $data['address'][$loc]['country'] = $value;
2161 }
2162 }
2163 elseif ($fieldName === 'county') {
2164 $data['address'][$loc]['county_id'] = $value;
2165 }
2166 elseif ($fieldName == 'address_name') {
2167 $data['address'][$loc]['name'] = $value;
2168 }
2169 elseif (substr($fieldName, 0, 14) === 'address_custom') {
2170 $data['address'][$loc][substr($fieldName, 8)] = $value;
2171 }
2172 else {
2173 $data['address'][$loc][$fieldName] = $value;
2174 }
2175 }
2176 }
2177 else {
2178 if (substr($key, 0, 4) === 'url-') {
2179 $websiteField = explode('-', $key);
2180 $data['website'][$websiteField[1]]['website_type_id'] = $websiteField[1];
2181 $data['website'][$websiteField[1]]['url'] = $value;
2182 }
2183 elseif (in_array($key, self::$_greetingTypes, TRUE)) {
2184 //save email/postal greeting and addressee values if any, CRM-4575
2185 $data[$key . '_id'] = $value;
2186 }
2187 elseif (!$skipCustom && ($customFieldId = CRM_Core_BAO_CustomField::getKeyID($key))) {
2188 // for autocomplete transfer hidden value instead of label
2189 if ($params[$key] && isset($params[$key . '_id'])) {
2190 $value = $params[$key . '_id'];
2191 }
2192
2193 // we need to append time with date
2194 if ($params[$key] && isset($params[$key . '_time'])) {
2195 $value .= ' ' . $params[$key . '_time'];
2196 }
2197
2198 // if auth source is not checksum / login && $value is blank, do not proceed - CRM-10128
2199 if (($session->get('authSrc') & (CRM_Core_Permission::AUTH_SRC_CHECKSUM + CRM_Core_Permission::AUTH_SRC_LOGIN)) == 0 &&
2200 ($value == '' || !isset($value))
2201 ) {
2202 continue;
2203 }
2204
2205 $valueId = NULL;
2206 if (!empty($params['customRecordValues'])) {
2207 if (is_array($params['customRecordValues']) && !empty($params['customRecordValues'])) {
2208 foreach ($params['customRecordValues'] as $recId => $customFields) {
2209 if (is_array($customFields) && !empty($customFields)) {
2210 foreach ($customFields as $customFieldName) {
2211 if ($customFieldName == $key) {
2212 $valueId = $recId;
2213 break;
2214 }
2215 }
2216 }
2217 }
2218 }
2219 }
2220
2221 //CRM-13596 - check for contact_sub_type_hidden first
2222 if (array_key_exists('contact_sub_type_hidden', $params)) {
2223 $type = $params['contact_sub_type_hidden'];
2224 }
2225 else {
2226 $type = $data['contact_type'];
2227 if (!empty($data['contact_sub_type'])) {
2228 $type = CRM_Utils_Array::explodePadded($data['contact_sub_type']);
2229 }
2230 }
2231
2232 CRM_Core_BAO_CustomField::formatCustomField($customFieldId,
2233 $data['custom'],
2234 $value,
2235 $type,
2236 $valueId,
2237 $contactID,
2238 FALSE,
2239 FALSE
2240 );
2241 }
2242 elseif ($key == 'edit') {
2243 continue;
2244 }
2245 else {
2246 if ($key == 'location') {
2247 foreach ($value as $locationTypeId => $field) {
2248 foreach ($field as $block => $val) {
2249 if ($block == 'address' && array_key_exists('address_name', $val)) {
2250 $value[$locationTypeId][$block]['name'] = $value[$locationTypeId][$block]['address_name'];
2251 }
2252 }
2253 }
2254 }
2255 if ($key == 'phone' && isset($params['phone_ext'])) {
2256 $data[$key] = $value;
2257 foreach ($value as $cnt => $phoneBlock) {
2258 if ($params[$key][$cnt]['location_type_id'] == $params['phone_ext'][$cnt]['location_type_id']) {
2259 $data[$key][$cnt]['phone_ext'] = CRM_Utils_Array::retrieveValueRecursive($params['phone_ext'][$cnt], 'phone_ext');
2260 }
2261 }
2262 }
2263 elseif (in_array($key,
2264 array(
2265 'nick_name',
2266 'job_title',
2267 'middle_name',
2268 'birth_date',
2269 'gender_id',
2270 'current_employer',
2271 'prefix_id',
2272 'suffix_id',
2273 )) &&
2274 ($value == '' || !isset($value)) &&
2275 ($session->get('authSrc') & (CRM_Core_Permission::AUTH_SRC_CHECKSUM + CRM_Core_Permission::AUTH_SRC_LOGIN)) == 0 ||
2276 ($key == 'current_employer' && empty($params['current_employer']))) {
2277 // CRM-10128: if auth source is not checksum / login && $value is blank, do not fill $data with empty value
2278 // to avoid update with empty values
2279 continue;
2280 }
2281 else {
2282 $data[$key] = $value;
2283 }
2284 }
2285 }
2286 }
2287
2288 if (!isset($data['contact_type'])) {
2289 $data['contact_type'] = 'Individual';
2290 }
2291
2292 //set the values for checkboxes (do_not_email, do_not_mail, do_not_trade, do_not_phone)
2293 $privacy = CRM_Core_SelectValues::privacy();
2294 foreach ($privacy as $key => $value) {
2295 if (array_key_exists($key, $fields)) {
2296 // do not reset values for existing contacts, if fields are added to a profile
2297 if (array_key_exists($key, $params)) {
2298 $data[$key] = $params[$key];
2299 if (empty($params[$key])) {
2300 $data[$key] = 0;
2301 }
2302 }
2303 elseif (!$contactID) {
2304 $data[$key] = 0;
2305 }
2306 }
2307 }
2308
2309 return array($data, $contactDetails);
2310 }
2311
2312 /**
2313 * Find the get contact details.
2314 *
2315 * This function does not respect ACLs for now, which might need to be rectified at some
2316 * stage based on how its used.
2317 *
2318 * @param string $mail
2319 * Primary email address of the contact.
2320 * @param string $ctype
2321 * Contact type.
2322 *
2323 * @return object|null
2324 * $dao contact details
2325 */
2326 public static function matchContactOnEmail($mail, $ctype = NULL) {
2327 $strtolower = function_exists('mb_strtolower') ? 'mb_strtolower' : 'strtolower';
2328 $mail = $strtolower(trim($mail));
2329 $query = "
2330 SELECT civicrm_contact.id as contact_id,
2331 civicrm_contact.hash as hash,
2332 civicrm_contact.contact_type as contact_type,
2333 civicrm_contact.contact_sub_type as contact_sub_type
2334 FROM civicrm_contact
2335 INNER JOIN civicrm_email ON ( civicrm_contact.id = civicrm_email.contact_id )";
2336
2337 if (Civi::settings()->get('uniq_email_per_site')) {
2338 // try to find a match within a site (multisite).
2339 $groups = CRM_Core_BAO_Domain::getChildGroupIds();
2340 if (!empty($groups)) {
2341 $query .= "
2342 INNER JOIN civicrm_group_contact gc ON
2343 (civicrm_contact.id = gc.contact_id AND gc.status = 'Added' AND gc.group_id IN (" . implode(',', $groups) . "))";
2344 }
2345 }
2346
2347 $query .= "
2348 WHERE civicrm_email.email = %1 AND civicrm_contact.is_deleted=0";
2349 $p = array(1 => array($mail, 'String'));
2350
2351 if ($ctype) {
2352 $query .= " AND civicrm_contact.contact_type = %3";
2353 $p[3] = array($ctype, 'String');
2354 }
2355
2356 $query .= " ORDER BY civicrm_email.is_primary DESC";
2357
2358 $dao = CRM_Core_DAO::executeQuery($query, $p);
2359
2360 if ($dao->fetch()) {
2361 return $dao;
2362 }
2363 return NULL;
2364 }
2365
2366 /**
2367 * Find the contact details associated with an OpenID.
2368 *
2369 * @param string $openId
2370 * OpenId of the contact.
2371 * @param string $ctype
2372 * Contact type.
2373 *
2374 * @return object|null
2375 * $dao contact details
2376 */
2377 public static function matchContactOnOpenId($openId, $ctype = NULL) {
2378 $strtolower = function_exists('mb_strtolower') ? 'mb_strtolower' : 'strtolower';
2379 $openId = $strtolower(trim($openId));
2380 $query = "
2381 SELECT civicrm_contact.id as contact_id,
2382 civicrm_contact.hash as hash,
2383 civicrm_contact.contact_type as contact_type,
2384 civicrm_contact.contact_sub_type as contact_sub_type
2385 FROM civicrm_contact
2386 INNER JOIN civicrm_openid ON ( civicrm_contact.id = civicrm_openid.contact_id )
2387 WHERE civicrm_openid.openid = %1";
2388 $p = array(1 => array($openId, 'String'));
2389
2390 if ($ctype) {
2391 $query .= " AND civicrm_contact.contact_type = %3";
2392 $p[3] = array($ctype, 'String');
2393 }
2394
2395 $query .= " ORDER BY civicrm_openid.is_primary DESC";
2396
2397 $dao = CRM_Core_DAO::executeQuery($query, $p);
2398
2399 if ($dao->fetch()) {
2400 return $dao;
2401 }
2402 return NULL;
2403 }
2404
2405 /**
2406 * Get primary email of the contact.
2407 *
2408 * @param int $contactID
2409 * Contact id.
2410 *
2411 * @return string
2412 * Email address if present else null
2413 */
2414 public static function getPrimaryEmail($contactID) {
2415 // fetch the primary email
2416 $query = "
2417 SELECT civicrm_email.email as email
2418 FROM civicrm_contact
2419 LEFT JOIN civicrm_email ON ( civicrm_contact.id = civicrm_email.contact_id )
2420 WHERE civicrm_email.is_primary = 1
2421 AND civicrm_contact.id = %1";
2422 $p = array(1 => array($contactID, 'Integer'));
2423 $dao = CRM_Core_DAO::executeQuery($query, $p);
2424
2425 $email = NULL;
2426 if ($dao->fetch()) {
2427 $email = $dao->email;
2428 }
2429 $dao->free();
2430 return $email;
2431 }
2432
2433 /**
2434 * Function to get primary OpenID of the contact.
2435 *
2436 * @param int $contactID
2437 * Contact id.
2438 *
2439 * @return string
2440 * >openid OpenID if present else null
2441 */
2442 public static function getPrimaryOpenId($contactID) {
2443 // fetch the primary OpenID
2444 $query = "
2445 SELECT civicrm_openid.openid as openid
2446 FROM civicrm_contact
2447 LEFT JOIN civicrm_openid ON ( civicrm_contact.id = civicrm_openid.contact_id )
2448 WHERE civicrm_contact.id = %1
2449 AND civicrm_openid.is_primary = 1";
2450 $p = array(1 => array($contactID, 'Integer'));
2451 $dao = CRM_Core_DAO::executeQuery($query, $p);
2452
2453 $openId = NULL;
2454 if ($dao->fetch()) {
2455 $openId = $dao->openid;
2456 }
2457 $dao->free();
2458 return $openId;
2459 }
2460
2461 /**
2462 * Fetch the object and store the values in the values array.
2463 *
2464 * @param array $params
2465 * Input parameters to find object.
2466 * @param array $values
2467 * Output values of the object.
2468 *
2469 * @return CRM_Contact_BAO_Contact|null
2470 * The found object or null
2471 */
2472 public static function getValues(&$params, &$values) {
2473 $contact = new CRM_Contact_BAO_Contact();
2474
2475 $contact->copyValues($params);
2476
2477 if ($contact->find(TRUE)) {
2478
2479 CRM_Core_DAO::storeValues($contact, $values);
2480
2481 $privacy = array();
2482 foreach (self::$_commPrefs as $name) {
2483 if (isset($contact->$name)) {
2484 $privacy[$name] = $contact->$name;
2485 }
2486 }
2487
2488 if (!empty($privacy)) {
2489 $values['privacy'] = $privacy;
2490 }
2491
2492 // communication Prefferance
2493 $preffComm = $comm = array();
2494 $comm = explode(CRM_Core_DAO::VALUE_SEPARATOR,
2495 $contact->preferred_communication_method
2496 );
2497 foreach ($comm as $value) {
2498 $preffComm[$value] = 1;
2499 }
2500 $temp = array('preferred_communication_method' => $contact->preferred_communication_method);
2501
2502 $names = array(
2503 'preferred_communication_method' => array(
2504 'newName' => 'preferred_communication_method_display',
2505 'groupName' => 'preferred_communication_method',
2506 ),
2507 );
2508
2509 // @todo This can be figured out from metadata & we can avoid the uncached query.
2510 CRM_Core_OptionGroup::lookupValues($temp, $names, FALSE);
2511
2512 $values['preferred_communication_method'] = $preffComm;
2513 $values['preferred_communication_method_display'] = CRM_Utils_Array::value('preferred_communication_method_display', $temp);
2514
2515 if ($contact->preferred_mail_format) {
2516 $preferredMailingFormat = CRM_Core_SelectValues::pmf();
2517 $values['preferred_mail_format'] = $preferredMailingFormat[$contact->preferred_mail_format];
2518 }
2519
2520 // get preferred languages
2521 if (!empty($contact->preferred_language)) {
2522 $values['preferred_language'] = CRM_Core_PseudoConstant::getLabel('CRM_Contact_DAO_Contact', 'preferred_language', $contact->preferred_language);
2523 }
2524
2525 // Calculating Year difference
2526 if ($contact->birth_date) {
2527 $birthDate = CRM_Utils_Date::customFormat($contact->birth_date, '%Y%m%d');
2528 if ($birthDate < date('Ymd')) {
2529 $age = CRM_Utils_Date::calculateAge($birthDate);
2530 $values['age']['y'] = CRM_Utils_Array::value('years', $age);
2531 $values['age']['m'] = CRM_Utils_Array::value('months', $age);
2532 }
2533 }
2534
2535 $contact->contact_id = $contact->id;
2536
2537 return $contact;
2538 }
2539 return NULL;
2540 }
2541
2542 /**
2543 * Given the component name and returns the count of participation of contact.
2544 *
2545 * @param string $component
2546 * Input component name.
2547 * @param int $contactId
2548 * Input contact id.
2549 * @param string $tableName
2550 * Optional tableName if component is custom group.
2551 *
2552 * @return int
2553 * total number in database
2554 */
2555 public static function getCountComponent($component, $contactId, $tableName = NULL) {
2556 $object = NULL;
2557 switch ($component) {
2558 case 'tag':
2559 return CRM_Core_BAO_EntityTag::getContactTags($contactId, TRUE);
2560
2561 case 'rel':
2562 $result = CRM_Contact_BAO_Relationship::getRelationship($contactId,
2563 CRM_Contact_BAO_Relationship::CURRENT,
2564 0, 1, 0,
2565 NULL, NULL,
2566 TRUE
2567 );
2568 return $result;
2569
2570 case 'group':
2571
2572 return CRM_Contact_BAO_GroupContact::getContactGroup($contactId, "Added", NULL, TRUE);
2573
2574 case 'log':
2575 if (CRM_Core_BAO_Log::useLoggingReport()) {
2576 return FALSE;
2577 }
2578 return CRM_Core_BAO_Log::getContactLogCount($contactId);
2579
2580 case 'note':
2581 return CRM_Core_BAO_Note::getContactNoteCount($contactId);
2582
2583 case 'contribution':
2584 return CRM_Contribute_BAO_Contribution::contributionCount($contactId);
2585
2586 case 'membership':
2587 return CRM_Member_BAO_Membership::getContactMembershipCount($contactId, TRUE);
2588
2589 case 'participant':
2590 return CRM_Event_BAO_Participant::getContactParticipantCount($contactId);
2591
2592 case 'pledge':
2593 return CRM_Pledge_BAO_Pledge::getContactPledgeCount($contactId);
2594
2595 case 'case':
2596 return CRM_Case_BAO_Case::caseCount($contactId);
2597
2598 case 'grant':
2599 return CRM_Grant_BAO_Grant::getContactGrantCount($contactId);
2600
2601 case 'activity':
2602 $input = array(
2603 'contact_id' => $contactId,
2604 'admin' => FALSE,
2605 'caseId' => NULL,
2606 'context' => 'activity',
2607 );
2608 return CRM_Activity_BAO_Activity::deprecatedGetActivitiesCount($input);
2609
2610 case 'mailing':
2611 $params = array('contact_id' => $contactId);
2612 return CRM_Mailing_BAO_Mailing::getContactMailingsCount($params);
2613
2614 default:
2615 $custom = explode('_', $component);
2616 if ($custom['0'] = 'custom') {
2617 if (!$tableName) {
2618 $tableName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $custom['1'], 'table_name');
2619 }
2620 $queryString = "SELECT count(id) FROM {$tableName} WHERE entity_id = {$contactId}";
2621 return CRM_Core_DAO::singleValueQuery($queryString);
2622 }
2623 }
2624 }
2625
2626 /**
2627 * Process greetings and cache.
2628 *
2629 * @param object $contact
2630 * Contact object after save.
2631 * @param bool $useDefaults
2632 * Use default greeting values.
2633 */
2634 public static function processGreetings(&$contact, $useDefaults = FALSE) {
2635 if ($useDefaults) {
2636 //retrieve default greetings
2637 $defaultGreetings = CRM_Core_PseudoConstant::greetingDefaults();
2638 $contactDefaults = $defaultGreetings[$contact->contact_type];
2639 }
2640
2641 // The contact object has not always required the
2642 // fields that are required to calculate greetings
2643 // so we need to retrieve it again.
2644 if ($contact->_query !== FALSE) {
2645 $contact->find(TRUE);
2646 }
2647
2648 // store object values to an array
2649 $contactDetails = array();
2650 CRM_Core_DAO::storeValues($contact, $contactDetails);
2651 $contactDetails = array(array($contact->id => $contactDetails));
2652
2653 $emailGreetingString = $postalGreetingString = $addresseeString = NULL;
2654 $updateQueryString = array();
2655
2656 //cache email and postal greeting to greeting display
2657 if ($contact->email_greeting_custom != 'null' && $contact->email_greeting_custom) {
2658 $emailGreetingString = $contact->email_greeting_custom;
2659 }
2660 elseif ($contact->email_greeting_id != 'null' && $contact->email_greeting_id) {
2661 // the filter value for Individual contact type is set to 1
2662 $filter = array(
2663 'contact_type' => $contact->contact_type,
2664 'greeting_type' => 'email_greeting',
2665 );
2666
2667 $emailGreeting = CRM_Core_PseudoConstant::greeting($filter);
2668 $emailGreetingString = $emailGreeting[$contact->email_greeting_id];
2669 $updateQueryString[] = " email_greeting_custom = NULL ";
2670 }
2671 else {
2672 if ($useDefaults) {
2673 reset($contactDefaults['email_greeting']);
2674 $emailGreetingID = key($contactDefaults['email_greeting']);
2675 $emailGreetingString = $contactDefaults['email_greeting'][$emailGreetingID];
2676 $updateQueryString[] = " email_greeting_id = $emailGreetingID ";
2677 $updateQueryString[] = " email_greeting_custom = NULL ";
2678 }
2679 elseif ($contact->email_greeting_custom) {
2680 $updateQueryString[] = " email_greeting_display = NULL ";
2681 }
2682 }
2683
2684 if ($emailGreetingString) {
2685 CRM_Contact_BAO_Contact_Utils::processGreetingTemplate($emailGreetingString,
2686 $contactDetails,
2687 $contact->id,
2688 'CRM_Contact_BAO_Contact'
2689 );
2690 $emailGreetingString = CRM_Core_DAO::escapeString(CRM_Utils_String::stripSpaces($emailGreetingString));
2691 $updateQueryString[] = " email_greeting_display = '{$emailGreetingString}'";
2692 }
2693
2694 //postal greetings
2695 if ($contact->postal_greeting_custom != 'null' && $contact->postal_greeting_custom) {
2696 $postalGreetingString = $contact->postal_greeting_custom;
2697 }
2698 elseif ($contact->postal_greeting_id != 'null' && $contact->postal_greeting_id) {
2699 $filter = array(
2700 'contact_type' => $contact->contact_type,
2701 'greeting_type' => 'postal_greeting',
2702 );
2703 $postalGreeting = CRM_Core_PseudoConstant::greeting($filter);
2704 $postalGreetingString = $postalGreeting[$contact->postal_greeting_id];
2705 $updateQueryString[] = " postal_greeting_custom = NULL ";
2706 }
2707 else {
2708 if ($useDefaults) {
2709 reset($contactDefaults['postal_greeting']);
2710 $postalGreetingID = key($contactDefaults['postal_greeting']);
2711 $postalGreetingString = $contactDefaults['postal_greeting'][$postalGreetingID];
2712 $updateQueryString[] = " postal_greeting_id = $postalGreetingID ";
2713 $updateQueryString[] = " postal_greeting_custom = NULL ";
2714 }
2715 elseif ($contact->postal_greeting_custom) {
2716 $updateQueryString[] = " postal_greeting_display = NULL ";
2717 }
2718 }
2719
2720 if ($postalGreetingString) {
2721 CRM_Contact_BAO_Contact_Utils::processGreetingTemplate($postalGreetingString,
2722 $contactDetails,
2723 $contact->id,
2724 'CRM_Contact_BAO_Contact'
2725 );
2726 $postalGreetingString = CRM_Core_DAO::escapeString(CRM_Utils_String::stripSpaces($postalGreetingString));
2727 $updateQueryString[] = " postal_greeting_display = '{$postalGreetingString}'";
2728 }
2729
2730 // addressee
2731 if ($contact->addressee_custom != 'null' && $contact->addressee_custom) {
2732 $addresseeString = $contact->addressee_custom;
2733 }
2734 elseif ($contact->addressee_id != 'null' && $contact->addressee_id) {
2735 $filter = array(
2736 'contact_type' => $contact->contact_type,
2737 'greeting_type' => 'addressee',
2738 );
2739
2740 $addressee = CRM_Core_PseudoConstant::greeting($filter);
2741 $addresseeString = $addressee[$contact->addressee_id];
2742 $updateQueryString[] = " addressee_custom = NULL ";
2743 }
2744 else {
2745 if ($useDefaults) {
2746 reset($contactDefaults['addressee']);
2747 $addresseeID = key($contactDefaults['addressee']);
2748 $addresseeString = $contactDefaults['addressee'][$addresseeID];
2749 $updateQueryString[] = " addressee_id = $addresseeID ";
2750 $updateQueryString[] = " addressee_custom = NULL ";
2751 }
2752 elseif ($contact->addressee_custom) {
2753 $updateQueryString[] = " addressee_display = NULL ";
2754 }
2755 }
2756
2757 if ($addresseeString) {
2758 CRM_Contact_BAO_Contact_Utils::processGreetingTemplate($addresseeString,
2759 $contactDetails,
2760 $contact->id,
2761 'CRM_Contact_BAO_Contact'
2762 );
2763 $addresseeString = CRM_Core_DAO::escapeString(CRM_Utils_String::stripSpaces($addresseeString));
2764 $updateQueryString[] = " addressee_display = '{$addresseeString}'";
2765 }
2766
2767 if (!empty($updateQueryString)) {
2768 $updateQueryString = implode(',', $updateQueryString);
2769 $queryString = "UPDATE civicrm_contact SET {$updateQueryString} WHERE id = {$contact->id}";
2770 CRM_Core_DAO::executeQuery($queryString);
2771 }
2772 }
2773
2774 /**
2775 * Retrieve loc block ids w/ given condition.
2776 *
2777 * @param int $contactId
2778 * Contact id.
2779 * @param array $criteria
2780 * Key => value pair which should be.
2781 * fulfill by return record ids.
2782 * @param string $condOperator
2783 * Operator use for grouping multiple conditions.
2784 *
2785 * @return array
2786 * loc block ids which fulfill condition.
2787 */
2788 public static function getLocBlockIds($contactId, $criteria = array(), $condOperator = 'AND') {
2789 $locBlockIds = array();
2790 if (!$contactId) {
2791 return $locBlockIds;
2792 }
2793
2794 foreach (array('Email', 'OpenID', 'Phone', 'Address', 'IM') as $block) {
2795 $name = strtolower($block);
2796 $className = "CRM_Core_DAO_$block";
2797 $blockDAO = new $className();
2798
2799 // build the condition.
2800 if (is_array($criteria)) {
2801 $fields = $blockDAO->fields();
2802 $conditions = array();
2803 foreach ($criteria as $field => $value) {
2804 if (array_key_exists($field, $fields)) {
2805 $cond = "( $field = $value )";
2806 // value might be zero or null.
2807 if (!$value || strtolower($value) == 'null') {
2808 $cond = "( $field = 0 OR $field IS NULL )";
2809 }
2810 $conditions[] = $cond;
2811 }
2812 }
2813 if (!empty($conditions)) {
2814 $blockDAO->whereAdd(implode(" $condOperator ", $conditions));
2815 }
2816 }
2817
2818 $blockDAO->contact_id = $contactId;
2819 $blockDAO->find();
2820 while ($blockDAO->fetch()) {
2821 $locBlockIds[$name][] = $blockDAO->id;
2822 }
2823 $blockDAO->free();
2824 }
2825
2826 return $locBlockIds;
2827 }
2828
2829 /**
2830 * Build context menu items.
2831 *
2832 * @param int $contactId
2833 *
2834 * @return array
2835 * Array of context menu for logged in user.
2836 */
2837 public static function contextMenu($contactId = NULL) {
2838 $menu = array(
2839 'view' => array(
2840 'title' => ts('View Contact'),
2841 'weight' => 0,
2842 'ref' => 'view-contact',
2843 'class' => 'no-popup',
2844 'key' => 'view',
2845 'permissions' => array('view all contacts'),
2846 ),
2847 'add' => array(
2848 'title' => ts('Edit Contact'),
2849 'weight' => 0,
2850 'ref' => 'edit-contact',
2851 'class' => 'no-popup',
2852 'key' => 'add',
2853 'permissions' => array('edit all contacts'),
2854 ),
2855 'delete' => array(
2856 'title' => ts('Delete Contact'),
2857 'weight' => 0,
2858 'ref' => 'delete-contact',
2859 'key' => 'delete',
2860 'permissions' => array('access deleted contacts', 'delete contacts'),
2861 ),
2862 'contribution' => array(
2863 'title' => ts('Add Contribution'),
2864 'weight' => 5,
2865 'ref' => 'new-contribution',
2866 'key' => 'contribution',
2867 'tab' => 'contribute',
2868 'component' => 'CiviContribute',
2869 'href' => CRM_Utils_System::url('civicrm/contact/view/contribution',
2870 'reset=1&action=add&context=contribution'
2871 ),
2872 'permissions' => array(
2873 'access CiviContribute',
2874 'edit contributions',
2875 ),
2876 ),
2877 'participant' => array(
2878 'title' => ts('Register for Event'),
2879 'weight' => 10,
2880 'ref' => 'new-participant',
2881 'key' => 'participant',
2882 'tab' => 'participant',
2883 'component' => 'CiviEvent',
2884 'href' => CRM_Utils_System::url('civicrm/contact/view/participant', 'reset=1&action=add&context=participant'),
2885 'permissions' => array(
2886 'access CiviEvent',
2887 'edit event participants',
2888 ),
2889 ),
2890 'activity' => array(
2891 'title' => ts('Record Activity'),
2892 'weight' => 35,
2893 'ref' => 'new-activity',
2894 'key' => 'activity',
2895 'permissions' => array('edit all contacts'),
2896 ),
2897 'pledge' => array(
2898 'title' => ts('Add Pledge'),
2899 'weight' => 15,
2900 'ref' => 'new-pledge',
2901 'key' => 'pledge',
2902 'tab' => 'pledge',
2903 'href' => CRM_Utils_System::url('civicrm/contact/view/pledge',
2904 'reset=1&action=add&context=pledge'
2905 ),
2906 'component' => 'CiviPledge',
2907 'permissions' => array(
2908 'access CiviPledge',
2909 'edit pledges',
2910 ),
2911 ),
2912 'membership' => array(
2913 'title' => ts('Add Membership'),
2914 'weight' => 20,
2915 'ref' => 'new-membership',
2916 'key' => 'membership',
2917 'tab' => 'member',
2918 'component' => 'CiviMember',
2919 'href' => CRM_Utils_System::url('civicrm/contact/view/membership',
2920 'reset=1&action=add&context=membership'
2921 ),
2922 'permissions' => array(
2923 'access CiviMember',
2924 'edit memberships',
2925 ),
2926 ),
2927 'case' => array(
2928 'title' => ts('Add Case'),
2929 'weight' => 25,
2930 'ref' => 'new-case',
2931 'key' => 'case',
2932 'tab' => 'case',
2933 'component' => 'CiviCase',
2934 'href' => CRM_Utils_System::url('civicrm/case/add', 'reset=1&action=add&context=case'),
2935 'permissions' => array('add cases'),
2936 ),
2937 'grant' => array(
2938 'title' => ts('Add Grant'),
2939 'weight' => 26,
2940 'ref' => 'new-grant',
2941 'key' => 'grant',
2942 'tab' => 'grant',
2943 'component' => 'CiviGrant',
2944 'href' => CRM_Utils_System::url('civicrm/contact/view/grant',
2945 'reset=1&action=add&context=grant'
2946 ),
2947 'permissions' => array('edit grants'),
2948 ),
2949 'rel' => array(
2950 'title' => ts('Add Relationship'),
2951 'weight' => 30,
2952 'ref' => 'new-relationship',
2953 'key' => 'rel',
2954 'tab' => 'rel',
2955 'href' => CRM_Utils_System::url('civicrm/contact/view/rel',
2956 'reset=1&action=add'
2957 ),
2958 'permissions' => array('edit all contacts'),
2959 ),
2960 'note' => array(
2961 'title' => ts('Add Note'),
2962 'weight' => 40,
2963 'ref' => 'new-note',
2964 'key' => 'note',
2965 'tab' => 'note',
2966 'class' => 'medium-popup',
2967 'href' => CRM_Utils_System::url('civicrm/contact/view/note',
2968 'reset=1&action=add'
2969 ),
2970 'permissions' => array('edit all contacts'),
2971 ),
2972 'email' => array(
2973 'title' => ts('Send an Email'),
2974 'weight' => 45,
2975 'ref' => 'new-email',
2976 'key' => 'email',
2977 'permissions' => array('view all contacts'),
2978 ),
2979 'group' => array(
2980 'title' => ts('Add to Group'),
2981 'weight' => 50,
2982 'ref' => 'group-add-contact',
2983 'key' => 'group',
2984 'tab' => 'group',
2985 'permissions' => array('edit groups'),
2986 ),
2987 'tag' => array(
2988 'title' => ts('Tag Contact'),
2989 'weight' => 55,
2990 'ref' => 'tag-contact',
2991 'key' => 'tag',
2992 'tab' => 'tag',
2993 'permissions' => array('edit all contacts'),
2994 ),
2995 );
2996
2997 $menu['otherActions'] = array(
2998 'print' => array(
2999 'title' => ts('Print Summary'),
3000 'description' => ts('Printer-friendly view of this page.'),
3001 'weight' => 5,
3002 'ref' => 'crm-contact-print',
3003 'key' => 'print',
3004 'tab' => 'print',
3005 'href' => CRM_Utils_System::url('civicrm/contact/view/print',
3006 "reset=1&print=1"
3007 ),
3008 'class' => 'print',
3009 'icon' => 'crm-i fa-print',
3010 ),
3011 'vcard' => array(
3012 'title' => ts('vCard'),
3013 'description' => ts('vCard record for this contact.'),
3014 'weight' => 10,
3015 'ref' => 'crm-contact-vcard',
3016 'key' => 'vcard',
3017 'tab' => 'vcard',
3018 'href' => CRM_Utils_System::url('civicrm/contact/view/vcard',
3019 "reset=1"
3020 ),
3021 'class' => 'vcard',
3022 'icon' => 'crm-i fa-list-alt',
3023 ),
3024 );
3025
3026 if (CRM_Core_Permission::check('access Contact Dashboard')) {
3027 $menu['otherActions']['dashboard'] = array(
3028 'title' => ts('Contact Dashboard'),
3029 'description' => ts('Contact Dashboard'),
3030 'weight' => 15,
3031 'ref' => 'crm-contact-dashboard',
3032 'key' => 'dashboard',
3033 'tab' => 'dashboard',
3034 'class' => 'dashboard',
3035 // NOTE: As an alternative you can also build url on CMS specific way
3036 // as CRM_Core_Config::singleton()->userSystem->getUserRecordUrl($contactId)
3037 'href' => CRM_Utils_System::url('civicrm/user', "reset=1&id={$contactId}"),
3038 'icon' => 'crm-i fa-tachometer',
3039 );
3040 }
3041
3042 $uid = CRM_Core_BAO_UFMatch::getUFId($contactId);
3043 if ($uid) {
3044 $menu['otherActions']['user-record'] = array(
3045 'title' => ts('User Record'),
3046 'description' => ts('User Record'),
3047 'weight' => 20,
3048 'ref' => 'crm-contact-user-record',
3049 'key' => 'user-record',
3050 'tab' => 'user-record',
3051 'class' => 'user-record',
3052 'href' => CRM_Core_Config::singleton()->userSystem->getUserRecordUrl($contactId),
3053 'icon' => 'crm-i fa-user',
3054 );
3055 }
3056 elseif (CRM_Core_Config::singleton()->userSystem->checkPermissionAddUser()) {
3057 $menu['otherActions']['user-add'] = array(
3058 'title' => ts('Create User Record'),
3059 'description' => ts('Create User Record'),
3060 'weight' => 25,
3061 'ref' => 'crm-contact-user-add',
3062 'key' => 'user-add',
3063 'tab' => 'user-add',
3064 'class' => 'user-add',
3065 'href' => CRM_Utils_System::url('civicrm/contact/view/useradd', 'reset=1&action=add&cid=' . $contactId),
3066 'icon' => 'crm-i fa-user-plus',
3067 );
3068 }
3069
3070 CRM_Utils_Hook::summaryActions($menu, $contactId);
3071 //1. check for component is active.
3072 //2. check for user permissions.
3073 //3. check for acls.
3074 //3. edit and view contact are directly accessible to user.
3075
3076 $aclPermissionedTasks = array(
3077 'view-contact',
3078 'edit-contact',
3079 'new-activity',
3080 'new-email',
3081 'group-add-contact',
3082 'tag-contact',
3083 'delete-contact',
3084 );
3085 $corePermission = CRM_Core_Permission::getPermission();
3086
3087 $contextMenu = array();
3088 foreach ($menu as $key => $values) {
3089 if ($key != 'otherActions') {
3090
3091 // user does not have necessary permissions.
3092 if (!self::checkUserMenuPermissions($aclPermissionedTasks, $corePermission, $values)) {
3093 continue;
3094 }
3095 // build directly accessible action menu.
3096 if (in_array($values['ref'], array(
3097 'view-contact',
3098 'edit-contact',
3099 ))) {
3100 $contextMenu['primaryActions'][$key] = array(
3101 'title' => $values['title'],
3102 'ref' => $values['ref'],
3103 'class' => CRM_Utils_Array::value('class', $values),
3104 'key' => $values['key'],
3105 );
3106 continue;
3107 }
3108
3109 // finally get menu item for -more- action widget.
3110 $contextMenu['moreActions'][$values['weight']] = array(
3111 'title' => $values['title'],
3112 'ref' => $values['ref'],
3113 'href' => CRM_Utils_Array::value('href', $values),
3114 'tab' => CRM_Utils_Array::value('tab', $values),
3115 'class' => CRM_Utils_Array::value('class', $values),
3116 'key' => $values['key'],
3117 );
3118 }
3119 else {
3120 foreach ($values as $value) {
3121 // user does not have necessary permissions.
3122 if (!self::checkUserMenuPermissions($aclPermissionedTasks, $corePermission, $value)) {
3123 continue;
3124 }
3125
3126 // finally get menu item for -more- action widget.
3127 $contextMenu['otherActions'][$value['weight']] = array(
3128 'title' => $value['title'],
3129 'ref' => $value['ref'],
3130 'href' => CRM_Utils_Array::value('href', $value),
3131 'tab' => CRM_Utils_Array::value('tab', $value),
3132 'class' => CRM_Utils_Array::value('class', $value),
3133 'icon' => CRM_Utils_Array::value('icon', $value),
3134 'key' => $value['key'],
3135 );
3136 }
3137 }
3138 }
3139
3140 ksort($contextMenu['moreActions']);
3141 ksort($contextMenu['otherActions']);
3142
3143 return $contextMenu;
3144 }
3145
3146 /**
3147 * Check if user has permissions to access items in action menu.
3148 *
3149 * @param array $aclPermissionedTasks
3150 * Array containing ACL related tasks.
3151 * @param string $corePermission
3152 * The permission of the user (edit or view or null).
3153 * @param array $menuOptions
3154 * Array containing params of the menu (title, href, etc).
3155 *
3156 * @return bool
3157 * TRUE if user has all permissions, FALSE if otherwise.
3158 */
3159 public static function checkUserMenuPermissions($aclPermissionedTasks, $corePermission, $menuOptions) {
3160 $componentName = CRM_Utils_Array::value('component', $menuOptions);
3161
3162 // if component action - make sure component is enable.
3163 if ($componentName && !in_array($componentName, CRM_Core_Config::singleton()->enableComponents)) {
3164 return FALSE;
3165 }
3166
3167 // make sure user has all required permissions.
3168 $hasAllPermissions = FALSE;
3169
3170 $permissions = CRM_Utils_Array::value('permissions', $menuOptions);
3171 if (!is_array($permissions) || empty($permissions)) {
3172 $hasAllPermissions = TRUE;
3173 }
3174
3175 // iterate for required permissions in given permissions array.
3176 if (!$hasAllPermissions) {
3177 $hasPermissions = 0;
3178 foreach ($permissions as $permission) {
3179 if (CRM_Core_Permission::check($permission)) {
3180 $hasPermissions++;
3181 }
3182 }
3183
3184 if (count($permissions) == $hasPermissions) {
3185 $hasAllPermissions = TRUE;
3186 }
3187
3188 // if still user does not have required permissions, check acl.
3189 if (!$hasAllPermissions && $menuOptions['ref'] != 'delete-contact') {
3190 if (in_array($menuOptions['ref'], $aclPermissionedTasks) &&
3191 $corePermission == CRM_Core_Permission::EDIT
3192 ) {
3193 $hasAllPermissions = TRUE;
3194 }
3195 elseif (in_array($menuOptions['ref'], array(
3196 'new-email',
3197 ))) {
3198 // grant permissions for these tasks.
3199 $hasAllPermissions = TRUE;
3200 }
3201 }
3202 }
3203
3204 return $hasAllPermissions;
3205 }
3206
3207 /**
3208 * Retrieve display name of contact that address is shared.
3209 *
3210 * This is based on $masterAddressId or $contactId .
3211 *
3212 * @param int $masterAddressId
3213 * Master id.
3214 * @param int $contactId
3215 * Contact id.
3216 *
3217 * @return string|null
3218 * the found display name or null.
3219 */
3220 public static function getMasterDisplayName($masterAddressId = NULL, $contactId = NULL) {
3221 $masterDisplayName = NULL;
3222 $sql = NULL;
3223 if (!$masterAddressId && !$contactId) {
3224 return $masterDisplayName;
3225 }
3226
3227 if ($masterAddressId) {
3228 $sql = "
3229 SELECT display_name from civicrm_contact
3230 LEFT JOIN civicrm_address ON ( civicrm_address.contact_id = civicrm_contact.id )
3231 WHERE civicrm_address.id = " . $masterAddressId;
3232 }
3233 elseif ($contactId) {
3234 $sql = "
3235 SELECT display_name from civicrm_contact cc, civicrm_address add1
3236 LEFT JOIN civicrm_address add2 ON ( add1.master_id = add2.id )
3237 WHERE cc.id = add2.contact_id AND add1.contact_id = " . $contactId;
3238 }
3239
3240 $masterDisplayName = CRM_Core_DAO::singleValueQuery($sql);
3241 return $masterDisplayName;
3242 }
3243
3244 /**
3245 * Get the creation/modification times for a contact.
3246 *
3247 * @param int $contactId
3248 *
3249 * @return array
3250 * Dates - ('created_date' => $, 'modified_date' => $)
3251 */
3252 public static function getTimestamps($contactId) {
3253 $timestamps = CRM_Core_DAO::executeQuery(
3254 'SELECT created_date, modified_date
3255 FROM civicrm_contact
3256 WHERE id = %1',
3257 array(
3258 1 => array($contactId, 'Integer'),
3259 )
3260 );
3261 if ($timestamps->fetch()) {
3262 return array(
3263 'created_date' => $timestamps->created_date,
3264 'modified_date' => $timestamps->modified_date,
3265 );
3266 }
3267 else {
3268 return NULL;
3269 }
3270 }
3271
3272 /**
3273 * Get a list of triggers for the contact table.
3274 *
3275 * @see hook_civicrm_triggerInfo
3276 * @see CRM_Core_DAO::triggerRebuild
3277 * @see http://issues.civicrm.org/jira/browse/CRM-10554
3278 *
3279 * @param $info
3280 * @param null $tableName
3281 */
3282 public static function triggerInfo(&$info, $tableName = NULL) {
3283 //during upgrade, first check for valid version and then create triggers
3284 //i.e the columns created_date and modified_date are introduced in 4.3.alpha1 so dont create triggers for older version
3285 if (CRM_Core_Config::isUpgradeMode()) {
3286 $currentVer = CRM_Core_BAO_Domain::version(TRUE);
3287 //if current version is less than 4.3.alpha1 dont create below triggers
3288 if (version_compare($currentVer, '4.3.alpha1') < 0) {
3289 return;
3290 }
3291 }
3292
3293 // Modifications to these records should update the contact timestamps.
3294 \Civi\Core\SqlTrigger\TimestampTriggers::create('civicrm_contact', 'Contact')
3295 ->setRelations(array(
3296 array('table' => 'civicrm_address', 'column' => 'contact_id'),
3297 array('table' => 'civicrm_email', 'column' => 'contact_id'),
3298 array('table' => 'civicrm_im', 'column' => 'contact_id'),
3299 array('table' => 'civicrm_phone', 'column' => 'contact_id'),
3300 array('table' => 'civicrm_website', 'column' => 'contact_id'),
3301 )
3302 )
3303 ->alterTriggerInfo($info, $tableName);
3304
3305 // Update phone table to populate phone_numeric field
3306 if (!$tableName || $tableName == 'civicrm_phone') {
3307 // Define stored sql function needed for phones
3308 $sqlTriggers = Civi::service('sql_triggers');
3309 $sqlTriggers->enqueueQuery(self::DROP_STRIP_FUNCTION_43);
3310 $sqlTriggers->enqueueQuery(self::CREATE_STRIP_FUNCTION_43);
3311 $info[] = array(
3312 'table' => array('civicrm_phone'),
3313 'when' => 'BEFORE',
3314 'event' => array('INSERT', 'UPDATE'),
3315 'sql' => "\nSET NEW.phone_numeric = civicrm_strip_non_numeric(NEW.phone);\n",
3316 );
3317 }
3318 }
3319
3320 /**
3321 * Check if contact is being used in civicrm_domain based on $contactId.
3322 *
3323 * @param int $contactId
3324 * Contact id.
3325 *
3326 * @return bool
3327 * true if present else false.
3328 */
3329 public static function checkDomainContact($contactId) {
3330 if (!$contactId) {
3331 return FALSE;
3332 }
3333 $domainId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_Domain', $contactId, 'id', 'contact_id');
3334
3335 if ($domainId) {
3336 return TRUE;
3337 }
3338 else {
3339 return FALSE;
3340 }
3341 }
3342
3343 /**
3344 * Get options for a given contact field.
3345 *
3346 * @see CRM_Core_DAO::buildOptions
3347 *
3348 * TODO: Should we always assume chainselect? What fn should be responsible for controlling that flow?
3349 * TODO: In context of chainselect, what to return if e.g. a country has no states?
3350 *
3351 * @param string $fieldName
3352 * @param string $context
3353 * @see CRM_Core_DAO::buildOptionsContext
3354 * @param array $props
3355 * whatever is known about this dao object.
3356 *
3357 * @return array|bool
3358 */
3359 public static function buildOptions($fieldName, $context = NULL, $props = array()) {
3360 $params = array();
3361 // Special logic for fields whose options depend on context or properties
3362 switch ($fieldName) {
3363 case 'contact_sub_type':
3364 if (!empty($props['contact_type'])) {
3365 $params['condition'] = "parent_id = (SELECT id FROM civicrm_contact_type WHERE name='{$props['contact_type']}')";
3366 }
3367 break;
3368
3369 case 'contact_type':
3370 if ($context == 'search') {
3371 // CRM-15495 - EntityRef filters and basic search forms expect this format
3372 // FIXME: Search builder does not
3373 return CRM_Contact_BAO_ContactType::getSelectElements();
3374 }
3375 break;
3376
3377 // The contact api supports some related entities so we'll honor that by fetching their options
3378 case 'group_id':
3379 case 'group':
3380 return CRM_Contact_BAO_GroupContact::buildOptions('group_id', $context, $props);
3381
3382 case 'tag_id':
3383 case 'tag':
3384 $props['entity_table'] = 'civicrm_contact';
3385 return CRM_Core_BAO_EntityTag::buildOptions('tag_id', $context, $props);
3386
3387 case 'state_province_id':
3388 case 'state_province':
3389 case 'state_province_name':
3390 case 'country_id':
3391 case 'country':
3392 case 'county_id':
3393 case 'worldregion':
3394 case 'worldregion_id':
3395 return CRM_Core_BAO_Address::buildOptions($fieldName, 'get', $props);
3396
3397 }
3398 return CRM_Core_PseudoConstant::get(__CLASS__, $fieldName, $params, $context);
3399 }
3400
3401 /**
3402 * Delete a contact-related object that has an 'is_primary' field.
3403 *
3404 * Ensures that is_primary gets assigned to another object if available
3405 * Also calls pre/post hooks
3406 *
3407 * @param string $type
3408 * @param int $id
3409 * @return bool
3410 */
3411 public static function deleteObjectWithPrimary($type, $id) {
3412 if (!$id || !is_numeric($id)) {
3413 return FALSE;
3414 }
3415 $daoName = "CRM_Core_DAO_$type";
3416 $obj = new $daoName();
3417 $obj->id = $id;
3418 $obj->find();
3419 if ($obj->fetch()) {
3420 CRM_Utils_Hook::pre('delete', $type, $id, CRM_Core_DAO::$_nullArray);
3421 $contactId = $obj->contact_id;
3422 $obj->delete();
3423 }
3424 else {
3425 return FALSE;
3426 }
3427 // is_primary is only relavent if this field belongs to a contact
3428 if ($contactId) {
3429 $dao = new $daoName();
3430 $dao->contact_id = $contactId;
3431 $dao->is_primary = 1;
3432 // Pick another record to be primary (if one isn't already)
3433 if (!$dao->find(TRUE)) {
3434 $dao->is_primary = 0;
3435 $dao->find();
3436 if ($dao->fetch()) {
3437 $dao->is_primary = 1;
3438 $dao->save();
3439 }
3440 }
3441 $dao->free();
3442 }
3443 CRM_Utils_Hook::post('delete', $type, $id, $obj);
3444 $obj->free();
3445 return TRUE;
3446 }
3447
3448 /**
3449 * @inheritDoc
3450 */
3451 public function addSelectWhereClause() {
3452 // We always return an array with these keys, even if they are empty,
3453 // because this tells the query builder that we have considered these fields for acls
3454 $clauses = array(
3455 'id' => (array) CRM_Contact_BAO_Contact_Permission::cacheSubquery(),
3456 'is_deleted' => CRM_Core_Permission::check('access deleted contacts') ? array() : array('!= 1'),
3457 );
3458 CRM_Utils_Hook::selectWhereClause($this, $clauses);
3459 return $clauses;
3460 }
3461
3462 /**
3463 * Get any existing duplicate contacts based on the input parameters.
3464 *
3465 * @param array $input
3466 * Input parameters to be matched.
3467 * @param string $contactType
3468 * @param string $rule
3469 * - Supervised
3470 * - Unsupervised
3471 * @param $excludedContactIDs
3472 * An array of ids not to be included in the results.
3473 * @param bool $checkPermissions
3474 * @param int $ruleGroupID
3475 * ID of the rule group to be used if an override is desirable.
3476 *
3477 * @return array
3478 */
3479 public static function getDuplicateContacts($input, $contactType, $rule = 'Unsupervised', $excludedContactIDs = array(), $checkPermissions = TRUE, $ruleGroupID = NULL) {
3480 $dedupeParams = CRM_Dedupe_Finder::formatParams($input, $contactType);
3481 $dedupeParams['check_permission'] = $checkPermissions;
3482 $ids = CRM_Dedupe_Finder::dupesByParams($dedupeParams, $contactType, $rule, $excludedContactIDs, $ruleGroupID);
3483 return $ids;
3484 }
3485
3486 /**
3487 * Get the first duplicate contacts based on the input parameters.
3488 *
3489 * @param array $input
3490 * Input parameters to be matched.
3491 * @param string $contactType
3492 * @param string $rule
3493 * - Supervised
3494 * - Unsupervised
3495 * @param $excludedContactIDs
3496 * An array of ids not to be included in the results.
3497 * @param bool $checkPermissions
3498 * @param int $ruleGroupID
3499 * ID of the rule group to be used if an override is desirable.
3500 *
3501 * @return int|NULL
3502 */
3503 public static function getFirstDuplicateContact($input, $contactType, $rule = 'Unsupervised', $excludedContactIDs = array(), $checkPermissions = TRUE, $ruleGroupID = NULL) {
3504 $ids = self::getDuplicateContacts($input, $contactType, $rule, $excludedContactIDs, $checkPermissions, $ruleGroupID);
3505 if (empty($ids)) {
3506 return NULL;
3507 }
3508 return $ids[0];
3509 }
3510
3511 /**
3512 * Check if a field is associated with an entity that has a location type.
3513 *
3514 * (ie. is an address, phone, email etc field).
3515 *
3516 * @param string $fieldTitle
3517 * Title of the field (not the name - create a new function for that if required).
3518 *
3519 * @return bool
3520 */
3521 public static function isFieldHasLocationType($fieldTitle) {
3522 foreach (CRM_Contact_BAO_Contact::importableFields() as $key => $field) {
3523 if ($field['title'] === $fieldTitle) {
3524 return CRM_Utils_Array::value('hasLocationType', $field);
3525 }
3526 }
3527 return FALSE;
3528 }
3529
3530 }