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