Merge pull request #4696 from colemanw/CRM-15669
[civicrm-core.git] / CRM / Core / BAO / UFMatch.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
36 /**
37 * The basic class that interfaces with the external user framework
38 */
39 class CRM_Core_BAO_UFMatch extends CRM_Core_DAO_UFMatch {
40
41 /*
42 * Create UF Match, Note that thsi function is here in it's simplest form @ the moment
43 *
44 *
45 * @param array $params input parameters
46 */
47 /**
48 * @param array $params
49 *
50 * @return CRM_Core_DAO_UFMatch
51 */
52 public static function create($params) {
53 $hook = empty($params['id']) ? 'create' : 'edit';
54 CRM_Utils_Hook::pre($hook, 'UFMatch', CRM_Utils_Array::value('id', $params), $params);
55 if(empty($params['domain_id'])) {
56 $params['domain_id'] = CRM_Core_Config::domainID();
57 }
58 $dao = new CRM_Core_DAO_UFMatch();
59 $dao->copyValues($params);
60 $dao->save();
61 CRM_Utils_Hook::post($hook, 'UFMatch', $dao->id, $dao);
62 return $dao;
63 }
64
65
66 /**
67 * Given a UF user object, make sure there is a contact
68 * object for this user. If the user has new values, we need
69 * to update the CRM DB with the new values
70 *
71 * @param Object $user the drupal user object
72 * @param boolean $update has the user object been edited
73 * @param $uf
74 *
75 * @param $ctype
76 * @param bool $isLogin
77 *
78 * @return void
79 * @static
80 */
81 public static function synchronize(&$user, $update, $uf, $ctype, $isLogin = FALSE) {
82 $userSystem = CRM_Core_Config::singleton()->userSystem;
83 $session = CRM_Core_Session::singleton();
84 if (!is_object($session)) {
85 CRM_Core_Error::fatal('wow, session is not an object?');
86 return;
87 }
88
89 $userSystemID = $userSystem->getBestUFID($user);
90 $uniqId = $userSystem->getBestUFUniqueIdentifier($user);
91
92 // if the id of the object is zero (true for anon users in drupal)
93 // have we already processed this user, if so early
94 // return.
95 $userID = $session->get('userID');
96 $ufID = $session->get('ufID');
97
98 if (!$update && $ufID == $userSystemID) {
99 return;
100 }
101
102 //check do we have logged in user.
103 $isUserLoggedIn = CRM_Utils_System::isUserLoggedIn();
104
105 // reset the session if we are a different user
106 if ($ufID && $ufID != $userSystemID) {
107 $session->reset();
108
109 //get logged in user ids, and set to session.
110 if ($isUserLoggedIn) {
111 $userIds = self::getUFValues();
112 $session->set('ufID', CRM_Utils_Array::value('uf_id', $userIds, ''));
113 $session->set('userID', CRM_Utils_Array::value('contact_id', $userIds, ''));
114 $session->set('ufUniqID', CRM_Utils_Array::value('uf_name', $userIds, ''));
115 }
116 }
117
118 // return early
119 if ($userSystemID == 0) {
120 return;
121 }
122
123 $ufmatch = self::synchronizeUFMatch($user, $userSystemID, $uniqId, $uf, NULL, $ctype, $isLogin);
124 if (!$ufmatch) {
125 return;
126 }
127
128 //make sure we have session w/ consistent ids.
129 $ufID = $ufmatch->uf_id;
130 $userID = $ufmatch->contact_id;
131 $ufUniqID = '';
132 if ($isUserLoggedIn) {
133 $loggedInUserUfID = CRM_Utils_System::getLoggedInUfID();
134 //are we processing logged in user.
135 if ($loggedInUserUfID && $loggedInUserUfID != $ufID) {
136 $userIds = self::getUFValues($loggedInUserUfID);
137 $ufID = CRM_Utils_Array::value('uf_id', $userIds, '');
138 $userID = CRM_Utils_Array::value('contact_id', $userIds, '');
139 $ufUniqID = CRM_Utils_Array::value('uf_name', $userIds, '');
140 }
141 }
142
143 //set user ids to session.
144 $session->set('ufID', $ufID);
145 $session->set('userID', $userID);
146 $session->set('ufUniqID', $ufUniqID);
147
148 // add current contact to recently viewed
149 if ($ufmatch->contact_id) {
150 list($displayName, $contactImage, $contactType, $contactSubtype, $contactImageUrl) =
151 CRM_Contact_BAO_Contact::getDisplayAndImage($ufmatch->contact_id, TRUE, TRUE);
152
153 $otherRecent = array(
154 'imageUrl' => $contactImageUrl,
155 'subtype' => $contactSubtype,
156 'editUrl' => CRM_Utils_System::url('civicrm/contact/add', "reset=1&action=update&cid={$ufmatch->contact_id}"),
157 );
158
159 CRM_Utils_Recent::add($displayName,
160 CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$ufmatch->contact_id}"),
161 $ufmatch->contact_id,
162 $contactType,
163 $ufmatch->contact_id,
164 $displayName,
165 $otherRecent
166 );
167 }
168 }
169
170 /**
171 * Synchronize the object with the UF Match entry. Can be called stand-alone from
172 * the drupalUsers script
173 *
174 * @param Object $user the drupal user object
175 * @param string $userKey the id of the user from the uf object
176 * @param string $uniqId the OpenID of the user
177 * @param string $uf the name of the user framework
178 * @param integer $status returns the status if user created or already exits (used for CMS sync)
179 *
180 * @param null $ctype
181 * @param bool $isLogin
182 *
183 * @return the ufmatch object that was found or created
184 * @static
185 */
186 public static function &synchronizeUFMatch(&$user, $userKey, $uniqId, $uf, $status = NULL, $ctype = NULL, $isLogin = FALSE) {
187 $config = CRM_Core_Config::singleton();
188
189 if (!CRM_Utils_Rule::email($uniqId)) {
190 $retVal = $status ? NULL : FALSE;
191 return $retVal;
192 }
193
194 $newContact = FALSE;
195
196 // make sure that a contact id exists for this user id
197 $ufmatch = new CRM_Core_DAO_UFMatch();
198 $ufmatch->domain_id = CRM_Core_Config::domainID();
199 $ufmatch->uf_id = $userKey;
200
201 if (!$ufmatch->find(TRUE)) {
202 $transaction = new CRM_Core_Transaction();
203
204 $dao = NULL;
205 if (!empty($_POST) && !$isLogin) {
206 $params = $_POST;
207 $params['email'] = $uniqId;
208
209 $dedupeParams = CRM_Dedupe_Finder::formatParams($params, 'Individual');
210 $dedupeParams['check_permission'] = FALSE;
211 $ids = CRM_Dedupe_Finder::dupesByParams($dedupeParams, 'Individual');
212
213 if (!empty($ids) &&
214 CRM_Core_BAO_Setting::getItem(
215 CRM_Core_BAO_Setting::MULTISITE_PREFERENCES_NAME,
216 'uniq_email_per_site'
217 )
218 ) {
219 // restrict dupeIds to ones that belong to current domain/site.
220 $siteContacts = CRM_Core_BAO_Domain::getContactList();
221 foreach ($ids as $index => $dupeId) {
222 if (!in_array($dupeId, $siteContacts)) {
223 unset($ids[$index]);
224 }
225 }
226 // re-index the array
227 $ids = array_values($ids);
228 }
229 if (!empty($ids)) {
230 $dao = new CRM_Core_DAO();
231 $dao->contact_id = $ids[0];
232 }
233 }
234 else {
235 $dao = CRM_Contact_BAO_Contact::matchContactOnEmail($uniqId, $ctype);
236 }
237
238 $found = FALSE;
239 if ($dao) {
240 // ensure there does not exists a contact_id / uf_id pair
241 // in the DB. This might be due to multiple emails per contact
242 // CRM-9091
243 $sql = "
244 SELECT id
245 FROM civicrm_uf_match
246 WHERE contact_id = %1
247 AND domain_id = %2
248 ";
249 $params = array(
250 1 => array($dao->contact_id, 'Integer'),
251 2 => array(CRM_Core_Config::domainID(), 'Integer'),
252 );
253 $conflict = CRM_Core_DAO::singleValueQuery($sql, $params);
254
255 if (!$conflict) {
256 $found = TRUE;
257 $ufmatch->contact_id = $dao->contact_id;
258 $ufmatch->uf_name = $uniqId;
259 }
260 }
261
262 if (!$found) {
263 if ($config->userSystem->is_drupal) {
264 $mail = 'mail';
265 }
266 elseif ($uf == 'WordPress') {
267 $mail = 'user_email';
268 }
269 else {
270 $mail = 'email';
271 }
272
273 if (is_object($user)) {
274 $params = array('email-Primary' => $user->$mail);
275 }
276
277 if ($ctype == 'Organization') {
278 $params['organization_name'] = $uniqId;
279 }
280 elseif ($ctype == 'Household') {
281 $params['household_name'] = $uniqId;
282 }
283
284 if (!$ctype) {
285 $ctype = "Individual";
286 }
287 $params['contact_type'] = $ctype;
288
289 // extract first / middle / last name
290 // for joomla
291 if ($uf == 'Joomla' && $user->name) {
292 CRM_Utils_String::extractName($user->name, $params);
293 }
294
295 if ($uf == 'WordPress') {
296 if ($user->first_name) {
297 $params['first_name'] = $user->first_name;
298 }
299
300 if ($user->last_name) {
301 $params['last_name'] = $user->last_name;
302 }
303 }
304
305 $contactId = CRM_Contact_BAO_Contact::createProfileContact($params, CRM_Core_DAO::$_nullArray);
306 $ufmatch->contact_id = $contactId;
307 $ufmatch->uf_name = $uniqId;
308 }
309
310 // check that there are not two CMS IDs matching the same CiviCRM contact - this happens when a civicrm
311 // user has two e-mails and there is a cms match for each of them
312 // the gets rid of the nasty fata error but still reports the error
313 $sql = "
314 SELECT uf_id
315 FROM civicrm_uf_match
316 WHERE ( contact_id = %1
317 OR uf_name = %2
318 OR uf_id = %3 )
319 AND domain_id = %4
320 ";
321 $params = array(
322 1 => array($ufmatch->contact_id, 'Integer'),
323 2 => array($ufmatch->uf_name, 'String'),
324 3 => array($ufmatch->uf_id, 'Integer'),
325 4 => array($ufmatch->domain_id, 'Integer'),
326 );
327
328 $conflict = CRM_Core_DAO::singleValueQuery($sql, $params);
329
330 if (!$conflict) {
331 $ufmatch = CRM_Core_BAO_UFMatch::create((array) $ufmatch);
332 $ufmatch->free();
333 $newContact = TRUE;
334 $transaction->commit();
335 }
336 else {
337 $msg = ts("Contact ID %1 is a match for %2 user %3 but has already been matched to %4",
338 array(
339 1 => $ufmatch->contact_id,
340 2 => $uf,
341 3 => $ufmatch->uf_id,
342 4 => $conflict
343 )
344 );
345 unset($conflict);
346 }
347 }
348
349 if ($status) {
350 return $newContact;
351 }
352 else {
353 return $ufmatch;
354 }
355 }
356
357 /**
358 * Update the uf_name in the user object
359 *
360 * @param int $contactId id of the contact to update
361 *
362 * @return void
363 * @static
364 */
365 public static function updateUFName($contactId) {
366 if (!$contactId) {
367 return;
368 }
369 $config = CRM_Core_Config::singleton();
370 $ufName = CRM_Contact_BAO_Contact::getPrimaryEmail($contactId);
371
372 if (!$ufName) {
373 return;
374 }
375
376 $update = FALSE;
377
378 // 1.do check for contact Id.
379 $ufmatch = new CRM_Core_DAO_UFMatch();
380 $ufmatch->contact_id = $contactId;
381 $ufmatch->domain_id = CRM_Core_Config::domainID();
382 if (!$ufmatch->find(TRUE)) {
383 return;
384 }
385 if ($ufmatch->uf_name != $ufName) {
386 $update = TRUE;
387 }
388
389 // CRM-6928
390 // 2.do check for duplicate ufName.
391 $ufDupeName = new CRM_Core_DAO_UFMatch();
392 $ufDupeName->uf_name = $ufName;
393 $ufDupeName->domain_id = CRM_Core_Config::domainID();
394 if ($ufDupeName->find(TRUE) &&
395 $ufDupeName->contact_id != $contactId
396 ) {
397 $update = FALSE;
398 }
399
400 if (!$update) {
401 return;
402 }
403
404 // save the updated ufmatch object
405 $ufmatch->uf_name = $ufName;
406 $ufmatch->save();
407 $config->userSystem->updateCMSName($ufmatch->uf_id, $ufName);
408 }
409
410 /**
411 * Update the email value for the contact and user profile
412 *
413 * @param $contactId Int Contact ID of the user
414 * @param $emailAddress email to be modified for the user
415 *
416 * @return void
417 * @static
418 */
419 public static function updateContactEmail($contactId, $emailAddress) {
420 $strtolower = function_exists('mb_strtolower') ? 'mb_strtolower' : 'strtolower';
421 $emailAddress = $strtolower($emailAddress);
422
423 $ufmatch = new CRM_Core_DAO_UFMatch();
424 $ufmatch->contact_id = $contactId;
425 $ufmatch->domain_id = CRM_Core_Config::domainID();
426 if ($ufmatch->find(TRUE)) {
427 // Save the email in UF Match table
428 $ufmatch->uf_name = $emailAddress;
429 CRM_Core_BAO_UFMatch::create((array) $ufmatch);
430
431 //check if the primary email for the contact exists
432 //$contactDetails[1] - email
433 //$contactDetails[3] - email id
434 $contactDetails = CRM_Contact_BAO_Contact_Location::getEmailDetails($contactId);
435
436 if (trim($contactDetails[1])) {
437 $emailID = $contactDetails[3];
438 //update if record is found
439 $query = "UPDATE civicrm_email
440 SET email = %1
441 WHERE id = %2";
442 $p = array(
443 1 => array($emailAddress, 'String'),
444 2 => array($emailID, 'Integer'),
445 );
446 $dao = CRM_Core_DAO::executeQuery($query, $p);
447 }
448 else {
449 //else insert a new email record
450 $email = new CRM_Core_DAO_Email();
451 $email->contact_id = $contactId;
452 $email->is_primary = 1;
453 $email->email = $emailAddress;
454 $email->save();
455 $emailID = $email->id;
456 }
457
458 CRM_Core_BAO_Log::register($contactId,
459 'civicrm_email',
460 $emailID
461 );
462 }
463 }
464
465 /**
466 * Delete the object records that are associated with this cms user
467 *
468 * @param int $ufID id of the user to delete
469 *
470 * @return void
471 * @static
472 */
473 public static function deleteUser($ufID) {
474 $ufmatch = new CRM_Core_DAO_UFMatch();
475
476 $ufmatch->uf_id = $ufID;
477 $ufmatch->domain_id = CRM_Core_Config::domainID();
478 $ufmatch->delete();
479 }
480
481 /**
482 * Get the contact_id given a uf_id
483 *
484 * @param int $ufID Id of UF for which related contact_id is required
485 *
486 * @return int contact_id on success, null otherwise
487 * @static
488 */
489 public static function getContactId($ufID) {
490 if (!isset($ufID)) {
491 return NULL;
492 }
493
494 $ufmatch = new CRM_Core_DAO_UFMatch();
495
496 $ufmatch->uf_id = $ufID;
497 $ufmatch->domain_id = CRM_Core_Config::domainID();
498 if ($ufmatch->find(TRUE)) {
499 return (int ) $ufmatch->contact_id;
500 }
501 return NULL;
502 }
503
504 /**
505 * Get the uf_id given a contact_id
506 *
507 * @param int $contactID ID of the contact for which related uf_id is required
508 *
509 * @return int uf_id of the given contact_id on success, null otherwise
510 * @static
511 */
512 public static function getUFId($contactID) {
513 if (!isset($contactID)) {
514 return NULL;
515 }
516 $domain = CRM_Core_BAO_Domain::getDomain();
517 $ufmatch = new CRM_Core_DAO_UFMatch();
518
519 $ufmatch->contact_id = $contactID;
520 $ufmatch->domain_id = $domain->id;
521 if ($ufmatch->find(TRUE)) {
522 return $ufmatch->uf_id;
523 }
524 return NULL;
525 }
526
527 /**
528 * @return bool
529 */
530 public static function isEmptyTable() {
531 $sql = "SELECT count(id) FROM civicrm_uf_match";
532 return CRM_Core_DAO::singleValueQuery($sql) > 0 ? FALSE : TRUE;
533 }
534
535 /**
536 * Get the list of contact_id
537 *
538 *
539 * @return int contact_id on success, null otherwise
540 * @static
541 */
542 public static function getContactIDs() {
543 $id = array();
544 $dao = new CRM_Core_DAO_UFMatch();
545 $dao->find();
546 while ($dao->fetch()) {
547 $id[] = $dao->contact_id;
548 }
549 return $id;
550 }
551
552 /**
553 * See if this user exists, and if so, if they're allowed to login
554 *
555 *
556 * @param int $openId
557 *
558 * @return bool true if allowed to login, false otherwise
559 * @static
560 */
561 public static function getAllowedToLogin($openId) {
562 $ufmatch = new CRM_Core_DAO_UFMatch();
563 $ufmatch->uf_name = $openId;
564 $ufmatch->allowed_to_login = 1;
565 if ($ufmatch->find(TRUE)) {
566 return TRUE;
567 }
568 return FALSE;
569 }
570
571 /**
572 * Get the next unused uf_id value, since the standalone UF doesn't
573 * have id's (it uses OpenIDs, which go in a different field)
574 *
575 *
576 * @return int next highest unused value for uf_id
577 * @static
578 */
579 public static function getNextUfIdValue() {
580 $query = "SELECT MAX(uf_id)+1 AS next_uf_id FROM civicrm_uf_match";
581 $dao = CRM_Core_DAO::executeQuery($query);
582 if ($dao->fetch()) {
583 $ufId = $dao->next_uf_id;
584 }
585
586 if (!isset($ufId)) {
587 $ufId = 1;
588 }
589 return $ufId;
590 }
591
592 /**
593 * @param $email
594 *
595 * @return bool
596 */
597 public static function isDuplicateUser($email) {
598 $session = CRM_Core_Session::singleton();
599 $contactID = $session->get('userID');
600 if (!empty($email) && isset($contactID)) {
601 $dao = new CRM_Core_DAO_UFMatch();
602 $dao->uf_name = $email;
603 if ($dao->find(TRUE) && $contactID != $dao->contact_id) {
604 return TRUE;
605 }
606 }
607 return FALSE;
608 }
609
610 /**
611 * Get uf match values for given uf id or logged in user.
612 *
613 * @param int $ufID uf id.
614 *
615 * return array $ufValues uf values.
616 **
617 *
618 * @return array
619 */
620 public static function getUFValues($ufID = NULL) {
621 if (!$ufID) {
622 //get logged in user uf id.
623 $ufID = CRM_Utils_System::getLoggedInUfID();
624 }
625 if (!$ufID) {
626 return array();
627 }
628
629 static $ufValues;
630 if ($ufID && !isset($ufValues[$ufID])) {
631 $ufmatch = new CRM_Core_DAO_UFMatch();
632 $ufmatch->uf_id = $ufID;
633 $ufmatch->domain_id = CRM_Core_Config::domainID();
634 if ($ufmatch->find(TRUE)) {
635 $ufValues[$ufID] = array(
636 'uf_id' => $ufmatch->uf_id,
637 'uf_name' => $ufmatch->uf_name,
638 'contact_id' => $ufmatch->contact_id,
639 'domain_id' => $ufmatch->domain_id,
640 );
641 }
642 }
643 return $ufValues[$ufID];
644 }
645 }