3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
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. |
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. |
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 +--------------------------------------------------------------------+
31 * @copyright CiviCRM LLC (c) 2004-2019
35 * Drupal specific stuff goes here.
37 class CRM_Utils_System_Drupal8
extends CRM_Utils_System_DrupalBase
{
42 public function createUser(&$params, $mail) {
43 $user = \Drupal
::currentUser();
44 $user_register_conf = \Drupal
::config('user.settings')->get('register');
45 $verify_mail_conf = \Drupal
::config('user.settings')->get('verify_mail');
47 // Don't create user if we don't have permission to.
48 if (!$user->hasPermission('administer users') && $user_register_conf == 'admin_only') {
52 /** @var \Drupal\user\Entity\User $account */
53 $account = entity_create('user');
54 $account->setUsername($params['cms_name'])->setEmail($params[$mail]);
56 // Allow user to set password only if they are an admin or if
57 // the site settings don't require email verification.
58 if (!$verify_mail_conf ||
$user->hasPermission('administer users')) {
59 // @Todo: do we need to check that passwords match or assume this has already been done for us?
60 $account->setPassword($params['cms_pass']);
63 // Only activate account if we're admin or if anonymous users don't require
64 // approval to create accounts.
65 if ($user_register_conf != 'visitors' && !$user->hasPermission('administer users')) {
68 elseif (!$verify_mail_conf) {
72 // Validate the user object
73 $violations = $account->validate();
74 if (count($violations)) {
78 // Let the Drupal module know we're already in CiviCRM.
79 $config = CRM_Core_Config
::singleton();
80 $config->inCiviCRM
= TRUE;
84 $config->inCiviCRM
= FALSE;
86 catch (\Drupal\Core\Entity\EntityStorageException
$e) {
87 $config->inCiviCRM
= FALSE;
91 // Send off any emails as required.
92 // Possible values for $op:
93 // - 'register_admin_created': Welcome message for user created by the admin.
94 // - 'register_no_approval_required': Welcome message when user
96 // - 'register_pending_approval': Welcome message, user pending admin
98 // @Todo: Should we only send off emails if $params['notify'] is set?
100 case $user_register_conf == 'admin_only' ||
$user->isAuthenticated():
101 _user_mail_notify('register_admin_created', $account);
104 case $user_register_conf == 'visitors':
105 _user_mail_notify('register_no_approval_required', $account);
108 case 'visitors_admin_approval':
109 _user_mail_notify('register_pending_approval', $account);
113 // If this is a user creating their own account, login them in!
114 if ($account->isActive() && $user->isAnonymous()) {
115 \
user_login_finalize($account);
118 return $account->id();
124 public function updateCMSName($ufID, $email) {
125 $user = entity_load('user', $ufID);
126 if ($user && $user->getEmail() != $email) {
127 $user->setEmail($email);
129 if (!count($user->validate())) {
136 * Check if username and email exists in the drupal db.
138 * @param array $params
139 * Array of name and mail values.
140 * @param array $errors
142 * @param string $emailName
143 * Field label for the 'email'.
145 public static function checkUserNameEmailExists(&$params, &$errors, $emailName = 'email') {
146 // If we are given a name, let's check to see if it already exists.
147 if (!empty($params['name'])) {
148 $name = $params['name'];
150 $user = entity_create('user');
151 $user->setUsername($name);
153 // This checks for both username uniqueness and validity.
154 $violations = iterator_to_array($user->validate());
155 // We only care about violations on the username field; discard the rest.
156 $violations = array_filter($violations, function ($v) {
157 return $v->getPropertyPath() == 'name';
159 if (count($violations) > 0) {
160 $errors['cms_name'] = (string) $violations[0]->getMessage();
164 // And if we are given an email address, let's check to see if it already exists.
165 if (!empty($params[$emailName])) {
166 $mail = $params[$emailName];
168 $user = entity_create('user');
169 $user->setEmail($mail);
171 // This checks for both email uniqueness.
172 $violations = iterator_to_array($user->validate());
173 // We only care about violations on the email field; discard the rest.
174 $violations = array_filter($violations, function ($v) {
175 return $v->getPropertyPath() == 'mail';
177 if (count($violations) > 0) {
178 $errors[$emailName] = (string) $violations[0]->getMessage();
186 public function getLoginURL($destination = '') {
187 $query = $destination ?
['destination' => $destination] : [];
188 return \Drupal
::url('user.login', [], ['query' => $query]);
194 public function setTitle($title, $pageTitle = NULL) {
198 \Drupal
::service('civicrm.page_state')->setTitle($pageTitle);
204 public function appendBreadCrumb($breadcrumbs) {
205 $civicrmPageState = \Drupal
::service('civicrm.page_state');
206 foreach ($breadcrumbs as $breadcrumb) {
207 $civicrmPageState->addBreadcrumb($breadcrumb['title'], $breadcrumb['url']);
214 public function resetBreadCrumb() {
215 \Drupal
::service('civicrm.page_state')->resetBreadcrumbs();
221 public function addHTMLHead($header) {
222 \Drupal
::service('civicrm.page_state')->addHtmlHeader($header);
228 public function addStyleUrl($url, $region) {
229 if ($region != 'html-header') {
236 'rel' => 'stylesheet',
239 \Drupal
::service('civicrm.page_state')->addCSS($css);
246 public function addStyle($code, $region) {
247 if ($region != 'html-header') {
254 \Drupal
::service('civicrm.page_state')->addCSS($css);
259 * Check if a resource url is within the drupal directory and format appropriately.
261 * This seems to be a legacy function. We assume all resources are within the drupal
262 * directory and always return TRUE. As well, we clean up the $url.
264 * FIXME: This is not a legacy function and the above is not a safe assumption.
265 * External urls are allowed by CRM_Core_Resources and this needs to return the correct value.
271 public function formatResourceUrl(&$url) {
272 // Remove leading slash if present.
273 $url = ltrim($url, '/');
275 // Remove query string — presumably added to stop intermediary caching.
276 if (($pos = strpos($url, '?')) !== FALSE) {
277 $url = substr($url, 0, $pos);
279 // FIXME: Should not unconditionally return true
284 * This function does nothing in Drupal 8. Changes to the base_url should be made
285 * in settings.php directly.
287 public function mapConfigToSSL() {
299 $forceBackend = FALSE
301 $query = html_entity_decode($query);
303 $config = CRM_Core_Config
::singleton();
304 $base = $absolute ?
$config->userFrameworkBaseURL
: 'internal:/';
306 $url = $this->parseURL("{$path}?{$query}");
308 // Not all links that CiviCRM generates are Drupal routes, so we use the weaker ::fromUri method.
310 $url = \Drupal\Core\Url
::fromUri("{$base}{$url['path']}", array(
311 'query' => $url['query'],
312 'fragment' => $fragment,
313 'absolute' => $absolute,
316 catch (Exception
$e) {
317 \Drupal
::logger('civicrm')->error($e->getMessage());
326 public function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realPath = NULL) {
327 $system = new CRM_Utils_System_Drupal8();
328 $system->loadBootStrap([], FALSE);
330 $uid = \Drupal
::service('user.auth')->authenticate($name, $password);
332 if ($this->loadUser($name)) {
333 $contact_id = CRM_Core_BAO_UFMatch
::getContactId($uid);
334 return [$contact_id, $uid, mt_rand()];
344 public function loadUser($username) {
345 $user = user_load_by_name($username);
350 // Set Drupal's current user to the loaded user.
351 \Drupal
::currentUser()->setAccount($user);
354 $contact_id = CRM_Core_BAO_UFMatch
::getContactId($uid);
356 // Store the contact id and user id in the session
357 $session = CRM_Core_Session
::singleton();
358 $session->set('ufID', $uid);
359 $session->set('userID', $contact_id);
364 * Determine the native ID of the CMS user.
366 * @param string $username
369 public function getUfId($username) {
370 if ($id = user_load_by_name($username)->id()) {
378 public function permissionDenied() {
379 \Drupal
::service('civicrm.page_state')->setAccessDenied();
383 * In previous versions, this function was the controller for logging out. In Drupal 8, we rewrite the route
384 * to hand off logout to the standard Drupal logout controller. This function should therefore never be called.
386 public function logout() {
391 * Load drupal bootstrap.
393 * @param array $params
394 * Either uid, or name & pass.
395 * @param bool $loadUser
396 * Boolean Require CMS user load.
397 * @param bool $throwError
398 * If true, print error on failure and exit.
399 * @param bool|string $realPath path to script
402 * @Todo Handle setting cleanurls configuration for CiviCRM?
404 public function loadBootStrap($params = [], $loadUser = TRUE, $throwError = TRUE, $realPath = NULL) {
405 static $run_once = FALSE;
413 if (!($root = $this->cmsRootPath())) {
418 // Create a mock $request object
419 $autoloader = require_once $root . '/autoload.php';
420 if ($autoloader === TRUE) {
421 $autoloader = ComposerAutoloaderInitDrupal8
::getLoader();
423 // @Todo: do we need to handle case where $_SERVER has no HTTP_HOST key, ie. when run via cli?
424 $request = new \Symfony\Component\HttpFoundation\
Request([], [], [], [], [], $_SERVER);
426 // Create a kernel and boot it.
427 \Drupal\Core\DrupalKernel
::createFromRequest($request, $autoloader, 'prod')->prepareLegacyRequest($request);
429 // Initialize Civicrm
430 \Drupal
::service('civicrm')->initialize();
432 // We need to call the config hook again, since we now know
433 // all the modules that are listening on it (CRM-8655).
434 CRM_Utils_Hook
::config($config);
437 if (!empty($params['uid']) && $username = \Drupal\user\Entity\User
::load($params['uid'])->getUsername()) {
438 $this->loadUser($username);
440 elseif (!empty($params['name']) && !empty($params['pass']) && \Drupal
::service('user.auth')->authenticate($params['name'], $params['pass'])) {
441 $this->loadUser($params['name']);
448 * Determine the location of the CMS root.
450 * @param string $path
452 * @return NULL|string
454 public function cmsRootPath($path = NULL) {
455 global $civicrm_paths;
456 if (!empty($civicrm_paths['cms.root']['path'])) {
457 return $civicrm_paths['cms.root']['path'];
460 if (defined('DRUPAL_ROOT')) {
464 // It looks like Drupal hasn't been bootstrapped.
465 // We're going to attempt to discover the root Drupal path
466 // by climbing out of the folder hierarchy and looking around to see
467 // if we've found the Drupal root directory.
469 $path = $_SERVER['SCRIPT_FILENAME'];
472 // Normalize and explode path into its component paths.
473 $paths = explode(DIRECTORY_SEPARATOR
, realpath($path));
475 // Remove script filename from array of directories.
478 while (count($paths)) {
479 $candidate = implode('/', $paths);
480 if (file_exists($candidate . "/core/includes/bootstrap.inc")) {
491 public function isUserLoggedIn() {
492 return \Drupal
::currentUser()->isAuthenticated();
498 public function isUserRegistrationPermitted() {
499 if (\Drupal
::config('user.settings')->get('register') == 'admin_only') {
508 public function isPasswordUserGenerated() {
509 if (\Drupal
::config('user.settings')->get('verify_mail') == TRUE) {
518 public function updateCategories() {
519 // @todo Is anything necessary?
525 public function getLoggedInUfID() {
526 if ($id = \Drupal
::currentUser()->id()) {
534 public function getDefaultBlockLocation() {
535 return 'sidebar_first';
541 public function flush() {
542 // CiviCRM and Drupal both provide (different versions of) Symfony (and possibly share other classes too).
543 // If we call drupal_flush_all_caches(), Drupal will attempt to rediscover all of its classes, use Civicrm's
544 // alternatives instead and then die. Instead, we only clear cache bins and no more.
545 foreach (Drupal\Core\Cache\Cache
::getBins() as $service_id => $cache_backend) {
546 $cache_backend->deleteAll();
553 public function getModules() {
556 $module_data = system_rebuild_module_data();
557 foreach ($module_data as $module_name => $extension) {
558 if (!isset($extension->info
['hidden']) && $extension->origin
!= 'core') {
559 $extension->schema_version
= drupal_get_installed_schema_version($module_name);
560 $modules[] = new CRM_Core_Module('drupal.' . $module_name, ($extension->status
== 1 ?
TRUE : FALSE));
569 public function getUser($contactID) {
570 $user_details = parent
::getUser($contactID);
571 $user_details['name'] = $user_details['name']->value
;
572 $user_details['email'] = $user_details['email']->value
;
573 return $user_details;
579 public function getUniqueIdentifierFromUserObject($user) {
580 return $user->get('mail')->value
;
586 public function getUserIDFromUserObject($user) {
587 return $user->get('uid')->value
;
593 public function synchronizeUsers() {
594 $config = CRM_Core_Config
::singleton();
595 if (PHP_SAPI
!= 'cli') {
600 $users = \Drupal
::entityTypeManager()->getStorage('user')->loadByProperties();
602 $uf = $config->userFramework
;
605 $contactMatching = 0;
606 foreach ($users as $user) {
607 $mail = $user->get('mail')->value
;
611 $uid = $user->get('uid')->value
;
613 if ($match = CRM_Core_BAO_UFMatch
::synchronizeUFMatch($user, $uid, $mail, $uf, 1, 'Individual', TRUE)) {
622 'contactCount' => $contactCount,
623 'contactMatching' => $contactMatching,
624 'contactCreated' => $contactCreated,
631 public function setMessage($message) {
632 // CiviCRM sometimes includes markup in messages (ex: Event Cart)
633 // it needs to be rendered before being displayed.
634 $message = \Drupal\Core\Render\Markup
::create($message);
635 \Drupal
::messenger()->addMessage($message);
639 * Drupal 8 has a different function to get current path, hence
640 * overriding the postURL function
642 * @param string $action
646 public function postURL($action) {
647 if (!empty($action)) {
650 $current_path = \Drupal
::service('path.current')->getPath();
651 return $this->url($current_path);
655 * Function to return current language of Drupal8
659 public function getCurrentLanguage() {
660 // Drupal might not be bootstrapped if being called by the REST API.
661 if (!class_exists('Drupal') ||
!\Drupal
::hasContainer()) {
665 return \Drupal
::languageManager()->getCurrentLanguage()->getId();
669 * Helper function to extract path, query and route name from Civicrm URLs.
671 * For example, 'civicrm/contact/view?reset=1&cid=66' will be returned as:
675 * 'path' => 'civicrm/contact/view',
676 * 'route' => 'civicrm.civicrm_contact_view',
677 * 'query' => array('reset' => '1', 'cid' => '66'),
685 * The parsed url parts, containing 'path', 'route' and 'query'.
687 public function parseUrl($url) {
688 $processed = ['path' => '', 'route_name' => '', 'query' => []];
690 // Remove leading '/' if it exists.
691 $url = ltrim($url, '/');
693 // Separate out the url into its path and query components.
694 $url = parse_url($url);
695 if (empty($url['path'])) {
698 $processed['path'] = $url['path'];
700 // Create a route name by replacing the forward slashes in the path with
701 // underscores, civicrm/contact/search => civicrm.civicrm_contact_search.
702 $processed['route_name'] = 'civicrm.' . implode('_', explode('/', $url['path']));
704 // Turn the query string (if it exists) into an associative array.
705 if (!empty($url['query'])) {
706 parse_str($url['query'], $processed['query']);
713 * Append Drupal8 js to coreResourcesList.
715 * @param \Civi\Core\Event\GenericHookEvent $e
717 public function appendCoreResources(\Civi\Core\Event\GenericHookEvent
$e) {
718 $e->list[] = 'js/crm.drupal8.js';
724 public function setUFLocale($civicrm_language) {
725 $langcode = substr(str_replace('_', '', $civicrm_language), 0, 2);
726 $languageManager = \Drupal
::languageManager();
727 $languages = $languageManager->getLanguages();
729 if (isset($languages[$langcode])) {
730 $languageManager->setConfigOverrideLanguage($languages[$langcode]);
732 // Config must be re-initialized to reset the base URL
733 // otherwise links will have the wrong language prefix/domain.
734 $config = CRM_Core_Config
::singleton();
746 public function languageNegotiationURL($url, $addLanguagePart = TRUE, $removeLanguagePart = FALSE) {
751 // Drupal might not be bootstrapped if being called by the REST API.
752 if (!class_exists('Drupal') ||
!\Drupal
::hasContainer()) {
756 $language = $this->getCurrentLanguage();
757 if (\Drupal
::service('module_handler')->moduleExists('language')) {
758 $config = \Drupal
::config('language.negotiation')->get('url');
760 //does user configuration allow language
761 //support from the URL (Path prefix or domain)
762 $enabledLanguageMethods = \Drupal
::config('language.types')->get('negotiation.language_interface.enabled') ?
: [];
763 if (array_key_exists(\Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl
::METHOD_ID
, $enabledLanguageMethods)) {
764 $urlType = $config['source'];
767 if ($urlType == \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl
::CONFIG_PATH_PREFIX
) {
768 if (!empty($language)) {
769 if ($addLanguagePart && !empty($config['prefixes'][$language])) {
770 $url .= $config['prefixes'][$language] . '/';
772 if ($removeLanguagePart) {
773 $url = str_replace("/" . $config['prefixes'][$language] . "/", '/', $url);
778 if ($urlType == \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl
::CONFIG_DOMAIN
) {
779 if (isset($language->domain
) && $language->domain
) {
780 if ($addLanguagePart) {
781 $url = (CRM_Utils_System
::isSSL() ?
'https' : 'http') . '://' . $config['domains'][$language] . base_path();
783 if ($removeLanguagePart && defined('CIVICRM_UF_BASEURL')) {
784 $url = str_replace('\\', '/', $url);
785 $parseUrl = parse_url($url);
787 //kinda hackish but not sure how to do it right
788 //hope http_build_url() will help at some point.
789 if (is_array($parseUrl) && !empty($parseUrl)) {
790 $urlParts = explode('/', $url);
791 $hostKey = array_search($parseUrl['host'], $urlParts);
792 $ufUrlParts = parse_url(CIVICRM_UF_BASEURL
);
793 $urlParts[$hostKey] = $ufUrlParts['host'];
794 $url = implode('/', $urlParts);