PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 1.7.0.9 All 211 releases
← All changes | includes/shortcodes/class-charitable-email-shortcode.php +158 -4 1.6.6 → 1.8.13 View file →
@@ -2,13 +2,14 @@
2 2 /**
3 3 * Email shortcode class.
4 4 *
5 5 * @package Charitable/Shortcodes/Email
6 - * @author Eric Daams
7 - * @copyright Copyright (c) 2018, Studio 164a
6 + * @author David Bisset
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.
@@ -133,9 +167,129 @@
133 167 * @param array $args Mixed arguments.
134 168 * @return string
135 169 */
136 170 public function get( $field, $args ) {
137 - return $this->fields->get_field_value( $field, $args );
171 + $value = apply_filters( 'charitable_email_shortcode_get_value', $this->fields->get_field_value( $field, $args ) );
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 + }
138 292 }
139 293 }
140 294
141 295 endif;