Do not notify user if not set in the params
[civicrm-core.git] / CRM / Utils / System / Drupal8.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 * Drupal specific stuff goes here.
20 */
21 class CRM_Utils_System_Drupal8 extends CRM_Utils_System_DrupalBase {
22
23 /**
24 * @inheritDoc
25 */
26 public function createUser(&$params, $mail) {
27 $user = \Drupal::currentUser();
28 $user_register_conf = \Drupal::config('user.settings')->get('register');
29 $verify_mail_conf = \Drupal::config('user.settings')->get('verify_mail');
30
31 // Don't create user if we don't have permission to.
32 if (!$user->hasPermission('administer users') && $user_register_conf == 'admin_only') {
33 return FALSE;
34 }
35
36 /** @var \Drupal\user\Entity\User $account */
37 $account = \Drupal::entityTypeManager()->getStorage('user')->create();
38 $account->setUsername($params['cms_name'])->setEmail($params[$mail]);
39
40 // Allow user to set password only if they are an admin or if
41 // the site settings don't require email verification.
42 if (!$verify_mail_conf || $user->hasPermission('administer users')) {
43 // @Todo: do we need to check that passwords match or assume this has already been done for us?
44 $account->setPassword($params['cms_pass']);
45 }
46
47 // Only activate account if we're admin or if anonymous users don't require
48 // approval to create accounts.
49 if ($user_register_conf != 'visitors' && !$user->hasPermission('administer users')) {
50 $account->block();
51 }
52 else {
53 $account->activate();
54 }
55
56 // Validate the user object
57 $violations = $account->validate();
58 if (count($violations)) {
59 foreach ($violations as $violation) {
60 CRM_Core_Session::setStatus($violation->getPropertyPath() . ': ' . $violation->getMessage(), '', 'alert');
61 }
62 return FALSE;
63 }
64
65 // Let the Drupal module know we're already in CiviCRM.
66 $config = CRM_Core_Config::singleton();
67 $config->inCiviCRM = TRUE;
68
69 try {
70 $account->save();
71 $config->inCiviCRM = FALSE;
72 }
73 catch (\Drupal\Core\Entity\EntityStorageException $e) {
74 $config->inCiviCRM = FALSE;
75 return FALSE;
76 }
77
78 // Send off any emails as required.
79 // Possible values for $op:
80 // - 'register_admin_created': Welcome message for user created by the admin.
81 // - 'register_no_approval_required': Welcome message when user
82 // self-registers.
83 // - 'register_pending_approval': Welcome message, user pending admin
84 // approval.
85 switch (TRUE) {
86 case $user_register_conf == 'admin_only' || $user->isAuthenticated():
87 if (!empty($params['notify'])) {
88 _user_mail_notify('register_admin_created', $account);
89 }
90 break;
91
92 case $user_register_conf == 'visitors':
93 _user_mail_notify('register_no_approval_required', $account);
94 break;
95
96 case 'visitors_admin_approval':
97 _user_mail_notify('register_pending_approval', $account);
98 break;
99 }
100
101 // If this is a user creating their own account, login them in!
102 if (!$verify_mail_conf && $account->isActive() && $user->isAnonymous()) {
103 \user_login_finalize($account);
104 }
105
106 return $account->id();
107 }
108
109 /**
110 * @inheritDoc
111 */
112 public function updateCMSName($ufID, $email) {
113 $user = \Drupal::entityTypeManager()->getStorage('user')->load($ufID);
114 if ($user && $user->getEmail() != $email) {
115 $user->setEmail($email);
116
117 if (!count($user->validate())) {
118 $user->save();
119 }
120 }
121 }
122
123 /**
124 * Check if username and email exists in the drupal db.
125 *
126 * @param array $params
127 * Array of name and mail values.
128 * @param array $errors
129 * Errors.
130 * @param string $emailName
131 * Field label for the 'email'.
132 */
133 public static function checkUserNameEmailExists(&$params, &$errors, $emailName = 'email') {
134 // If we are given a name, let's check to see if it already exists.
135 if (!empty($params['name'])) {
136 $name = $params['name'];
137
138 $user = \Drupal::entityTypeManager()->getStorage('user')->create();
139 $user->setUsername($name);
140
141 // This checks for both username uniqueness and validity.
142 $violations = iterator_to_array($user->validate());
143 // We only care about violations on the username field; discard the rest.
144 $violations = array_values(array_filter($violations, function ($v) {
145 return $v->getPropertyPath() == 'name';
146 }));
147 if (count($violations) > 0) {
148 $errors['cms_name'] = (string) $violations[0]->getMessage();
149 }
150 }
151
152 // And if we are given an email address, let's check to see if it already exists.
153 if (!empty($params['mail'])) {
154 $mail = $params['mail'];
155
156 $user = \Drupal::entityTypeManager()->getStorage('user')->create();
157 $user->setEmail($mail);
158
159 // This checks for both email uniqueness.
160 $violations = iterator_to_array($user->validate());
161 // We only care about violations on the email field; discard the rest.
162 $violations = array_values(array_filter($violations, function ($v) {
163 return $v->getPropertyPath() == 'mail';
164 }));
165 if (count($violations) > 0) {
166 $errors[$emailName] = (string) $violations[0]->getMessage();
167 }
168 }
169 }
170
171 /**
172 * @inheritDoc
173 */
174 public function getLoginURL($destination = '') {
175 $query = $destination ? ['destination' => $destination] : [];
176 return \Drupal\Core\Url::fromRoute('user.login', [], ['query' => $query])->toString();
177 }
178
179 /**
180 * @inheritDoc
181 */
182 public function setTitle($title, $pageTitle = NULL) {
183 if (!$pageTitle) {
184 $pageTitle = $title;
185 }
186 \Drupal::service('civicrm.page_state')->setTitle($pageTitle);
187 }
188
189 /**
190 * @inheritDoc
191 */
192 public function appendBreadCrumb($breadcrumbs) {
193 $civicrmPageState = \Drupal::service('civicrm.page_state');
194 foreach ($breadcrumbs as $breadcrumb) {
195 $civicrmPageState->addBreadcrumb($breadcrumb['title'], $breadcrumb['url']);
196 }
197 }
198
199 /**
200 * @inheritDoc
201 */
202 public function resetBreadCrumb() {
203 \Drupal::service('civicrm.page_state')->resetBreadcrumbs();
204 }
205
206 /**
207 * @inheritDoc
208 */
209 public function addHTMLHead($header) {
210 \Drupal::service('civicrm.page_state')->addHtmlHeader($header);
211 }
212
213 /**
214 * @inheritDoc
215 */
216 public function addStyleUrl($url, $region) {
217 if ($region != 'html-header') {
218 return FALSE;
219 }
220 $css = [
221 '#tag' => 'link',
222 '#attributes' => [
223 'href' => $url,
224 'rel' => 'stylesheet',
225 ],
226 ];
227 \Drupal::service('civicrm.page_state')->addCSS($css);
228 return TRUE;
229 }
230
231 /**
232 * @inheritDoc
233 */
234 public function addStyle($code, $region) {
235 if ($region != 'html-header') {
236 return FALSE;
237 }
238 $css = [
239 '#tag' => 'style',
240 '#value' => $code,
241 ];
242 \Drupal::service('civicrm.page_state')->addCSS($css);
243 return TRUE;
244 }
245
246 /**
247 * Check if a resource url is within the drupal directory and format appropriately.
248 *
249 * This seems to be a legacy function. We assume all resources are within the drupal
250 * directory and always return TRUE. As well, we clean up the $url.
251 *
252 * FIXME: This is not a legacy function and the above is not a safe assumption.
253 * External urls are allowed by CRM_Core_Resources and this needs to return the correct value.
254 *
255 * @param $url
256 *
257 * @return bool
258 */
259 public function formatResourceUrl(&$url) {
260 // Remove leading slash if present.
261 $url = ltrim($url, '/');
262
263 // Remove query string — presumably added to stop intermediary caching.
264 if (($pos = strpos($url, '?')) !== FALSE) {
265 $url = substr($url, 0, $pos);
266 }
267 // FIXME: Should not unconditionally return true
268 return TRUE;
269 }
270
271 /**
272 * This function does nothing in Drupal 8. Changes to the base_url should be made
273 * in settings.php directly.
274 */
275 public function mapConfigToSSL() {
276 }
277
278 /**
279 * @inheritDoc
280 */
281 public function url(
282 $path = '',
283 $query = '',
284 $absolute = FALSE,
285 $fragment = NULL,
286 $frontend = FALSE,
287 $forceBackend = FALSE,
288 $htmlize = TRUE
289 ) {
290 $query = html_entity_decode($query);
291
292 $config = CRM_Core_Config::singleton();
293 $base = $absolute ? $config->userFrameworkBaseURL : 'internal:/';
294
295 $url = $this->parseURL("{$path}?{$query}");
296
297 // Not all links that CiviCRM generates are Drupal routes, so we use the weaker ::fromUri method.
298 try {
299 $url = \Drupal\Core\Url::fromUri("{$base}{$url['path']}", array(
300 'query' => $url['query'],
301 'fragment' => $fragment,
302 'absolute' => $absolute,
303 ))->toString();
304 }
305 catch (Exception $e) {
306 \Drupal::logger('civicrm')->error($e->getMessage());
307 }
308
309 return $url;
310 }
311
312 /**
313 * @inheritDoc
314 */
315 public function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realPath = NULL) {
316 $system = new CRM_Utils_System_Drupal8();
317 $system->loadBootStrap([], FALSE);
318
319 $uid = \Drupal::service('user.auth')->authenticate($name, $password);
320 if ($uid) {
321 if ($this->loadUser($name)) {
322 $contact_id = CRM_Core_BAO_UFMatch::getContactId($uid);
323 return [$contact_id, $uid, mt_rand()];
324 }
325 }
326
327 return FALSE;
328 }
329
330 /**
331 * @inheritDoc
332 */
333 public function loadUser($username) {
334 $user = user_load_by_name($username);
335 if (!$user) {
336 return FALSE;
337 }
338
339 // Set Drupal's current user to the loaded user.
340 \Drupal::currentUser()->setAccount($user);
341
342 $uid = $user->id();
343 $contact_id = CRM_Core_BAO_UFMatch::getContactId($uid);
344
345 // Store the contact id and user id in the session
346 $session = CRM_Core_Session::singleton();
347 $session->set('ufID', $uid);
348 $session->set('userID', $contact_id);
349 return TRUE;
350 }
351
352 /**
353 * Determine the native ID of the CMS user.
354 *
355 * @param string $username
356 * @return int|null
357 */
358 public function getUfId($username) {
359 if ($id = user_load_by_name($username)->id()) {
360 return $id;
361 }
362 }
363
364 /**
365 * @inheritDoc
366 */
367 public function permissionDenied() {
368 \Drupal::service('civicrm.page_state')->setAccessDenied();
369 }
370
371 /**
372 * In previous versions, this function was the controller for logging out. In Drupal 8, we rewrite the route
373 * to hand off logout to the standard Drupal logout controller. This function should therefore never be called.
374 */
375 public function logout() {
376 // Pass
377 }
378
379 /**
380 * Load drupal bootstrap.
381 *
382 * @param array $params
383 * Either uid, or name & pass.
384 * @param bool $loadUser
385 * Boolean Require CMS user load.
386 * @param bool $throwError
387 * If true, print error on failure and exit.
388 * @param bool|string $realPath path to script
389 *
390 * @return bool
391 * @Todo Handle setting cleanurls configuration for CiviCRM?
392 */
393 public function loadBootStrap($params = [], $loadUser = TRUE, $throwError = TRUE, $realPath = NULL) {
394 static $run_once = FALSE;
395 if ($run_once) {
396 return TRUE;
397 }
398 else {
399 $run_once = TRUE;
400 }
401
402 if (!($root = $this->cmsRootPath())) {
403 return FALSE;
404 }
405 chdir($root);
406
407 // Create a mock $request object
408 $autoloader = require_once $root . '/autoload.php';
409 if ($autoloader === TRUE) {
410 $autoloader = ComposerAutoloaderInitDrupal8::getLoader();
411 }
412 // @Todo: do we need to handle case where $_SERVER has no HTTP_HOST key, ie. when run via cli?
413 $request = new \Symfony\Component\HttpFoundation\Request([], [], [], [], [], $_SERVER);
414
415 // Create a kernel and boot it.
416 $kernel = \Drupal\Core\DrupalKernel::createFromRequest($request, $autoloader, 'prod');
417 $kernel->boot();
418 $kernel->preHandle($request);
419 $container = $kernel->rebuildContainer();
420 // Add our request to the stack and route context.
421 $request->attributes->set(\Symfony\Cmf\Component\Routing\RouteObjectInterface::ROUTE_OBJECT, new \Symfony\Component\Routing\Route('<none>'));
422 $request->attributes->set(\Symfony\Cmf\Component\Routing\RouteObjectInterface::ROUTE_NAME, '<none>');
423 $container->get('request_stack')->push($request);
424 $container->get('router.request_context')->fromRequest($request);
425
426 // Initialize Civicrm
427 \Drupal::service('civicrm')->initialize();
428
429 // We need to call the config hook again, since we now know
430 // all the modules that are listening on it (CRM-8655).
431 CRM_Utils_Hook::config($config);
432
433 if ($loadUser) {
434 if (!empty($params['uid']) && $username = \Drupal\user\Entity\User::load($params['uid'])->getAccountName()) {
435 $this->loadUser($username);
436 }
437 elseif (!empty($params['name']) && !empty($params['pass']) && \Drupal::service('user.auth')->authenticate($params['name'], $params['pass'])) {
438 $this->loadUser($params['name']);
439 }
440 }
441 return TRUE;
442 }
443
444 /**
445 * Determine the location of the CMS root.
446 *
447 * @param string $path
448 *
449 * @return NULL|string
450 */
451 public function cmsRootPath($path = NULL) {
452 global $civicrm_paths;
453 if (!empty($civicrm_paths['cms.root']['path'])) {
454 return $civicrm_paths['cms.root']['path'];
455 }
456
457 if (defined('DRUPAL_ROOT')) {
458 return DRUPAL_ROOT;
459 }
460
461 // It looks like Drupal hasn't been bootstrapped.
462 // We're going to attempt to discover the root Drupal path
463 // by climbing out of the folder hierarchy and looking around to see
464 // if we've found the Drupal root directory.
465 if (!$path) {
466 $path = $_SERVER['SCRIPT_FILENAME'];
467 }
468
469 // Normalize and explode path into its component paths.
470 $paths = explode(DIRECTORY_SEPARATOR, realpath($path));
471
472 // Remove script filename from array of directories.
473 array_pop($paths);
474
475 while (count($paths)) {
476 $candidate = implode('/', $paths);
477 if (file_exists($candidate . "/core/includes/bootstrap.inc")) {
478 return $candidate;
479 }
480
481 array_pop($paths);
482 }
483 }
484
485 /**
486 * @inheritDoc
487 */
488 public function isUserLoggedIn() {
489 return \Drupal::currentUser()->isAuthenticated();
490 }
491
492 /**
493 * @inheritDoc
494 */
495 public function isUserRegistrationPermitted() {
496 if (\Drupal::config('user.settings')->get('register') == 'admin_only') {
497 return FALSE;
498 }
499 return TRUE;
500 }
501
502 /**
503 * @inheritDoc
504 */
505 public function isPasswordUserGenerated() {
506 if (\Drupal::config('user.settings')->get('verify_mail') == TRUE) {
507 return FALSE;
508 }
509 return TRUE;
510 }
511
512 /**
513 * @inheritDoc
514 */
515 public function updateCategories() {
516 // @todo Is anything necessary?
517 }
518
519 /**
520 * @inheritDoc
521 */
522 public function getLoggedInUfID() {
523 if ($id = \Drupal::currentUser()->id()) {
524 return $id;
525 }
526 }
527
528 /**
529 * @inheritDoc
530 */
531 public function getDefaultBlockLocation() {
532 return 'sidebar_first';
533 }
534
535 /**
536 * @inheritDoc
537 */
538 public function flush() {
539 // CiviCRM and Drupal both provide (different versions of) Symfony (and possibly share other classes too).
540 // If we call drupal_flush_all_caches(), Drupal will attempt to rediscover all of its classes, use Civicrm's
541 // alternatives instead and then die. Instead, we only clear cache bins and no more.
542 foreach (Drupal\Core\Cache\Cache::getBins() as $service_id => $cache_backend) {
543 $cache_backend->deleteAll();
544 }
545 }
546
547 /**
548 * @inheritDoc
549 */
550 public function getModules() {
551 $modules = [];
552
553 $module_data = \Drupal::service('extension.list.module')->reset()->getList();
554 foreach ($module_data as $module_name => $extension) {
555 if (!isset($extension->info['hidden']) && $extension->origin != 'core') {
556 $extension->schema_version = drupal_get_installed_schema_version($module_name);
557 $modules[] = new CRM_Core_Module('drupal.' . $module_name, ($extension->status == 1));
558 }
559 }
560 return $modules;
561 }
562
563 /**
564 * @inheritDoc
565 */
566 public function getUser($contactID) {
567 $user_details = parent::getUser($contactID);
568 $user_details['name'] = $user_details['name']->value;
569 $user_details['email'] = $user_details['email']->value;
570 return $user_details;
571 }
572
573 /**
574 * @inheritDoc
575 */
576 public function getUniqueIdentifierFromUserObject($user) {
577 return $user->get('mail')->value;
578 }
579
580 /**
581 * @inheritDoc
582 */
583 public function getUserIDFromUserObject($user) {
584 return $user->get('uid')->value;
585 }
586
587 /**
588 * @inheritDoc
589 */
590 public function synchronizeUsers() {
591 $config = CRM_Core_Config::singleton();
592 if (PHP_SAPI != 'cli') {
593 set_time_limit(300);
594 }
595
596 $users = [];
597 $users = \Drupal::entityTypeManager()->getStorage('user')->loadByProperties();
598
599 $uf = $config->userFramework;
600 $contactCount = 0;
601 $contactCreated = 0;
602 $contactMatching = 0;
603 foreach ($users as $user) {
604 $mail = $user->get('mail')->value;
605 if (empty($mail)) {
606 continue;
607 }
608 $uid = $user->get('uid')->value;
609 $contactCount++;
610 if ($match = CRM_Core_BAO_UFMatch::synchronizeUFMatch($user, $uid, $mail, $uf, 1, 'Individual', TRUE)) {
611 $contactCreated++;
612 }
613 else {
614 $contactMatching++;
615 }
616 }
617
618 return [
619 'contactCount' => $contactCount,
620 'contactMatching' => $contactMatching,
621 'contactCreated' => $contactCreated,
622 ];
623 }
624
625 /**
626 * @inheritDoc
627 */
628 public function setMessage($message) {
629 // CiviCRM sometimes includes markup in messages (ex: Event Cart)
630 // it needs to be rendered before being displayed.
631 $message = \Drupal\Core\Render\Markup::create($message);
632 \Drupal::messenger()->addMessage($message);
633 }
634
635 /**
636 * Drupal 8 has a different function to get current path, hence
637 * overriding the postURL function
638 *
639 * @param string $action
640 *
641 * @return string
642 */
643 public function postURL($action) {
644 if (!empty($action)) {
645 return $action;
646 }
647 $current_path = \Drupal::service('path.current')->getPath();
648 return $this->url($current_path);
649 }
650
651 /**
652 * Function to return current language of Drupal8
653 *
654 * @return string
655 */
656 public function getCurrentLanguage() {
657 // Drupal might not be bootstrapped if being called by the REST API.
658 if (!class_exists('Drupal') || !\Drupal::hasContainer()) {
659 return NULL;
660 }
661
662 return \Drupal::languageManager()->getConfigOverrideLanguage()->getId();
663 }
664
665 /**
666 * Helper function to extract path, query and route name from Civicrm URLs.
667 *
668 * For example, 'civicrm/contact/view?reset=1&cid=66' will be returned as:
669 *
670 * ```
671 * array(
672 * 'path' => 'civicrm/contact/view',
673 * 'route' => 'civicrm.civicrm_contact_view',
674 * 'query' => array('reset' => '1', 'cid' => '66'),
675 * );
676 * ```
677 *
678 * @param string $url
679 * The url to parse.
680 *
681 * @return string[]
682 * The parsed url parts, containing 'path', 'route' and 'query'.
683 */
684 public function parseUrl($url) {
685 $processed = ['path' => '', 'route_name' => '', 'query' => []];
686
687 // Remove leading '/' if it exists.
688 $url = ltrim($url, '/');
689
690 // Separate out the url into its path and query components.
691 $url = parse_url($url);
692 if (empty($url['path'])) {
693 return $processed;
694 }
695 $processed['path'] = $url['path'];
696
697 // Create a route name by replacing the forward slashes in the path with
698 // underscores, civicrm/contact/search => civicrm.civicrm_contact_search.
699 $processed['route_name'] = 'civicrm.' . implode('_', explode('/', $url['path']));
700
701 // Turn the query string (if it exists) into an associative array.
702 if (!empty($url['query'])) {
703 parse_str($url['query'], $processed['query']);
704 }
705
706 return $processed;
707 }
708
709 /**
710 * Append Drupal8 js to coreResourcesList.
711 *
712 * @param \Civi\Core\Event\GenericHookEvent $e
713 */
714 public function appendCoreResources(\Civi\Core\Event\GenericHookEvent $e) {
715 $e->list[] = 'js/crm.drupal8.js';
716 }
717
718 /**
719 * @inheritDoc
720 */
721 public function getTimeZoneString() {
722 $timezone = date_default_timezone_get();
723 return $timezone;
724 }
725
726 /**
727 * @inheritDoc
728 */
729 public function setUFLocale($civicrm_language) {
730 $langcode = substr(str_replace('_', '', $civicrm_language), 0, 2);
731 $languageManager = \Drupal::languageManager();
732 $languages = $languageManager->getLanguages();
733
734 if (isset($languages[$langcode])) {
735 $languageManager->setConfigOverrideLanguage($languages[$langcode]);
736
737 // Config must be re-initialized to reset the base URL
738 // otherwise links will have the wrong language prefix/domain.
739 $config = CRM_Core_Config::singleton();
740 $config->free();
741
742 return TRUE;
743 }
744
745 return FALSE;
746 }
747
748 /**
749 * @inheritDoc
750 */
751 public function languageNegotiationURL($url, $addLanguagePart = TRUE, $removeLanguagePart = FALSE) {
752 if (empty($url)) {
753 return $url;
754 }
755
756 // Drupal might not be bootstrapped if being called by the REST API.
757 if (!class_exists('Drupal') || !\Drupal::hasContainer()) {
758 return $url;
759 }
760
761 $language = $this->getCurrentLanguage();
762 if (\Drupal::service('module_handler')->moduleExists('language')) {
763 $config = \Drupal::config('language.negotiation')->get('url');
764
765 //does user configuration allow language
766 //support from the URL (Path prefix or domain)
767 $enabledLanguageMethods = \Drupal::config('language.types')->get('negotiation.language_interface.enabled') ?: [];
768 if (array_key_exists(\Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl::METHOD_ID, $enabledLanguageMethods)) {
769 $urlType = $config['source'];
770
771 //url prefix
772 if ($urlType == \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl::CONFIG_PATH_PREFIX) {
773 if (!empty($language)) {
774 if ($addLanguagePart && !empty($config['prefixes'][$language])) {
775 $url .= $config['prefixes'][$language] . '/';
776 }
777 if ($removeLanguagePart && !empty($config['prefixes'][$language])) {
778 $url = str_replace("/" . $config['prefixes'][$language] . "/", '/', $url);
779 }
780 }
781 }
782 //domain
783 if ($urlType == \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl::CONFIG_DOMAIN) {
784 if (isset($language->domain) && $language->domain) {
785 if ($addLanguagePart) {
786 $url = (CRM_Utils_System::isSSL() ? 'https' : 'http') . '://' . $config['domains'][$language] . base_path();
787 }
788 if ($removeLanguagePart && defined('CIVICRM_UF_BASEURL')) {
789 $url = str_replace('\\', '/', $url);
790 $parseUrl = parse_url($url);
791
792 //kinda hackish but not sure how to do it right
793 //hope http_build_url() will help at some point.
794 if (is_array($parseUrl) && !empty($parseUrl)) {
795 $urlParts = explode('/', $url);
796 $hostKey = array_search($parseUrl['host'], $urlParts);
797 $ufUrlParts = parse_url(CIVICRM_UF_BASEURL);
798 $urlParts[$hostKey] = $ufUrlParts['host'];
799 $url = implode('/', $urlParts);
800 }
801 }
802 }
803 }
804 }
805 }
806
807 return $url;
808 }
809
810 /**
811 * Get role names
812 *
813 * @return array|null
814 */
815 public function getRoleNames() {
816 return user_role_names();
817 }
818
819 /**
820 * Determine if the Views module exists.
821 *
822 * @return bool
823 */
824 public function viewsExists() {
825 if (\Drupal::moduleHandler()->moduleExists('views')) {
826 return TRUE;
827 }
828 return FALSE;
829 }
830
831 /**
832 * Return the CMS-specific url for its permissions page
833 * @return array
834 */
835 public function getCMSPermissionsUrlParams() {
836 return ['ufAccessURL' => \Drupal\Core\Url::fromRoute('user.admin_permissions')->toString()];
837 }
838
839 /**
840 * Start a new session.
841 */
842 public function sessionStart() {
843 if (\Drupal::hasContainer()) {
844 $session = \Drupal::service('session');
845 if (!$session->isStarted()) {
846 $session->start();
847 }
848 }
849 }
850
851 /**
852 * Load the user object.
853 *
854 * @param int $userID
855 *
856 * @return object
857 */
858 public function getUserObject($userID) {
859 return \Drupal::entityTypeManager()->getStorage('user')->load($userID);
860 }
861
862 /**
863 * Helper function to rebuild the Drupal 8 or 9 dynamic routing cache.
864 * We need to do this after enabling extensions that add routes and it's worth doing when we reset Civi paths.
865 */
866 public function invalidateRouteCache() {
867 if (class_exists('\Drupal') && \Drupal::hasContainer()) {
868 \Drupal::service('router.builder')->rebuild();
869 }
870 }
871
872 }