Skip to content
Snippets Groups Projects
civicrm.basepage.php 28.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • Kevin Cristiano's avatar
    Kevin Cristiano committed
    <?php
    /*
     +--------------------------------------------------------------------+
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
     | Copyright CiviCRM LLC. All rights reserved.                        |
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
     |                                                                    |
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
     | 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       |
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
     +--------------------------------------------------------------------+
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
     */
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
    /**
     *
     * @package CRM
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
     * @copyright CiviCRM LLC https://civicrm.org/licensing
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
     *
     */
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    // This file must not accessed directly.
    if (!defined('ABSPATH')) {
      exit;
    }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
    /**
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
     * Define CiviCRM_For_WordPress_Basepage Class.
     *
     * @since 4.6
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
     */
    class CiviCRM_For_WordPress_Basepage {
    
      /**
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * @var object
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * Plugin object reference.
       * @since 4.6
       * @access public
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       */
      public $civi;
    
    
      /**
       * @var bool
       * Base Page parsed flag.
       * @since 4.6
       * @access public
       */
      public $basepage_parsed = FALSE;
    
      /**
       * @var string
       * Base Page title.
       * @since 4.6
       * @access public
       */
      public $basepage_title = '';
    
      /**
       * @var string
       * Base Page markup.
       * @since 4.6
       * @access public
       */
      public $basepage_markup = '';
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
      /**
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * Instance constructor.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       *
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * @since 4.6
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       */
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
      public function __construct() {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Store reference to CiviCRM plugin object.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        $this->civi = civi_wp();
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Always listen for activation action.
        add_action('civicrm_activation', [$this, 'activate']);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Always listen for deactivation action.
        add_action('civicrm_deactivation', [$this, 'deactivate']);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Always check if the Base Page needs to be created.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        add_action('civicrm_instance_loaded', [$this, 'maybe_create_basepage']);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
      }
    
      /**
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * Register hooks to handle CiviCRM in a WordPress Base Page context.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       *
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * @since 4.6
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       */
      public function register_hooks() {
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Kick out if not CiviCRM.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        if (!$this->civi->initialize()) {
          return;
        }
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Cache CiviCRM Base Page markup.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        add_action('wp', [$this, 'basepage_handler'], 10, 1);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * Triggers the process whereby the WordPress Base Page is created.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * Sets a one-time-only option to flag that we need to create a Base Page -
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * it will not update the option once it has been set to another value nor
       * create a new option with the same name.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       *
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * As a result of doing this, we know that a Base Page needs to be created,
       * but the moment to do so is once CiviCRM has been successfully installed.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * @see self::maybe_create_basepage()
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       *
       * @since 5.6
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       */
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
      public function activate() {
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Save option.
        add_option('civicrm_activation_create_basepage', 'true');
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
      }
    
      /**
       * Plugin deactivation.
       *
       * @since 5.6
       */
      public function deactivate() {
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Delete option.
        delete_option('civicrm_activation_create_basepage');
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * Auto-creates the WordPress Base Page if necessary.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * Changes the one-time-only option so that the Base Page can only be created
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * once. Thereafter, we're on our own until there's a 'delete_post' callback
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * to prevent the Base Page from being deleted.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       *
       * @since 5.6
       */
      public function maybe_create_basepage() {
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Bail if CiviCRM not installed.
        if (!CIVICRM_INSTALLED) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Bail if not installing.
        if (get_option('civicrm_activation_create_basepage') !== 'true') {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Create the Base Page.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        add_action('wp_loaded', [$this, 'create_wp_basepage']);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Change option so the callback above never runs again.
        update_option('civicrm_activation_create_basepage', 'done');
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * Creates the WordPress Base Page and saves the CiviCRM "wpBasePage" setting.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       *
       * @since 4.6
       * @since 5.6 Relocated from CiviCRM_For_WordPress to here.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * @since 5.44 Returns success or failure.
       *
       * @return bool TRUE if successful, FALSE otherwise.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       */
      public function create_wp_basepage() {
    
        if (!$this->civi->initialize()) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          return FALSE;
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        if (version_compare(CRM_Core_BAO_Domain::getDomain()->version, '4.7.0', '<')) {
          return FALSE;
        }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Bail if we already have a Base Page setting.
        $config = CRM_Core_Config::singleton();
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        if (!empty($config->wpBasePage)) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          return TRUE;
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
         * Filter the default Base Page slug.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
         *
         * @since 4.6
         *
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
         * @param str The default Base Page slug.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        $slug = apply_filters('civicrm_basepage_slug', 'civicrm');
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Get existing Page with that slug.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        $page = get_page_by_path($slug);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Get the ID if the Base Page already exists.
        $result = 0;
        if ($page instanceof WP_Post) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          $result = $page->ID;
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Create the Base Page if it's missing.
        if ($result === 0) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          $result = $this->create_basepage($slug);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Save the Page slug as the setting if we have one.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        if ($result !== 0 && !is_wp_error($result)) {
          $post = get_post($result);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          civicrm_api3('Setting', 'create', [
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
            'wpBasePage' => $post->post_name,
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          ]);
          return TRUE;
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        return FALSE;
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * Create a WordPress page to act as the CiviCRM Base Page.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       *
       * @since 4.6
       * @since 5.6 Relocated from CiviCRM_For_WordPress to here.
       *
       * @param string $slug The unique slug for the page - same as wpBasePage setting.
       * @return int|WP_Error The page ID on success. The value 0 or WP_Error on failure.
       */
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
      private function create_basepage($slug) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // If multisite, switch to main site.
        if (is_multisite() && !is_main_site()) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          /**
           * Allow plugins to override the switch to the main site.
           *
           * This filter changes the default behaviour on WordPress Multisite so
           * that the Base Page *is* created on every site on which CiviCRM is
           * activated. This is a more sensible and inclusive default, since the
           * absence of the Base Page on a sub-site often leads to confusion.
           *
           * To restore the previous functionality, return boolean TRUE.
           *
           * The previous functionality may be the desired behaviour when the
           * WordPress Multisite instance in question is one where sub-sites aren't
           * truly "separate" e.g. sites built on frameworks such as "Commons in
           * a Box" or "MultilingualPress".
           *
           * @since 5.44
           *
           * @param bool False by default prevents the switch to the main site.
           */
          $switch = apply_filters('civicrm/basepage/main_site_only', FALSE);
    
          if ($switch !== FALSE) {
    
            // Store this site.
            $original_site = get_current_blog_id();
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
            // Switch to main site.
            switch_to_blog(get_main_site_id());
    
          }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Define Base Page.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        $page = [
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          'post_status' => 'publish',
          'post_type' => 'page',
          'post_parent' => 0,
          'comment_status' => 'closed',
          'ping_status' => 'closed',
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          // Quick fix for Windows.
          'to_ping' => '',
          // Quick fix for Windows.
          'pinged' => '',
          // Quick fix for Windows.
          'post_content_filtered' => '',
          // Quick fix for Windows.
          'post_excerpt' => '',
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          'menu_order' => 0,
          'post_name' => $slug,
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        ];
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
        /**
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
         * Filter the default Base Page title.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
         *
         * @since 4.6
         *
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
         * @param str The default Base Page title.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        $page['post_title'] = apply_filters('civicrm_basepage_title', __('CiviCRM', 'civicrm'));
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Default content.
        $content = __('Do not delete this page. Page content is generated by CiviCRM.', 'civicrm');
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
         * Filter the default Base Page content.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
         *
         * @since 4.6
         *
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
         * @param str $content The default Base Page content.
         * @return str $content The modified Base Page content.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        $page['post_content'] = apply_filters('civicrm_basepage_content', $content);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Insert the post into the database.
        $page_id = wp_insert_post($page);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Switch back if we've switched.
        if (isset($original_site)) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          restore_current_blog();
        }
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Make sure Rewrite Rules are flushed.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        delete_option('civicrm_rules_flushed');
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        return $page_id;
    
      }
    
      /**
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * Build CiviCRM Base Page content.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * Callback method for 'wp' hook, always called from WordPress front-end.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       *
       * @since 4.6
       *
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * @param object $wp The WordPress object, present but not used.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
      public function basepage_handler($wp) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
         * At this point, all conditional tags are available.
         * @see https://codex.wordpress.org/Conditional_Tags
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
         */
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Bail if this is a 404.
        if (is_404()) {
          return;
        }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Bail if this is a Favicon request.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        if (function_exists('is_favicon') && is_favicon()) {
    
        // Check for the Base Page query conditions.
        $is_basepage_query = FALSE;
        if ($this->civi->civicrm_in_wordpress() && $this->civi->is_page_request()) {
          $is_basepage_query = TRUE;
        }
    
        // Do not proceed without them.
        if (!$is_basepage_query) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Kick out if not CiviCRM.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        if (!$this->civi->initialize()) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          return;
    
        /**
         * Fires before the Base Page is processed.
         *
         * @since 5.66
         */
        do_action('civicrm/basepage/handler/pre');
    
        // Set a "found" flag.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        $basepage_found = FALSE;
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Check permission.
        $denied = TRUE;
        $argdata = $this->civi->get_request_args();
        if ($this->civi->users->check_permission($argdata['args'])) {
          $denied = FALSE;
        }
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Get the Shortcode Mode setting.
        $shortcode_mode = $this->civi->admin->get_shortcode_mode();
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        /*
         * Let's do the_loop.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
         * This has the effect of bypassing the logic in:
         * @see https://github.com/civicrm/civicrm-wordpress/pull/36
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        if (have_posts()) {
          while (have_posts()) {
    
            the_post();
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
            global $post;
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
            /**
             * Allow "Base Page mode" to be forced.
             *
             * Return TRUE to force CiviCRM to render a Post/Page as if on the Base Page.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
             *
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
             * @since 5.44
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
             *
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
             * @param bool By default "Base Page mode" should not be triggered.
             * @param WP_Post $post The current WordPress Post object.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
             */
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
            $basepage_mode = (bool) apply_filters('civicrm_force_basepage_mode', FALSE, $post);
    
    
            // Determine if the current Post is the Base Page.
            $is_basepage = $this->is_match($post->ID);
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
            // Skip when this is not the Base Page or when "Base Page mode" is not forced or not in "legacy mode".
    
            if ($is_basepage || $basepage_mode || $shortcode_mode === 'legacy') {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
              // Set context.
              $this->civi->civicrm_context_set('basepage');
    
              // Start buffering.
              ob_start();
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
              // Now, instead of echoing, Base Page output ends up in buffer.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
              $this->civi->invoke();
              // Save the output and flush the buffer.
              $this->basepage_markup = ob_get_clean();
    
              /*
               * The following logic is in response to some of the complexities of how
               * titles are handled in WordPress, particularly when there are SEO
               * plugins present that modify the title for Open Graph purposes. There
               * have also been issues with the default WordPress themes, which modify
               * the title using the 'wp_title' filter.
               *
               * First, we try and set the title of the page object, which will work
               * if the loop is not run subsequently and if there are no additional
               * filters on the title.
               *
               * Second, we store the CiviCRM title so that we can construct the base
               * page title if other plugins modify it.
               */
    
              // Override post title.
              global $civicrm_wp_title;
              $post->post_title = $civicrm_wp_title;
    
              // Because the above seems unreliable, store title for later use.
              $this->basepage_title = $civicrm_wp_title;
    
              // Disallow commenting.
              $post->comment_status = 'closed';
    
              // Put CiviCRM into "Base Page mode".
              $basepage_found = TRUE;
    
            }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Reset loop.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        rewind_posts();
    
    
        /**
         * Fires after the Base Page may have been processed.
         *
         * @since 5.66
         *
         * @param bool $basepage_found TRUE if the CiviCRM Base Page was found, FALSE otherwise.
         */
        do_action('civicrm/basepage/handler/post', $basepage_found);
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Bail if the Base Page has not been processed.
        if (!$basepage_found) {
          return;
        }
    
        // Hide the edit link.
        add_action('edit_post_link', [$this, 'clear_edit_post_link']);
    
        // Tweak admin bar.
        add_action('wp_before_admin_bar_render', [$this, 'clear_edit_post_menu_item']);
    
        // Add body classes for easier styling.
        add_filter('body_class', [$this, 'add_body_classes']);
    
        // In WordPress 4.6.0+, tell it URL params are part of canonical URL.
        add_filter('get_canonical_url', [$this, 'basepage_canonical_url'], 999);
    
        // Yoast SEO has separate way of establishing canonical URL.
        add_filter('wpseo_canonical', [$this, 'basepage_canonical_url'], 999);
    
        // And also for All in One SEO to handle canonical URL.
        add_filter('aioseop_canonical_url', [$this, 'basepage_canonical_url'], 999);
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Override page title with high priority.
        add_filter('wp_title', [$this, 'wp_page_title'], 100, 3);
        add_filter('document_title_parts', [$this, 'wp_page_title_parts'], 100, 1);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Regardless of URL, load page template.
        add_filter('template_include', [$this, 'basepage_template'], 999);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Show content based on permission.
        if ($denied) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          // Do not show content.
          add_filter('the_content', [$this->civi->users, 'get_permission_denied']);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        }
        else {
    
          // Add core resources for front end.
          add_action('wp', [$this->civi, 'front_end_page_load'], 100);
    
          // Include this content when Base Page is rendered.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          add_filter('the_content', [$this, 'basepage_render'], 21);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Flag that we have parsed the Base Page.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        $this->basepage_parsed = TRUE;
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        /**
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
         * Broadcast that the Base Page is parsed.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
         *
         * @since 4.4
         */
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        do_action('civicrm_basepage_parsed');
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * Get CiviCRM Base Page title for <title> element.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       *
       * Callback method for 'wp_title' hook, called at the end of function wp_title.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       *
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * @since 4.6
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       *
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * @param string $title Title that might have already been set.
       * @param string $separator Separator determined in theme (but defaults to WordPress default).
       * @param string $separator_location Whether the separator should be left or right.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       */
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
      public function wp_page_title($title, $separator = '&raquo;', $separator_location = '') {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // If feed, return just the title.
        if (is_feed()) {
          return $this->basepage_title;
        }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Set default separator location, if it isn't defined.
        if ('' === trim($separator_location)) {
          $separator_location = (is_rtl()) ? 'left' : 'right';
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // If we have WP SEO present, use its separator.
        if (class_exists('WPSEO_Options')) {
          $separator_code = WPSEO_Options::get_default('wpseo_titles', 'separator');
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          $separator_array = WPSEO_Option_Titles::get_instance()->get_separator_options();
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          if (array_key_exists($separator_code, $separator_array)) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
            $separator = $separator_array[$separator_code];
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Construct title depending on separator location.
    
        if ($separator_location === 'right') {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          $title = $this->basepage_title . " $separator " . get_bloginfo('name', 'display');
        }
        else {
          $title = get_bloginfo('name', 'display') . " $separator " . $this->basepage_title;
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Return modified title.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        return $title;
    
      }
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * Get CiviCRM Base Page title for <title> element.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       *
       * Callback method for 'document_title_parts' hook. This filter was introduced
       * in WordPress 3.8 but it depends on whether the theme has implemented that
       * method for generating the title or not.
       *
       * @since 5.14
       *
       * @param array $parts The existing title parts.
       * @return array $parts The modified title parts.
       */
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
      public function wp_page_title_parts($parts) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Override with CiviCRM's title.
        if (isset($parts['title'])) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          $parts['title'] = $this->basepage_title;
        }
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Return modified title parts.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
      /**
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * Get CiviCRM Base Page content.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * Callback method for 'the_content' hook, always called from WordPress
       * front-end.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       *
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * @since 4.6
       *
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * @return str $basepage_markup The Base Page markup.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       */
      public function basepage_render() {
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Hand back our Base Page markup.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        return $this->basepage_markup;
    
      }
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * Provide the canonical URL for a page accessed through a Base Page.
    
       *
       * WordPress will default to saying the canonical URL is the URL of the base
       * page itself, but we need to indicate that in this case, the whole thing
       * matters.
       *
       * Note: this function is used for three different but similar hooks:
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       *  - `get_canonical_url` (WordPress 4.6.0+)
    
       *  - `aioseop_canonical_url` (All in One SEO)
       *  - `wpseo_canonical` (Yoast WordPress SEO)
       *
       * The native WordPress one passes the page object, while the other two do
       * not.  We don't actually need the page object, so the argument is omitted
       * here.
       *
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * @since 4.6
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * @param string $canonical The canonical URL.
       * @return string The complete URL to the page as it should be accessed.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
      public function basepage_canonical_url($canonical) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Access CiviCRM config object.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        $config = CRM_Core_Config::singleton();
    
    
        // None of the following needs a nonce check.
        // phpcs:disable WordPress.Security.NonceVerification.Recommended
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Retain old logic when not using clean URLs.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        if (!$config->cleanURL) {
    
    
          $civiwp = empty($_GET['civiwp']) ? '' : sanitize_text_field(wp_unslash($_GET['civiwp']));
          $q = empty($_GET['q']) ? '' : sanitize_text_field(wp_unslash($_GET['q']));
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          /*
           * It would be better to specify which params are okay to accept as the
           * canonical URLs, but this will work for the time being.
           */
    
          if (empty($civiwp)
            || 'CiviCRM' !== $civiwp
            || empty($q)) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
            return $canonical;
          }
    
          $path = $q;
          unset($q, $_GET['q'], $civiwp, $_GET['civiwp']);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          $query = http_build_query($_GET);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
        }
        else {
    
          $argdata = $this->civi->get_request_args();
          $path = $argdata['argString'];
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          $query = http_build_query($_GET);
    
        // phpcs:enable WordPress.Security.NonceVerification.Recommended
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        /*
         * We should, however, build the URL the way that CiviCRM expects it to be
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
         * (rather than through some other funny Base Page).
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
         */
        return CRM_Utils_System::url($path, $query);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
      /**
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * Get CiviCRM Base Page template.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * Callback method for 'template_include' hook, always called from WordPress
       * front-end.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       *
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * @since 4.6
       *
       * @param string $template The path to the existing template.
       * @return string $template The modified path to the desired template.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       */
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
      public function basepage_template($template) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Get template path relative to the theme's root directory.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        $template_name = str_replace(trailingslashit(get_stylesheet_directory()), '', $template);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
        // If the above fails, try parent theme.
    
        if ($template_name === $template) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          $template_name = str_replace(trailingslashit(get_template_directory()), '', $template);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        }
    
        // Bail in the unlikely event that the template name has not been found.
    
        if ($template_name === $template) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          return $template;
        }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        /**
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
         * Allow Base Page template to be overridden.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
         *
         * In most cases, the logic will not progress beyond here. Shortcodes in
         * posts and pages will have a template set, so we leave them alone unless
         * specifically overridden by the filter.
         *
         * @since 4.6
         *
         * @param string $template_name The provided template name.
         */
        $basepage_template = apply_filters('civicrm_basepage_template', $template_name);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Find the Base Page template.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        $page_template = locate_template([$basepage_template]);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // If not homepage and template is found.
    
        if (!is_front_page() && !empty($page_template)) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
          return $page_template;
        }
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        /**
         * Override the template, but allow plugins to amend.
         *
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
         * This filter handles the scenario where no Base Page has been set, in
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
         * which case CiviCRM will try to load its content in the site's homepage.
         * Many themes, however, do not have a call to "the_content()" on the
         * homepage - it is often used as a gateway page to display widgets,
         * archives and so forth.
         *
         * Be aware that if the homepage is set to show latest posts, then this
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
         * template override will not have the desired effect. A Base Page *must*
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
         * be set if this is the case.
         *
         * @since 4.6
         *
         * @param string The template name (set to the default page template).
         */
        $home_template_name = apply_filters('civicrm_basepage_home_template', 'page.php');
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Find the homepage template.
        $home_template = locate_template([$home_template_name]);
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Use it if found.
    
        if (!empty($home_template)) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Fall back to provided template.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        return $template;
    
      }
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * Add classes to body element when on Base Page.
    
       *
       * This allows selectors to be written for particular CiviCRM "pages" despite
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * them all being rendered on the one WordPress Base Page.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
       * @since 4.7.18
       *
       * @param array $classes The existing body classes.
       * @return array $classes The modified body classes.
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
      public function add_body_classes($classes) {
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        $args = $this->civi->get_request_args();
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Bail if we don't have any.
        if (is_null($args['argString'])) {
          return $classes;
        }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Check for top level - it can be assumed this always 'civicrm'.
        if (isset($args['args'][0]) && !empty($args['args'][0])) {
          $classes[] = $args['args'][0];
        }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Check for second level - the component.
        if (isset($args['args'][1]) && !empty($args['args'][1])) {
          $classes[] = $args['args'][0] . '-' . $args['args'][1];
        }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
    
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Check for third level - the component's configuration.
        if (isset($args['args'][2]) && !empty($args['args'][2])) {
          $classes[] = $args['args'][0] . '-' . $args['args'][1] . '-' . $args['args'][2];
        }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        // Check for fourth level - because well, why not?
        if (isset($args['args'][3]) && !empty($args['args'][3])) {
          $classes[] = $args['args'][0] . '-' . $args['args'][1] . '-' . $args['args'][2] . '-' . $args['args'][3];
        }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
        return $classes;
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
      /**
       * Remove edit link from page content.
       *
       * Callback from 'edit_post_link' hook.
       *
       * @since 4.6
       * @since 5.33 Moved to this class.
       *
       * @return string Always empty.
       */
      public function clear_edit_post_link() {
        return '';
      }
    
      /**
       * Remove edit link in WordPress Admin Bar.
       *
       * Callback from 'wp_before_admin_bar_render' hook.
       *
       * @since 4.6
       */
      public function clear_edit_post_menu_item() {
    
        // Access object.
        global $wp_admin_bar;
    
        // Bail if in admin.
        if (is_admin()) {
          return;
        }
    
        // Remove the menu item from front end.
        $wp_admin_bar->remove_menu('edit');
    
      }
    
    Kevin Cristiano's avatar
    Kevin Cristiano committed
      /**
       * Gets the current Base Page object.
       *
       * @since 5.44
       *
       * @return WP_Post|bool The Base Page object or FALSE on failure.
       */
      public function basepage_get() {
    
    
        // Bail if CiviCRM not bootstrapped.
        if (!$this->civi->initialize()) {
          return FALSE;
        }
    
        // Get config.
        $config = CRM_Core_Config::singleton();
    
        // Get Base Page object.
        $basepage = get_page_by_path($config->wpBasePage);
        if (is_null($basepage) || !($basepage instanceof WP_Post)) {
          return FALSE;
        }
    
        /**
         * Filters the CiviCRM Base Page object.
         *
         * @since 5.66
         *
         * @param WP_Post $basepage The CiviCRM Base Page object.
         */
        return apply_filters('civicrm/basepage', $basepage);
    
      }
    
    
      /**
       * Gets a URL that points to the CiviCRM Base Page.
       *
       * There can be situations where `CRM_Utils_System::url` does not return
       * a link to the Base Page, e.g. in a page template where the content
       * contains a Shortcode. This utility method will always return a URL
       * that points to the CiviCRM Base Page.
       *
       * @see https://lab.civicrm.org/dev/wordpress/-/issues/144
       *
       * @since 5.69
       *
       * @param string $path The path being linked to, such as "civicrm/add".
       * @param array|string $query A query string to append to the link, or an array of key-value pairs.
       * @param bool $absolute Whether to force the output to be an absolute link.
       * @param string $fragment A fragment identifier (named anchor) to append to the link.
       * @param bool $htmlize Whether to encode special html characters such as &.
       * @return string $link An HTML string containing a link to the given path.
       */
      public function url(
        $path = '',
        $query = '',
        $absolute = TRUE,
        $fragment = NULL,
        $htmlize = TRUE
      ) {
    
        // Return early if no CiviCRM.
        $link = '';
        if (!$this->civi->initialize()) {
          return $link;
        }
    
        // Add modifying callbacks prior to multi-lingual compat.
        add_filter('civicrm/basepage/match', [$this, 'ensure_match'], 9);
        add_filter('civicrm/core/url/base', [$this, 'ensure_url'], 9, 2);
    
        // Pass to CiviCRM to construct front-end URL.
        $link = CRM_Utils_System::url(
          $path,
          $query,
          TRUE,
          $fragment,
          $htmlize,
          TRUE,
          FALSE
        );
    
        // Remove callbacks.
        remove_filter('civicrm/basepage/match', [$this, 'ensure_match'], 9);
        remove_filter('civicrm/core/url/base', [$this, 'ensure_url'], 9);
    
        return $link;
    
      }
    
      /**
       * Callback to ensure CiviCRM returns a Base Page URL.
       *
       * @since 5.69
       *
       * @return bool
       */
      public function ensure_match() {
        return TRUE;
      }
    
      /**
       * Callback to ensure CiviCRM builds a Base Page URL.
       *
       * @since 5.69
       *
       * @param str $url The "base" URL as built by CiviCRM.
       * @param bool $admin_request True if building an admin URL, false otherwise.
       * @return str $url The Base Page URL.
       */
      public function ensure_url($url, $admin_request) {
    
        // Skip when not defined.
        if (empty($url) || $admin_request) {
          return $url;
        }
    
        // Return the Base Page URL.
        return $this->url_get();
    
      }
    
    
      /**
       * Gets the current Base Page ID.
       *
       * @since 5.66
       *
       * @return int|bool The Base Page ID or FALSE on failure.
       */
      public function id_get() {
    
        // Get the Base Page object.
        $basepage = $this->basepage_get();
        if (!($basepage instanceof WP_Post)) {
          return FALSE;
        }
    
        return $basepage->ID;
    
      }
    
      /**
       * Gets the current Base Page URL.
       *
       * @since 5.66
       *
       * @return str The Base Page URL or empty on failure.
       */
      public function url_get() {
    
        // Get the Base Page object.
        $basepage = $this->basepage_get();
        if (!($basepage instanceof WP_Post)) {
          return '';
        }