Cleanup phpdoc comments
[civicrm-core.git] / CRM / Utils / System / Drupal8.php
CommitLineData
d3e88312
EM
1<?php
2/*
3 +--------------------------------------------------------------------+
06b69b18 4 | CiviCRM version 4.5 |
d3e88312 5 +--------------------------------------------------------------------+
06b69b18 6 | Copyright CiviCRM LLC (c) 2004-2014 |
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 +--------------------------------------------------------------------+
26*/
27
28/**
29 *
30 * @package CRM
06b69b18 31 * @copyright CiviCRM LLC (c) 2004-2014
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 /**
100fef9d 42 * Create a user in Drupal.
7e9cadcf
EM
43 *
44 * @param array $params associated array
45 * @param string $mail email id for cms user
46 *
47 * @return uid if user exists, false otherwise
48 *
49 * @access public
50 *
51 */
52 function createUser(&$params, $mail) {
53 $user = \Drupal::currentUser();
54 $user_register_conf = \Drupal::config('user.settings')->get('register');
55 $verify_mail_conf = \Drupal::config('user.settings')->get('verify_mail');
56
57 // Don't create user if we don't have permission to.
58 if (!$user->hasPermission('administer users') && $user_register_conf == 'admin_only') {
59 return FALSE;
60 }
61
62 $account = entity_create('user');
63 $account->setUsername($params['cms_name'])->setEmail($params[$mail]);
64
65 // Allow user to set password only if they are an admin or if
66 // the site settings don't require email verification.
67 if (!$verify_mail_conf || $user->hasPermission('administer users')) {
68 // @Todo: do we need to check that passwords match or assume this has already been done for us?
69 $account->setPassword($params['cms_pass']);
70 }
71
72 // Only activate account if we're admin or if anonymous users don't require
73 // approval to create accounts.
74 if ($user_register_conf != 'visitors' && !$user->hasPermission('administer users')) {
75 $account->block();
76 }
77
78 // Validate the user object
79 $violations = $account->validate();
80 if (count($violations)) {
81 return FALSE;
82 }
83
84 try {
85 $account->save();
86 }
87 catch (\Drupal\Core\Entity\EntityStorageException $e) {
88 return FALSE;
89 }
90
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
95 // self-registers.
96 // - 'register_pending_approval': Welcome message, user pending admin
97 // approval.
98 // @Todo: Should we only send off emails if $params['notify'] is set?
99 switch (TRUE) {
100 case $user_register_conf == 'admin_only' || $user->isAuthenticated():
101 _user_mail_notify('register_admin_created', $account);
102 break;
103 case $user_register_conf == 'visitors':
104 _user_mail_notify('register_no_approval_required', $account);
105 break;
106 case 'visitors_admin_approval':
107 _user_mail_notify('register_pending_approval', $account);
108 break;
109 }
110
111 return $account->id();
112 }
113
114 /**
115 * Update the Drupal user's email address.
116 *
117 * @param integer $ufID User ID in CMS
118 * @param string $email Primary contact email address
119 */
120 function updateCMSName($ufID, $email) {
121 $user = user_load($ufID);
122 if ($user && $user->getEmail() != $email) {
123 $user->setEmail($email);
124
125 if (!count($user->validate())) {
126 $user->save();
127 }
128 }
129 }
130
131 /**
132 * Check if username and email exists in the drupal db
133 *
134 * @param array $params array of name and mail values
135 * @param array $errors errors
136 * @param string $emailName field label for the 'email'
137 *
138 *
139 * @return void
140 */
141 static function checkUserNameEmailExists(&$params, &$errors, $emailName = 'email') {
142 // If we are given a name, let's check to see if it already exists.
143 if (!empty($params['name'])) {
144 $name = $params['name'];
145
146 $user = entity_create('user');
147 $user->setUsername($name);
148
149 // This checks for both username uniqueness and validity.
150 $violations = iterator_to_array($user->validate());
151 // We only care about violations on the username field; discard the rest.
152 $violations = array_filter($violations, function ($v) { return $v->getPropertyPath() == 'name.0.value'; });
153 if (count($violations) > 0) {
154 $errors['cms_name'] = $violations[0]->getMessage();
155 }
156 }
157
158 // And if we are given an email address, let's check to see if it already exists.
159 if (!empty($params[$emailName])) {
160 $mail = $params[$emailName];
161
162 $user = entity_create('user');
163 $user->setEmail($mail);
164
165 // This checks for both email uniqueness.
166 $violations = iterator_to_array($user->validate());
167 // We only care about violations on the email field; discard the rest.
168 $violations = array_filter($violations, function ($v) { return $v->getPropertyPath() == 'mail.0.value'; });
169 if (count($violations) > 0) {
170 $errors[$emailName] = $violations[0]->getMessage();
171 }
172 }
173 }
174
175 /**
100fef9d 176 * Get the drupal destination string. When this is passed in the
7e9cadcf
EM
177 * URL the user will be directed to it after filling in the drupal form
178 *
c490a46a 179 * @param CRM_Core_Form $form Form object representing the 'current' form - to which the user will be returned
7e9cadcf
EM
180 * @return string $destination destination value for URL
181 *
182 */
183 function getLoginDestination(&$form) {
184 $args = NULL;
185
186 $id = $form->get('id');
187 if ($id) {
188 $args .= "&id=$id";
189 }
190 else {
191 $gid = $form->get('gid');
192 if ($gid) {
193 $args .= "&gid=$gid";
194 }
195 else {
196 // Setup Personal Campaign Page link uses pageId
197 $pageId = $form->get('pageId');
198 if ($pageId) {
199 $component = $form->get('component');
200 $args .= "&pageId=$pageId&component=$component&action=add";
201 }
202 }
203 }
204
205 $destination = NULL;
206 if ($args) {
207 // append destination so user is returned to form they came from after login
208 $destination = CRM_Utils_System::currentPath() . '?reset=1' . $args;
209 }
210 return $destination;
211 }
212
d3e88312
EM
213 /**
214 * Get user login URL for hosting CMS (method declared in each CMS system class)
215 *
216 * @param string $destination - if present, add destination to querystring (works for Drupal only)
217 *
218 * @return string - loginURL for the current CMS
219 * @static
220 */
221 public function getLoginURL($destination = '') {
7e9cadcf
EM
222 $query = $destination ? array('destination' => $destination) : array();
223 return \Drupal::url('user.page', array(), array('query' => $query));
224 }
225
226
227 /**
100fef9d 228 * Sets the title of the page
7e9cadcf
EM
229 *
230 * @param string $title
231 * @param string $pageTitle
232 *
233 * @return void
234 * @access public
235 */
236 function setTitle($title, $pageTitle = NULL) {
237 if (!$pageTitle) {
238 $pageTitle = $title;
239 }
240
241 \Drupal::service('civicrm.page_state')->setTitle($pageTitle);
242 }
243
244 /**
245 * Append an additional breadcrumb tag to the existing breadcrumb
246 *
247 * @param $breadcrumbs
248 *
249 * @internal param string $title
250 * @internal param string $url
251 *
252 * @return void
253 * @access public
254 */
255 function appendBreadCrumb($breadcrumbs) {
256 $civicrmPageState = \Drupal::service('civicrm.page_state');
257 foreach ($breadcrumbs as $breadcrumb) {
258 $civicrmPageState->addBreadcrumb($breadcrumb['title'], $breadcrumb['url']);
259 }
260 }
261
262 /**
263 * Reset an additional breadcrumb tag to the existing breadcrumb
264 *
265 * @return void
266 * @access public
267 */
268 function resetBreadCrumb() {
269 \Drupal::service('civicrm.page_state')->resetBreadcrumbs();
270 }
271
272 /**
273 * Append a string to the head of the html file
274 *
275 * @param string $header the new string to be appended
276 *
277 * @return void
278 * @access public
279 */
280 function addHTMLHead($header) {
281 \Drupal::service('civicrm.page_state')->addHtmlHeader($header);
282 }
283
284 /**
285 * Add a script file
286 *
287 * @param $url: string, absolute path to file
288 * @param $region string, location within the document: 'html-header', 'page-header', 'page-footer'
289 *
290 * Note: This function is not to be called directly
291 * @see CRM_Core_Region::render()
292 *
293 * @return bool TRUE if we support this operation in this CMS, FALSE otherwise
294 * @access public
295 */
296 public function addScriptUrl($url, $region) {
297 $options = array('group' => JS_LIBRARY, 'weight' => 10);
298 switch ($region) {
299 case 'html-header':
300 case 'page-footer':
301 $options['scope'] = substr($region, 5);
302 break;
303 default:
304 return FALSE;
305 }
306 // If the path is within the drupal directory we can use the more efficient 'file' setting
307 $options['type'] = $this->formatResourceUrl($url) ? 'file' : 'external';
308 \Drupal::service('civicrm.page_state')->addJS($url, $options);
309 return TRUE;
310 }
311
312 /**
313 * Add an inline script
314 *
315 * @param $code: string, javascript code
316 * @param $region string, location within the document: 'html-header', 'page-header', 'page-footer'
317 *
318 * Note: This function is not to be called directly
319 * @see CRM_Core_Region::render()
320 *
321 * @return bool TRUE if we support this operation in this CMS, FALSE otherwise
322 * @access public
323 */
324 public function addScript($code, $region) {
325 $options = array('type' => 'inline', 'group' => JS_LIBRARY, 'weight' => 10);
326 switch ($region) {
327 case 'html-header':
328 case 'page-footer':
329 $options['scope'] = substr($region, 5);
330 break;
331 default:
332 return FALSE;
333 }
334 \Drupal::service('civicrm.page_state')->addJS($code, $options);
335 return TRUE;
336 }
337
338 /**
339 * Add a css file
340 *
341 * @param $url: string, absolute path to file
342 * @param $region string, location within the document: 'html-header', 'page-header', 'page-footer'
343 *
344 * Note: This function is not to be called directly
345 * @see CRM_Core_Region::render()
346 *
347 * @return bool TRUE if we support this operation in this CMS, FALSE otherwise
348 * @access public
349 */
350 public function addStyleUrl($url, $region) {
351 if ($region != 'html-header') {
352 return FALSE;
d3e88312 353 }
7e9cadcf
EM
354 $options = array();
355 // If the path is within the drupal directory we can use the more efficient 'file' setting
356 $options['type'] = $this->formatResourceUrl($url) ? 'file' : 'external';
357 \Drupal::service('civicrm.page_state')->addCSS($url, $options);
358 return TRUE;
d3e88312
EM
359 }
360
7e9cadcf
EM
361 /**
362 * Add an inline style
363 *
364 * @param $code: string, css code
365 * @param $region string, location within the document: 'html-header', 'page-header', 'page-footer'
366 *
367 * Note: This function is not to be called directly
368 * @see CRM_Core_Region::render()
369 *
370 * @return bool TRUE if we support this operation in this CMS, FALSE otherwise
371 * @access public
372 */
373 public function addStyle($code, $region) {
374 if ($region != 'html-header') {
375 return FALSE;
376 }
377 $options = array('type' => 'inline');
378 \Drupal::service('civicrm.page_state')->addCSS($code, $options);
379 return TRUE;
380 }
381
382 /**
383 * Check if a resource url is within the drupal directory and format appropriately
384 *
385 * This seems to be a legacy function. We assume all resources are within the drupal
386 * directory and always return TRUE. As well, we clean up the $url.
387 *
388 * @param $url
389 *
390 * @return bool
391 */
392 function formatResourceUrl(&$url) {
393 // Remove leading slash if present.
394 $url = ltrim($url, '/');
395
396 // Remove query string — presumably added to stop intermediary caching.
397 if (($pos = strpos($url, '?')) !== FALSE) {
398 $url = substr($url, 0, $pos);
399 }
400
401 return TRUE;
402 }
403
404 /**
405 * Rewrite various system urls to https
406 *
407 * This function does nothing in Drupal 8. Changes to the base_url should be made
408 * in settings.php directly.
409 *
410 * @param null
411 *
412 * @return void
413 * @access public
414 */
415 function mapConfigToSSL() {
416 }
417
418 /**
419 * @param string $path The base path (eg. civicrm/search/contact)
420 * @param string $query The query string (eg. reset=1&cid=66) but html encoded(?) (optional)
421 * @param bool $absolute Produce an absolute including domain and protocol (optional)
422 * @param string $fragment A named anchor (optional)
423 * @param bool $htmlize Produce a html encoded url (optional)
424 * @param bool $frontend A joomla hack (unused)
425 * @param bool $forceBackend A joomla jack (unused)
426 * @return string
427 */
428 function url($path = '', $query = '', $absolute = FALSE, $fragment = '', $htmlize = FALSE, $frontend = FALSE, $forceBackend = FALSE) {
429 $query = html_entity_decode($query);
430 $url = \Drupal\civicrm\CivicrmHelper::parseURL("{$path}?{$query}");
431
432 try {
433 $url = \Drupal::url($url['route_name'], array(), array(
434 'query' => $url['query'],
435 'absolute' => $absolute,
436 'fragment' => $fragment,
437 ));
438 }
439 catch (Exception $e) {
440 $url = '';
441 }
442
443 if ($htmlize) {
444 $url = htmlentities($url);
445 }
446 return $url;
447 }
448
449
450 /**
451 * Authenticate the user against the drupal db
452 *
453 * @param string $name the user name
454 * @param string $password the password for the above user name
455 * @param boolean $loadCMSBootstrap load cms bootstrap?
456 * @param NULL|string $realPath filename of script
457 *
458 * @return mixed false if no auth
459 * array(
460 * contactID, ufID, unique string ) if success
461 * @access public
462 *
463 * This always bootstraps Drupal
464 */
465 function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realPath = NULL) {
466 (new CRM_Utils_System_Drupal8())->loadBootStrap(array(), FALSE);
467
468 $uid = \Drupal::service('user.auth')->authenticate($name, $password);
469 $contact_id = CRM_Core_BAO_UFMatch::getContactId($uid);
470
471 return array($contact_id, $uid, mt_rand());
472 }
473
474 /**
475 * Load user into session
476 */
477 function loadUser($username) {
478 $user = user_load_by_name($username);
479 if (!$user) {
480 return FALSE;
481 }
482
483 // Set Drupal's current user to the loaded user.
484 \Drupal::currentUser()->setAccount($user);
485
486 $uid = $user->id();
487 $contact_id = CRM_Core_BAO_UFMatch::getContactId($uid);
488
489 // Store the contact id and user id in the session
490 $session = CRM_Core_Session::singleton();
491 $session->set('ufID', $uid);
492 $session->set('userID', $contact_id);
493 return TRUE;
494 }
495
496 /**
497 * Determine the native ID of the CMS user
498 *
100fef9d 499 * @param string $username
7e9cadcf
EM
500 * @return int|NULL
501 */
502 function getUfId($username) {
503 if ($id = user_load_by_name($username)->id()) {
504 return $id;
505 }
506 }
507
508 /**
509 * Set a message in the UF to display to a user
510 *
511 * @param string $message the message to set
512 *
513 * @access public
514 */
515 function setMessage($message) {
516 drupal_set_message($message);
517 }
518
519 function permissionDenied() {
520 \Drupal::service('civicrm.page_state')->setAccessDenied();
521 }
522
523 /**
524 * In previous versions, this function was the controller for logging out. In Drupal 8, we rewrite the route
525 * to hand off logout to the standard Drupal logout controller. This function should therefore never be called.
526 */
527 function logout() {
528 // Pass
529 }
530
531 /**
100fef9d 532 * Load drupal bootstrap
7e9cadcf
EM
533 *
534 * @param array $params Either uid, or name & pass.
535 * @param boolean $loadUser boolean Require CMS user load.
536 * @param boolean $throwError If true, print error on failure and exit.
537 * @param boolean|string $realPath path to script
538 *
539 * @return bool
540 * @Todo Handle setting cleanurls configuration for CiviCRM?
541 */
542 function loadBootStrap($params = array(), $loadUser = TRUE, $throwError = TRUE, $realPath = NULL) {
543 static $run_once = FALSE;
544 if ($run_once) return TRUE; else $run_once = TRUE;
545
546 if (!($root = $this->cmsRootPath())) {
547 return FALSE;
548 }
549 chdir($root);
550
551 // Create a mock $request object
552 $autoloader = require_once $root . '/core/vendor/autoload.php';
553 // @Todo: do we need to handle case where $_SERVER has no HTTP_HOST key, ie. when run via cli?
554 $request = new \Symfony\Component\HttpFoundation\Request(array(), array(), array(), array(), array(), $_SERVER);
555
556 // Create a kernel and boot it.
557 \Drupal\Core\DrupalKernel::createFromRequest($request, $autoloader, 'prod')->prepareLegacyRequest($request);
558
559 // Initialize Civicrm
560 \Drupal::service('civicrm');
561
562 // We need to call the config hook again, since we now know
563 // all the modules that are listening on it (CRM-8655).
564 CRM_Utils_Hook::config($config);
565
566 if ($loadUser) {
567 if (!empty($params['uid']) && $username = \Drupal\user\Entity\User::load($uid)->getUsername()) {
568 $this->loadUser($username);
569 }
570 elseif (!empty($params['name']) && !empty($params['pass']) && $this->authenticate($params['name'], $params['pass'])) {
571 $this->loadUser($params['name']);
572 }
573 }
574 return TRUE;
575 }
576
577 /**
578 * Determine the location of the CMS root.
579 * @param null $path
580 *
581 * @return NULL|string
582 */
583 function cmsRootPath($path = NULL) {
584 if (defined('DRUPAL_ROOT')) {
585 return DRUPAL_ROOT;
586 }
587
588 // It looks like Drupal hasn't been bootstrapped.
589 // We're going to attempt to discover the root Drupal path
590 // by climbing out of the folder hierarchy and looking around to see
591 // if we've found the Drupal root directory.
592 if (!$path) {
593 $path = $_SERVER['SCRIPT_FILENAME'];
594 }
595
596 // Normalize and explode path into its component paths.
597 $paths = explode(DIRECTORY_SEPARATOR, realpath($path));
598
599 // Remove script filename from array of directories.
600 array_pop($paths);
601
602 while (count($paths)) {
603 $candidate = implode('/', $paths);
604 if (file_exists($candidate . "/core/includes/bootstrap.inc")) {
605 return $candidate;
606 }
607
608 array_pop($paths);
609 }
610 }
611
612 /**
613 * Check if user is logged in.
614 *
615 * @return bool
616 */
617 public function isUserLoggedIn() {
618 return \Drupal::currentUser()->isAuthenticated();
619 }
620
621 /**
622 * Get currently logged in user uf id.
623 *
624 * @return int $userID logged in user uf id.
625 */
626 public function getLoggedInUfID() {
627 if ($id = \Drupal::currentUser()->id()) {
628 return $id;
629 }
630 }
624142d4
EM
631
632 /**
633 * Get the default location for CiviCRM blocks
634 *
635 * @return string
636 *
637 */
638 function getDefaultBlockLocation() {
639 return 'sidebar_first';
640 }
7e9cadcf 641}