WordPress.php 24.3 KB
Newer Older
Kevin Cristiano's avatar
Kevin Cristiano committed
1 2 3
<?php
/*
 +--------------------------------------------------------------------+
Kevin Cristiano's avatar
Kevin Cristiano committed
4
 | CiviCRM version 5                                                  |
Kevin Cristiano's avatar
Kevin Cristiano committed
5
 +--------------------------------------------------------------------+
Kevin Cristiano's avatar
Kevin Cristiano committed
6
 | Copyright CiviCRM LLC (c) 2004-2019                                |
Kevin Cristiano's avatar
Kevin Cristiano committed
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
 +--------------------------------------------------------------------+
 | This file is a part of CiviCRM.                                    |
 |                                                                    |
 | CiviCRM is free software; you can copy, modify, and distribute it  |
 | under the terms of the GNU Affero General Public License           |
 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
 |                                                                    |
 | CiviCRM is distributed in the hope that it will be useful, but     |
 | WITHOUT ANY WARRANTY; without even the implied warranty of         |
 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
 | See the GNU Affero General Public License for more details.        |
 |                                                                    |
 | You should have received a copy of the GNU Affero General Public   |
 | License and the CiviCRM Licensing Exception along                  |
 | with this program; if not, contact CiviCRM LLC                     |
 | at info[AT]civicrm[DOT]org. If you have questions about the        |
 | GNU Affero General Public License or the licensing of CiviCRM,     |
 | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
 +--------------------------------------------------------------------+
 */

/**
 *
 * @package CRM
Kevin Cristiano's avatar
Kevin Cristiano committed
31
 * @copyright CiviCRM LLC (c) 2004-2019
Kevin Cristiano's avatar
Kevin Cristiano committed
32 33 34 35 36 37 38 39
 * $Id$
 *
 */

/**
 * WordPress specific stuff goes here
 */
class CRM_Utils_System_WordPress extends CRM_Utils_System_Base {
Kevin Cristiano's avatar
Kevin Cristiano committed
40

Kevin Cristiano's avatar
Kevin Cristiano committed
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
  /**
   */
  public function __construct() {
    /**
     * 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
     * functions and leave the codebase oblivious to the type of CMS
     * @deprecated
     * @var bool
     */
    $this->is_drupal = FALSE;
    $this->is_wordpress = TRUE;
  }

  /**
   * @inheritDoc
   */
  public function setTitle($title, $pageTitle = NULL) {
    if (!$pageTitle) {
      $pageTitle = $title;
    }

    // FIXME: Why is this global?
    global $civicrm_wp_title;
    $civicrm_wp_title = $title;

    // yes, set page title, depending on context
    $context = civi_wp()->civicrm_context_get();
    switch ($context) {
      case 'admin':
      case 'shortcode':
        $template = CRM_Core_Smarty::singleton();
        $template->assign('pageTitle', $pageTitle);
    }
  }

  /**
   * Moved from CRM_Utils_System_Base
   */
  public function getDefaultFileStorage() {
    $config = CRM_Core_Config::singleton();
    $cmsUrl = CRM_Utils_System::languageNegotiationURL($config->userFrameworkBaseURL, FALSE, TRUE);
    $cmsPath = $this->cmsRootPath();
    $filesPath = CRM_Utils_File::baseFilePath();
    $filesRelPath = CRM_Utils_File::relativize($filesPath, $cmsPath);
    $filesURL = rtrim($cmsUrl, '/') . '/' . ltrim($filesRelPath, ' /');
Kevin Cristiano's avatar
Kevin Cristiano committed
86
    return [
Kevin Cristiano's avatar
Kevin Cristiano committed
87 88
      'url' => CRM_Utils_File::addTrailingSlash($filesURL, '/'),
      'path' => CRM_Utils_File::addTrailingSlash($filesPath),
Kevin Cristiano's avatar
Kevin Cristiano committed
89
    ];
Kevin Cristiano's avatar
Kevin Cristiano committed
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
  }

  /**
   * Determine the location of the CiviCRM source tree.
   *
   * @return array
   *   - url: string. ex: "http://example.com/sites/all/modules/civicrm"
   *   - path: string. ex: "/var/www/sites/all/modules/civicrm"
   */
  public function getCiviSourceStorage() {
    global $civicrm_root;

    // Don't use $config->userFrameworkBaseURL; it has garbage on it.
    // More generally, we shouldn't be using $config here.
    if (!defined('CIVICRM_UF_BASEURL')) {
      throw new RuntimeException('Undefined constant: CIVICRM_UF_BASEURL');
    }

    $cmsPath = $this->cmsRootPath();

    // $config  = CRM_Core_Config::singleton();
    // overkill? // $cmsUrl = CRM_Utils_System::languageNegotiationURL($config->userFrameworkBaseURL, FALSE, TRUE);
    $cmsUrl = CIVICRM_UF_BASEURL;
    if (CRM_Utils_System::isSSL()) {
      $cmsUrl = str_replace('http://', 'https://', $cmsUrl);
    }
Kevin Cristiano's avatar
Kevin Cristiano committed
116
    $civiRelPath = CRM_Utils_File::relativize(realpath($civicrm_root), realpath($cmsPath));
Kevin Cristiano's avatar
Kevin Cristiano committed
117
    $civiUrl = rtrim($cmsUrl, '/') . '/' . ltrim($civiRelPath, ' /');
Kevin Cristiano's avatar
Kevin Cristiano committed
118
    return [
Kevin Cristiano's avatar
Kevin Cristiano committed
119 120
      'url' => CRM_Utils_File::addTrailingSlash($civiUrl, '/'),
      'path' => CRM_Utils_File::addTrailingSlash($civicrm_root),
Kevin Cristiano's avatar
Kevin Cristiano committed
121
    ];
Kevin Cristiano's avatar
Kevin Cristiano committed
122 123 124 125 126 127 128 129 130 131 132
  }

  /**
   * @inheritDoc
   */
  public function appendBreadCrumb($breadCrumbs) {
    $breadCrumb = wp_get_breadcrumb();

    if (is_array($breadCrumbs)) {
      foreach ($breadCrumbs as $crumbs) {
        if (stripos($crumbs['url'], 'id%%')) {
Kevin Cristiano's avatar
Kevin Cristiano committed
133
          $args = ['cid', 'mid'];
Kevin Cristiano's avatar
Kevin Cristiano committed
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
          foreach ($args as $a) {
            $val = CRM_Utils_Request::retrieve($a, 'Positive', CRM_Core_DAO::$_nullObject,
              FALSE, NULL, $_GET
            );
            if ($val) {
              $crumbs['url'] = str_ireplace("%%{$a}%%", $val, $crumbs['url']);
            }
          }
        }
        $breadCrumb[] = "<a href=\"{$crumbs['url']}\">{$crumbs['title']}</a>";
      }
    }

    $template = CRM_Core_Smarty::singleton();
    $template->assign_by_ref('breadcrumb', $breadCrumb);
    wp_set_breadcrumb($breadCrumb);
  }

  /**
   * @inheritDoc
   */
  public function resetBreadCrumb() {
Kevin Cristiano's avatar
Kevin Cristiano committed
156
    $bc = [];
Kevin Cristiano's avatar
Kevin Cristiano committed
157 158 159 160 161 162 163 164 165 166
    wp_set_breadcrumb($bc);
  }

  /**
   * @inheritDoc
   */
  public function addHTMLHead($head) {
    static $registered = FALSE;
    if (!$registered) {
      // front-end view
Kevin Cristiano's avatar
Kevin Cristiano committed
167
      add_action('wp_head', [__CLASS__, '_showHTMLHead']);
Kevin Cristiano's avatar
Kevin Cristiano committed
168
      // back-end views
Kevin Cristiano's avatar
Kevin Cristiano committed
169
      add_action('admin_head', [__CLASS__, '_showHTMLHead']);
Kevin Cristiano's avatar
Kevin Cristiano committed
170
    }
Kevin Cristiano's avatar
Kevin Cristiano committed
171
    CRM_Core_Region::instance('wp_head')->add([
Kevin Cristiano's avatar
Kevin Cristiano committed
172
      'markup' => $head,
Kevin Cristiano's avatar
Kevin Cristiano committed
173
    ]);
Kevin Cristiano's avatar
Kevin Cristiano committed
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
  }

  /**
   * WP action callback.
   */
  public static function _showHTMLHead() {
    $region = CRM_Core_Region::instance('wp_head', FALSE);
    if ($region) {
      echo $region->render('');
    }
  }

  /**
   * @inheritDoc
   */
  public function mapConfigToSSL() {
    global $base_url;
    $base_url = str_replace('http://', 'https://', $base_url);
  }

  /**
   * @inheritDoc
   */
  public function url(
    $path = NULL,
    $query = NULL,
    $absolute = FALSE,
    $fragment = NULL,
    $frontend = FALSE,
    $forceBackend = FALSE
  ) {
    $config = CRM_Core_Config::singleton();
    $script = '';
    $separator = '&';
    $wpPageParam = '';
    $fragment = isset($fragment) ? ('#' . $fragment) : '';

    $path = CRM_Utils_String::stripPathChars($path);
Kevin Cristiano's avatar
Kevin Cristiano committed
212
    $basepage = FALSE;
Kevin Cristiano's avatar
Kevin Cristiano committed
213 214 215

    //this means wp function we are trying to use is not available,
    //so load bootStrap
Kevin Cristiano's avatar
Kevin Cristiano committed
216
    // FIXME: Why bootstrap in url()? Generally want to define 1-2 strategic places to put bootstrap
Kevin Cristiano's avatar
Kevin Cristiano committed
217
    if (!function_exists('get_option')) {
Kevin Cristiano's avatar
Kevin Cristiano committed
218
      $this->loadBootStrap();
Kevin Cristiano's avatar
Kevin Cristiano committed
219
    }
Kevin Cristiano's avatar
Kevin Cristiano committed
220

Kevin Cristiano's avatar
Kevin Cristiano committed
221
    if ($config->userFrameworkFrontend) {
Kevin Cristiano's avatar
Kevin Cristiano committed
222
      global $post;
Kevin Cristiano's avatar
Kevin Cristiano committed
223 224 225
      if (get_option('permalink_structure') != '') {
        $script = get_permalink($post->ID);
      }
Kevin Cristiano's avatar
Kevin Cristiano committed
226 227 228
      if ($config->wpBasePage == $post->post_name) {
        $basepage = TRUE;
      }
Kevin Cristiano's avatar
Kevin Cristiano committed
229 230
      // when shortcode is included in page
      // also make sure we have valid query object
Kevin Cristiano's avatar
Kevin Cristiano committed
231
      // FIXME: $wpPageParam has no effect and is only set on the *basepage*
Kevin Cristiano's avatar
Kevin Cristiano committed
232
      global $wp_query;
Kevin Cristiano's avatar
Kevin Cristiano committed
233
      if (get_option('permalink_structure') == '' && method_exists($wp_query, 'get')) {
Kevin Cristiano's avatar
Kevin Cristiano committed
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
        if (get_query_var('page_id')) {
          $wpPageParam = "page_id=" . get_query_var('page_id');
        }
        elseif (get_query_var('p')) {
          // when shortcode is inserted in post
          $wpPageParam = "p=" . get_query_var('p');
        }
      }
    }

    $base = $this->getBaseUrl($absolute, $frontend, $forceBackend);

    if (!isset($path) && !isset($query)) {
      // FIXME: This short-circuited codepath is the same as the general one below, except
      // in that it ignores "permlink_structure" /  $wpPageParam / $script . I don't know
      // why it's different (and I can only find two obvious use-cases for this codepath,
      // of which at least one looks gratuitous). A more ambitious person would simply remove
      // this code.
      return $base . $fragment;
    }

    if (!$forceBackend && get_option('permalink_structure') != '' && ($wpPageParam || $script != '')) {
      $base = $script;
    }

Kevin Cristiano's avatar
Kevin Cristiano committed
259
    $queryParts = [];
Kevin Cristiano's avatar
Kevin Cristiano committed
260 261 262 263 264 265 266 267 268 269 270 271 272

    if (
      // not using clean URLs
      !$config->cleanURL
      // requesting an admin URL
      || ((is_admin() && !$frontend) || $forceBackend)
      // is shortcode
      || (!$basepage && $script != '')
    ) {

      // pre-existing logic
      if (isset($path)) {
        $queryParts[] = 'page=CiviCRM';
Kevin Cristiano's avatar
Kevin Cristiano committed
273
        $queryParts[] = 'q=' . rawurlencode($path);
Kevin Cristiano's avatar
Kevin Cristiano committed
274 275 276 277
      }
      if ($wpPageParam) {
        $queryParts[] = $wpPageParam;
      }
Kevin Cristiano's avatar
Kevin Cristiano committed
278
      if (!empty($query)) {
Kevin Cristiano's avatar
Kevin Cristiano committed
279 280 281 282 283
        $queryParts[] = $query;
      }

      $final = $base . '?' . implode($separator, $queryParts) . $fragment;

Kevin Cristiano's avatar
Kevin Cristiano committed
284
    }
Kevin Cristiano's avatar
Kevin Cristiano committed
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
    else {

      // clean URLs
      if (isset($path)) {
        $base = trailingslashit($base) . str_replace('civicrm/', '', $path) . '/';
      }
      if (isset($query)) {
        $query = ltrim($query, '=?&');
        $queryParts[] = $query;
      }

      if (!empty($queryParts)) {
        $final = $base . '?' . implode($separator, $queryParts) . $fragment;
      }
      else {
        $final = $base . $fragment;
      }

Kevin Cristiano's avatar
Kevin Cristiano committed
303 304
    }

Kevin Cristiano's avatar
Kevin Cristiano committed
305
    return $final;
Kevin Cristiano's avatar
Kevin Cristiano committed
306 307 308
  }

  /**
Kevin Cristiano's avatar
Kevin Cristiano committed
309 310 311 312 313 314
   * 27-09-2016
   * CRM-16421 CRM-17633 WIP Changes to support WP in it's own directory
   * https://wiki.civicrm.org/confluence/display/CRM/WordPress+installed+in+its+own+directory+issues
   * For now leave hard coded wp-admin references.
   * TODO: remove wp-admin references and replace with admin_url() in the future.  Look at best way to get path to admin_url
   *
Kevin Cristiano's avatar
Kevin Cristiano committed
315 316 317 318 319 320 321 322 323
   * @param $absolute
   * @param $frontend
   * @param $forceBackend
   *
   * @return mixed|null|string
   */
  private function getBaseUrl($absolute, $frontend, $forceBackend) {
    $config = CRM_Core_Config::singleton();
    if ((is_admin() && !$frontend) || $forceBackend) {
Kevin Cristiano's avatar
Kevin Cristiano committed
324
      return Civi::paths()->getUrl('[wp.backend]/.', $absolute ? 'absolute' : 'relative');
Kevin Cristiano's avatar
Kevin Cristiano committed
325
    }
Kevin Cristiano's avatar
Kevin Cristiano committed
326 327
    else {
      return Civi::paths()->getUrl('[wp.frontend]/.', $absolute ? 'absolute' : 'relative');
Kevin Cristiano's avatar
Kevin Cristiano committed
328 329 330 331 332 333 334 335 336 337
    }
  }

  /**
   * @inheritDoc
   */
  public function authenticate($name, $password, $loadCMSBootstrap = FALSE, $realPath = NULL) {
    $config = CRM_Core_Config::singleton();

    if ($loadCMSBootstrap) {
Kevin Cristiano's avatar
Kevin Cristiano committed
338 339 340 341
      $config->userSystem->loadBootStrap([
        'name' => $name,
        'pass' => $password,
      ]);
Kevin Cristiano's avatar
Kevin Cristiano committed
342 343 344 345 346 347 348 349 350 351 352 353 354 355
    }

    $user = wp_authenticate($name, $password);
    if (is_a($user, 'WP_Error')) {
      return FALSE;
    }

    // TODO: need to change this to make sure we matched only one row

    CRM_Core_BAO_UFMatch::synchronizeUFMatch($user->data, $user->data->ID, $user->data->user_email, 'WordPress');
    $contactID = CRM_Core_BAO_UFMatch::getContactId($user->data->ID);
    if (!$contactID) {
      return FALSE;
    }
Kevin Cristiano's avatar
Kevin Cristiano committed
356
    return [$contactID, $user->data->ID, mt_rand()];
Kevin Cristiano's avatar
Kevin Cristiano committed
357 358 359 360 361 362 363 364 365 366 367
  }

  /**
   * FIXME: Do something
   *
   * @param string $message
   */
  public function setMessage($message) {
  }

  /**
368
   * @param \string $user
Kevin Cristiano's avatar
Kevin Cristiano committed
369 370 371 372
   *
   * @return bool
   */
  public function loadUser($user) {
373 374 375 376
    $userdata = get_user_by('login', $user);
    if (!$userdata->data->ID) {
      return FALSE;
    }
377

378 379 380
    $uid = $userdata->data->ID;
    wp_set_current_user($uid);
    $contactID = CRM_Core_BAO_UFMatch::getContactId($uid);
381

382 383 384 385
    // lets store contact id and user id in session
    $session = CRM_Core_Session::singleton();
    $session->set('ufID', $uid);
    $session->set('userID', $contactID);
Kevin Cristiano's avatar
Kevin Cristiano committed
386 387 388 389 390 391 392 393 394 395
    return TRUE;
  }

  /**
   * FIXME: Use CMS-native approach
   */
  public function permissionDenied() {
    CRM_Core_Error::fatal(ts('You do not have permission to access this page.'));
  }

Kevin Cristiano's avatar
Kevin Cristiano committed
396 397 398 399
  /**
   * Determine the native ID of the CMS user.
   *
   * @param string $username
Kevin Cristiano's avatar
Kevin Cristiano committed
400 401
   *
   * @return int|null
Kevin Cristiano's avatar
Kevin Cristiano committed
402 403 404 405 406 407 408 409 410
   */
  public function getUfId($username) {
    $userdata = get_user_by('login', $username);
    if (!$userdata->data->ID) {
      return NULL;
    }
    return $userdata->data->ID;
  }

Kevin Cristiano's avatar
Kevin Cristiano committed
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
  /**
   * @inheritDoc
   */
  public function logout() {
    // destroy session
    if (session_id()) {
      session_destroy();
    }
    wp_logout();
    wp_redirect(wp_login_url());
  }

  /**
   * @inheritDoc
   */
  public function getUFLocale() {
Kevin Cristiano's avatar
Kevin Cristiano committed
427 428 429 430
    // Polylang plugin
    if (function_exists('pll_current_language')) {
      $language = pll_current_language();
    }
Kevin Cristiano's avatar
Kevin Cristiano committed
431
    // WPML plugin
Kevin Cristiano's avatar
Kevin Cristiano committed
432
    elseif (defined('ICL_LANGUAGE_CODE')) {
Kevin Cristiano's avatar
Kevin Cristiano committed
433 434 435 436 437
      $language = ICL_LANGUAGE_CODE;
    }

    // TODO: set language variable for others WordPress plugin

Kevin Cristiano's avatar
Kevin Cristiano committed
438
    if (!empty($language)) {
Kevin Cristiano's avatar
Kevin Cristiano committed
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
      return CRM_Core_I18n_PseudoConstant::longForShort(substr($language, 0, 2));
    }
    else {
      return NULL;
    }
  }

  /**
   * @inheritDoc
   */
  public function setUFLocale($civicrm_language) {
    // TODO (probably not possible with WPML?)
    return TRUE;
  }

  /**
   * Load wordpress bootstrap.
   *
Kevin Cristiano's avatar
Kevin Cristiano committed
457 458 459 460 461 462 463
   * @param array $params
   *   Optional credentials
   *   - name: string, cms username
   *   - pass: string, cms password
   * @param bool $loadUser
   * @param bool $throwError
   * @param mixed $realPath
Kevin Cristiano's avatar
Kevin Cristiano committed
464 465 466
   *
   * @return bool
   */
Kevin Cristiano's avatar
Kevin Cristiano committed
467
  public function loadBootStrap($params = [], $loadUser = TRUE, $throwError = TRUE, $realPath = NULL) {
Kevin Cristiano's avatar
Kevin Cristiano committed
468 469
    global $wp, $wp_rewrite, $wp_the_query, $wp_query, $wpdb, $current_site, $current_blog, $current_user;

Kevin Cristiano's avatar
Kevin Cristiano committed
470 471 472
    $name = CRM_Utils_Array::value('name', $params);
    $pass = CRM_Utils_Array::value('pass', $params);

Kevin Cristiano's avatar
Kevin Cristiano committed
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496
    if (!defined('WP_USE_THEMES')) {
      define('WP_USE_THEMES', FALSE);
    }

    $cmsRootPath = $this->cmsRootPath();
    if (!$cmsRootPath) {
      CRM_Core_Error::fatal("Could not find the install directory for WordPress");
    }
    $path = Civi::settings()->get('wpLoadPhp');
    if (!empty($path)) {
      require_once $path;
    }
    elseif (file_exists($cmsRootPath . DIRECTORY_SEPARATOR . 'wp-load.php')) {
      require_once $cmsRootPath . DIRECTORY_SEPARATOR . 'wp-load.php';
    }
    else {
      CRM_Core_Error::fatal("Could not find the bootstrap file for WordPress");
    }
    $wpUserTimezone = get_option('timezone_string');
    if ($wpUserTimezone) {
      date_default_timezone_set($wpUserTimezone);
      CRM_Core_Config::singleton()->userSystem->setMySQLTimeZone();
    }
    require_once $cmsRootPath . DIRECTORY_SEPARATOR . 'wp-includes/pluggable.php';
Kevin Cristiano's avatar
Kevin Cristiano committed
497
    $uid = CRM_Utils_Array::value('uid', $params);
Kevin Cristiano's avatar
Kevin Cristiano committed
498 499 500 501
    if (!$uid) {
      $name = $name ? $name : trim(CRM_Utils_Array::value('name', $_REQUEST));
      $pass = $pass ? $pass : trim(CRM_Utils_Array::value('pass', $_REQUEST));
      if ($name) {
Kevin Cristiano's avatar
Kevin Cristiano committed
502
        $uid = wp_authenticate($name, $pass);
Kevin Cristiano's avatar
Kevin Cristiano committed
503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534
        if (!$uid) {
          if ($throwError) {
            echo '<br />Sorry, unrecognized username or password.';
            exit();
          }
          return FALSE;
        }
      }
    }
    if ($uid) {
      if ($uid instanceof WP_User) {
        $account = wp_set_current_user($uid->ID);
      }
      else {
        $account = wp_set_current_user($uid);
      }
      if ($account && $account->data->ID) {
        global $user;
        $user = $account;
        return TRUE;
      }
    }
    return TRUE;
  }

  /**
   * @param $dir
   *
   * @return bool
   */
  public function validInstallDir($dir) {
    $includePath = "$dir/wp-includes";
Kevin Cristiano's avatar
Kevin Cristiano committed
535
    if (@file_exists("$includePath/version.php")) {
Kevin Cristiano's avatar
Kevin Cristiano committed
536 537 538 539 540 541 542 543 544 545 546 547
      return TRUE;
    }
    return FALSE;
  }

  /**
   * Determine the location of the CMS root.
   *
   * @return string|NULL
   *   local file system path to CMS root, or NULL if it cannot be determined
   */
  public function cmsRootPath() {
Kevin Cristiano's avatar
Kevin Cristiano committed
548 549

    // Return early if the path is already set.
Kevin Cristiano's avatar
Kevin Cristiano committed
550 551 552 553 554
    global $civicrm_paths;
    if (!empty($civicrm_paths['cms.root']['path'])) {
      return $civicrm_paths['cms.root']['path'];
    }

Kevin Cristiano's avatar
Kevin Cristiano committed
555
    // Return early if constant has been defined.
Kevin Cristiano's avatar
Kevin Cristiano committed
556 557
    if (defined('CIVICRM_CMSDIR')) {
      if ($this->validInstallDir(CIVICRM_CMSDIR)) {
Kevin Cristiano's avatar
Kevin Cristiano committed
558
        return CIVICRM_CMSDIR;
Kevin Cristiano's avatar
Kevin Cristiano committed
559 560
      }
    }
Kevin Cristiano's avatar
Kevin Cristiano committed
561 562 563 564

    // Return early if path to wp-load.php can be retrieved from settings.
    $setting = Civi::settings()->get('wpLoadPhp');
    if (!empty($setting)) {
565 566
      $path = str_replace('wp-load.php', '', $setting);
      $cmsRoot = rtrim($path, '/\\');
Kevin Cristiano's avatar
Kevin Cristiano committed
567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599
      if ($this->validInstallDir($cmsRoot)) {
        return $cmsRoot;
      }
    }

    /*
     * Keep previous logic as fallback of last resort.
     *
     * At some point, it would be good to remove this because there are serious
     * problems in correctly locating WordPress in this manner. In summary, it
     * is impossible to do so reliably.
     *
     * @see https://github.com/civicrm/civicrm-wordpress/pull/63#issuecomment-61792328
     * @see https://github.com/civicrm/civicrm-core/pull/11086#issuecomment-335454992
     */
    $cmsRoot = $valid = NULL;

    $pathVars = explode('/', str_replace('\\', '/', $_SERVER['SCRIPT_FILENAME']));

    // Might be Windows installation.
    $firstVar = array_shift($pathVars);
    if ($firstVar) {
      $cmsRoot = $firstVar;
    }

    // Start with CMS dir search.
    foreach ($pathVars as $var) {
      $cmsRoot .= "/$var";
      if ($this->validInstallDir($cmsRoot)) {
        // Stop as we found bootstrap.
        $valid = TRUE;
        break;
      }
Kevin Cristiano's avatar
Kevin Cristiano committed
600 601 602 603 604 605 606 607 608
    }

    return ($valid) ? $cmsRoot : NULL;
  }

  /**
   * @inheritDoc
   */
  public function createUser(&$params, $mail) {
Kevin Cristiano's avatar
Kevin Cristiano committed
609
    $user_data = [
Kevin Cristiano's avatar
Kevin Cristiano committed
610 611 612 613 614 615
      'ID' => '',
      'user_pass' => $params['cms_pass'],
      'user_login' => $params['cms_name'],
      'user_email' => $params[$mail],
      'nickname' => $params['cms_name'],
      'role' => get_option('default_role'),
Kevin Cristiano's avatar
Kevin Cristiano committed
616
    ];
Kevin Cristiano's avatar
Kevin Cristiano committed
617 618 619 620 621 622 623 624 625 626 627 628 629 630
    if (isset($params['contactID'])) {
      $contactType = CRM_Contact_BAO_Contact::getContactType($params['contactID']);
      if ($contactType == 'Individual') {
        $user_data['first_name'] = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact',
          $params['contactID'], 'first_name'
        );
        $user_data['last_name'] = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact',
          $params['contactID'], 'last_name'
        );
      }
    }

    $uid = wp_insert_user($user_data);

Kevin Cristiano's avatar
Kevin Cristiano committed
631
    $creds = [];
Kevin Cristiano's avatar
Kevin Cristiano committed
632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649
    $creds['user_login'] = $params['cms_name'];
    $creds['user_password'] = $params['cms_pass'];
    $creds['remember'] = TRUE;
    $user = wp_signon($creds, FALSE);

    wp_new_user_notification($uid, $user_data['user_pass']);
    return $uid;
  }

  /**
   * @inheritDoc
   */
  public function updateCMSName($ufID, $ufName) {
    // CRM-10620
    if (function_exists('wp_update_user')) {
      $ufID = CRM_Utils_Type::escape($ufID, 'Integer');
      $ufName = CRM_Utils_Type::escape($ufName, 'String');

Kevin Cristiano's avatar
Kevin Cristiano committed
650
      $values = ['ID' => $ufID, 'user_email' => $ufName];
Kevin Cristiano's avatar
Kevin Cristiano committed
651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673
      if ($ufID) {
        wp_update_user($values);
      }
    }
  }

  /**
   * @param array $params
   * @param $errors
   * @param string $emailName
   */
  public function checkUserNameEmailExists(&$params, &$errors, $emailName = 'email') {
    $config = CRM_Core_Config::singleton();

    $dao = new CRM_Core_DAO();
    $name = $dao->escape(CRM_Utils_Array::value('name', $params));
    $email = $dao->escape(CRM_Utils_Array::value('mail', $params));

    if (!empty($params['name'])) {
      if (!validate_username($params['name'])) {
        $errors['cms_name'] = ts("Your username contains invalid characters");
      }
      elseif (username_exists(sanitize_user($params['name']))) {
Kevin Cristiano's avatar
Kevin Cristiano committed
674
        $errors['cms_name'] = ts('The username %1 is already taken. Please select another username.', [1 => $params['name']]);
Kevin Cristiano's avatar
Kevin Cristiano committed
675 676 677 678 679 680 681 682 683
      }
    }

    if (!empty($params['mail'])) {
      if (!is_email($params['mail'])) {
        $errors[$emailName] = "Your email is invaid";
      }
      elseif (email_exists($params['mail'])) {
        $errors[$emailName] = ts('The email address %1 already has an account associated with it. <a href="%2">Have you forgotten your password?</a>',
Kevin Cristiano's avatar
Kevin Cristiano committed
684
          [1 => $params['mail'], 2 => wp_lostpassword_url()]
Kevin Cristiano's avatar
Kevin Cristiano committed
685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701
        );
      }
    }
  }

  /**
   * @inheritDoc
   */
  public function isUserLoggedIn() {
    $isloggedIn = FALSE;
    if (function_exists('is_user_logged_in')) {
      $isloggedIn = is_user_logged_in();
    }

    return $isloggedIn;
  }

Kevin Cristiano's avatar
Kevin Cristiano committed
702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718
  /**
   * @inheritDoc
   */
  public function isUserRegistrationPermitted() {
    if (!get_option('users_can_register')) {
      return FALSE;
    }
    return TRUE;
  }

  /**
   * @inheritDoc
   */
  public function isPasswordUserGenerated() {
    return TRUE;
  }

Kevin Cristiano's avatar
Kevin Cristiano committed
719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770
  /**
   * @return mixed
   */
  public function getLoggedInUserObject() {
    if (function_exists('is_user_logged_in') &&
      is_user_logged_in()
    ) {
      global $current_user;
    }
    return $current_user;
  }

  /**
   * @inheritDoc
   */
  public function getLoggedInUfID() {
    $ufID = NULL;
    $current_user = $this->getLoggedInUserObject();
    return isset($current_user->ID) ? $current_user->ID : NULL;
  }

  /**
   * @inheritDoc
   */
  public function getLoggedInUniqueIdentifier() {
    $user = $this->getLoggedInUserObject();
    return $this->getUniqueIdentifierFromUserObject($user);
  }

  /**
   * Get User ID from UserFramework system (Joomla)
   * @param object $user
   *   Object as described by the CMS.
   *
   * @return int|null
   */
  public function getUserIDFromUserObject($user) {
    return !empty($user->ID) ? $user->ID : NULL;
  }

  /**
   * @inheritDoc
   */
  public function getUniqueIdentifierFromUserObject($user) {
    return empty($user->user_email) ? NULL : $user->user_email;
  }

  /**
   * @inheritDoc
   */
  public function getLoginURL($destination = '') {
    $config = CRM_Core_Config::singleton();
Kevin Cristiano's avatar
Kevin Cristiano committed
771
    $loginURL = wp_login_url();
Kevin Cristiano's avatar
Kevin Cristiano committed
772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810
    return $loginURL;
  }

  /**
   * FIXME: Do something.
   *
   * @param \CRM_Core_Form $form
   *
   * @return NULL|string
   */
  public function getLoginDestination(&$form) {
    return NULL;
  }

  /**
   * @inheritDoc
   */
  public function getVersion() {
    if (function_exists('get_bloginfo')) {
      return get_bloginfo('version', 'display');
    }
    else {
      return 'Unknown';
    }
  }

  /**
   * @inheritDoc
   */
  public function getTimeZoneString() {
    return get_option('timezone_string');
  }

  /**
   * @inheritDoc
   */
  public function getUserRecordUrl($contactID) {
    $uid = CRM_Core_BAO_UFMatch::getUFId($contactID);
    if (CRM_Core_Session::singleton()
Kevin Cristiano's avatar
Kevin Cristiano committed
811
      ->get('userID') == $contactID || CRM_Core_Permission::checkAnyPerm(['cms:administer users'])
Kevin Cristiano's avatar
Kevin Cristiano committed
812 813 814 815 816 817 818 819
    ) {
      return CRM_Core_Config::singleton()->userFrameworkBaseURL . "wp-admin/user-edit.php?user_id=" . $uid;
    }
  }

  /**
   * Append WP js to coreResourcesList.
   *
Kevin Cristiano's avatar
Kevin Cristiano committed
820
   * @param \Civi\Core\Event\GenericHookEvent $e
Kevin Cristiano's avatar
Kevin Cristiano committed
821
   */
Kevin Cristiano's avatar
Kevin Cristiano committed
822 823 824 825 826 827 828 829 830 831 832 833
  public function appendCoreResources(\Civi\Core\Event\GenericHookEvent $e) {
    $e->list[] = 'js/crm.wordpress.js';
  }

  /**
   * @inheritDoc
   */
  public function alterAssetUrl(\Civi\Core\Event\GenericHookEvent $e) {
    // Set menubar breakpoint to match WP admin theme
    if ($e->asset == 'crm-menubar.css') {
      $e->params['breakpoint'] = 783;
    }
Kevin Cristiano's avatar
Kevin Cristiano committed
834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851
  }

  /**
   * @inheritDoc
   */
  public function synchronizeUsers() {
    $config = CRM_Core_Config::singleton();
    if (PHP_SAPI != 'cli') {
      set_time_limit(300);
    }
    $id = 'ID';
    $mail = 'user_email';

    $uf = $config->userFramework;
    $contactCount = 0;
    $contactCreated = 0;
    $contactMatching = 0;

852 853 854 855 856
    // previously used $wpdb - which means WordPress *must* be bootstrapped
    $wpUsers = get_users(array(
      'blog_id' => get_current_blog_id(),
      'number' => -1,
    ));
Kevin Cristiano's avatar
Kevin Cristiano committed
857

858
    foreach ($wpUsers as $wpUserData) {
Kevin Cristiano's avatar
Kevin Cristiano committed
859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875
      $contactCount++;
      if ($match = CRM_Core_BAO_UFMatch::synchronizeUFMatch($wpUserData,
        $wpUserData->$id,
        $wpUserData->$mail,
        $uf,
        1,
        'Individual',
        TRUE
      )
      ) {
        $contactCreated++;
      }
      else {
        $contactMatching++;
      }
    }

Kevin Cristiano's avatar
Kevin Cristiano committed
876
    return [
Kevin Cristiano's avatar
Kevin Cristiano committed
877 878 879
      'contactCount' => $contactCount,
      'contactMatching' => $contactMatching,
      'contactCreated' => $contactCreated,
Kevin Cristiano's avatar
Kevin Cristiano committed
880
    ];
Kevin Cristiano's avatar
Kevin Cristiano committed
881 882
  }

Kevin Cristiano's avatar
Kevin Cristiano committed
883 884 885 886 887 888 889 890 891 892 893 894 895 896 897
  /**
   * Send an HTTP Response base on PSR HTTP RespnseInterface response.
   *
   * @param \Psr\Http\Message\ResponseInterface $response
   */
  public function sendResponse(\Psr\Http\Message\ResponseInterface $response) {
    // use WordPress function status_header to ensure 404 response is sent
    status_header($response->getStatusCode());
    foreach ($response->getHeaders() as $name => $values) {
      CRM_Utils_System::setHttpHeader($name, implode(', ', (array) $values));
    }
    echo $response->getBody();
    CRM_Utils_System::civiExit();
  }

Kevin Cristiano's avatar
Kevin Cristiano committed
898
}