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