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