<?php /* +--------------------------------------------------------------------+ | CiviCRM version 4.7 | +--------------------------------------------------------------------+ | Copyright CiviCRM LLC (c) 2004-2018 | +--------------------------------------------------------------------+ | 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 * @copyright CiviCRM LLC (c) 2004-2018 * */ // this file must not accessed directly if ( ! defined( 'ABSPATH' ) ) exit; /** * Define CiviCRM_For_WordPress_Shortcodes Class */ class CiviCRM_For_WordPress_Shortcodes { /** * Declare our properties */ // init property to store reference to Civi public $civi; // init property to store shortcodes public $shortcodes = array(); // init property to store shortcode markup public $shortcode_markup = array(); // count multiple passes of do_shortcode in a post public $shortcode_in_post = array(); /** * Instance constructor * * @return object $this The object instance */ function __construct() { // store reference to Civi object $this->civi = civi_wp(); } /** * Register hooks to handle the presence of shortcodes in content * * @return void */ public function register_hooks() { // register the CiviCRM shortcode add_shortcode( 'civicrm', array( $this, 'render_single' ) ); // add CiviCRM core resources when a shortcode is detected in the post content add_action( 'wp', array( $this, 'prerender' ), 10, 1 ); } /** * Determine if a CiviCRM shortcode is present in any of the posts about to be displayed * Callback method for 'wp' hook, always called from WP front-end * * @param object $wp The WP object, present but not used * @return void */ public function prerender( $wp ) { /** * At this point, all conditional tags are available * @see http://codex.wordpress.org/Conditional_Tags */ // bail if this is a 404 if ( is_404() ) return; // a counter's useful $shortcodes_present = 0; // let's loop through the results // this also has the effect of bypassing the logic in // https://github.com/civicrm/civicrm-wordpress/pull/36 if ( have_posts() ) { while ( have_posts() ) : the_post(); global $post; // check for existence of shortcode in content if ( has_shortcode( $post->post_content, 'civicrm' ) ) { // get CiviCRM shortcodes in this post $shortcodes_array = $this->get_for_post( $post->post_content ); // sanity check if ( !empty( $shortcodes_array ) ) { // add it to our property $this->shortcodes[$post->ID] = $shortcodes_array; // bump shortcode counter $shortcodes_present += count( $this->shortcodes[$post->ID] ); } } endwhile; } // reset loop rewind_posts(); // did we get any? if ( $shortcodes_present ) { // we need CiviCRM initialised prior to parsing shortcodes if (!$this->civi->initialize()) { return; } // how should we handle multiple shortcodes? if ( $shortcodes_present > 1 ) { // add CSS resources for front end add_action( 'wp_enqueue_scripts', array( $this->civi, 'front_end_css_load' ), 100 ); // let's add dummy markup foreach( $this->shortcodes AS $post_id => $shortcode_array ) { // set flag if there are multple shortcodes in this post $multiple = ( count( $shortcode_array ) > 1 ) ? 1 : 0; foreach( $shortcode_array AS $shortcode ) { // mimic invoke in multiple shortcode context $this->shortcode_markup[$post_id][] = $this->render_multiple( $post_id, $shortcode, $multiple ); } } } else { // add core resources for front end add_action( 'wp', array( $this->civi, 'front_end_page_load' ), 100 ); // since we have only one shortcode, run the_loop again // the DB query has already been done, so this has no significant impact if ( have_posts() ) { while ( have_posts() ) : the_post(); global $post; // is this the post? if ( ! array_key_exists( $post->ID, $this->shortcodes ) ) { continue; } // the shortcode must be the first item in the shortcodes array $shortcode = $this->shortcodes[$post->ID][0]; // check to see if a shortcode component has been repeated? $atts = $this->get_atts( $shortcode ); // test for hijacking if ( isset( $atts['hijack'] ) AND $atts['hijack'] == '1' ) { add_filter( 'civicrm_context', array( $this, 'get_context' ) ); } // store corresponding markup $this->shortcode_markup[$post->ID][] = do_shortcode( $shortcode ); // test for hijacking if ( isset( $atts['hijack'] ) AND $atts['hijack'] == '1' ) { // ditch the filter remove_filter( 'civicrm_context', array( $this, 'get_context' ) ); // set title global $civicrm_wp_title; $post->post_title = $civicrm_wp_title; // override page title add_filter( 'single_post_title', array( $this->civi, 'single_page_title' ), 50, 2 ); // overwrite content add_filter( 'the_content', array( $this, 'get_content' ) ); } endwhile; } // reset loop rewind_posts(); } } // flag that we have parsed shortcodes $this->shortcodes_parsed = TRUE; // broadcast this as well do_action( 'civicrm_shortcodes_parsed' ); } /** * Handles CiviCRM-defined shortcodes * * @param array Shortcode attributes array * @return string HTML for output */ public function render_single( $atts ) { // Do not parse shortcodes in REST context for PUT, POST and DELETE methods if(defined('REST_REQUEST') && REST_REQUEST && (isset($_PUT) || isset($_POST) || isset($_DELETE)) ){ // Return the original shortcode $shortcode = '[civicrm'; foreach($atts as $att=>$val){ $shortcode.=' '.$att.'="'.$val.'"'; } $shortcode.=']'; return $shortcode; } // check if we've already parsed this shortcode global $post; if ( is_object($post) ) { if ( !empty( $this->shortcode_markup ) ) { if ( isset( $this->shortcode_markup[$post->ID] ) ) { // set counter flag if ( ! isset( $this->shortcode_in_post[$post->ID] ) ) { $this->shortcode_in_post[$post->ID] = 0; } else { $this->shortcode_in_post[$post->ID]++; } // this shortcode must have been rendered return $this->shortcode_markup[$post->ID][$this->shortcode_in_post[$post->ID]]; } } } // preprocess shortcode attributes $args = $this->preprocess_atts( $atts ); // invoke() requires environment variables to be set foreach ( $args as $key => $value ) { if ( $value !== NULL ) { $_REQUEST[$key] = $_GET[$key] = $value; } } // kick out if not CiviCRM if (!$this->civi->initialize()) { return ''; } // check permission $argdata = $this->civi->get_request_args(); if ( ! $this->civi->users->check_permission( $argdata['args'] ) ) { return $this->civi->users->get_permission_denied();; } // CMW: why do we need this? Nothing that follows uses it... require_once ABSPATH . WPINC . '/pluggable.php'; ob_start(); // start buffering $this->civi->invoke(); // now, instead of echoing, shortcode output ends up in buffer $content = ob_get_clean(); // save the output and flush the buffer return $content; } /** * Return a generic display for a shortcode instead of a CiviCRM invocation * * @param int $post_id The containing WordPress post ID * @param string $shortcode The shortcode being parsed * @param bool $multiple Boolean flag, TRUE if post has multiple shortcodes, FALSE otherwise * @return string $markup Generic markup for multiple instances */ private function render_multiple( $post_id = FALSE, $shortcode = FALSE, $multiple = 0 ) { // get attributes $atts = $this->get_atts( $shortcode ); // pre-process shortcode and retrieve args $args = $this->preprocess_atts( $atts ); // get data for this shortcode $data = $this->get_data( $atts, $args ); // sanity check if ( $data === FALSE ) return ''; // did we get a title? $title = __( 'Content via CiviCRM', 'civicrm' ); if ( ! empty( $data['title'] ) ) $title = $data['title']; // init title flag $show_title = TRUE; // default link $link = get_permalink( $post_id ); // default to no class $class = ''; // access CIvi config object $config = CRM_Core_Config::singleton(); // do we have multiple shortcodes? if ( $multiple != 0 ) { $links = array(); foreach( $args AS $var => $arg ) { if ( ! empty( $arg ) AND $var != 'q' ) { $links[] = $var . '=' . $arg; } } $query = implode( '&', $links ); // $absolute, $frontend, $forceBackend $base_url = $this->civi->get_base_url(TRUE, FALSE, FALSE); // construct query parts $queryParts = array(); $queryParts[] = 'page=CiviCRM'; if (isset($args['q'])) { $queryParts[] = 'q=' . $args['q']; } if (isset($query)) { $queryParts[] = $query; } // construct link $link = trailingslashit( $base_url ) . '?' . implode('&', $queryParts); // add a class for styling purposes $class = ' civicrm-shortcode-multiple'; } // test for hijacking if ( !$multiple ) { if ( isset( $atts['hijack'] ) AND $atts['hijack'] == '1' ) { // add title to array $this->post_titles[$post_id] = $data['title']; // override title add_filter( 'the_title', array( $this, 'get_title' ), 100, 2 ); // overwrite content add_filter( 'the_content', array( $this, 'get_content' ) ); // don't show title $show_title = FALSE; // add a class for styling purposes $class = ' civicrm-shortcode-single'; } } // set some template variables // description $description = FALSE; if ( isset( $data['text'] ) AND ! empty( $data['text'] ) ) { $description = $data['text']; } // provide an enticing link $more_link = sprintf( '<a href="%s">%s</a>', $link, apply_filters( 'civicrm_shortcode_more_link', __( 'Find out more...', 'civicrm' ) ) ); // assume CiviCRM footer is not enabled $empowered_enabled = FALSE; $footer = ''; // test config object for setting if ( $config->empoweredBy == 1 ) { // footer enabled - define it $civi = __( 'CiviCRM.org - Growing and Sustaining Relationships', 'civicrm' ); $logo = '<div class="empowered-by-logo"><span>' . __( 'CiviCRM', 'civicrm' ) . '</span></div>'; $civi_link = '<a href="http://civicrm.org/" title="' . $civi . '" target="_blank" class="empowered-by-link">' . $logo . '</a>'; $empowered = sprintf( __( 'Empowered by %s', 'civicrm' ), $civi_link ); $footer = apply_filters( 'civicrm_shortcode_footer', $empowered ); $empowered_enabled = TRUE; } // start buffering ob_start(); // include template include( CIVICRM_PLUGIN_DIR . 'assets/templates/civicrm.shortcode.php' ); // save the output and flush the buffer $markup = ob_get_clean(); // allow plugins to override return apply_filters( 'civicrm_shortcode_render_multiple', $markup, $post_id, $shortcode ); } /** * In order to hijack the page, we need to override the context * * @return string Overridden context code */ public function get_context() { return 'nonpage'; } /** * In order to hijack the page, we need to override the content * * @return string Overridden content */ public function get_content( $content ) { global $post; // is this the post? if ( ! array_key_exists( $post->ID, $this->shortcode_markup ) ) { return $content; } // bail if it has multiple shortcodes if ( count( $this->shortcode_markup[$post->ID] ) > 1 ) { return $content; } return $this->shortcode_markup[$post->ID][0]; } /** * In order to hijack the page, we need to override the title * * @return string Overridden title */ public function get_title( $title, $post_id ) { // is this the post? if ( ! array_key_exists( $post_id, $this->shortcode_markup ) ) { return $title; } // bail if it has multiple shortcodes if ( count( $this->shortcode_markup[$post_id] ) > 1 ) { return $title; } // shortcodes may or may not override title if ( array_key_exists( $post_id, $this->post_titles ) ) { $title = $this->post_titles[$post_id]; } return $title; } /** * Detect and return CiviCRM shortcodes in post content * * @param $content The content to parse * @return array $shortcodes Array of shortcodes */ private function get_for_post( $content ) { // init return array $shortcodes = array(); // attempt to discover all instances of the shortcode $pattern = get_shortcode_regex(); if ( preg_match_all( '/' . $pattern . '/s', $content, $matches ) && array_key_exists( 2, $matches ) && in_array( 'civicrm', $matches[2] ) ) { // get keys for our shortcode $keys = array_keys( $matches[2], 'civicrm' ); foreach( $keys AS $key ) { $shortcodes[] = $matches[0][$key]; } } return $shortcodes; } /** * Return attributes for a given CiviCRM shortcode * * @param $shortcode The shortcode to parse * @return array $shortcode_atts Array of shortcode attributes */ private function get_atts( $shortcode ) { // strip all but attributes definitions $text = str_replace( '[civicrm ', '', $shortcode ); $text = str_replace( ']', '', $text ); // extract attributes $shortcode_atts = shortcode_parse_atts( $text ); return $shortcode_atts; } /** * Preprocess CiviCRM-defined shortcodes * * @param array $atts Shortcode attributes array * @return array $args Shortcode arguments array */ public function preprocess_atts( $atts ) { $shortcode_atts = shortcode_atts( array( 'component' => 'contribution', 'action' => NULL, 'mode' => NULL, 'id' => NULL, 'cid' => NULL, 'gid' => NULL, 'cs' => NULL, 'force' => NULL, ), $atts, 'civicrm' ); extract( $shortcode_atts ); $args = array( 'reset' => 1, 'id' => $id, 'force' => $force, ); switch ( $component ) { case 'contribution': if ( $mode == 'preview' || $mode == 'test' ) { $args['action'] = 'preview'; } $args['q'] = 'civicrm/contribute/transact'; break; case 'event': switch ( $action ) { case 'register': $args['q'] = 'civicrm/event/register'; if ( $mode == 'preview' || $mode == 'test' ) { $args['action'] = 'preview'; } break; case 'info': $args['q'] = 'civicrm/event/info'; $_REQUEST['page'] = $_GET['page'] = 'CiviCRM'; break; default: echo '<p>' . __( 'Do not know how to handle this shortcode', 'civicrm' ) . '</p>'; return; } break; case 'user-dashboard': $args['q'] = 'civicrm/user'; unset( $args['id'] ); break; case 'profile': if ($mode == 'edit') { $args['q'] = 'civicrm/profile/edit'; } elseif ($mode == 'view') { $args['q'] = 'civicrm/profile/view'; } elseif ($mode == 'search') { $args['q'] = 'civicrm/profile'; } else { $args['q'] = 'civicrm/profile/create'; } $args['gid'] = $gid; break; case 'petition': $args['q'] = 'civicrm/petition/sign'; $args['sid'] = $args['id']; unset($args['id']); break; default: echo '<p>' . __( 'Do not know how to handle this shortcode', 'civicrm' ) . '</p>'; return; } /** * Filter the CiviCRM shortcode arguments. * * This filter allows plugins or CiviExtensions to modify the attributes * that the 'civicrm' shortcode allows. Injected attributes and their values * will also become available in the $_REQUEST and $_GET arrays. * * @param array $args Existing shortcode arguments * @param array $shortcode_atts Shortcode attributes * @return array $args Modified shortcode arguments */ return apply_filters( 'civicrm_shortcode_preprocess_atts', $args, $shortcode_atts ); } /** * Post-process CiviCRM-defined shortcodes * * @param array $atts Shortcode attributes array * @param array $args Shortcode arguments array * @return array|bool $data The array data used to build the shortcode markup (or false on failure) */ public function get_data( $atts, $args ) { // init return array $data = array(); if (!$this->civi->initialize()) { return FALSE; } /** * Filter the base CiviCRM API parameters. * * This filter allows plugins or CiviExtensions to modify the API call when * there are multiple shortcodes being rendered. * * @param array $params Existing API params * @param array $atts Shortcode attributes array * @param array $args Shortcode arguments array * @return array $params Modified API params */ $params = apply_filters( 'civicrm_shortcode_api_params', array( 'version' => 3, 'page' => 'CiviCRM', 'q' => 'civicrm/ajax/rest', 'sequential' => '1', ), $atts, $args ); // get the Civi entity via the API switch ( $atts['component'] ) { case 'contribution': // add event ID $params['id'] = $args['id']; // call API $civi_entity = civicrm_api( 'contribution_page', 'getsingle', $params ); // set title $data['title'] = $civi_entity['title']; // set text, if present $data['text'] = ''; if ( isset( $civi_entity['intro_text'] ) ) { $data['text'] = $civi_entity['intro_text']; } break; case 'event': // add event ID $params['id'] = $args['id']; // call API $civi_entity = civicrm_api( 'event', 'getsingle', $params ); // set title switch ( $atts['action'] ) { case 'register': $data['title'] = sprintf( __( 'Register for %s', 'civicrm' ), $civi_entity['title'] ); break; case 'info': default: $data['title'] = $civi_entity['title']; break; } // set text, if present $data['text'] = ''; if ( isset( $civi_entity['summary'] ) ) { $data['text'] = $civi_entity['summary']; } if ( // summary is not present or is empty ( !isset($civi_entity['summary']) OR empty($civi_entity['summary']) ) AND // we do have a description isset( $civi_entity['description'] ) AND !empty( $civi_entity['description'] ) ) { // override with description $data['text'] = $civi_entity['description']; } break; case 'user-dashboard': // set title $data['title'] = __( 'Dashboard', 'civicrm' ); break; case 'profile': // add event ID $params['id'] = $args['gid']; // call API $civi_entity = civicrm_api( 'uf_group', 'getsingle', $params ); // set title $data['title'] = $civi_entity['title']; // set text to empty $data['text'] = ''; break; case 'petition': // add petition ID $params['id'] = $atts['id']; // call API $civi_entity = civicrm_api( 'survey', 'getsingle', $params ); // set title $data['title'] = $civi_entity['title']; // set text, if present $data['text'] = ''; if ( isset( $civi_entity['instructions'] ) ) { $data['text'] = $civi_entity['instructions']; } break; default: // do we need to protect against malformed shortcodes? break; } /** * Filter the CiviCRM shortcode data array. * * This filter allows plugins or CiviExtensions to modify the data used to * display the shortcode when there are multiple shortcodes being rendered. * * @param array $data Existing shortcode data * @param array $atts Shortcode attributes array * @param array $args Shortcode arguments array * @return array $data Modified shortcode data */ return apply_filters( 'civicrm_shortcode_get_data', $data, $atts, $args ); } } // class CiviCRM_For_WordPress_Shortcodes ends