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