| @@ -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 `'` 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 | + * (', ', ', ", ...), 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. |