minor fix
[civicrm-core.git] / CRM / Utils / System / WordPress.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2015 |
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
31 * @copyright CiviCRM LLC (c) 2004-2015
32 * $Id$
33 *
34 */
35
36 /**
37 * WordPress specific stuff goes here
38 */
39 class CRM_Utils_System_WordPress extends CRM_Utils_System_Base {
40 /**
41 */
42 public function __construct() {
43 /**
44 * deprecated property to check if this is a drupal install. The correct method is to have functions on the UF classes for all UF specific
45 * functions and leave the codebase oblivious to the type of CMS
46 * @deprecated
47 * @var bool
48 */
49 $this->is_drupal = FALSE;
50 $this->is_wordpress = TRUE;
51 }
52
53 /**
54 * @inheritDoc
55 */
56 public function setTitle($title, $pageTitle = NULL) {
57 if (!$pageTitle) {
58 $pageTitle = $title;
59 }
60
61 // FIXME: Why is this global?
62 global $civicrm_wp_title;
63 $civicrm_wp_title = $title;
64
65 // yes, set page title, depending on context
66 $context = civi_wp()->civicrm_context_get();
67 switch ($context) {
68 case 'admin':
69 case 'shortcode':
70 $template = CRM_Core_Smarty::singleton();
71 $template->assign('pageTitle', $pageTitle);
72 }
73 }
74
75 /**
76 * Moved from CRM_Utils_System_Base
77 */
78 public function getDefaultFileStorage() {
79 $config = CRM_Core_Config::singleton();
80 $cmsUrl = CRM_Utils_System::languageNegotiationURL($config->userFrameworkBaseURL, FALSE, TRUE);
81 $cmsPath = $this->cmsRootPath();
82 $filesPath = CRM_Utils_File::baseFilePath();
83 $filesRelPath = CRM_Utils_File::relativize($filesPath, $cmsPath);
84 $filesURL = rtrim($cmsUrl, '/') . '/' . ltrim($filesRelPath, ' /');
85 return array(
86 'url' => CRM_Utils_File::addTrailingSlash($filesURL, '/'),
87 'path' => CRM_Utils_File::addTrailingSlash($filesPath),
88 );
89 }
90
91 /**
92 * Determine the location of the CiviCRM source tree.
93 *
94 * @return array
95 * - url: string. ex: "http://example.com/sites/all/modules/civicrm"
96 * - path: string. ex: "/var/www/sites/all/modules/civicrm"
97 */
98 public function getCiviSourceStorage() {
99 global $civicrm_root;
100
101 // Don't use $config->userFrameworkBaseURL; it has garbage on it.
102 // More generally, we shouldn't be using $config here.
103 if (!defined('CIVICRM_UF_BASEURL')) {
104 throw new RuntimeException('Undefined constant: CIVICRM_UF_BASEURL');
105 }
106
107 $cmsPath = $this->cmsRootPath();
108
109 // $config = CRM_Core_Config::singleton();
110 // overkill? // $cmsUrl = CRM_Utils_System::languageNegotiationURL($config->userFrameworkBaseURL, FALSE, TRUE);
111 $cmsUrl = CIVICRM_UF_BASEURL;
112 if (CRM_Utils_System::isSSL()) {
113 $cmsUrl = str_replace('http://', 'https://', $cmsUrl);
114 }
115
116 $civiRelPath = CRM_Utils_File::relativize($civicrm_root, $cmsPath);
117 $civiUrl = rtrim($cmsUrl, '/') . '/' . ltrim($civiRelPath, ' /');
118 return array(
119 'url' => CRM_Utils_File::addTrailingSlash($civiUrl, '/'),
120 'path' => CRM_Utils_File::addTrailingSlash($civicrm_root),
121 );
122 }
123
124 /**
125 * @inheritDoc
126 */
127 public function appendBreadCrumb($breadCrumbs) {
128 $breadCrumb = wp_get_breadcrumb();
129
130 if (is_array($breadCrumbs)) {
131 foreach ($breadCrumbs as $crumbs) {
132 if (stripos($crumbs['url'], 'id%%')) {
133 $args = array('cid', 'mid');
134 foreach ($args as $a) {
135 $val = CRM_Utils_Request::retrieve($a, 'Positive', CRM_Core_DAO::$_nullObject,
136 FALSE, NULL, $_GET
137 );
138 if ($val) {
139 $crumbs['url'] = str_ireplace("%%{$a}%%", $val, $crumbs['url']);
140 }
141 }
142 }
143 $breadCrumb[] = "<a href=\"{$crumbs['url']}\">{$crumbs['title']}</a>";
144 }
145 }
146
147 $template = CRM_Core_Smarty::singleton();
148 $template->assign_by_ref('breadcrumb', $breadCrumb);
149 wp_set_breadcrumb($breadCrumb);
150 }
151
152 /**
153 * @inheritDoc
154 */
155 public function resetBreadCrumb() {
156 $bc = array();
157 wp_set_breadcrumb($bc);
158 }
159
160 /**
161 * @inheritDoc
162 */
163 public function addHTMLHead($head) {
164 static $registered = FALSE;
165 if (!$registered) {
166 // front-end view
167 add_action('wp_head', array(__CLASS__, '_showHTMLHead'));
168 // back-end views
169 add_action('admin_head', array(__CLASS__, '_showHTMLHead'));
170 }
171 CRM_Core_Region::instance('wp_head')->add(array(
172 'markup' => $head,
173 ));
174 }
175
176 /**
177 * WP action callback.
178 */
179 public static function _showHTMLHead() {
180 $region = CRM_Core_Region::instance('wp_head', FALSE);
181 if ($region) {
182 echo $region->render('');
183 }
184 }
185
186 /**
187 * @inheritDoc
188 */
189 public function mapConfigToSSL() {
190 global $base_url;
191 $base_url = str_replace('http://', 'https://', $base_url);
192 }
193
194 /**
195 * @inheritDoc
196 */
197 public function url(
198 $path = NULL,
199 $query = NULL,
200 $absolute = FALSE,
201 $fragment = NULL,
202 $frontend = FALSE,
203 $forceBackend = FALSE
204 ) {
205 $config = CRM_Core_Config::singleton();
206 $script = '';
207 $separator = '&';
208 $wpPageParam = '';
209 $fragment = isset($fragment) ? ('#' . $fragment) : '';
210
211 $path = CRM_Utils_String::stripPathChars($path);
212
213 //this means wp function we are trying to use is not available,
214 //so load bootStrap
215 if (!function_exists('get_option')) {
216 $this->loadBootStrap(); // FIXME: Why bootstrap in url()? Generally want to define 1-2 strategic places to put bootstrap
217 }
218 if ($config->userFrameworkFrontend) {
219 if (get_option('permalink_structure') != '') {
220 global $post;
221 $script = get_permalink($post->ID);
222 }
223
224 // when shortcode is included in page
225 // also make sure we have valid query object
226 global $wp_query;
227 if (method_exists($wp_query, 'get')) {
228 if (get_query_var('page_id')) {
229 $wpPageParam = "page_id=" . get_query_var('page_id');
230 }
231 elseif (get_query_var('p')) {
232 // when shortcode is inserted in post
233 $wpPageParam = "p=" . get_query_var('p');
234 }
235 }
236 }
237
238 $base = $this->getBaseUrl($absolute, $frontend, $forceBackend);
239
240 if (!isset($path) && !isset($query)) {
241 // FIXME: This short-circuited codepath is the same as the general one below, except
242 // in that it ignores "permlink_structure" / $wpPageParam / $script . I don't know
243 // why it's different (and I can only find two obvious use-cases for this codepath,
244 // of which at least one looks gratuitous). A more ambitious person would simply remove
245 // this code.
246 return $base . $fragment;
247 }
248
249 if (!$forceBackend && get_option('permalink_structure') != '' && ($wpPageParam || $script != '')) {
250 $base = $script;
251 }
252
253 $queryParts = array();
254 if (isset($path)) {
255 $queryParts[] = 'page=CiviCRM';
256 $queryParts[] = "q={$path}";
257 }
258 if ($wpPageParam) {
259 $queryParts[] = $wpPageParam;
260 }
261 if (isset($query)) {
262 $queryParts[] = $query;
263 }
264
265 return $base . '?' . implode($separator, $queryParts) . $fragment;
266 }
267
268 /**
269 * @param $absolute
270 * @param $frontend
271 * @param $forceBackend
272 *
273 * @return mixed|null|string
274 */
275 private function getBaseUrl($absolute, $frontend, $forceBackend) {
276 $config = CRM_Core_Config::singleton();
277
278 $base = $absolute ? $config->userFrameworkBaseURL : $config->useFrameworkRelativeBase;
279
280 if ((is_admin() && !$frontend) || $forceBackend) {
281 $base .= 'wp-admin/admin.php';
282 return $base;
283 }
284 elseif (defined('CIVICRM_UF_WP_BASEPAGE')) {
285 $base .= CIVICRM_UF_WP_BASEPAGE;
286 return $base;
287 }
288 elseif (isset($config->wpBasePage)) {
289 $base .= $config->wpBasePage;
290 return $base;
291 }
292 return $base;
293 }
294
295 /**
296 * @inheritDoc
297 */
298 public function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realPath = NULL) {
299 $config = CRM_Core_Config::singleton();
300
301 if ($loadCMSBootstrap) {
302 $config->userSystem->loadBootStrap($name, $password);
303 }
304
305 $user = wp_authenticate($name, $password);
306 if (is_a($user, 'WP_Error')) {
307 return FALSE;
308 }
309
310 // TODO: need to change this to make sure we matched only one row
311
312 CRM_Core_BAO_UFMatch::synchronizeUFMatch($user->data, $user->data->ID, $user->data->user_email, 'WordPress');
313 $contactID = CRM_Core_BAO_UFMatch::getContactId($user->data->ID);
314 if (!$contactID) {
315 return FALSE;
316 }
317 return array($contactID, $user->data->ID, mt_rand());
318 }
319
320 /**
321 * FIXME: Do something
322 *
323 * @param string $message
324 */
325 public function setMessage($message) {
326 }
327
328 /**
329 * @param \string $user
330 *
331 * @return bool
332 */
333 public function loadUser($user) {
334 $userdata = get_user_by('login', $user);
335 if (!$userdata->data->ID) {
336 return FALSE;
337 }
338
339 $uid = $userdata->data->ID;
340 wp_set_current_user($uid);
341 $contactID = CRM_Core_BAO_UFMatch::getContactId($uid);
342
343 // lets store contact id and user id in session
344 $session = CRM_Core_Session::singleton();
345 $session->set('ufID', $uid);
346 $session->set('userID', $contactID);
347 return TRUE;
348 }
349
350 /**
351 * FIXME: Use CMS-native approach
352 */
353 public function permissionDenied() {
354 CRM_Core_Error::fatal(ts('You do not have permission to access this page.'));
355 }
356
357 /**
358 * @inheritDoc
359 */
360 public function logout() {
361 // destroy session
362 if (session_id()) {
363 session_destroy();
364 }
365 wp_logout();
366 wp_redirect(wp_login_url());
367 }
368
369 /**
370 * @inheritDoc
371 */
372 public function getUFLocale() {
373 // WPML plugin
374 if (defined('ICL_LANGUAGE_CODE')) {
375 $language = ICL_LANGUAGE_CODE;
376 }
377
378 // TODO: set language variable for others WordPress plugin
379
380 if (isset($language)) {
381 return CRM_Core_I18n_PseudoConstant::longForShort(substr($language, 0, 2));
382 }
383 else {
384 return NULL;
385 }
386 }
387
388 /**
389 * @inheritDoc
390 */
391 public function setUFLocale($civicrm_language) {
392 // TODO (probably not possible with WPML?)
393 return TRUE;
394 }
395
396 /**
397 * Load wordpress bootstrap.
398 *
399 * @param string $name
400 * optional username for login.
401 * @param string $pass
402 * optional password for login.
403 *
404 * @return bool
405 */
406 public function loadBootStrap($name = NULL, $pass = NULL) {
407 global $wp, $wp_rewrite, $wp_the_query, $wp_query, $wpdb, $current_site, $current_blog, $current_user;
408
409 if (!defined('WP_USE_THEMES')) {
410 define('WP_USE_THEMES', FALSE);
411 }
412
413 $cmsRootPath = $this->cmsRootPath();
414 if (!$cmsRootPath) {
415 CRM_Core_Error::fatal("Could not find the install directory for WordPress");
416 }
417 $path = Civi::settings()->get('wpLoadPhp');
418 if (!empty($path)) {
419 require_once $path;
420 }
421 elseif (file_exists($cmsRootPath . DIRECTORY_SEPARATOR . 'wp-load.php')) {
422 require_once $cmsRootPath . DIRECTORY_SEPARATOR . 'wp-load.php';
423 }
424 else {
425 CRM_Core_Error::fatal("Could not find the bootstrap file for WordPress");
426 }
427 $wpUserTimezone = get_option('timezone_string');
428 if ($wpUserTimezone) {
429 date_default_timezone_set($wpUserTimezone);
430 CRM_Core_Config::singleton()->userSystem->setMySQLTimeZone();
431 }
432 require_once $cmsRootPath . DIRECTORY_SEPARATOR . 'wp-includes/pluggable.php';
433 $uid = CRM_Utils_Array::value('uid', $name);
434 if (!$uid) {
435 $name = $name ? $name : trim(CRM_Utils_Array::value('name', $_REQUEST));
436 $pass = $pass ? $pass : trim(CRM_Utils_Array::value('pass', $_REQUEST));
437 if ($name) {
438 $uid = wp_authenticate($name, $pass); // this returns a WP_User object if successful
439 if (!$uid) {
440 if ($throwError) {
441 echo '<br />Sorry, unrecognized username or password.';
442 exit();
443 }
444 return FALSE;
445 }
446 }
447 }
448 if ($uid) {
449 if ($uid instanceof WP_User) {
450 $account = wp_set_current_user($uid->ID);
451 }
452 else {
453 $account = wp_set_current_user($uid);
454 }
455 if ($account && $account->data->ID) {
456 global $user;
457 $user = $account;
458 return TRUE;
459 }
460 }
461 return TRUE;
462 }
463
464 /**
465 * @param $dir
466 *
467 * @return bool
468 */
469 public function validInstallDir($dir) {
470 $includePath = "$dir/wp-includes";
471 if (file_exists("$includePath/version.php")) {
472 return TRUE;
473 }
474 return FALSE;
475 }
476
477 /**
478 * Determine the location of the CMS root.
479 *
480 * @return string|NULL
481 * local file system path to CMS root, or NULL if it cannot be determined
482 */
483 public function cmsRootPath() {
484 $cmsRoot = $valid = NULL;
485 if (defined('CIVICRM_CMSDIR')) {
486 if ($this->validInstallDir(CIVICRM_CMSDIR)) {
487 $cmsRoot = CIVICRM_CMSDIR;
488 $valid = TRUE;
489 }
490 }
491 else {
492 $pathVars = explode('/', str_replace('\\', '/', $_SERVER['SCRIPT_FILENAME']));
493
494 //might be windows installation.
495 $firstVar = array_shift($pathVars);
496 if ($firstVar) {
497 $cmsRoot = $firstVar;
498 }
499
500 //start w/ csm dir search.
501 foreach ($pathVars as $var) {
502 $cmsRoot .= "/$var";
503 if ($this->validInstallDir($cmsRoot)) {
504 //stop as we found bootstrap.
505 $valid = TRUE;
506 break;
507 }
508 }
509 }
510
511 return ($valid) ? $cmsRoot : NULL;
512 }
513
514 /**
515 * @inheritDoc
516 */
517 public function createUser(&$params, $mail) {
518 $user_data = array(
519 'ID' => '',
520 'user_pass' => $params['cms_pass'],
521 'user_login' => $params['cms_name'],
522 'user_email' => $params[$mail],
523 'nickname' => $params['cms_name'],
524 'role' => get_option('default_role'),
525 );
526 if (isset($params['contactID'])) {
527 $contactType = CRM_Contact_BAO_Contact::getContactType($params['contactID']);
528 if ($contactType == 'Individual') {
529 $user_data['first_name'] = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact',
530 $params['contactID'], 'first_name'
531 );
532 $user_data['last_name'] = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact',
533 $params['contactID'], 'last_name'
534 );
535 }
536 }
537
538 $uid = wp_insert_user($user_data);
539
540 $creds = array();
541 $creds['user_login'] = $params['cms_name'];
542 $creds['user_password'] = $params['cms_pass'];
543 $creds['remember'] = TRUE;
544 $user = wp_signon($creds, FALSE);
545
546 wp_new_user_notification($uid, $user_data['user_pass']);
547 return $uid;
548 }
549
550 /**
551 * @inheritDoc
552 */
553 public function updateCMSName($ufID, $ufName) {
554 // CRM-10620
555 if (function_exists('wp_update_user')) {
556 $ufID = CRM_Utils_Type::escape($ufID, 'Integer');
557 $ufName = CRM_Utils_Type::escape($ufName, 'String');
558
559 $values = array('ID' => $ufID, 'user_email' => $ufName);
560 if ($ufID) {
561 wp_update_user($values);
562 }
563 }
564 }
565
566 /**
567 * @param array $params
568 * @param $errors
569 * @param string $emailName
570 */
571 public function checkUserNameEmailExists(&$params, &$errors, $emailName = 'email') {
572 $config = CRM_Core_Config::singleton();
573
574 $dao = new CRM_Core_DAO();
575 $name = $dao->escape(CRM_Utils_Array::value('name', $params));
576 $email = $dao->escape(CRM_Utils_Array::value('mail', $params));
577
578 if (!empty($params['name'])) {
579 if (!validate_username($params['name'])) {
580 $errors['cms_name'] = ts("Your username contains invalid characters");
581 }
582 elseif (username_exists(sanitize_user($params['name']))) {
583 $errors['cms_name'] = ts('The username %1 is already taken. Please select another username.', array(1 => $params['name']));
584 }
585 }
586
587 if (!empty($params['mail'])) {
588 if (!is_email($params['mail'])) {
589 $errors[$emailName] = "Your email is invaid";
590 }
591 elseif (email_exists($params['mail'])) {
592 $errors[$emailName] = ts('The email address %1 already has an account associated with it. <a href="%2">Have you forgotten your password?</a>',
593 array(1 => $params['mail'], 2 => wp_lostpassword_url())
594 );
595 }
596 }
597 }
598
599 /**
600 * @inheritDoc
601 */
602 public function isUserLoggedIn() {
603 $isloggedIn = FALSE;
604 if (function_exists('is_user_logged_in')) {
605 $isloggedIn = is_user_logged_in();
606 }
607
608 return $isloggedIn;
609 }
610
611 /**
612 * @return mixed
613 */
614 public function getLoggedInUserObject() {
615 if (function_exists('is_user_logged_in') &&
616 is_user_logged_in()
617 ) {
618 global $current_user;
619 }
620 return $current_user;
621 }
622
623 /**
624 * @inheritDoc
625 */
626 public function getLoggedInUfID() {
627 $ufID = NULL;
628 $current_user = $this->getLoggedInUserObject();
629 return isset($current_user->ID) ? $current_user->ID : NULL;
630 }
631
632 /**
633 * @inheritDoc
634 */
635 public function getLoggedInUniqueIdentifier() {
636 $user = $this->getLoggedInUserObject();
637 return $this->getUniqueIdentifierFromUserObject($user);
638 }
639
640 /**
641 * Get User ID from UserFramework system (Joomla)
642 * @param object $user
643 * Object as described by the CMS.
644 *
645 * @return int|null
646 */
647 public function getUserIDFromUserObject($user) {
648 return !empty($user->ID) ? $user->ID : NULL;
649 }
650
651 /**
652 * @inheritDoc
653 */
654 public function getUniqueIdentifierFromUserObject($user) {
655 return empty($user->user_email) ? NULL : $user->user_email;
656 }
657
658 /**
659 * @inheritDoc
660 */
661 public function getLoginURL($destination = '') {
662 $config = CRM_Core_Config::singleton();
663 $loginURL = wp_login_url();
664 return $loginURL;
665 }
666
667 /**
668 * FIXME: Do something.
669 *
670 * @param \CRM_Core_Form $form
671 *
672 * @return NULL|string
673 */
674 public function getLoginDestination(&$form) {
675 return NULL;
676 }
677
678 /**
679 * @inheritDoc
680 */
681 public function getVersion() {
682 if (function_exists('get_bloginfo')) {
683 return get_bloginfo('version', 'display');
684 }
685 else {
686 return 'Unknown';
687 }
688 }
689
690 /**
691 * @inheritDoc
692 */
693 public function getTimeZoneString() {
694 return get_option('timezone_string');
695 }
696
697 /**
698 * @inheritDoc
699 */
700 public function getUserRecordUrl($contactID) {
701 $uid = CRM_Core_BAO_UFMatch::getUFId($contactID);
702 if (CRM_Core_Session::singleton()
703 ->get('userID') == $contactID || CRM_Core_Permission::checkAnyPerm(array('cms:administer users'))
704 ) {
705 return CRM_Core_Config::singleton()->userFrameworkBaseURL . "wp-admin/user-edit.php?user_id=" . $uid;
706 }
707 }
708
709 /**
710 * Append WP js to coreResourcesList.
711 *
712 * @param array $list
713 */
714 public function appendCoreResources(&$list) {
715 $list[] = 'js/crm.wordpress.js';
716 }
717
718 /**
719 * @inheritDoc
720 */
721 public function synchronizeUsers() {
722 $config = CRM_Core_Config::singleton();
723 if (PHP_SAPI != 'cli') {
724 set_time_limit(300);
725 }
726 $id = 'ID';
727 $mail = 'user_email';
728
729 $uf = $config->userFramework;
730 $contactCount = 0;
731 $contactCreated = 0;
732 $contactMatching = 0;
733
734 global $wpdb;
735 $wpUserIds = $wpdb->get_col("SELECT $wpdb->users.ID FROM $wpdb->users");
736
737 foreach ($wpUserIds as $wpUserId) {
738 $wpUserData = get_userdata($wpUserId);
739 $contactCount++;
740 if ($match = CRM_Core_BAO_UFMatch::synchronizeUFMatch($wpUserData,
741 $wpUserData->$id,
742 $wpUserData->$mail,
743 $uf,
744 1,
745 'Individual',
746 TRUE
747 )
748 ) {
749 $contactCreated++;
750 }
751 else {
752 $contactMatching++;
753 }
754 if (is_object($match)) {
755 $match->free();
756 }
757 }
758
759 return array(
760 'contactCount' => $contactCount,
761 'contactMatching' => $contactMatching,
762 'contactCreated' => $contactCreated,
763 );
764 }
765
766 }