Avoid CiviCRM running full drupal cache flush, as this results in CiviCRM clobbering...
[civicrm-core.git] / CRM / Utils / System / Drupal8.php
CommitLineData
d3e88312
EM
1<?php
2/*
3 +--------------------------------------------------------------------+
9242538c 4 | CiviCRM version 4.6 |
d3e88312 5 +--------------------------------------------------------------------+
e7112fa7 6 | Copyright CiviCRM LLC (c) 2004-2015 |
d3e88312
EM
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 +--------------------------------------------------------------------+
d25dd0ee 26 */
d3e88312
EM
27
28/**
29 *
30 * @package CRM
e7112fa7 31 * @copyright CiviCRM LLC (c) 2004-2015
d3e88312
EM
32 * $Id$
33 *
34 */
35
36/**
37 * Drupal specific stuff goes here
38 */
39class CRM_Utils_System_Drupal8 extends CRM_Utils_System_DrupalBase {
40
7e9cadcf 41 /**
17f443df 42 * @inheritDoc
7e9cadcf 43 */
00be9182 44 public function createUser(&$params, $mail) {
7e9cadcf
EM
45 $user = \Drupal::currentUser();
46 $user_register_conf = \Drupal::config('user.settings')->get('register');
47 $verify_mail_conf = \Drupal::config('user.settings')->get('verify_mail');
48
49 // Don't create user if we don't have permission to.
50 if (!$user->hasPermission('administer users') && $user_register_conf == 'admin_only') {
51 return FALSE;
52 }
53
54 $account = entity_create('user');
55 $account->setUsername($params['cms_name'])->setEmail($params[$mail]);
56
57 // Allow user to set password only if they are an admin or if
58 // the site settings don't require email verification.
59 if (!$verify_mail_conf || $user->hasPermission('administer users')) {
60 // @Todo: do we need to check that passwords match or assume this has already been done for us?
61 $account->setPassword($params['cms_pass']);
62 }
63
64 // Only activate account if we're admin or if anonymous users don't require
65 // approval to create accounts.
66 if ($user_register_conf != 'visitors' && !$user->hasPermission('administer users')) {
67 $account->block();
68 }
69
70 // Validate the user object
71 $violations = $account->validate();
72 if (count($violations)) {
73 return FALSE;
74 }
75
76 try {
77 $account->save();
78 }
79 catch (\Drupal\Core\Entity\EntityStorageException $e) {
80 return FALSE;
81 }
82
83 // Send off any emails as required.
84 // Possible values for $op:
85 // - 'register_admin_created': Welcome message for user created by the admin.
86 // - 'register_no_approval_required': Welcome message when user
87 // self-registers.
88 // - 'register_pending_approval': Welcome message, user pending admin
89 // approval.
90 // @Todo: Should we only send off emails if $params['notify'] is set?
91 switch (TRUE) {
92 case $user_register_conf == 'admin_only' || $user->isAuthenticated():
93 _user_mail_notify('register_admin_created', $account);
94 break;
e7292422 95
7e9cadcf
EM
96 case $user_register_conf == 'visitors':
97 _user_mail_notify('register_no_approval_required', $account);
98 break;
e7292422 99
7e9cadcf
EM
100 case 'visitors_admin_approval':
101 _user_mail_notify('register_pending_approval', $account);
102 break;
103 }
104
105 return $account->id();
106 }
107
108 /**
17f443df 109 * @inheritDoc
7e9cadcf 110 */
00be9182 111 public function updateCMSName($ufID, $email) {
ce391511 112 $user = entity_load('user', $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
EM
131 *
132 *
133 * @return void
134 */
00be9182 135 public static function checkUserNameEmailExists(&$params, &$errors, $emailName = 'email') {
7e9cadcf
EM
136 // If we are given a name, let's check to see if it already exists.
137 if (!empty($params['name'])) {
138 $name = $params['name'];
139
140 $user = entity_create('user');
141 $user->setUsername($name);
142
143 // This checks for both username uniqueness and validity.
144 $violations = iterator_to_array($user->validate());
145 // We only care about violations on the username field; discard the rest.
353ffa53
TO
146 $violations = array_filter($violations, function ($v) {
147 return $v->getPropertyPath() == 'name.0.value';
e7292422 148 });
7e9cadcf
EM
149 if (count($violations) > 0) {
150 $errors['cms_name'] = $violations[0]->getMessage();
151 }
152 }
153
154 // And if we are given an email address, let's check to see if it already exists.
155 if (!empty($params[$emailName])) {
156 $mail = $params[$emailName];
157
158 $user = entity_create('user');
159 $user->setEmail($mail);
160
161 // This checks for both email uniqueness.
162 $violations = iterator_to_array($user->validate());
163 // We only care about violations on the email field; discard the rest.
353ffa53
TO
164 $violations = array_filter($violations, function ($v) {
165 return $v->getPropertyPath() == 'mail.0.value';
e7292422 166 });
7e9cadcf
EM
167 if (count($violations) > 0) {
168 $errors[$emailName] = $violations[0]->getMessage();
169 }
170 }
171 }
172
173 /**
17f443df 174 * @inheritDoc
d3e88312
EM
175 */
176 public function getLoginURL($destination = '') {
7e9cadcf
EM
177 $query = $destination ? array('destination' => $destination) : array();
178 return \Drupal::url('user.page', array(), array('query' => $query));
179 }
180
7e9cadcf 181 /**
17f443df 182 * @inheritDoc
7e9cadcf 183 */
00be9182 184 public function setTitle($title, $pageTitle = NULL) {
7e9cadcf
EM
185 if (!$pageTitle) {
186 $pageTitle = $title;
187 }
7e9cadcf
EM
188 \Drupal::service('civicrm.page_state')->setTitle($pageTitle);
189 }
190
191 /**
17f443df 192 * @inheritDoc
7e9cadcf 193 */
00be9182 194 public function appendBreadCrumb($breadcrumbs) {
7e9cadcf
EM
195 $civicrmPageState = \Drupal::service('civicrm.page_state');
196 foreach ($breadcrumbs as $breadcrumb) {
197 $civicrmPageState->addBreadcrumb($breadcrumb['title'], $breadcrumb['url']);
198 }
199 }
200
201 /**
17f443df 202 * @inheritDoc
7e9cadcf 203 */
00be9182 204 public function resetBreadCrumb() {
7e9cadcf
EM
205 \Drupal::service('civicrm.page_state')->resetBreadcrumbs();
206 }
207
208 /**
17f443df 209 * @inheritDoc
7e9cadcf 210 */
00be9182 211 public function addHTMLHead($header) {
7e9cadcf
EM
212 \Drupal::service('civicrm.page_state')->addHtmlHeader($header);
213 }
214
215 /**
17f443df 216 * @inheritDoc
7e9cadcf
EM
217 */
218 public function addScriptUrl($url, $region) {
ce391511
T
219 static $weight = 0;
220
7e9cadcf
EM
221 switch ($region) {
222 case 'html-header':
223 case 'page-footer':
7e9cadcf
EM
224 break;
225 default:
226 return FALSE;
227 }
ce391511
T
228
229 $script = array(
230 '#tag' => 'script',
231 '#attributes' => array(
232 'src' => $url,
233 ),
234 '#weight' => $weight,
235 );
236 $weight++;
237 \Drupal::service('civicrm.page_state')->addJS($script);
7e9cadcf
EM
238 return TRUE;
239 }
240
241 /**
17f443df 242 * @inheritDoc
7e9cadcf
EM
243 */
244 public function addScript($code, $region) {
7e9cadcf
EM
245 switch ($region) {
246 case 'html-header':
247 case 'page-footer':
7e9cadcf
EM
248 break;
249 default:
250 return FALSE;
251 }
ce391511
T
252
253 $script = array(
254 '#tag' => 'script',
255 '#value' => $code,
256 );
257 \Drupal::service('civicrm.page_state')->addJS($script);
7e9cadcf
EM
258 return TRUE;
259 }
260
261 /**
17f443df 262 * @inheritDoc
7e9cadcf
EM
263 */
264 public function addStyleUrl($url, $region) {
265 if ($region != 'html-header') {
266 return FALSE;
d3e88312 267 }
ce391511
T
268 $css = array(
269 '#tag' => 'link',
270 '#attributes' => array(
271 'href' => $url,
272 'rel' => 'stylesheet',
273 ),
274 );
275 \Drupal::service('civicrm.page_state')->addCSS($css);
7e9cadcf 276 return TRUE;
d3e88312
EM
277 }
278
7e9cadcf 279 /**
17f443df 280 * @inheritDoc
7e9cadcf
EM
281 */
282 public function addStyle($code, $region) {
283 if ($region != 'html-header') {
284 return FALSE;
285 }
ce391511
T
286 $css = array(
287 '#tag' => 'style',
288 '#value' => $code,
289 );
290 \Drupal::service('civicrm.page_state')->addCSS($css);
7e9cadcf
EM
291 return TRUE;
292 }
293
294 /**
fe482240 295 * Check if a resource url is within the drupal directory and format appropriately.
7e9cadcf
EM
296 *
297 * This seems to be a legacy function. We assume all resources are within the drupal
298 * directory and always return TRUE. As well, we clean up the $url.
299 *
17f443df
CW
300 * FIXME: This is not a legacy function and the above is not a safe assumption.
301 * External urls are allowed by CRM_Core_Resources and this needs to return the correct value.
302 *
7e9cadcf
EM
303 * @param $url
304 *
305 * @return bool
306 */
00be9182 307 public function formatResourceUrl(&$url) {
7e9cadcf
EM
308 // Remove leading slash if present.
309 $url = ltrim($url, '/');
310
311 // Remove query string — presumably added to stop intermediary caching.
312 if (($pos = strpos($url, '?')) !== FALSE) {
313 $url = substr($url, 0, $pos);
314 }
17f443df 315 // FIXME: Should not unconditionally return true
7e9cadcf
EM
316 return TRUE;
317 }
318
319 /**
7e9cadcf
EM
320 * This function does nothing in Drupal 8. Changes to the base_url should be made
321 * in settings.php directly.
7e9cadcf 322 */
00be9182 323 public function mapConfigToSSL() {
7e9cadcf
EM
324 }
325
326 /**
17f443df 327 * @inheritDoc
7e9cadcf 328 */
17f443df
CW
329 public function url(
330 $path = '',
331 $query = '',
332 $absolute = FALSE,
333 $fragment = NULL,
756ad860 334 $htmlize = FALSE,
17f443df
CW
335 $frontend = FALSE,
336 $forceBackend = FALSE
337 ) {
7e9cadcf 338 $query = html_entity_decode($query);
756ad860 339
7e9cadcf
EM
340 $url = \Drupal\civicrm\CivicrmHelper::parseURL("{$path}?{$query}");
341
756ad860 342 // Not all links that CiviCRM generates are Drupal routes, so we use the weaker ::fromUri method.
7e9cadcf 343 try {
756ad860 344 $url = \Drupal\Core\Url::fromUri("base:{$url['path']}", [
7e9cadcf 345 'query' => $url['query'],
7e9cadcf 346 'fragment' => $fragment,
756ad860
T
347 'absolute' => $absolute,
348 ])->toString();
7e9cadcf
EM
349 }
350 catch (Exception $e) {
756ad860 351 // @Todo: log to watchdog
7e9cadcf
EM
352 $url = '';
353 }
354
756ad860
T
355 // Special case: CiviCRM passes us "*path*?*query*" as a skeleton, but asterisks
356 // are invalid and Drupal will attempt to escape them. We unescape them here:
357 if ($path == '*path*') {
358 // First remove trailing equals sign that has been added since the key '?*query*' has no value.
359 $url = rtrim($url, '=');
360 $url = urldecode($url);
361 }
362
7e9cadcf
EM
363 if ($htmlize) {
364 $url = htmlentities($url);
365 }
366 return $url;
367 }
368
7e9cadcf 369 /**
17f443df 370 * @inheritDoc
7e9cadcf 371 */
00be9182 372 public function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realPath = NULL) {
d90814f1
CW
373 $system = new CRM_Utils_System_Drupal8();
374 $system->loadBootStrap(array(), FALSE);
7e9cadcf
EM
375
376 $uid = \Drupal::service('user.auth')->authenticate($name, $password);
377 $contact_id = CRM_Core_BAO_UFMatch::getContactId($uid);
378
379 return array($contact_id, $uid, mt_rand());
380 }
381
382 /**
17f443df 383 * @inheritDoc
7e9cadcf 384 */
00be9182 385 public function loadUser($username) {
7e9cadcf
EM
386 $user = user_load_by_name($username);
387 if (!$user) {
388 return FALSE;
389 }
390
391 // Set Drupal's current user to the loaded user.
392 \Drupal::currentUser()->setAccount($user);
393
394 $uid = $user->id();
395 $contact_id = CRM_Core_BAO_UFMatch::getContactId($uid);
396
397 // Store the contact id and user id in the session
398 $session = CRM_Core_Session::singleton();
399 $session->set('ufID', $uid);
400 $session->set('userID', $contact_id);
401 return TRUE;
402 }
403
404 /**
fe482240 405 * Determine the native ID of the CMS user.
7e9cadcf 406 *
100fef9d 407 * @param string $username
7e9cadcf
EM
408 * @return int|NULL
409 */
00be9182 410 public function getUfId($username) {
7e9cadcf
EM
411 if ($id = user_load_by_name($username)->id()) {
412 return $id;
413 }
414 }
415
416 /**
17f443df 417 * @inheritDoc
7e9cadcf 418 */
00be9182 419 public function permissionDenied() {
7e9cadcf
EM
420 \Drupal::service('civicrm.page_state')->setAccessDenied();
421 }
422
423 /**
424 * In previous versions, this function was the controller for logging out. In Drupal 8, we rewrite the route
425 * to hand off logout to the standard Drupal logout controller. This function should therefore never be called.
426 */
00be9182 427 public function logout() {
7e9cadcf
EM
428 // Pass
429 }
430
431 /**
fe482240 432 * Load drupal bootstrap.
7e9cadcf 433 *
77855840
TO
434 * @param array $params
435 * Either uid, or name & pass.
436 * @param bool $loadUser
437 * Boolean Require CMS user load.
438 * @param bool $throwError
439 * If true, print error on failure and exit.
440 * @param bool|string $realPath path to script
7e9cadcf
EM
441 *
442 * @return bool
443 * @Todo Handle setting cleanurls configuration for CiviCRM?
444 */
00be9182 445 public function loadBootStrap($params = array(), $loadUser = TRUE, $throwError = TRUE, $realPath = NULL) {
7e9cadcf 446 static $run_once = FALSE;
a3e55d9c
TO
447 if ($run_once) {
448 return TRUE;
0db6c3e1
TO
449 }
450 else {
a3e55d9c 451 $run_once = TRUE;
e7292422 452 }
7e9cadcf
EM
453
454 if (!($root = $this->cmsRootPath())) {
455 return FALSE;
456 }
457 chdir($root);
458
459 // Create a mock $request object
460 $autoloader = require_once $root . '/core/vendor/autoload.php';
461 // @Todo: do we need to handle case where $_SERVER has no HTTP_HOST key, ie. when run via cli?
462 $request = new \Symfony\Component\HttpFoundation\Request(array(), array(), array(), array(), array(), $_SERVER);
463
464 // Create a kernel and boot it.
465 \Drupal\Core\DrupalKernel::createFromRequest($request, $autoloader, 'prod')->prepareLegacyRequest($request);
466
467 // Initialize Civicrm
468 \Drupal::service('civicrm');
469
470 // We need to call the config hook again, since we now know
471 // all the modules that are listening on it (CRM-8655).
472 CRM_Utils_Hook::config($config);
473
474 if ($loadUser) {
475 if (!empty($params['uid']) && $username = \Drupal\user\Entity\User::load($uid)->getUsername()) {
476 $this->loadUser($username);
477 }
478 elseif (!empty($params['name']) && !empty($params['pass']) && $this->authenticate($params['name'], $params['pass'])) {
479 $this->loadUser($params['name']);
480 }
481 }
482 return TRUE;
483 }
484
485 /**
486 * Determine the location of the CMS root.
487 * @param null $path
488 *
489 * @return NULL|string
490 */
00be9182 491 public function cmsRootPath($path = NULL) {
7e9cadcf
EM
492 if (defined('DRUPAL_ROOT')) {
493 return DRUPAL_ROOT;
494 }
495
496 // It looks like Drupal hasn't been bootstrapped.
497 // We're going to attempt to discover the root Drupal path
498 // by climbing out of the folder hierarchy and looking around to see
499 // if we've found the Drupal root directory.
500 if (!$path) {
501 $path = $_SERVER['SCRIPT_FILENAME'];
502 }
503
504 // Normalize and explode path into its component paths.
505 $paths = explode(DIRECTORY_SEPARATOR, realpath($path));
506
507 // Remove script filename from array of directories.
508 array_pop($paths);
509
510 while (count($paths)) {
511 $candidate = implode('/', $paths);
512 if (file_exists($candidate . "/core/includes/bootstrap.inc")) {
513 return $candidate;
514 }
515
516 array_pop($paths);
517 }
518 }
519
520 /**
17f443df 521 * @inheritDoc
7e9cadcf
EM
522 */
523 public function isUserLoggedIn() {
524 return \Drupal::currentUser()->isAuthenticated();
525 }
526
527 /**
17f443df 528 * @inheritDoc
7e9cadcf
EM
529 */
530 public function getLoggedInUfID() {
531 if ($id = \Drupal::currentUser()->id()) {
532 return $id;
533 }
534 }
624142d4
EM
535
536 /**
17f443df 537 * @inheritDoc
624142d4 538 */
00be9182 539 public function getDefaultBlockLocation() {
624142d4
EM
540 return 'sidebar_first';
541 }
96025800 542
f38178e6
T
543 /**
544 * @inheritDoc
545 */
546 public function flush() {
547 // CiviCRM and Drupal both provide (different versions of) Symfony (and possibly share other classes too).
548 // If we call drupal_flush_all_caches(), Drupal will attempt to rediscover all of its classes, use Civicrm's
549 // alternatives instead and then die. Instead, we only clear cache bins and no more.
550 foreach (Drupal\Core\Cache\Cache::getBins() as $service_id => $cache_backend) {
551 $cache_backend->deleteAll();
552 }
553 }
7e9cadcf 554}