← All changes
|
includes/shortcodes/class-charitable-email-shortcode.php
+155
-2
1.7.0.7
→
1.8.13
View file →
| @@ -3,12 +3,13 @@ | ||
| 3 | 3 | * Email shortcode class. |
| 4 | 4 | * |
| 5 | 5 | * @package Charitable/Shortcodes/Email |
| 6 | 6 | * @author David Bisset |
| 7 | - * @copyright Copyright (c) 2022, WP Charitable LLC | |
| 7 | + * @copyright Copyright (c) 2023, WP Charitable LLC | |
| 8 | 8 | * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License |
| 9 | 9 | * @since 1.5.0 |
| 10 | 10 | * @version 1.5.0 |
| 11 | + * @version 1.8.9.5 Added recursion prevention for email shortcodes. | |
| 11 | 12 | */ |
| 12 | 13 | |
| 13 | 14 | // Exit if accessed directly. |
| 14 | 15 | if ( ! defined( 'ABSPATH' ) ) { |
| @@ -42,8 +43,26 @@ | ||
| 42 | 43 | */ |
| 43 | 44 | private $fields; |
| 44 | 45 | |
| 45 | 46 | /** |
| 47 | + * Processing stack to track active shortcodes and prevent recursion. | |
| 48 | + * | |
| 49 | + * @since 1.8.9.5 | |
| 50 | + * | |
| 51 | + * @var array | |
| 52 | + */ | |
| 53 | + private static $processing_stack = array(); | |
| 54 | + | |
| 55 | + /** | |
| 56 | + * Processing depth counter for recursion detection. | |
| 57 | + * | |
| 58 | + * @since 1.8.9.5 | |
| 59 | + * | |
| 60 | + * @var int | |
| 61 | + */ | |
| 62 | + private static $processing_depth = 0; | |
| 63 | + | |
| 64 | + /** | |
| 46 | 65 | * Set up class instance. |
| 47 | 66 | * |
| 48 | 67 | * @since 1.5.0 |
| 49 | 68 | * |
| @@ -120,9 +139,24 @@ | ||
| 120 | 139 | if ( ! isset( $args['show'] ) ) { |
| 121 | 140 | return ''; |
| 122 | 141 | } |
| 123 | 142 | |
| 124 | - return self::$instance->get( $args['show'], $args ); | |
| 143 | + // Recursion prevention - Added in 1.8.9.5 | |
| 144 | + $recursion_result = self::check_recursion( $args['show'], $args ); | |
| 145 | + if ( false !== $recursion_result ) { | |
| 146 | + return $recursion_result; | |
| 147 | + } | |
| 148 | + | |
| 149 | + // Add to processing stack | |
| 150 | + self::push_processing_stack( $args['show'] ); | |
| 151 | + | |
| 152 | + // Process the shortcode | |
| 153 | + $result = self::$instance->get( $args['show'], $args ); | |
| 154 | + | |
| 155 | + // Remove from processing stack | |
| 156 | + self::pop_processing_stack( $args['show'] ); | |
| 157 | + | |
| 158 | + return $result; | |
| 125 | 159 | } |
| 126 | 160 | |
| 127 | 161 | /** |
| 128 | 162 | * Return the value for a particular variable. |
| @@ -135,8 +169,127 @@ | ||
| 135 | 169 | */ |
| 136 | 170 | public function get( $field, $args ) { |
| 137 | 171 | $value = apply_filters( 'charitable_email_shortcode_get_value', $this->fields->get_field_value( $field, $args ) ); |
| 138 | 172 | return $value; |
| 173 | + } | |
| 174 | + | |
| 175 | + /** | |
| 176 | + * Check for recursion and return fallback content if detected. | |
| 177 | + * | |
| 178 | + * @since 1.8.9.5 | |
| 179 | + * | |
| 180 | + * @param string $show The field to show. | |
| 181 | + * @param array $args Mixed arguments. | |
| 182 | + * @return string|false False if no recursion, fallback content if recursion detected. | |
| 183 | + */ | |
| 184 | + private static function check_recursion( $show, $args ) { | |
| 185 | + // Check depth limit | |
| 186 | + $max_depth = apply_filters( 'charitable_email_recursion_depth_limit', 3 ); | |
| 187 | + if ( self::$processing_depth >= $max_depth ) { | |
| 188 | + self::log_recursion_attempt( 'depth', $show, self::$processing_depth, $max_depth ); | |
| 189 | + return self::get_context_specific_fallback( $show, 'depth_limit' ); | |
| 190 | + } | |
| 191 | + | |
| 192 | + // Check for direct recursion (same field already being processed) | |
| 193 | + $stack_key = $show; | |
| 194 | + if ( in_array( $stack_key, self::$processing_stack, true ) ) { | |
| 195 | + self::log_recursion_attempt( 'circular', $show, count( self::$processing_stack ), $max_depth ); | |
| 196 | + return self::get_context_specific_fallback( $show, 'circular_reference' ); | |
| 197 | + } | |
| 198 | + | |
| 199 | + return false; // No recursion detected | |
| 200 | + } | |
| 201 | + | |
| 202 | + /** | |
| 203 | + * Add a field to the processing stack. | |
| 204 | + * | |
| 205 | + * @since 1.8.9.5 | |
| 206 | + * | |
| 207 | + * @param string $show The field being processed. | |
| 208 | + */ | |
| 209 | + private static function push_processing_stack( $show ) { | |
| 210 | + self::$processing_stack[] = $show; | |
| 211 | + self::$processing_depth++; | |
| 212 | + } | |
| 213 | + | |
| 214 | + /** | |
| 215 | + * Remove a field from the processing stack. | |
| 216 | + * | |
| 217 | + * @since 1.8.9.5 | |
| 218 | + * | |
| 219 | + * @param string $show The field being processed. | |
| 220 | + */ | |
| 221 | + private static function pop_processing_stack( $show ) { | |
| 222 | + $index = array_search( $show, self::$processing_stack, true ); | |
| 223 | + if ( false !== $index ) { | |
| 224 | + unset( self::$processing_stack[ $index ] ); | |
| 225 | + self::$processing_stack = array_values( self::$processing_stack ); // Re-index array | |
| 226 | + } | |
| 227 | + self::$processing_depth = max( 0, self::$processing_depth - 1 ); | |
| 228 | + } | |
| 229 | + | |
| 230 | + /** | |
| 231 | + * Get context-specific fallback content when recursion is detected. | |
| 232 | + * | |
| 233 | + * @since 1.8.9.5 | |
| 234 | + * | |
| 235 | + * @param string $show The field that caused recursion. | |
| 236 | + * @param string $reason The reason for fallback (depth_limit, circular_reference). | |
| 237 | + * @return string Fallback content. | |
| 238 | + */ | |
| 239 | + private static function get_context_specific_fallback( $show, $reason ) { | |
| 240 | + $fallbacks = array( | |
| 241 | + 'offline_instructions' => __( 'Payment instructions will be provided separately.', 'charitable' ), | |
| 242 | + 'donation_summary' => __( 'Donation summary unavailable.', 'charitable' ), | |
| 243 | + 'donor' => __( 'Donor information unavailable.', 'charitable' ), | |
| 244 | + 'donor_email' => __( 'Email unavailable.', 'charitable' ), | |
| 245 | + 'site_name' => get_bloginfo( 'name' ), | |
| 246 | + 'campaign_title' => __( 'Campaign information unavailable.', 'charitable' ), | |
| 247 | + ); | |
| 248 | + | |
| 249 | + // Get specific fallback or generic one | |
| 250 | + $fallback = isset( $fallbacks[ $show ] ) ? $fallbacks[ $show ] : __( 'Content temporarily unavailable.', 'charitable' ); | |
| 251 | + | |
| 252 | + /** | |
| 253 | + * Filter the fallback content for recursive email shortcodes. | |
| 254 | + * | |
| 255 | + * @since 1.8.9.5 | |
| 256 | + * | |
| 257 | + * @param string $fallback The default fallback content. | |
| 258 | + * @param string $show The field that caused recursion. | |
| 259 | + * @param string $reason The reason for fallback. | |
| 260 | + */ | |
| 261 | + return apply_filters( 'charitable_email_shortcode_recursion_fallback', $fallback, $show, $reason ); | |
| 262 | + } | |
| 263 | + | |
| 264 | + /** | |
| 265 | + * Log recursion attempts for debugging. | |
| 266 | + * | |
| 267 | + * @since 1.8.9.5 | |
| 268 | + * | |
| 269 | + * @param string $type Type of recursion (circular, depth). | |
| 270 | + * @param string $show The field that caused recursion. | |
| 271 | + * @param int $current Current depth/stack size. | |
| 272 | + * @param int $limit The configured limit. | |
| 273 | + */ | |
| 274 | + private static function log_recursion_attempt( $type, $show, $current, $limit ) { | |
| 275 | + // Only log if both CHARITABLE_DEBUG and CHARITABLE_DEBUG_EMAILS are enabled | |
| 276 | + if ( ! charitable_is_debug() || ! defined( 'CHARITABLE_DEBUG_EMAILS' ) || ! CHARITABLE_DEBUG_EMAILS ) { | |
| 277 | + return; | |
| 278 | + } | |
| 279 | + | |
| 280 | + $message = sprintf( | |
| 281 | + '[CHARITABLE_EMAIL_DEBUG] Recursion prevented: %s recursion detected for shortcode [charitable_email show="%s"]. Current: %d, Limit: %d, Stack: [%s]', | |
| 282 | + $type, | |
| 283 | + $show, | |
| 284 | + $current, | |
| 285 | + $limit, | |
| 286 | + implode( ', ', self::$processing_stack ) | |
| 287 | + ); | |
| 288 | + | |
| 289 | + if ( charitable_is_debug() ) { | |
| 290 | + error_log( $message ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log | |
| 291 | + } | |
| 139 | 292 | } |
| 140 | 293 | } |
| 141 | 294 | |
| 142 | 295 | endif; |