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/public/charitable-template-helpers.php +85 -14 1.7.0 → 1.8.13 View file →
@@ -5,12 +5,12 @@
5 5 * Functions used to assist with rendering templates.
6 6 *
7 7 * @package Charitable/Functions/Templates
8 8 * @author David Bisset
9 - * @copyright Copyright (c) 2022, WP Charitable LLC
9 + * @copyright Copyright (c) 2023, WP Charitable LLC
10 10 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
11 11 * @since 1.0.0
12 - * @version 1.6.34
12 + * @version 1.8.13
13 13 */
14 14
15 15 // Exit if accessed directly.
16 16 if ( ! defined( 'ABSPATH' ) ) {
@@ -67,9 +67,9 @@
67 67 ob_start();
68 68
69 69 charitable_template( $template_name, $args, $classname );
70 70
71 - echo charitable_template_from_session_content( $template_key, $wrapper_args, ob_get_clean() );
71 + echo charitable_template_from_session_content( $template_key, $wrapper_args, ob_get_clean() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
72 72 }
73 73
74 74 /**
75 75 * Returns a piece of content or an empty string, wrapped in the correct markup
@@ -91,9 +91,9 @@
91 91 if ( charitable_get_session()->has_session_id() ) {
92 92 return $default;
93 93 }
94 94
95 - $content = '<div class="charitable-session-content" data-template="' . esc_attr( $template_key ) . '" data-args="' . esc_attr( json_encode( $wrapper_args ) ) . '" style="display: none;">' . $default . '</div>';
95 + $content = '<div class="charitable-session-content" data-template="' . esc_attr( $template_key ) . '" data-args="' . esc_attr( wp_json_encode( $wrapper_args ) ) . '" style="display: none;">' . $default . '</div>';
96 96 $content .= '<noscript>' . $default . '</noscript>';
97 97
98 98 return $content;
99 99 }
@@ -265,21 +265,64 @@
265 265 * Charitable's filters touching other the_content instances outside the main
266 266 * loop.
267 267 *
268 268 * @since 1.4.11
269 + * @since 1.8.1 added 'charitable_is_main_loop' filter.
269 270 *
270 271 * @return boolean
271 272 */
272 273 function charitable_is_main_loop() {
273 - return is_single() && in_the_loop() && is_main_query();
274 + $is_main_loop = is_single() && in_the_loop() && is_main_query();
275 + return apply_filters( 'charitable_is_main_loop', $is_main_loop );
274 276 }
275 277
276 278 /**
279 + * Re-encodes a raw query string so that every key and value is percent-encoded.
280 + *
281 + * The query string arrives from the request untrusted and unencoded. Rebuilding it
282 + * pair by pair guarantees the result contains no character that could break out of
283 + * the HTML attribute, JavaScript string or URL context it is later printed into --
284 + * including HTML entities such as `&apos;` that survive esc_url() and are decoded
285 + * back into a quote by the browser's HTML parser.
286 + *
287 + * Query strings are application/x-www-form-urlencoded, so urldecode()/urlencode()
288 + * is the correct pair here: it round-trips `+` as a space the way the request did.
289 + *
290 + * @since 1.8.13
291 + *
292 + * @param string $query_string Raw query string, without the leading '?'.
293 + * @return string Re-encoded query string, or an empty string if nothing usable remains.
294 + */
295 +function charitable_sanitize_query_string( $query_string ) {
296 + $pairs = array();
297 +
298 + foreach ( explode( '&', (string) $query_string ) as $pair ) {
299 + if ( '' === $pair ) {
300 + continue;
301 + }
302 +
303 + $parts = explode( '=', $pair, 2 );
304 + $key = urlencode( urldecode( $parts[0] ) );
305 +
306 + if ( '' === $key ) {
307 + continue;
308 + }
309 +
310 + $pairs[] = isset( $parts[1] ) ? $key . '=' . urlencode( urldecode( $parts[1] ) ) : $key;
311 + }
312 +
313 + return implode( '&', $pairs );
314 +}
315 +
316 +/**
277 317 * Returns the current URL.
278 318 *
279 319 * @see https://gist.github.com/leereamsnyder/fac3b9ccb6b99ab14f36
280 320 *
281 321 * @since 1.0.0
322 + * @since 1.8.13 The path and query string are both percent-encoded instead of being
323 + * reflected raw, and the trailing slash is applied to the path rather
324 + * than to the end of the query string.
282 325 *
283 326 * @global WP $wp
284 327 * @return string
285 328 */
@@ -285,15 +328,38 @@
285 328 */
286 329 function charitable_get_current_url() {
287 330 global $wp;
288 331
289 - return trailingslashit(
290 - add_query_arg(
291 - array_key_exists( 'QUERY_STRING', $_SERVER ) ? $_SERVER['QUERY_STRING'] : '',
292 - '',
293 - home_url( $wp->request )
294 - )
332 + $path = isset( $wp->request ) ? (string) $wp->request : '';
333 +
334 + /*
335 + * The requested path is reflected here too, and WordPress hands it over exactly as
336 + * it arrived: any %XX escapes are already inert, but characters that arrived
337 + * literally are not. Encoding '&' closes off every HTML entity form at once
338 + * (&apos;, &#39;, &#x27;, &quot;, ...), which is what a URL escaper cannot do
339 + * because those entities are made of characters it considers legal; the quotes and
340 + * angle brackets cover the literal cases. Only these five are touched, so existing
341 + * %XX escapes and ordinary slugs pass through unchanged and the result is idempotent.
342 + */
343 + $path = str_replace(
344 + array( '&', "'", '"', '<', '>' ),
345 + array( '%26', '%27', '%22', '%3C', '%3E' ),
346 + $path
295 347 );
348 +
349 + $url = trailingslashit( home_url( $path ) );
350 +
351 + if ( empty( $_SERVER['QUERY_STRING'] ) ) {
352 + return $url;
353 + }
354 +
355 + $query_string = charitable_sanitize_query_string( wp_unslash( $_SERVER['QUERY_STRING'] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
356 +
357 + if ( '' === $query_string ) {
358 + return $url;
359 + }
360 +
361 + return $url . '?' . $query_string;
296 362 }
297 363
298 364 /**
299 365 * Returns the URL to which the user should be redirected after signing on or registering an account.
@@ -302,10 +368,10 @@
302 368 *
303 369 * @return string
304 370 */
305 371 function charitable_get_login_redirect_url() {
306 - if ( isset( $_REQUEST['redirect_to'] ) ) {
307 - $redirect = $_REQUEST['redirect_to'];
372 + if ( isset( $_REQUEST['redirect_to'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
373 + $redirect = $_REQUEST['redirect_to']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
308 374 } elseif ( charitable_get_permalink( 'profile_page' ) ) {
309 375 $redirect = charitable_get_permalink( 'profile_page' );
310 376 } else {
311 377 $redirect = home_url();
@@ -355,13 +421,14 @@
355 421 /**
356 422 * Get the button classes for a Charitable button.
357 423 *
358 424 * @since 1.6.29
425 + * @version 1.8.3.6 Added $disabled parameter.
359 426 *
360 427 * @param string $button Which button we're showing.
361 428 * @return string
362 429 */
363 -function charitable_get_button_class( $button ) {
430 +function charitable_get_button_class( $button, $disabled = false ) {
364 431 $classes = [ 'button', 'charitable-button' ];
365 432
366 433 switch ( $button ) {
367 434 case 'donate':
@@ -370,8 +437,12 @@
370 437 break;
371 438
372 439 default:
373 440 $classes[] = $button . '-button';
441 + }
442 +
443 + if ( $disabled ) {
444 + $classes[] = 'charitable-disabled';
374 445 }
375 446
376 447 /**
377 448 * Filter the button classes.