<?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; } /** * Define CiviCRM_For_WordPress_Shortcodes Class. * * @since 4.6 */ class CiviCRM_For_WordPress_Shortcodes { /** * @var object * Plugin object reference. * @since 4.6 * @access public */ public $civi; /** * @var array * The stored Shortcodes. * @since 4.6 * @access public */ public $shortcodes = []; /** * @var array * The array of rendered Shortcode markup. * @since 4.6 * @access public */ public $shortcode_markup = []; /** * @var array * Count multiple passes of do_shortcode in a post. * @since 4.6 * @access public */ public $shortcode_in_post = []; /** * @var array * Simple flag to note that Shortcode parsing has occurred. * @since 4.6 * @access public */ public $shortcodes_parsed = FALSE; /** * Instance constructor. * * @since 4.6 */ public function __construct() { // Store reference to CiviCRM plugin object. $this->civi = civi_wp(); } /** * Register hooks to handle the presence of Shortcodes in content. * * @since 4.6 */ public function register_hooks() { // Register the CiviCRM Shortcode. add_shortcode('civicrm', [$this, 'render_single']); // Pre-render CiviCRM content when one or more Shortcodes are detected. add_action('wp', [$this, 'prerender'], 20, 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 WordPress front-end. * * @since 4.6 * * @param object $wp The WordPress object, present but not used. */ public function prerender($wp) { /* * At this point, all conditional tags are available * @see https://codex.wordpress.org/Conditional_Tags */ // Bail if this is a 404. if (is_404()) { return; } // Bail if this is a Favicon request. if (function_exists('is_favicon') && is_favicon()) { return; } /** * Filter the Shortcode Components that do not invoke CiviCRM. * * Shortcodes for Components such as Afform do load CiviCRM resources but do * not have a CiviCRM path and are not rendered via the `invoke()` method. * We can allow multiple instances of these Shortcodes in a single page load. * * @since 5.56 * * @param array $components The array of Components that do not invoke CiviCRM. */ $components = apply_filters('civicrm_no_invoke_shortcode_components', ['afform']); // Track the Shortcodes for Components that do and do not invoke CiviCRM. $no_invoke_shortcodes = []; $total_no_invoke_shortcodes = 0; $invoke_shortcodes = []; $total_invoke_shortcodes = 0; // Track the total number of CiviCRM Shortcodes. $total_shortcodes = 0; /* * Let's loop through the results. * This also has the effect of bypassing the logic in: * @see https://github.com/civicrm/civicrm-wordpress/pull/36 */ if (have_posts()) { while (have_posts()) { the_post(); global $post; /** * Filters the Post content. * * @since 5.75 * * @param string $post_content The Post content. * @param WP_Post $post The WordPress Post object. */ $post->post_content = apply_filters('civicrm_prerender_post_content', $post->post_content, $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 collection of Shortcodes. $this->shortcodes[$post->ID] = $shortcodes_array; // Bump Shortcode counter. $total_shortcodes += count($this->shortcodes[$post->ID]); // Check for Components that do not invoke CiviCRM. foreach ($shortcodes_array as $key => $shortcode) { $atts = $this->get_atts($shortcode); if (!empty($atts['component']) && in_array($atts['component'], $components)) { $no_invoke_shortcodes[$post->ID][$key] = $shortcode; $total_no_invoke_shortcodes++; } else { $invoke_shortcodes[$post->ID][$key] = $shortcode; $total_invoke_shortcodes++; } } } } } } // Reset loop. rewind_posts(); // Bail if there are no Shortcodes. if ($total_shortcodes === 0) { return; } // Set context. $this->civi->civicrm_context_set('shortcode'); // We need CiviCRM initialised prior to parsing Shortcodes. if (!$this->civi->initialize()) { return; } if ($total_invoke_shortcodes === 1) { /* * 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, $invoke_shortcodes)) { continue; } // The Shortcode must be the item in the Shortcodes array. $shortcode = reset($invoke_shortcodes[$post->ID]); $key = key($invoke_shortcodes[$post->ID]); // Check to see if a Shortcode component has been repeated? $atts = $this->get_atts($shortcode); // Test for hijacking. if (isset($atts['hijack']) && 1 === (int) $atts['hijack']) { add_filter('civicrm_context', [$this, 'get_context']); } // Store corresponding markup. $this->shortcode_markup[$post->ID][$key] = do_shortcode($shortcode); // Test for hijacking. if (isset($atts['hijack']) && 1 === (int) $atts['hijack']) { // Ditch the filter. remove_filter('civicrm_context', [$this, 'get_context']); // Set title. global $civicrm_wp_title; $post->post_title = $civicrm_wp_title; // Override page title. add_filter('single_post_title', [$this, 'single_page_title'], 50, 2); // Overwrite content. add_filter('the_content', [$this, 'get_content']); } } } // Reset loop. rewind_posts(); } // How should we handle multiple non-invoking Shortcodes? if ($total_no_invoke_shortcodes > 0) { // Let's render Shortcodes that do not invoke CiviCRM. foreach ($no_invoke_shortcodes as $post_id => $shortcode_array) { // Set flag if there are multiple Shortcodes in this post. $multiple = (count($shortcode_array) > 1) ? 1 : 0; foreach ($shortcode_array as $key => $shortcode) { // Mimic invoke in multiple Shortcode context. $this->shortcode_markup[$post_id][$key] = $this->render_multiple($post_id, $shortcode, $multiple); } } } // How should we handle multiple invoking Shortcodes? if ($total_invoke_shortcodes > 1) { // Let's add dummy markup for Shortcodes that invoke CiviCRM. foreach ($invoke_shortcodes as $post_id => $shortcode_array) { // Set flag if there are multiple Shortcodes in this post. $multiple = (count($shortcode_array) > 1) ? 1 : 0; foreach ($shortcode_array as $key => $shortcode) { // Mimic invoke in multiple Shortcode context. $this->shortcode_markup[$post_id][$key] = $this->render_multiple($post_id, $shortcode, $multiple); } } } // A single Shortcode and any pathless Shortcodes need CiviCRM resources. if ($total_no_invoke_shortcodes > 0 || $total_shortcodes === 1) { // Add CiviCRM resources for front end. add_action('wp', [$this->civi, 'front_end_page_load'], 100); } // Multiple invoking Shortcodes need the CiviCRM CSS file. if ($total_invoke_shortcodes > 1) { // Add CSS resources for front end. add_action('wp_enqueue_scripts', [$this->civi, 'front_end_css_load'], 100); } // Flag that we have parsed Shortcodes. $this->shortcodes_parsed = TRUE; /** * Broadcast that Shortcodes have been parsed. * * @since 4.6 */ do_action('civicrm_shortcodes_parsed'); } /** * Handles CiviCRM-defined Shortcodes. * * @since 4.6 * * @param array $atts Shortcode attributes array. * @return string HTML for output. */ public function render_single($atts) { global $post; // Do not parse Shortcodes in REST context for PUT, POST and DELETE methods. // Nonce is not necessary here. // phpcs:ignore WordPress.Security.NonceVerification.Missing 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; } // Get the Shortcode Theme Compatibility setting. $theme_mode = $this->civi->admin->get_theme_compatibility_mode(); // Check the chosen theme compatibility scenario. if ($theme_mode === 'loop') { $theme_check = in_the_loop(); } else { $theme_check = ('the_content' === current_filter()) ? TRUE : FALSE; } /** * Filters the chosen theme compatibility check. * * If neither 'loop' nor 'filter' work for your theme, you can implement a custom * theme compatibility check using this filter. You will most likely have to dig * into how your theme renders content to find out what check to implement. * * @since 5.80 * * @param bool $theme_check True if the check has passed, false otherwise. * @param bool $theme_mode The Shortcode Theme Compatibility setting. */ $theme_check = apply_filters('civicrm_theme_compatibility_mode', $theme_check, $theme_mode); /* * Check if we've already rendered this Shortcode by parsing post content in * The Loop in `prerender()` above. CiviCRM Shortcodes that are rendered via * `do_shortcode('[civicrm ...]')` elsewhere (e.g. in a template) will never * have been prerendered and cannot be present in the Shortcode markup array. */ if ($theme_check) { 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); // Check for pathless Shortcode. if (empty($args['q'])) { $content = '<p>' . __('This Shortcode could not be handled. It could be malformed or used incorrectly.', 'civicrm') . '</p>'; /** * Get the markup for "pathless" Shortcodes. * * This filter allows plugins or CiviCRM Extensions to modify the markup used * to display a Shortcode that has no CiviCRM route/path. This may be: * * * Accidental due to an improperly constructed Shortcode or * * Deliberate because a component may not require a route/path * * Used internally by: * * - afform_shortcode_content() * * @since 5.37 * * @param str $content The default markup for an improperly constructed Shortcode. * @param array $atts The Shortcode attributes array. * @param array $args The Shortcode arguments array. * @param str Context flag - value is either 'single' or 'multiple'. */ return apply_filters('civicrm_shortcode_get_markup', $content, $atts, $args, 'single'); } // If there are *actual* CiviCRM query vars, let them take priority. if (!$this->civi->civicrm_in_wordpress()) { // Get the Shortcode Mode setting. $shortcode_mode = $this->civi->admin->get_shortcode_mode(); /** This filter is documented in includes/civicrm.basepage.php */ $basepage_mode = (bool) apply_filters('civicrm_force_basepage_mode', FALSE, $post); // Skip unless in "legacy mode" or "Base Page mode" is forced. if ($shortcode_mode !== 'legacy' || !$basepage_mode) { // invoke() requires environment variables to be set. foreach ($args as $key => $value) { if ($value !== NULL) { set_query_var($key, $value); $_REQUEST[$key] = $_GET[$key] = $value; } } } } 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(); } // Start buffering. ob_start(); // Now, instead of echoing, Shortcode output ends up in buffer. $this->civi->invoke(); // Save the output and flush the buffer. $content = ob_get_clean(); return $content; } /** * Return a generic display for a Shortcode instead of a CiviCRM invocation. * * @since 4.6 * * @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 pathless markup from filter callback. if (empty($args['q'])) { $markup = ''; /** This filter is documented in includes/civicrm-shortcodes.php */ return apply_filters('civicrm_shortcode_get_markup', $markup, $atts, $args, 'multiple'); } // 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 = ''; // Do we have multiple Shortcodes? if ($multiple !== 0) { // Build link to Base Page. $path = $args['q']; unset($args['q']); $link = civicrm_basepage_url($path, $args, TRUE, NULL, FALSE); // Add a class for styling purposes. $class = ' civicrm-shortcode-multiple'; } // Test for hijacking. if (!$multiple) { if (isset($atts['hijack']) && 1 === (int) $atts['hijack']) { // Add title to array. $this->post_titles[$post_id] = $data['title']; // Override title. add_filter('the_title', [$this, 'get_title'], 100, 2); // Overwrite content. add_filter('the_content', [$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']) && !empty($data['text'])) { $description = $data['text']; } /** * Filter the CiviCRM Shortcode more link text. * * @since 4.6 * * @param str The existing Shortcode more link text. */ $link_text = apply_filters('civicrm_shortcode_more_link', __('Find out more...', 'civicrm')); // Provide an enticing link. $more_link = sprintf('<a href="%s">%s</a>', $link, $link_text); // Assume CiviCRM footer is not enabled. $empowered_enabled = FALSE; $footer = ''; // Test config object for setting. if (1 === (int) CRM_Core_Config::singleton()->empoweredBy) { // 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="https://civicrm.org/" title="' . $civi . '" target="_blank" class="empowered-by-link">' . $logo . '</a>'; /* translators: %s: The link to the CiviCRM website. */ $empowered = sprintf(__('Empowered by %s', 'civicrm'), $civi_link); /** * Filter the CiviCRM Shortcode footer text. * * @since 4.6 * * @param str $empowered The existing Shortcode footer. */ $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(); /** * Filter the computed CiviCRM Shortcode markup. * * @since 4.6 * * @param str $markup The computed Shortcode markup. * @param int $post_id The numeric ID of the WordPress post. * @param string $shortcode The Shortcode being parsed. */ return apply_filters('civicrm_shortcode_render_multiple', $markup, $post_id, $shortcode); } /** * In order to hijack the page, we need to override the context. * * @since 4.6 * * @return string The overridden context code. */ public function get_context() { return 'nonpage'; } /** * In order to hijack the page, we need to override the content. * * @since 4.6 * * @param string $content The content. * @return string $content The 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. * * @since 4.6 * * @param string $title The existing title. * @param int $post_id The numeric ID of the WordPress post. * @return string $title The overridden title. */ public function get_title($title, $post_id = 0) { // Bail if there is no Post ID. if (empty($post_id)) { return $title; } // 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; } /** * Override a WordPress page title with the CiviCRM entity title. * * Callback method for 'single_page_title' hook, always called from WordPress * front-end. * * @since 4.6 * @since 5.33 Moved to this class. * * @param string $post_title The title of the WordPress page or post. * @param object $post The WordPress post object the title applies to. * @return string $civicrm_wp_title The title of the CiviCRM entity. */ public function single_page_title($post_title, $post) { // Sanity check and override. global $civicrm_wp_title; if (!empty($civicrm_wp_title)) { return $civicrm_wp_title; } // Fallback. return $post_title; } /** * Detect and return CiviCRM Shortcodes in post content. * * @since 4.6 * @since 5.44 Made recursive. * * @param str $content The content to parse. * @return array $shortcodes Array of Shortcodes. */ private function get_for_post($content) { // Init return array. $shortcodes = []; // 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) ) { // Get keys for our Shortcode. $keys = array_keys($matches[2], 'civicrm'); // Add found Shortcodes at this level. if (!empty($keys)) { foreach ($keys as $key) { $shortcodes[] = $matches[0][$key]; } } // Recurse when nested Shortcodes are found. if (!empty($matches[5])) { foreach ($matches[5] as $match) { if (!empty($match)) { $shortcodes_deeper = $this->get_for_post($match); $shortcodes = array_merge($shortcodes, $shortcodes_deeper); } } } } return $shortcodes; } /** * Return attributes for a given CiviCRM Shortcode. * * @since 4.6 * * @param string $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. * * @since 4.6 * * @param array $atts Shortcode attributes array. * @return array $args Shortcode arguments array. */ public function preprocess_atts($atts) { $defaults = [ 'component' => 'contribution', 'action' => NULL, 'mode' => NULL, 'id' => NULL, 'cid' => NULL, 'gid' => NULL, 'cs' => NULL, 'force' => NULL, ]; // Parse Shortcode attributes. $attributes = shortcode_atts($defaults, $atts, 'civicrm'); $args = [ 'reset' => 1, 'id' => $attributes['id'], 'force' => $attributes['force'], ]; // Construct args for known components. switch ($attributes['component']) { case 'contribution': if ($attributes['mode'] === 'preview' || $attributes['mode'] === 'test') { $args['action'] = 'preview'; } switch ($attributes['action']) { case 'setup': $args['q'] = 'civicrm/contribute/campaign'; $args['action'] = 'add'; $args['component'] = 'contribute'; unset($args['id']); $args['pageId'] = $attributes['id']; break; case 'transact': default: $args['q'] = 'civicrm/contribute/transact'; break; } break; case 'pcp': if ($attributes['mode'] === 'preview' || $attributes['mode'] === 'test') { $args['action'] = 'preview'; } switch ($attributes['action']) { case 'transact': $args['q'] = 'civicrm/contribute/transact'; $args['pcpId'] = $args['id']; $args['id'] = civicrm_api3('Pcp', 'getvalue', [ 'return' => 'page_id', 'id' => $args['pcpId'], ]); break; case 'info': default: $args['q'] = 'civicrm/pcp/info'; break; } break; case 'event': switch ($attributes['action']) { case 'register': $args['q'] = 'civicrm/event/register'; if ($attributes['mode'] === 'preview' || $attributes['mode'] === 'test') { $args['action'] = 'preview'; } break; case 'info': $args['q'] = 'civicrm/event/info'; break; default: return FALSE; } break; case 'user-dashboard': $args['q'] = 'civicrm/user'; unset($args['id']); break; case 'profile': if ($attributes['mode'] === 'edit') { $args['q'] = 'civicrm/profile/edit'; } elseif ($attributes['mode'] === 'view') { $args['q'] = 'civicrm/profile/view'; } elseif ($attributes['mode'] === 'search') { $args['q'] = 'civicrm/profile'; } elseif ($attributes['mode'] === 'map') { $args['q'] = 'civicrm/profile/map'; $args['map'] = 1; } else { $args['q'] = 'civicrm/profile/create'; } $args['gid'] = $attributes['gid']; break; case 'petition': $args['q'] = 'civicrm/petition/sign'; $args['sid'] = $args['id']; unset($args['id']); break; } /** * Filter the CiviCRM Shortcode arguments. * * This filter allows plugins or CiviCRM Extensions to modify the attributes * that the 'civicrm' Shortcode allows. Injected attributes and their values * will also become available in the $_REQUEST and $_GET arrays. * * @since 4.7.28 * * @param array $args Existing Shortcode arguments. * @param array $attributes Shortcode attributes. */ return apply_filters('civicrm_shortcode_preprocess_atts', $args, $attributes); } /** * Post-process CiviCRM-defined Shortcodes. * * @since 4.6 * * @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 = []; if (!$this->civi->initialize()) { return FALSE; } /** * Filter the base CiviCRM API parameters. * * This filter allows plugins or CiviCRM Extensions to modify the API call * when there are multiple Shortcodes being rendered. * * @since 4.7.28 * * @param array $params Existing API params. * @param array $atts Shortcode attributes array. * @param array $args Shortcode arguments array. */ $params = apply_filters('civicrm_shortcode_api_params', [ 'sequential' => 1, ], $atts, $args); // Get the CiviCRM entity via the API. switch ($atts['component']) { case 'contribution': // Add Contribution Page ID. $params['id'] = $args['id']; // Call API. $civi_entity = civicrm_api3('ContributionPage', '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_api3('Event', 'getsingle', $params); // Set title. switch ($atts['action']) { case 'register': $data['title'] = sprintf( /* translators: %s: The event title. */ __('Register for %s', 'civicrm'), $civi_entity['title'] ); break; case 'info': default: $data['title'] = ''; if (!empty($civi_entity['title'])) { $data['title'] = $civi_entity['title']; } break; } // Set text, if present. $data['text'] = ''; if (!empty($civi_entity['summary'])) { $data['text'] = $civi_entity['summary']; } // Override with "description" if "summary" is empty. if (empty($civi_entity['summary']) && !empty($civi_entity['description'])) { $data['text'] = $civi_entity['description']; } break; case 'user-dashboard': // Set title. $data['title'] = __('Dashboard', 'civicrm'); break; case 'profile': // Add Profile ID. $params['id'] = $args['gid']; // Call API. $civi_entity = civicrm_api3('UFGroup', '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_api3('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 CiviCRM Extensions to modify the data used * to display the Shortcode when there are multiple Shortcodes being rendered. * * @since 4.7.28 * * @param array $data The existing Shortcode data. * @param array $atts Shortcode attributes array. * @param array $args Shortcode arguments array. */ return apply_filters('civicrm_shortcode_get_data', $data, $atts, $args); } }