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