Merge pull request #13374 from cividesk/dev-627
[civicrm-core.git] / CRM / Core / BAO / UFMatch.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
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-2019
32 */
33
34 /**
35 * The basic class that interfaces with the external user framework.
36 */
37 class CRM_Core_BAO_UFMatch extends CRM_Core_DAO_UFMatch {
38
39 /**
40 * Create UF Match, Note that this function is here in it's simplest form @ the moment
41 *
42 * @param $params
43 *
44 * @return \CRM_Core_DAO_UFMatch
45 */
46 public static function create($params) {
47 $hook = empty($params['id']) ? 'create' : 'edit';
48 CRM_Utils_Hook::pre($hook, 'UFMatch', CRM_Utils_Array::value('id', $params), $params);
49 if (empty($params['domain_id'])) {
50 $params['domain_id'] = CRM_Core_Config::domainID();
51 }
52 $dao = new CRM_Core_DAO_UFMatch();
53 $dao->copyValues($params);
54 // Fixme: this function cannot update records
55 if (!$dao->find(TRUE)) {
56 $dao->save();
57 Civi::$statics[__CLASS__][$params['domain_id']][(int) $dao->contact_id] = (int) $dao->uf_id;
58 CRM_Utils_Hook::post($hook, 'UFMatch', $dao->id, $dao);
59 }
60 return $dao;
61 }
62
63 /**
64 * Given a UF user object, make sure there is a contact
65 * object for this user. If the user has new values, we need
66 * to update the CRM DB with the new values
67 *
68 * @param Object $user
69 * The drupal user object.
70 * @param bool $update
71 * Has the user object been edited.
72 * @param $uf
73 *
74 * @param $ctype
75 * @param bool $isLogin
76 */
77 public 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 = [
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
171 * The drupal user object.
172 * @param string $userKey
173 * The id of the user from the uf object.
174 * @param string $uniqId
175 * The OpenID of the user.
176 * @param string $uf
177 * The name of the user framework.
178 * @param int $status
179 * Returns the status if user created or already exits (used for CMS sync).
180 * @param string $ctype
181 * contact type
182 * @param bool $isLogin
183 *
184 * @return CRM_Core_DAO_UFMatch|bool
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 $ids = CRM_Contact_BAO_Contact::getDuplicateContacts($params, 'Individual', 'Unsupervised', [], FALSE);
210
211 if (!empty($ids) && Civi::settings()->get('uniq_email_per_site')) {
212 // restrict dupeIds to ones that belong to current domain/site.
213 $siteContacts = CRM_Core_BAO_Domain::getContactList();
214 foreach ($ids as $index => $dupeId) {
215 if (!in_array($dupeId, $siteContacts)) {
216 unset($ids[$index]);
217 }
218 }
219 // re-index the array
220 $ids = array_values($ids);
221 }
222 if (!empty($ids)) {
223 $dao = new CRM_Core_DAO();
224 $dao->contact_id = $ids[0];
225 }
226 }
227 else {
228 $dao = CRM_Contact_BAO_Contact::matchContactOnEmail($uniqId, $ctype);
229 }
230
231 $found = FALSE;
232 if ($dao) {
233 // ensure there does not exists a contact_id / uf_id pair
234 // in the DB. This might be due to multiple emails per contact
235 // CRM-9091
236 $sql = "
237 SELECT id
238 FROM civicrm_uf_match
239 WHERE contact_id = %1
240 AND domain_id = %2
241 ";
242 $params = [
243 1 => [$dao->contact_id, 'Integer'],
244 2 => [CRM_Core_Config::domainID(), 'Integer'],
245 ];
246 $conflict = CRM_Core_DAO::singleValueQuery($sql, $params);
247
248 if (!$conflict) {
249 $found = TRUE;
250 $ufmatch->contact_id = $dao->contact_id;
251 $ufmatch->uf_name = $uniqId;
252 }
253 }
254
255 if (!$found) {
256 // Not sure why we're testing for this. Is there ever a case
257 // in which $user is not an object?
258 if (is_object($user)) {
259 if ($config->userSystem->is_drupal) {
260 $primary_email = $uniqId;
261 }
262 elseif ($uf == 'WordPress') {
263 $primary_email = $user->user_email;
264 }
265 else {
266 $primary_email = $user->email;
267 }
268 $params = ['email-Primary' => $primary_email];
269 }
270
271 if ($ctype == 'Organization') {
272 $params['organization_name'] = $uniqId;
273 }
274 elseif ($ctype == 'Household') {
275 $params['household_name'] = $uniqId;
276 }
277
278 if (!$ctype) {
279 $ctype = "Individual";
280 }
281 $params['contact_type'] = $ctype;
282
283 // extract first / middle / last name
284 // for joomla
285 if ($uf == 'Joomla' && $user->name) {
286 CRM_Utils_String::extractName($user->name, $params);
287 }
288
289 if ($uf == 'WordPress') {
290 if ($user->first_name) {
291 $params['first_name'] = $user->first_name;
292 }
293
294 if ($user->last_name) {
295 $params['last_name'] = $user->last_name;
296 }
297 }
298
299 $contactId = CRM_Contact_BAO_Contact::createProfileContact($params, CRM_Core_DAO::$_nullArray);
300 $ufmatch->contact_id = $contactId;
301 $ufmatch->uf_name = $uniqId;
302 }
303
304 // check that there are not two CMS IDs matching the same CiviCRM contact - this happens when a civicrm
305 // user has two e-mails and there is a cms match for each of them
306 // the gets rid of the nasty fata error but still reports the error
307 $sql = "
308 SELECT uf_id
309 FROM civicrm_uf_match
310 WHERE ( contact_id = %1
311 OR uf_name = %2
312 OR uf_id = %3 )
313 AND domain_id = %4
314 ";
315 $params = [
316 1 => [$ufmatch->contact_id, 'Integer'],
317 2 => [$ufmatch->uf_name, 'String'],
318 3 => [$ufmatch->uf_id, 'Integer'],
319 4 => [$ufmatch->domain_id, 'Integer'],
320 ];
321
322 $conflict = CRM_Core_DAO::singleValueQuery($sql, $params);
323
324 if (!$conflict) {
325 $ufmatch = CRM_Core_BAO_UFMatch::create((array) $ufmatch);
326 $ufmatch->free();
327 $newContact = TRUE;
328 $transaction->commit();
329 }
330 else {
331 $msg = ts("Contact ID %1 is a match for %2 user %3 but has already been matched to %4",
332 [
333 1 => $ufmatch->contact_id,
334 2 => $uf,
335 3 => $ufmatch->uf_id,
336 4 => $conflict,
337 ]
338 );
339 unset($conflict);
340 }
341 }
342
343 if ($status) {
344 return $newContact;
345 }
346 else {
347 return $ufmatch;
348 }
349 }
350
351 /**
352 * Update the uf_name in the user object.
353 *
354 * @param int $contactId
355 * Id of the contact to update.
356 */
357 public static function updateUFName($contactId) {
358 if (!Civi::settings()->get('syncCMSEmail') || !$contactId) {
359 return;
360 }
361
362 $config = CRM_Core_Config::singleton();
363 $ufName = CRM_Contact_BAO_Contact::getPrimaryEmail($contactId);
364
365 if (!$ufName) {
366 return;
367 }
368
369 $update = FALSE;
370
371 // 1.do check for contact Id.
372 $ufmatch = new CRM_Core_DAO_UFMatch();
373 $ufmatch->contact_id = $contactId;
374 $ufmatch->domain_id = CRM_Core_Config::domainID();
375 if (!$ufmatch->find(TRUE)) {
376 return;
377 }
378 if ($ufmatch->uf_name != $ufName) {
379 $update = TRUE;
380 }
381
382 // CRM-6928
383 // 2.do check for duplicate ufName.
384 $ufDupeName = new CRM_Core_DAO_UFMatch();
385 $ufDupeName->uf_name = $ufName;
386 $ufDupeName->domain_id = CRM_Core_Config::domainID();
387 if ($ufDupeName->find(TRUE) &&
388 $ufDupeName->contact_id != $contactId
389 ) {
390 $update = FALSE;
391 }
392
393 if (!$update) {
394 return;
395 }
396
397 // save the updated ufmatch object
398 $ufmatch->uf_name = $ufName;
399 $ufmatch->save();
400 $config->userSystem->updateCMSName($ufmatch->uf_id, $ufName);
401 }
402
403 /**
404 * Update the email value for the contact and user profile.
405 *
406 * @param int $contactId
407 * Contact ID of the user.
408 * @param string $emailAddress
409 * Email to be modified for the user.
410 */
411 public static function updateContactEmail($contactId, $emailAddress) {
412 $strtolower = function_exists('mb_strtolower') ? 'mb_strtolower' : 'strtolower';
413 $emailAddress = $strtolower($emailAddress);
414
415 $ufmatch = new CRM_Core_DAO_UFMatch();
416 $ufmatch->contact_id = $contactId;
417 $ufmatch->domain_id = CRM_Core_Config::domainID();
418 if ($ufmatch->find(TRUE)) {
419 // Save the email in UF Match table
420 $ufmatch->uf_name = $emailAddress;
421 CRM_Core_BAO_UFMatch::create((array) $ufmatch);
422
423 // If CMS integration is disabled skip Civi email update if CMS user email is changed
424 if (Civi::settings()->get('syncCMSEmail') == FALSE) {
425 return;
426 }
427
428 //check if the primary email for the contact exists
429 //$contactDetails[1] - email
430 //$contactDetails[3] - email id
431 $contactDetails = CRM_Contact_BAO_Contact_Location::getEmailDetails($contactId);
432
433 if (trim($contactDetails[1])) {
434 $emailID = $contactDetails[3];
435 //update if record is found
436 $query = "UPDATE civicrm_email
437 SET email = %1
438 WHERE id = %2";
439 $p = [
440 1 => [$emailAddress, 'String'],
441 2 => [$emailID, 'Integer'],
442 ];
443 $dao = CRM_Core_DAO::executeQuery($query, $p);
444 }
445 else {
446 //else insert a new email record
447 $email = new CRM_Core_DAO_Email();
448 $email->contact_id = $contactId;
449 $email->is_primary = 1;
450 $email->email = $emailAddress;
451 $email->save();
452 $emailID = $email->id;
453 }
454
455 CRM_Core_BAO_Log::register($contactId,
456 'civicrm_email',
457 $emailID
458 );
459 }
460 }
461
462 /**
463 * Delete the object records that are associated with this cms user.
464 *
465 * @param int $ufID
466 * Id of the user to delete.
467 */
468 public static function deleteUser($ufID) {
469 $ufmatch = new CRM_Core_DAO_UFMatch();
470
471 $ufmatch->uf_id = $ufID;
472 $ufmatch->domain_id = $domainId = CRM_Core_Config::domainID();
473 $ufmatch->delete();
474
475 // Flush cache
476 Civi::$statics[__CLASS__][$domainId] = [];
477 }
478
479 /**
480 * Get the contact_id given a uf_id.
481 *
482 * @param int $ufID
483 * Id of UF for which related contact_id is required.
484 *
485 * @return int|null
486 * contact_id on success, null otherwise
487 */
488 public static function getContactId($ufID) {
489 if (!$ufID) {
490 return NULL;
491 }
492 $domainId = CRM_Core_Config::domainID();
493
494 if (!isset(Civi::$statics[__CLASS__][$domainId])) {
495 Civi::$statics[__CLASS__][$domainId] = [];
496 }
497 $contactId = array_search($ufID, Civi::$statics[__CLASS__][$domainId]);
498 if ($contactId) {
499 return $contactId;
500 }
501 $ufmatch = new CRM_Core_DAO_UFMatch();
502 $ufmatch->uf_id = $ufID;
503 $ufmatch->domain_id = $domainId;
504 if ($ufmatch->find(TRUE)) {
505 $contactId = (int) $ufmatch->contact_id;
506 Civi::$statics[__CLASS__][$domainId][$contactId] = (int) $ufID;
507 return $contactId;
508 }
509 return NULL;
510 }
511
512 /**
513 * Get the uf_id given a contact_id.
514 *
515 * @param int $contactID
516 * ID of the contact for which related uf_id is required.
517 *
518 * @return int|null
519 * uf_id of the given contact_id on success, null otherwise
520 */
521 public static function getUFId($contactID) {
522 if (!$contactID) {
523 return NULL;
524 }
525 $domainId = CRM_Core_Config::domainID();
526 $contactID = (int) $contactID;
527
528 if (empty(Civi::$statics[__CLASS__][$domainId]) || !array_key_exists($contactID, Civi::$statics[__CLASS__][$domainId])) {
529 Civi::$statics[__CLASS__][$domainId][$contactID] = NULL;
530 $ufmatch = new CRM_Core_DAO_UFMatch();
531 $ufmatch->contact_id = $contactID;
532 $ufmatch->domain_id = $domainId;
533 if ($ufmatch->find(TRUE)) {
534 Civi::$statics[__CLASS__][$domainId][$contactID] = (int) $ufmatch->uf_id;
535 }
536 }
537 return Civi::$statics[__CLASS__][$domainId][$contactID];
538 }
539
540 /**
541 * @return bool
542 */
543 public static function isEmptyTable() {
544 $sql = "SELECT count(id) FROM civicrm_uf_match";
545 return CRM_Core_DAO::singleValueQuery($sql) > 0 ? FALSE : TRUE;
546 }
547
548 /**
549 * Get the list of contact_id.
550 *
551 *
552 * @return int
553 * contact_id on success, null otherwise
554 */
555 public static function getContactIDs() {
556 $id = [];
557 $dao = new CRM_Core_DAO_UFMatch();
558 $dao->find();
559 while ($dao->fetch()) {
560 $id[] = $dao->contact_id;
561 }
562 return $id;
563 }
564
565 /**
566 * See if this user exists, and if so, if they're allowed to login
567 *
568 *
569 * @param int $openId
570 *
571 * @return bool
572 * true if allowed to login, false otherwise
573 */
574 public static function getAllowedToLogin($openId) {
575 $ufmatch = new CRM_Core_DAO_UFMatch();
576 $ufmatch->uf_name = $openId;
577 $ufmatch->allowed_to_login = 1;
578 if ($ufmatch->find(TRUE)) {
579 return TRUE;
580 }
581 return FALSE;
582 }
583
584 /**
585 * Get the next unused uf_id value, since the standalone UF doesn't
586 * have id's (it uses OpenIDs, which go in a different field)
587 *
588 *
589 * @return int
590 * next highest unused value for uf_id
591 */
592 public static function getNextUfIdValue() {
593 $query = "SELECT MAX(uf_id)+1 AS next_uf_id FROM civicrm_uf_match";
594 $dao = CRM_Core_DAO::executeQuery($query);
595 if ($dao->fetch()) {
596 $ufId = $dao->next_uf_id;
597 }
598
599 if (!isset($ufId)) {
600 $ufId = 1;
601 }
602 return $ufId;
603 }
604
605 /**
606 * @param $email
607 *
608 * @return bool
609 */
610 public static function isDuplicateUser($email) {
611 $session = CRM_Core_Session::singleton();
612 $contactID = $session->get('userID');
613 if (!empty($email) && isset($contactID)) {
614 $dao = new CRM_Core_DAO_UFMatch();
615 $dao->uf_name = $email;
616 if ($dao->find(TRUE) && $contactID != $dao->contact_id) {
617 return TRUE;
618 }
619 }
620 return FALSE;
621 }
622
623 /**
624 * Get uf match values for given uf id or logged in user.
625 *
626 * @param int $ufID
627 * Uf id.
628 *
629 * @return array
630 * uf values.
631 */
632 public static function getUFValues($ufID = NULL) {
633 if (!$ufID) {
634 //get logged in user uf id.
635 $ufID = CRM_Utils_System::getLoggedInUfID();
636 }
637 if (!$ufID) {
638 return [];
639 }
640
641 static $ufValues;
642 if ($ufID && !isset($ufValues[$ufID])) {
643 $ufmatch = new CRM_Core_DAO_UFMatch();
644 $ufmatch->uf_id = $ufID;
645 $ufmatch->domain_id = CRM_Core_Config::domainID();
646 if ($ufmatch->find(TRUE)) {
647 $ufValues[$ufID] = [
648 'uf_id' => $ufmatch->uf_id,
649 'uf_name' => $ufmatch->uf_name,
650 'contact_id' => $ufmatch->contact_id,
651 'domain_id' => $ufmatch->domain_id,
652 ];
653 }
654 }
655 return $ufValues[$ufID];
656 }
657
658 /**
659 * @inheritDoc
660 */
661 public function addSelectWhereClause() {
662 // Prevent default behavior of joining ACLs onto the contact_id field
663 $clauses = [];
664 CRM_Utils_Hook::selectWhereClause($this, $clauses);
665 return $clauses;
666 }
667
668 }