<?php
/*
 +--------------------------------------------------------------------+
 | Copyright CiviCRM LLC. All rights reserved.                        |
 |                                                                    |
 | This work is published under the GNU AGPLv3 license with some      |
 | permitted exceptions and without any warranty. For full license    |
 | and copyright information, see https://civicrm.org/licensing       |
 +--------------------------------------------------------------------+
 */

/**
 *
 * @package CRM
 * @copyright CiviCRM LLC https://civicrm.org/licensing
 *
 */

// This file must not accessed directly.
if (!defined('ABSPATH')) {
  exit;
}

/**
 * Polylang plugin compatatibility class.
 *
 * @since 5.66
 */
class CiviCRM_For_WordPress_Compat_Polylang {

  /**
   * @var object
   * Plugin object reference.
   * @since 5.66
   * @access public
   */
  public $civi;

  /**
   * @var array
   * Base Page data.
   * @since 5.66
   * @access private
   */
  private $basepages = [];

  /**
   * @var array
   * Collected rewrites.
   * @since 5.66
   * @access private
   */
  private $rewrites = [];

  /**
   * Instance constructor.
   *
   * @since 5.66
   */
  public function __construct() {

    // Store reference to CiviCRM plugin object.
    $this->civi = civi_wp();

    // Register plugin compatibility hooks.
    $this->register_hooks();

  }

  /**
   * Register hooks.
   *
   * This is called via the constructor during the "plugins_loaded" action which
   * is much earlier that CiviCRM's own internal hooks. The reason for this is
   * that compability may need callbacks for events that fire well before "init"
   * which is when CiviCRM begins to load.
   *
   * @since 5.66
   */
  public function register_hooks() {

    // Bail if CiviCRM not installed yet.
    if (!CIVICRM_INSTALLED) {
      return;
    }

    // Bail if Polylang is not present.
    if (!function_exists('pll_languages_list')) {
      return;
    }

    // Register Polylang compatibility callbacks.
    add_action('civicrm_after_rewrite_rules', [$this, 'rewrite_rules'], 10, 2);
    add_filter('pll_check_canonical_url', [$this, 'canonical_url'], 10, 2);
    add_filter('civicrm/basepage/match', [$this, 'basepage_match'], 10, 2);
    add_filter('civicrm/core/url/base', [$this, 'base_url_filter'], 10, 2);
    add_filter('civicrm/core/locale', [$this, 'locale_filter'], 10, 2);

  }

  /**
   * Support Polylang.
   *
   * @since 5.24
   * @since 5.66 Moved to this class.
   *
   * @param bool $flush_rewrite_rules True if rules flushed, false otherwise.
   * @param WP_Post $basepage The Base Page post object.
   */
  public function rewrite_rules($flush_rewrite_rules, $basepage) {

    /*
     * Collect all rewrite rules into an array.
     *
     * Because the array of specific Post IDs is added *after* the array of
     * paths for the Base Page ID, those specific rewrite rules will "win" over
     * the more general Base Page rules.
     */
    $collected_rewrites = [];

    // Support prefixes for a single Base Page.
    $basepage_url = get_permalink($basepage->ID);
    $basepage_raw_url = PLL()->links_model->remove_language_from_link($basepage_url);
    $language_slugs = pll_languages_list();
    foreach ($language_slugs as $slug) {
      $language = PLL()->model->get_language($slug);
      $language_url = PLL()->links_model->add_language_to_link($basepage_raw_url, $language);
      $parsed_url = wp_parse_url($language_url, PHP_URL_PATH);
      $regex_path = substr($parsed_url, 1);
      $collected_rewrites[$basepage->ID][] = $regex_path;
      $post_id = pll_get_post($basepage->ID, $slug);
      if (!empty($post_id)) {
        $collected_rewrites[$post_id][] = $regex_path;
      }
    };

    // Support prefixes for Base Pages in multiple languages.
    foreach ($language_slugs as $slug) {
      $post_id = pll_get_post($basepage->ID, $slug);
      if (empty($post_id)) {
        continue;
      }
      $url = get_permalink($post_id);
      $parsed_url = wp_parse_url($url, PHP_URL_PATH);
      $regex_path = substr($parsed_url, 1);
      $collected_rewrites[$basepage->ID][] = $regex_path;
      $collected_rewrites[$post_id][] = $regex_path;
    };

    // Make collection unique and add remaining rewrite rules.
    $this->rewrites = array_map('array_unique', $collected_rewrites);
    if (!empty($this->rewrites)) {
      foreach ($this->rewrites as $post_id => $rewrite) {
        foreach ($rewrite as $path) {
          add_rewrite_rule(
            '^' . $path . '([^?]*)?',
            'index.php?page_id=' . $post_id . '&civiwp=CiviCRM&q=civicrm%2F$matches[1]',
            'top'
          );
        }
      }
    }

    // Maybe force flush.
    if ($flush_rewrite_rules) {
      flush_rewrite_rules();
    }

  }

  /**
   * Prevents Polylang from redirecting CiviCRM URLs.
   *
   * @since 5.66
   *
   * @param string|false $redirect_url False or the URL to redirect to.
   * @param PLL_Language $language The language detected.
   * @return string|false $redirect_url False or the URL to redirect to.
   */
  public function canonical_url($redirect_url, $language) {

    // Bail if this is not a Page.
    if (!is_page()) {
      return $redirect_url;
    }

    // Bail if there is no Post object.
    $post = get_post();
    if (!($post instanceof WP_Post)) {
      return $redirect_url;
    }

    // Bail if this is not a Base Page.
    if (!empty($this->rewrites)) {
      foreach ($this->rewrites as $post_id => $rewrite) {
        if ($post_id === $post->ID) {
          return FALSE;
        }
      }
    }

    return $redirect_url;

  }

  /**
   * Checks Polylang for CiviCRM Base Page matches.
   *
   * @since 5.66
   *
   * @param bool $is_basepage TRUE if the Post ID matches the Base Page ID, FALSE otherwise.
   * @param int $post_id The WordPress Post ID to check.
   * @return bool $is_basepage TRUE if the Post ID matches the Base Page ID, FALSE otherwise.
   */
  public function basepage_match($is_basepage, $post_id) {

    // Bail if this is already the Base Page.
    if ($is_basepage) {
      return $is_basepage;
    }

    // Bail if there are no rewrites.
    if (empty($this->rewrites)) {
      return $is_basepage;
    }

    foreach ($this->rewrites as $page_id => $rewrite) {
      if ($post_id === $page_id) {
        $is_basepage = TRUE;
      }
    }

    return $is_basepage;

  }

  /**
   * Filters the CiviCRM Base URL for the current language reported by Polylang.
   *
   * Only filters URLs that point to the front-end, since WordPress admin URLs are not
   * rewritten by Polylang.
   *
   * @since 5.66
   *
   * @param str $url The URL as built by CiviCRM.
   * @param bool $admin_request True if building an admin URL, false otherwise.
   * @return str $url The URL as modified by Polylang.
   */
  public function base_url_filter($url, $admin_request) {

    // Skip when not defined.
    if (empty($url) || $admin_request) {
      return $url;
    }

    // Find the language slug.
    $slug = pll_current_language();
    if (empty($slug)) {
      return $url;
    }

    // Build the modified URL.
    $raw_url = PLL()->links_model->remove_language_from_link($url);
    $language = PLL()->model->get_language($slug);
    $language_url = PLL()->links_model->add_language_to_link($raw_url, $language);

    return $language_url;

  }

  /**
   * Filters the CiviCRM locale for the current language as set by Polylang.
   *
   * @since 5.66
   *
   * @param str $locale The locale as reported by WordPress.
   * @return str $locale The locale as modified by Polylang.
   */
  public function locale_filter($locale) {

    $pll_locale = pll_current_language('locale');
    if (!empty($pll_locale)) {
      $locale = $pll_locale;
    }

    return $locale;

  }

}
