Merge pull request #17868 from seamuslee001/remove_civicrm_files_override
[civicrm-core.git] / CRM / Utils / System / WordPress.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 * $Id$
17 *
18 */
19
20 /**
21 * WordPress specific stuff goes here
22 */
23 class CRM_Utils_System_WordPress extends CRM_Utils_System_Base {
24
25 /**
26 */
27 public function __construct() {
28 /**
29 * 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
30 * functions and leave the codebase oblivious to the type of CMS
31 * @deprecated
32 * @var bool
33 */
34 $this->is_drupal = FALSE;
35 $this->is_wordpress = TRUE;
36 }
37
38 public function initialize() {
39 parent::initialize();
40 $this->registerPathVars();
41 }
42
43 /**
44 * Specify the default computation for various paths/URLs.
45 */
46 protected function registerPathVars():void {
47 $isNormalBoot = function_exists('get_option');
48 if ($isNormalBoot) {
49 // Normal mode - CMS boots first, then calls Civi. "Normal" web pages and newer extern routes.
50 // To simplify the code-paths, some items are re-registered with WP-specific functions.
51 $cmsRoot = function() {
52 return [
53 'path' => untrailingslashit(ABSPATH),
54 'url' => home_url(),
55 ];
56 };
57 Civi::paths()->register('cms', $cmsRoot);
58 Civi::paths()->register('cms.root', $cmsRoot);
59 Civi::paths()->register('civicrm.root', function () {
60 return [
61 'path' => CIVICRM_PLUGIN_DIR . 'civicrm' . DIRECTORY_SEPARATOR,
62 'url' => CIVICRM_PLUGIN_URL . 'civicrm/',
63 ];
64 });
65 Civi::paths()->register('wp.frontend.base', function () {
66 return [
67 'url' => home_url('/'),
68 ];
69 });
70 Civi::paths()->register('wp.frontend', function () {
71 $config = CRM_Core_Config::singleton();
72 $basepage = get_page_by_path($config->wpBasePage);
73 return [
74 'url' => get_permalink($basepage->ID),
75 ];
76 });
77 Civi::paths()->register('wp.backend.base', function () {
78 return [
79 'url' => admin_url(),
80 ];
81 });
82 Civi::paths()->register('wp.backend', function() {
83 return [
84 'url' => admin_url('admin.php'),
85 ];
86 });
87 }
88 else {
89 // Legacy support - only relevant for older extern routes.
90 Civi::paths()
91 ->register('wp.frontend.base', function () {
92 return ['url' => rtrim(CIVICRM_UF_BASEURL, '/') . '/'];
93 })
94 ->register('wp.frontend', function () {
95 $config = \CRM_Core_Config::singleton();
96 $suffix = defined('CIVICRM_UF_WP_BASEPAGE') ? CIVICRM_UF_WP_BASEPAGE : $config->wpBasePage;
97 return [
98 'url' => Civi::paths()->getVariable('wp.frontend.base', 'url') . $suffix,
99 ];
100 })
101 ->register('wp.backend.base', function () {
102 return ['url' => rtrim(CIVICRM_UF_BASEURL, '/') . '/wp-admin/'];
103 })
104 ->register('wp.backend', function () {
105 return [
106 'url' => Civi::paths()->getVariable('wp.backend.base', 'url') . 'admin.php',
107 ];
108 });
109 }
110 }
111
112 /**
113 * @inheritDoc
114 */
115 public function setTitle($title, $pageTitle = NULL) {
116 if (!$pageTitle) {
117 $pageTitle = $title;
118 }
119
120 // FIXME: Why is this global?
121 global $civicrm_wp_title;
122 $civicrm_wp_title = $title;
123
124 // yes, set page title, depending on context
125 $context = civi_wp()->civicrm_context_get();
126 switch ($context) {
127 case 'admin':
128 case 'shortcode':
129 $template = CRM_Core_Smarty::singleton();
130 $template->assign('pageTitle', $pageTitle);
131 }
132 }
133
134 /**
135 * Moved from CRM_Utils_System_Base
136 */
137 public function getDefaultFileStorage() {
138 $config = CRM_Core_Config::singleton();
139 $cmsUrl = CRM_Utils_System::languageNegotiationURL($config->userFrameworkBaseURL, FALSE, TRUE);
140 $cmsPath = $this->cmsRootPath();
141 $filesPath = CRM_Utils_File::baseFilePath();
142 $filesRelPath = CRM_Utils_File::relativize($filesPath, $cmsPath);
143 $filesURL = rtrim($cmsUrl, '/') . '/' . ltrim($filesRelPath, ' /');
144 return [
145 'url' => CRM_Utils_File::addTrailingSlash($filesURL, '/'),
146 'path' => CRM_Utils_File::addTrailingSlash($filesPath),
147 ];
148 }
149
150 /**
151 * Determine the location of the CiviCRM source tree.
152 *
153 * @return array
154 * - url: string. ex: "http://example.com/sites/all/modules/civicrm"
155 * - path: string. ex: "/var/www/sites/all/modules/civicrm"
156 */
157 public function getCiviSourceStorage() {
158 global $civicrm_root;
159
160 // Don't use $config->userFrameworkBaseURL; it has garbage on it.
161 // More generally, we shouldn't be using $config here.
162 if (!defined('CIVICRM_UF_BASEURL')) {
163 throw new RuntimeException('Undefined constant: CIVICRM_UF_BASEURL');
164 }
165
166 $cmsPath = $this->cmsRootPath();
167
168 // $config = CRM_Core_Config::singleton();
169 // overkill? // $cmsUrl = CRM_Utils_System::languageNegotiationURL($config->userFrameworkBaseURL, FALSE, TRUE);
170 $cmsUrl = CIVICRM_UF_BASEURL;
171 if (CRM_Utils_System::isSSL()) {
172 $cmsUrl = str_replace('http://', 'https://', $cmsUrl);
173 }
174 $civiRelPath = CRM_Utils_File::relativize(realpath($civicrm_root), realpath($cmsPath));
175 $civiUrl = rtrim($cmsUrl, '/') . '/' . ltrim($civiRelPath, ' /');
176 return [
177 'url' => CRM_Utils_File::addTrailingSlash($civiUrl, '/'),
178 'path' => CRM_Utils_File::addTrailingSlash($civicrm_root),
179 ];
180 }
181
182 /**
183 * @inheritDoc
184 */
185 public function appendBreadCrumb($breadCrumbs) {
186 $breadCrumb = wp_get_breadcrumb();
187
188 if (is_array($breadCrumbs)) {
189 foreach ($breadCrumbs as $crumbs) {
190 if (stripos($crumbs['url'], 'id%%')) {
191 $args = ['cid', 'mid'];
192 foreach ($args as $a) {
193 $val = CRM_Utils_Request::retrieve($a, 'Positive', CRM_Core_DAO::$_nullObject,
194 FALSE, NULL, $_GET
195 );
196 if ($val) {
197 $crumbs['url'] = str_ireplace("%%{$a}%%", $val, $crumbs['url']);
198 }
199 }
200 }
201 $breadCrumb[] = "<a href=\"{$crumbs['url']}\">{$crumbs['title']}</a>";
202 }
203 }
204
205 $template = CRM_Core_Smarty::singleton();
206 $template->assign_by_ref('breadcrumb', $breadCrumb);
207 wp_set_breadcrumb($breadCrumb);
208 }
209
210 /**
211 * @inheritDoc
212 */
213 public function resetBreadCrumb() {
214 $bc = [];
215 wp_set_breadcrumb($bc);
216 }
217
218 /**
219 * @inheritDoc
220 */
221 public function addHTMLHead($head) {
222 static $registered = FALSE;
223 if (!$registered) {
224 // front-end view
225 add_action('wp_head', [__CLASS__, '_showHTMLHead']);
226 // back-end views
227 add_action('admin_head', [__CLASS__, '_showHTMLHead']);
228 }
229 CRM_Core_Region::instance('wp_head')->add([
230 'markup' => $head,
231 ]);
232 }
233
234 /**
235 * WP action callback.
236 */
237 public static function _showHTMLHead() {
238 $region = CRM_Core_Region::instance('wp_head', FALSE);
239 if ($region) {
240 echo $region->render('');
241 }
242 }
243
244 /**
245 * @inheritDoc
246 */
247 public function mapConfigToSSL() {
248 global $base_url;
249 $base_url = str_replace('http://', 'https://', $base_url);
250 }
251
252 /**
253 * @inheritDoc
254 */
255 public function url(
256 $path = NULL,
257 $query = NULL,
258 $absolute = FALSE,
259 $fragment = NULL,
260 $frontend = FALSE,
261 $forceBackend = FALSE,
262 $htmlize = TRUE
263 ) {
264 $config = CRM_Core_Config::singleton();
265 $script = '';
266 $separator = '&';
267 $wpPageParam = '';
268 $fragment = isset($fragment) ? ('#' . $fragment) : '';
269
270 $path = CRM_Utils_String::stripPathChars($path);
271 $basepage = FALSE;
272
273 //this means wp function we are trying to use is not available,
274 //so load bootStrap
275 // FIXME: Why bootstrap in url()? Generally want to define 1-2 strategic places to put bootstrap
276 if (!function_exists('get_option')) {
277 $this->loadBootStrap();
278 }
279
280 if ($config->userFrameworkFrontend) {
281 global $post;
282 if (get_option('permalink_structure') != '') {
283 $script = get_permalink($post->ID);
284 }
285 if ($config->wpBasePage == $post->post_name) {
286 $basepage = TRUE;
287 }
288 // when shortcode is included in page
289 // also make sure we have valid query object
290 // FIXME: $wpPageParam has no effect and is only set on the *basepage*
291 global $wp_query;
292 if (get_option('permalink_structure') == '' && method_exists($wp_query, 'get')) {
293 if (get_query_var('page_id')) {
294 $wpPageParam = "page_id=" . get_query_var('page_id');
295 }
296 elseif (get_query_var('p')) {
297 // when shortcode is inserted in post
298 $wpPageParam = "p=" . get_query_var('p');
299 }
300 }
301 }
302
303 $base = $this->getBaseUrl($absolute, $frontend, $forceBackend);
304
305 if (!isset($path) && !isset($query)) {
306 // FIXME: This short-circuited codepath is the same as the general one below, except
307 // in that it ignores "permlink_structure" / $wpPageParam / $script . I don't know
308 // why it's different (and I can only find two obvious use-cases for this codepath,
309 // of which at least one looks gratuitous). A more ambitious person would simply remove
310 // this code.
311 return $base . $fragment;
312 }
313
314 if (!$forceBackend && get_option('permalink_structure') != '' && ($wpPageParam || $script != '')) {
315 $base = $script;
316 }
317
318 $queryParts = [];
319
320 if (
321 // not using clean URLs
322 !$config->cleanURL
323 // requesting an admin URL
324 || ((is_admin() && !$frontend) || $forceBackend)
325 // is shortcode
326 || (!$basepage && $script != '')
327 ) {
328
329 // pre-existing logic
330 if (isset($path)) {
331 // Admin URLs still need "page=CiviCRM", front-end URLs do not.
332 if ((is_admin() && !$frontend) || $forceBackend) {
333 $queryParts[] = 'page=CiviCRM';
334 }
335 else {
336 $queryParts[] = 'civiwp=CiviCRM';
337 }
338 $queryParts[] = 'q=' . rawurlencode($path);
339 }
340 if ($wpPageParam) {
341 $queryParts[] = $wpPageParam;
342 }
343 if (!empty($query)) {
344 $queryParts[] = $query;
345 }
346
347 $final = $base . '?' . implode($separator, $queryParts) . $fragment;
348
349 }
350 else {
351
352 // clean URLs
353 if (isset($path)) {
354 $base = trailingslashit($base) . str_replace('civicrm/', '', $path) . '/';
355 }
356 if (isset($query)) {
357 $query = ltrim($query, '=?&');
358 $queryParts[] = $query;
359 }
360
361 if (!empty($queryParts)) {
362 $final = $base . '?' . implode($separator, $queryParts) . $fragment;
363 }
364 else {
365 $final = $base . $fragment;
366 }
367
368 }
369
370 return $final;
371 }
372
373 /**
374 * 27-09-2016
375 * CRM-16421 CRM-17633 WIP Changes to support WP in it's own directory
376 * https://wiki.civicrm.org/confluence/display/CRM/WordPress+installed+in+its+own+directory+issues
377 * For now leave hard coded wp-admin references.
378 * TODO: remove wp-admin references and replace with admin_url() in the future. Look at best way to get path to admin_url
379 *
380 * @param $absolute
381 * @param $frontend
382 * @param $forceBackend
383 *
384 * @return mixed|null|string
385 */
386 private function getBaseUrl($absolute, $frontend, $forceBackend) {
387 $config = CRM_Core_Config::singleton();
388 if ((is_admin() && !$frontend) || $forceBackend) {
389 return Civi::paths()->getUrl('[wp.backend]/.', $absolute ? 'absolute' : 'relative');
390 }
391 else {
392 return Civi::paths()->getUrl('[wp.frontend]/.', $absolute ? 'absolute' : 'relative');
393 }
394 }
395
396 /**
397 * @inheritDoc
398 */
399 public function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realPath = NULL) {
400 $config = CRM_Core_Config::singleton();
401
402 if ($loadCMSBootstrap) {
403 $config->userSystem->loadBootStrap([
404 'name' => $name,
405 'pass' => $password,
406 ]);
407 }
408
409 $user = wp_authenticate($name, $password);
410 if (is_a($user, 'WP_Error')) {
411 return FALSE;
412 }
413
414 // TODO: need to change this to make sure we matched only one row
415
416 CRM_Core_BAO_UFMatch::synchronizeUFMatch($user->data, $user->data->ID, $user->data->user_email, 'WordPress');
417 $contactID = CRM_Core_BAO_UFMatch::getContactId($user->data->ID);
418 if (!$contactID) {
419 return FALSE;
420 }
421 return [$contactID, $user->data->ID, mt_rand()];
422 }
423
424 /**
425 * FIXME: Do something
426 *
427 * @param string $message
428 */
429 public function setMessage($message) {
430 }
431
432 /**
433 * @param \string $user
434 *
435 * @return bool
436 */
437 public function loadUser($user) {
438 $userdata = get_user_by('login', $user);
439 if (!$userdata->data->ID) {
440 return FALSE;
441 }
442
443 $uid = $userdata->data->ID;
444 wp_set_current_user($uid);
445 $contactID = CRM_Core_BAO_UFMatch::getContactId($uid);
446
447 // lets store contact id and user id in session
448 $session = CRM_Core_Session::singleton();
449 $session->set('ufID', $uid);
450 $session->set('userID', $contactID);
451 return TRUE;
452 }
453
454 /**
455 * FIXME: Use CMS-native approach
456 * @throws \CRM_Core_Exception
457 */
458 public function permissionDenied() {
459 throw new CRM_Core_Exception(ts('You do not have permission to access this page.'));
460 }
461
462 /**
463 * Determine the native ID of the CMS user.
464 *
465 * @param string $username
466 *
467 * @return int|null
468 */
469 public function getUfId($username) {
470 $userdata = get_user_by('login', $username);
471 if (!$userdata->data->ID) {
472 return NULL;
473 }
474 return $userdata->data->ID;
475 }
476
477 /**
478 * @inheritDoc
479 */
480 public function logout() {
481 // destroy session
482 if (session_id()) {
483 session_destroy();
484 }
485 wp_logout();
486 wp_redirect(wp_login_url());
487 }
488
489 /**
490 * @inheritDoc
491 */
492 public function getUFLocale() {
493 // Bail early if method is called when WordPress isn't bootstrapped.
494 // Additionally, the function checked here is located in pluggable.php
495 // and is required by wp_get_referer() - so this also bails early if it is
496 // called too early in the request lifecycle.
497 // @see https://core.trac.wordpress.org/ticket/25294
498 if (!function_exists('wp_validate_redirect')) {
499 return NULL;
500 }
501
502 // Default to WordPress User locale.
503 $locale = get_user_locale();
504
505 // Is this a "back-end" AJAX call?
506 $is_backend = FALSE;
507 if (wp_doing_ajax() && FALSE !== strpos(wp_get_referer(), admin_url())) {
508 $is_backend = TRUE;
509 }
510
511 // Ignore when in WordPress admin or it's a "back-end" AJAX call.
512 if (!(is_admin() || $is_backend)) {
513
514 // Reaching here means it is very likely to be a front-end context.
515
516 // Default to WordPress locale.
517 $locale = get_locale();
518
519 // Maybe override with the locale that Polylang reports.
520 if (function_exists('pll_current_language')) {
521 $pll_locale = pll_current_language('locale');
522 if (!empty($pll_locale)) {
523 $locale = $pll_locale;
524 }
525 }
526
527 // Maybe override with the locale that WPML reports.
528 elseif (defined('ICL_LANGUAGE_CODE')) {
529 $languages = apply_filters('wpml_active_languages', NULL);
530 foreach ($languages as $language) {
531 if ($language['active']) {
532 $locale = $language['default_locale'];
533 break;
534 }
535 }
536 }
537
538 // TODO: Set locale for other WordPress plugins.
539 // @see https://wordpress.org/plugins/tags/multilingual/
540 // A hook would be nice here.
541
542 }
543
544 if (!empty($locale)) {
545 // If for some reason only we get a language code, convert it to a locale.
546 if (2 === strlen($locale)) {
547 $locale = CRM_Core_I18n_PseudoConstant::longForShort($locale);
548 }
549 return $locale;
550 }
551 else {
552 return NULL;
553 }
554 }
555
556 /**
557 * @inheritDoc
558 */
559 public function setUFLocale($civicrm_language) {
560 // TODO (probably not possible with WPML?)
561 return TRUE;
562 }
563
564 /**
565 * Load wordpress bootstrap.
566 *
567 * @param array $params
568 * Optional credentials
569 * - name: string, cms username
570 * - pass: string, cms password
571 * @param bool $loadUser
572 * @param bool $throwError
573 * @param mixed $realPath
574 *
575 * @return bool
576 * @throws \CRM_Core_Exception
577 */
578 public function loadBootStrap($params = [], $loadUser = TRUE, $throwError = TRUE, $realPath = NULL) {
579 global $wp, $wp_rewrite, $wp_the_query, $wp_query, $wpdb, $current_site, $current_blog, $current_user;
580
581 $name = $params['name'] ?? NULL;
582 $pass = $params['pass'] ?? NULL;
583
584 if (!defined('WP_USE_THEMES')) {
585 define('WP_USE_THEMES', FALSE);
586 }
587
588 $cmsRootPath = $this->cmsRootPath();
589 if (!$cmsRootPath) {
590 throw new CRM_Core_Exception("Could not find the install directory for WordPress");
591 }
592 $path = Civi::settings()->get('wpLoadPhp');
593 if (!empty($path)) {
594 require_once $path;
595 }
596 elseif (file_exists($cmsRootPath . DIRECTORY_SEPARATOR . 'wp-load.php')) {
597 require_once $cmsRootPath . DIRECTORY_SEPARATOR . 'wp-load.php';
598 }
599 else {
600 throw new CRM_Core_Exception("Could not find the bootstrap file for WordPress");
601 }
602 $wpUserTimezone = get_option('timezone_string');
603 if ($wpUserTimezone) {
604 date_default_timezone_set($wpUserTimezone);
605 CRM_Core_Config::singleton()->userSystem->setMySQLTimeZone();
606 }
607 require_once $cmsRootPath . DIRECTORY_SEPARATOR . 'wp-includes/pluggable.php';
608 $uid = $params['uid'] ?? NULL;
609 if (!$uid) {
610 $name = $name ? $name : trim(CRM_Utils_Array::value('name', $_REQUEST));
611 $pass = $pass ? $pass : trim(CRM_Utils_Array::value('pass', $_REQUEST));
612 if ($name) {
613 $uid = wp_authenticate($name, $pass);
614 if (!$uid) {
615 if ($throwError) {
616 echo '<br />Sorry, unrecognized username or password.';
617 exit();
618 }
619 return FALSE;
620 }
621 }
622 }
623 if ($uid) {
624 if ($uid instanceof WP_User) {
625 $account = wp_set_current_user($uid->ID);
626 }
627 else {
628 $account = wp_set_current_user($uid);
629 }
630 if ($account && $account->data->ID) {
631 global $user;
632 $user = $account;
633 return TRUE;
634 }
635 }
636 return TRUE;
637 }
638
639 /**
640 * @param $dir
641 *
642 * @return bool
643 */
644 public function validInstallDir($dir) {
645 $includePath = "$dir/wp-includes";
646 if (@file_exists("$includePath/version.php")) {
647 return TRUE;
648 }
649 return FALSE;
650 }
651
652 /**
653 * Determine the location of the CMS root.
654 *
655 * @return string|NULL
656 * local file system path to CMS root, or NULL if it cannot be determined
657 */
658 public function cmsRootPath() {
659
660 // Return early if the path is already set.
661 global $civicrm_paths;
662 if (!empty($civicrm_paths['cms.root']['path'])) {
663 return $civicrm_paths['cms.root']['path'];
664 }
665
666 // Return early if constant has been defined.
667 if (defined('CIVICRM_CMSDIR')) {
668 if ($this->validInstallDir(CIVICRM_CMSDIR)) {
669 return CIVICRM_CMSDIR;
670 }
671 }
672
673 // Return early if path to wp-load.php can be retrieved from settings.
674 $setting = Civi::settings()->get('wpLoadPhp');
675 if (!empty($setting)) {
676 $path = str_replace('wp-load.php', '', $setting);
677 $cmsRoot = rtrim($path, '/\\');
678 if ($this->validInstallDir($cmsRoot)) {
679 return $cmsRoot;
680 }
681 }
682
683 /*
684 * Keep previous logic as fallback of last resort.
685 *
686 * At some point, it would be good to remove this because there are serious
687 * problems in correctly locating WordPress in this manner. In summary, it
688 * is impossible to do so reliably.
689 *
690 * @see https://github.com/civicrm/civicrm-wordpress/pull/63#issuecomment-61792328
691 * @see https://github.com/civicrm/civicrm-core/pull/11086#issuecomment-335454992
692 */
693 $cmsRoot = $valid = NULL;
694
695 $pathVars = explode('/', str_replace('\\', '/', $_SERVER['SCRIPT_FILENAME']));
696
697 // Might be Windows installation.
698 $firstVar = array_shift($pathVars);
699 if ($firstVar) {
700 $cmsRoot = $firstVar;
701 }
702
703 // Start with CMS dir search.
704 foreach ($pathVars as $var) {
705 $cmsRoot .= "/$var";
706 if ($this->validInstallDir($cmsRoot)) {
707 // Stop as we found bootstrap.
708 $valid = TRUE;
709 break;
710 }
711 }
712
713 return ($valid) ? $cmsRoot : NULL;
714 }
715
716 /**
717 * @inheritDoc
718 */
719 public function createUser(&$params, $mail) {
720 $user_data = [
721 'ID' => '',
722 'user_pass' => $params['cms_pass'],
723 'user_login' => $params['cms_name'],
724 'user_email' => $params[$mail],
725 'nickname' => $params['cms_name'],
726 'role' => get_option('default_role'),
727 ];
728 if (isset($params['contactID'])) {
729 $contactType = CRM_Contact_BAO_Contact::getContactType($params['contactID']);
730 if ($contactType == 'Individual') {
731 $user_data['first_name'] = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact',
732 $params['contactID'], 'first_name'
733 );
734 $user_data['last_name'] = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact',
735 $params['contactID'], 'last_name'
736 );
737 }
738 }
739
740 $uid = wp_insert_user($user_data);
741
742 $creds = [];
743 $creds['user_login'] = $params['cms_name'];
744 $creds['user_password'] = $params['cms_pass'];
745 $creds['remember'] = TRUE;
746 $user = wp_signon($creds, FALSE);
747
748 wp_new_user_notification($uid, $user_data['user_pass']);
749 return $uid;
750 }
751
752 /**
753 * @inheritDoc
754 */
755 public function updateCMSName($ufID, $ufName) {
756 // CRM-10620
757 if (function_exists('wp_update_user')) {
758 $ufID = CRM_Utils_Type::escape($ufID, 'Integer');
759 $ufName = CRM_Utils_Type::escape($ufName, 'String');
760
761 $values = ['ID' => $ufID, 'user_email' => $ufName];
762 if ($ufID) {
763 wp_update_user($values);
764 }
765 }
766 }
767
768 /**
769 * @param array $params
770 * @param $errors
771 * @param string $emailName
772 */
773 public function checkUserNameEmailExists(&$params, &$errors, $emailName = 'email') {
774 $config = CRM_Core_Config::singleton();
775
776 $dao = new CRM_Core_DAO();
777 $name = $dao->escape(CRM_Utils_Array::value('name', $params));
778 $email = $dao->escape(CRM_Utils_Array::value('mail', $params));
779
780 if (!empty($params['name'])) {
781 if (!validate_username($params['name'])) {
782 $errors['cms_name'] = ts("Your username contains invalid characters");
783 }
784 elseif (username_exists(sanitize_user($params['name']))) {
785 $errors['cms_name'] = ts('The username %1 is already taken. Please select another username.', [1 => $params['name']]);
786 }
787 }
788
789 if (!empty($params['mail'])) {
790 if (!is_email($params['mail'])) {
791 $errors[$emailName] = "Your email is invaid";
792 }
793 elseif (email_exists($params['mail'])) {
794 $errors[$emailName] = ts('The email address %1 already has an account associated with it. <a href="%2">Have you forgotten your password?</a>',
795 [1 => $params['mail'], 2 => wp_lostpassword_url()]
796 );
797 }
798 }
799 }
800
801 /**
802 * @inheritDoc
803 */
804 public function isUserLoggedIn() {
805 $isloggedIn = FALSE;
806 if (function_exists('is_user_logged_in')) {
807 $isloggedIn = is_user_logged_in();
808 }
809
810 return $isloggedIn;
811 }
812
813 /**
814 * @inheritDoc
815 */
816 public function isUserRegistrationPermitted() {
817 if (!get_option('users_can_register')) {
818 return FALSE;
819 }
820 return TRUE;
821 }
822
823 /**
824 * @inheritDoc
825 */
826 public function isPasswordUserGenerated() {
827 return TRUE;
828 }
829
830 /**
831 * @return mixed
832 */
833 public function getLoggedInUserObject() {
834 if (function_exists('is_user_logged_in') &&
835 is_user_logged_in()
836 ) {
837 global $current_user;
838 }
839 return $current_user;
840 }
841
842 /**
843 * @inheritDoc
844 */
845 public function getLoggedInUfID() {
846 $ufID = NULL;
847 $current_user = $this->getLoggedInUserObject();
848 return $current_user->ID ?? NULL;
849 }
850
851 /**
852 * @inheritDoc
853 */
854 public function getLoggedInUniqueIdentifier() {
855 $user = $this->getLoggedInUserObject();
856 return $this->getUniqueIdentifierFromUserObject($user);
857 }
858
859 /**
860 * Get User ID from UserFramework system (Joomla)
861 * @param object $user
862 * Object as described by the CMS.
863 *
864 * @return int|null
865 */
866 public function getUserIDFromUserObject($user) {
867 return !empty($user->ID) ? $user->ID : NULL;
868 }
869
870 /**
871 * @inheritDoc
872 */
873 public function getUniqueIdentifierFromUserObject($user) {
874 return empty($user->user_email) ? NULL : $user->user_email;
875 }
876
877 /**
878 * @inheritDoc
879 */
880 public function getLoginURL($destination = '') {
881 $config = CRM_Core_Config::singleton();
882 $loginURL = wp_login_url();
883 return $loginURL;
884 }
885
886 /**
887 * FIXME: Do something.
888 *
889 * @param \CRM_Core_Form $form
890 *
891 * @return NULL|string
892 */
893 public function getLoginDestination(&$form) {
894 return NULL;
895 }
896
897 /**
898 * @inheritDoc
899 */
900 public function getVersion() {
901 if (function_exists('get_bloginfo')) {
902 return get_bloginfo('version', 'display');
903 }
904 else {
905 return 'Unknown';
906 }
907 }
908
909 /**
910 * @inheritDoc
911 */
912 public function getTimeZoneString() {
913 return get_option('timezone_string');
914 }
915
916 /**
917 * @inheritDoc
918 */
919 public function getUserRecordUrl($contactID) {
920 $uid = CRM_Core_BAO_UFMatch::getUFId($contactID);
921 if (CRM_Core_Session::singleton()
922 ->get('userID') == $contactID || CRM_Core_Permission::checkAnyPerm(['cms:administer users'])
923 ) {
924 return CRM_Core_Config::singleton()->userFrameworkBaseURL . "wp-admin/user-edit.php?user_id=" . $uid;
925 }
926 }
927
928 /**
929 * Append WP js to coreResourcesList.
930 *
931 * @param \Civi\Core\Event\GenericHookEvent $e
932 */
933 public function appendCoreResources(\Civi\Core\Event\GenericHookEvent $e) {
934 $e->list[] = 'js/crm.wordpress.js';
935 }
936
937 /**
938 * @inheritDoc
939 */
940 public function alterAssetUrl(\Civi\Core\Event\GenericHookEvent $e) {
941 // Set menubar breakpoint to match WP admin theme
942 if ($e->asset == 'crm-menubar.css') {
943 $e->params['breakpoint'] = 783;
944 }
945 }
946
947 /**
948 * @inheritDoc
949 */
950 public function synchronizeUsers() {
951 $config = CRM_Core_Config::singleton();
952 if (PHP_SAPI != 'cli') {
953 set_time_limit(300);
954 }
955 $id = 'ID';
956 $mail = 'user_email';
957
958 $uf = $config->userFramework;
959 $contactCount = 0;
960 $contactCreated = 0;
961 $contactMatching = 0;
962
963 // Previously used the $wpdb global - which means WordPress *must* be bootstrapped.
964 $wpUsers = get_users(array(
965 'blog_id' => get_current_blog_id(),
966 'number' => -1,
967 ));
968
969 foreach ($wpUsers as $wpUserData) {
970 $contactCount++;
971 if ($match = CRM_Core_BAO_UFMatch::synchronizeUFMatch($wpUserData,
972 $wpUserData->$id,
973 $wpUserData->$mail,
974 $uf,
975 1,
976 'Individual',
977 TRUE
978 )
979 ) {
980 $contactCreated++;
981 }
982 else {
983 $contactMatching++;
984 }
985 if (is_object($match)) {
986 $match->free();
987 }
988 }
989
990 return [
991 'contactCount' => $contactCount,
992 'contactMatching' => $contactMatching,
993 'contactCreated' => $contactCreated,
994 ];
995 }
996
997 /**
998 * Send an HTTP Response base on PSR HTTP RespnseInterface response.
999 *
1000 * @param \Psr\Http\Message\ResponseInterface $response
1001 */
1002 public function sendResponse(\Psr\Http\Message\ResponseInterface $response) {
1003 // use WordPress function status_header to ensure 404 response is sent
1004 status_header($response->getStatusCode());
1005 foreach ($response->getHeaders() as $name => $values) {
1006 CRM_Utils_System::setHttpHeader($name, implode(', ', (array) $values));
1007 }
1008 echo $response->getBody();
1009 CRM_Utils_System::civiExit();
1010 }
1011
1012 /**
1013 * Start a new session if there's no existing session ID.
1014 *
1015 * Checks are needed to prevent sessions being started when not necessary.
1016 */
1017 public function sessionStart() {
1018 $session_id = session_id();
1019
1020 // Check WordPress pseudo-cron.
1021 $wp_cron = FALSE;
1022 if (function_exists('wp_doing_cron') && wp_doing_cron()) {
1023 $wp_cron = TRUE;
1024 }
1025
1026 // Check WP-CLI.
1027 $wp_cli = FALSE;
1028 if (defined('WP_CLI') && WP_CLI) {
1029 $wp_cli = TRUE;
1030 }
1031
1032 // Check PHP on the command line - e.g. `cv`.
1033 $php_cli = TRUE;
1034 if (PHP_SAPI !== 'cli') {
1035 $php_cli = FALSE;
1036 }
1037
1038 // Maybe start session.
1039 if (empty($session_id) && !$wp_cron && !$wp_cli && !$php_cli) {
1040 session_start();
1041 }
1042 }
1043
1044 }