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/endpoints/class-charitable-endpoints.php +425 -14 1.6.6 → 1.8.13 View file →
@@ -1,17 +1,20 @@
1 1 <?php
2 2 /**
3 3 * The endpoint registry class, providing a clean way to access details about individual endpoints.
4 4 *
5 - * @package Charitable/Classes/Charitable_Endpoints
6 - * @version 1.5.0
7 - * @author Eric Daams
8 - * @copyright Copyright (c) 2018, Studio 164a
9 - * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
5 + * @package Charitable/Classes/Charitable_Endpoints
6 + * @author David Bisset
7 + * @copyright Copyright (c) 2023, WP Charitable LLC
8 + * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 + * @since 1.5.0
10 + * @version 1.8.9.1
10 11 */
11 12
12 13 // Exit if accessed directly.
13 -if ( ! defined( 'ABSPATH' ) ) { exit; }
14 +if ( ! defined( 'ABSPATH' ) ) {
15 + exit;
16 +}
14 17
15 18 if ( ! class_exists( 'Charitable_Endpoints' ) ) :
16 19
17 20 /**
@@ -30,8 +33,17 @@
30 33 */
31 34 protected $endpoints;
32 35
33 36 /**
37 + * Endpoints ordered by priority.
38 + *
39 + * @since 1.6.37
40 + *
41 + * @var array
42 + */
43 + protected $endpoints_prioritized = array();
44 +
45 + /**
34 46 * Current endpoint.
35 47 *
36 48 * @since 1.5.0
37 49 *
@@ -46,13 +58,25 @@
46 58 */
47 59 public function __construct() {
48 60 $this->endpoints = array();
49 61
62 + add_action( 'wp', array( $this, 'disable_endpoint_cache' ) );
63 + add_filter( 'pre_handle_404', array( $this, 'block_404_on_endpoints' ) );
50 64 add_action( 'init', array( $this, 'setup_rewrite_rules' ) );
51 65 add_filter( 'query_vars', array( $this, 'add_query_vars' ) );
66 + add_action( 'template_redirect', array( $this, 'maybe_redirect' ) );
52 67 add_filter( 'template_include', array( $this, 'template_loader' ), 12 );
68 + add_filter( 'home_template_hierarchy', array( $this, 'home_template_hierarchy' ) );
53 69 add_filter( 'the_content', array( $this, 'get_content' ) );
54 70 add_filter( 'body_class', array( $this, 'add_body_classes' ) );
71 + add_filter( 'comments_open', array( $this, 'maybe_disable_comments' ) );
72 + add_filter( 'comments_template', array( $this, 'maybe_remove_comments_template' ) );
73 + add_filter( 'nav_menu_meta_box_object', array( $this, 'add_endpoints_menu_meta_box' ) );
74 + add_filter( 'customize_nav_menu_available_item_types', array( $this, 'add_endpoints_menu_meta_box_to_customizer' ) );
75 + add_filter( 'customize_nav_menu_available_items', array( $this, 'add_endpoints_menu_meta_box_items_to_customizer' ), 10, 4 );
76 +
77 + /* Avoid Polylang rewriting the rewrite rules. */
78 + add_filter( 'pll_modify_rewrite_rule', array( $this, 'prevent_polylang_rewrite_modification' ), 10, 2 );
55 79 }
56 80
57 81 /**
58 82 * Register an endpoint.
@@ -67,8 +91,9 @@
67 91
68 92 if ( $this->endpoint_exists( $endpoint_id ) ) {
69 93 charitable_get_deprecated()->doing_it_wrong(
70 94 __METHOD__,
95 + /* translators: %s: endpoint id */
71 96 sprintf( __( 'Endpoint %s has already been registered.', 'charitable' ), $endpoint_id ),
72 97 '1.5.0'
73 98 );
74 99
@@ -76,8 +101,15 @@
76 101 }
77 102
78 103 $this->endpoints[ $endpoint_id ] = $endpoint;
79 104
105 + /* Record the endpoint by priority. */
106 + if ( ! array_key_exists( $endpoint::PRIORITY, $this->endpoints_prioritized ) ) {
107 + $this->endpoints_prioritized[ $endpoint::PRIORITY ] = array();
108 + }
109 +
110 + $this->endpoints_prioritized[ $endpoint::PRIORITY ][] = $endpoint_id;
111 +
80 112 return true;
81 113 }
82 114
83 115 /**
@@ -160,9 +192,13 @@
160 192
161 193 if ( ! $this->endpoint_exists( $endpoint ) ) {
162 194 charitable_get_deprecated()->doing_it_wrong(
163 195 __METHOD__,
164 - sprintf( __( 'Endpoint %s has not been registered.', 'charitable' ), $endpoint ),
196 + sprintf(
197 + /* translators: %s: endpoint id */
198 + __( 'Endpoint %s has not been registered.', 'charitable' ),
199 + $endpoint
200 + ),
165 201 '1.5.0'
166 202 );
167 203
168 204 return $default_template;
@@ -171,8 +207,94 @@
171 207 return $this->endpoints[ $endpoint ]->get_template( $default_template );
172 208 }
173 209
174 210 /**
211 + * Disable page cache on non-cacheable endpoints using the DONOTCACHEPAGE constant.
212 + *
213 + * @since 1.6.14
214 + *
215 + * @return void
216 + */
217 + public function disable_endpoint_cache() {
218 + if ( defined( 'DONOTCACHEPAGE' ) ) {
219 + return;
220 + }
221 +
222 + $endpoint_id = $this->get_current_endpoint();
223 +
224 + if ( ! $endpoint_id ) {
225 + return;
226 + }
227 +
228 + if ( $this->get_endpoint( $endpoint_id )->is_cacheable() ) {
229 + return;
230 + }
231 +
232 + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- DONOTCACHEPAGE is a third-party constant used by caching plugins (WP Super Cache, etc.) for compatibility.
233 + define( 'DONOTCACHEPAGE', true );
234 +
235 + /**
236 + * Fire action to note that the current page should not be cached.
237 + *
238 + * @since 1.6.14
239 + */
240 + do_action( 'charitable_do_not_cache' );
241 + }
242 +
243 + /**
244 + * If we're viewing a Charitable endpoint, we're not on a 404, even
245 + * though WordPress interprets some pages (like the Forgot Password)
246 + * as a 404 in certain cases.
247 + *
248 + * @since 1.6.41
249 + *
250 + * @param boolean $not_a_404 Whether to preempt WordPress and instruct
251 + * it that this is not a 404 request.
252 + * @return boolean
253 + */
254 + public function block_404_on_endpoints( $not_a_404 ) {
255 + if ( false !== $this->get_current_endpoint() ) {
256 + $not_a_404 = true;
257 + } else {
258 + /*
259 + Some endpoints will return false at this point since
260 + * it's so early, so we unset the class property to make
261 + * sure they are tested again later on.
262 + */
263 + unset( $this->current_endpoint );
264 + }
265 +
266 + return $not_a_404;
267 + }
268 +
269 + /**
270 + * Prevent Polylang from changing some rewrite rules.
271 + *
272 + * @since 1.6.21
273 + *
274 + * @param boolean $modify Whether to modify or not the rule, defaults to true.
275 + * @param array $rule Original rewrite rule.
276 + * @return boolean
277 + */
278 + public function prevent_polylang_rewrite_modification( $modify, $rule ) {
279 + /**
280 + * Filter the list of endpoint URLs that Polylang won't touch.
281 + *
282 + * @since 1.6.21
283 + *
284 + * @param array $protected_rules The protected rules.
285 + */
286 + $protected_rules = apply_filters(
287 + 'charitable_polylang_protected_rewrite_rules',
288 + array(
289 + 'charitable-listener(/(.*))?/?$',
290 + )
291 + );
292 +
293 + return $modify && ! in_array( key( $rule ), $protected_rules );
294 + }
295 +
296 + /**
175 297 * Set up the rewrite rules for the site.
176 298 *
177 299 * @since 1.5.0
178 300 *
@@ -203,14 +325,39 @@
203 325 return array_merge( $vars, array( 'donation_id', 'cancel' ) );
204 326 }
205 327
206 328 /**
329 + * Check the current endpoint to see if we should redirect the user to a different page.
330 + *
331 + * @since 1.6.26
332 + *
333 + * @return void
334 + */
335 + public function maybe_redirect() {
336 + $current_endpoint = $this->get_current_endpoint();
337 +
338 + if ( ! $current_endpoint ) {
339 + return;
340 + }
341 +
342 + $url = $this->endpoints[ $current_endpoint ]->get_redirect();
343 +
344 + if ( ! $url ) {
345 + return;
346 + }
347 +
348 + wp_safe_redirect( $url );
349 +
350 + exit;
351 + }
352 +
353 + /**
207 354 * Load templates for our endpoints.
208 355 *
209 356 * @since 1.5.0
210 357 *
211 358 * @param string $template The default template.
212 - * @return void
359 + * @return string
213 360 */
214 361 public function template_loader( $template ) {
215 362 $current_endpoint = $this->get_current_endpoint();
216 363
@@ -217,14 +364,29 @@
217 364 if ( ! $current_endpoint ) {
218 365 return $template;
219 366 }
220 367
221 - $template_options = $this->endpoints[ $current_endpoint ]->get_template( $template );
368 + $endpoint = $this->endpoints[ $current_endpoint ];
222 369
223 - if ( $template_options == $template ) {
370 + if ( ! $endpoint->uses_custom_template() ) {
371 + return $template;
372 + }
373 +
374 + $endpoint->setup_template();
375 +
376 + $template_options = $endpoint->get_template( $template );
377 +
378 + if ( $template_options === $template ) {
224 379 return $template_options;
225 380 }
226 381
382 + /**
383 + * Filter the list of template options for the given endpoint.
384 + *
385 + * @since 1.0.0
386 + *
387 + * @param array|string $template_options
388 + */
227 389 $template_options = apply_filters( 'charitable_' . $current_endpoint . '_page_template', $template_options );
228 390
229 391 return charitable_get_template_path( $template_options, $template );
230 392 }
@@ -229,8 +391,29 @@
229 391 return charitable_get_template_path( $template_options, $template );
230 392 }
231 393
232 394 /**
395 + * When using a block theme, dynamically generated pages like the donation receipt
396 + * page are interpreted as using the home template. The following workaround inserts
397 + * page.php as the primary template for these page.
398 + *
399 + * @param mixed $templates The list of templates.
400 + *
401 + * @since 1.6.55
402 + *
403 + * @return mixed
404 + */
405 + public function home_template_hierarchy( $templates ) {
406 + $current_endpoint = $this->get_current_endpoint();
407 +
408 + if ( false !== $current_endpoint ) {
409 + $templates = $this->endpoints[ $current_endpoint ]->get_template( $templates );
410 + }
411 +
412 + return $templates;
413 + }
414 +
415 + /**
233 416 * Get the content to display for the endpoint we're viewing.
234 417 *
235 418 * @since 1.5.0
236 419 *
@@ -270,11 +453,207 @@
270 453 return $classes;
271 454 }
272 455
273 456 /**
457 + * If we're on an endpoint where comments should be disabled, do so.
458 + *
459 + * @since 1.6.36
460 + *
461 + * @param boolean $open Whether comments are open.
462 + * @return boolean
463 + */
464 + public function maybe_disable_comments( $open ) {
465 + if ( ! $open ) {
466 + return $open;
467 + }
468 +
469 + $endpoint = $this->get_current_endpoint();
470 +
471 + if ( ! $endpoint ) {
472 + return $open;
473 + }
474 +
475 + return ! $this->endpoints[ $endpoint ]->comments_disabled();
476 + }
477 +
478 + /**
479 + * If we are on an endpoint where comments are disabled, return an
480 + * empty string for the template, so WordPress will not display
481 + * anything.
482 + *
483 + * @since 1.6.36
484 + *
485 + * @param string $template The path to the theme template file.
486 + * @return string
487 + */
488 + public function maybe_remove_comments_template( $template ) {
489 + $endpoint = $this->get_current_endpoint();
490 +
491 + if ( $endpoint && $this->endpoints[ $endpoint ]->comments_disabled() ) {
492 + $template = charitable_get_template_path( 'comments/disabled-comments.php' );
493 + }
494 +
495 + return $template;
496 + }
497 +
498 + /**
499 + * Add a "Charitable" menus meta box.
500 + *
501 + * @since 1.6.29
502 + *
503 + * @param object $object The meta box object.
504 + * @return object
505 + */
506 + public function add_endpoints_menu_meta_box( $object ) { // phpcs:ignore
507 + add_meta_box(
508 + 'add-charitable-endpoints',
509 + __( 'Charitable', 'charitable' ),
510 + [ $this, 'endpoints_menu_meta_box' ],
511 + 'nav-menus',
512 + 'side',
513 + 'low'
514 + );
515 +
516 + return $object;
517 + }
518 +
519 + /**
520 + * The content of the endpoints menu meta box.
521 + *
522 + * @since 1.6.29
523 + *
524 + * @global int|string $nav_menu_selected_id (id, name or slug) of the currently-selected menu.
525 + * @return void
526 + */
527 + public function endpoints_menu_meta_box() {
528 + global $nav_menu_selected_id;
529 +
530 + $walker = new Charitable_Walker_Nav_Menu_Checklist();
531 +
532 + $current_tab = 'all';
533 + $endpoints = $this->get_endpoints_for_nav_menu();
534 +
535 + $removed_args = array( 'action', 'customlink-tab', 'edit-menu-item', 'menu-item', 'page-tab', '_wpnonce' );
536 + ?>
537 + <div id="charitable" class="categorydiv">
538 + <ul id="charitable-tabs" class="charitable-tabs add-menu-item-tabs">
539 + <li <?php echo ( 'all' == $current_tab ? ' class="tabs"' : '' ); ?>>
540 + <a class="nav-tab-link" data-type="tabs-panel-charitable-all" href="
541 + <?php
542 + if ( $nav_menu_selected_id ) {
543 + echo esc_url( add_query_arg( 'charitable-tab', 'all', remove_query_arg( $removed_args ) ) );}
544 + ?>
545 + #tabs-panel-charitable-all">
546 + <?php esc_html_e( 'View All', 'charitable' ); ?>
547 + </a>
548 + </li><!-- /.tabs -->
549 + </ul>
550 + <div id="tabs-panel-charitable-all" class="tabs-panel tabs-panel-view-all <?php echo ( 'all' === $current_tab ? 'tabs-panel-active' : 'tabs-panel-inactive' ); ?>">
551 + <ul id="charitable-checklist-all" class="categorychecklist form-no-clear">
552 + <?php
553 + echo walk_nav_menu_tree( array_map( 'wp_setup_nav_menu_item', $endpoints ), 0, (object) array( 'walker' => $walker ) );
554 + ?>
555 + </ul>
556 + </div><!-- /.tabs-panel -->
557 + <p class="button-controls wp-clearfix">
558 + <span class="list-controls">
559 + <a href="
560 + <?php
561 + echo esc_url(
562 + add_query_arg(
563 + array(
564 + 'charitable-tab' => 'all',
565 + 'selectall' => 1,
566 + ),
567 + remove_query_arg( $removed_args )
568 + )
569 + );
570 + ?>
571 + #charitable" class="select-all"><?php esc_html_e( 'Select All', 'charitable' ); ?></a>
572 + </span>
573 + <span class="add-to-menu">
574 + <input type="submit"<?php wp_nav_menu_disabled_check( $nav_menu_selected_id ); ?> class="button-secondary submit-add-to-menu right" value="<?php esc_attr_e( 'Add to Menu', 'charitable' ); ?>" name="add-charitable-menu-item" id="submit-charitable" />
575 + <span class="spinner"></span>
576 + </span>
577 + </p>
578 + </div><!-- /.categorydiv -->
579 + <?php
580 + }
581 +
582 + /**
583 + * Add Charitable menu meta box to the Customizer.
584 + *
585 + * @since 1.6.29
586 + *
587 + * @param array $item_types An associative array structured for the customizer.
588 + * @return array
589 + */
590 + public function add_endpoints_menu_meta_box_to_customizer( $item_types ) {
591 + return array_merge(
592 + $item_types,
593 + array(
594 + 'charitable_nav' => array(
595 + 'title' => _x( 'Charitable', 'customizer menu section title', 'charitable' ),
596 + 'type' => 'charitable_nav',
597 + 'object' => 'charitable_nav',
598 + ),
599 + )
600 + );
601 + }
602 +
603 + /**
604 + * Add meta box items to the Customizer.
605 + *
606 + * @since 1.6.29
607 + *
608 + * @param array $items The array of menu items.
609 + * @param string $type The requested type.
610 + * @param string $object The requested object name.
611 + * @param integer $page The page num being requested.
612 + * @return array The paginated Charitable user nav items.
613 + */
614 + public function add_endpoints_menu_meta_box_items_to_customizer( $items = array(), $type = '', $object = '', $page = 0 ) {
615 + if ( 'charitable_nav' !== $object ) {
616 + return $items;
617 + }
618 +
619 + foreach ( $this->get_endpoints_for_nav_menu() as $item ) {
620 + $item = (array) $item;
621 + $item['id'] = 'charitable-' . $item['object_id'];
622 + $item['classes'] = implode( ' ', $item['classes'] );
623 + $item['type_label'] = _x( 'Custom Link', 'customizer menu type label', 'charitable' );
624 +
625 + $items[] = $item;
626 + }
627 +
628 + return array_slice( $items, 10 * $page, 10 );
629 + }
630 +
631 + /**
632 + * Return all endpoints that can be added to navigation menus.
633 + *
634 + * @since 1.6.29
635 + *
636 + * @return object[]
637 + */
638 + public function get_endpoints_for_nav_menu() {
639 + $endpoints = [];
640 +
641 + foreach ( $this->endpoints as $endpoint_id => $endpoint ) {
642 + $menu_object = $endpoint->nav_menu_object();
643 + if ( ! is_null( $menu_object ) ) {
644 + $endpoints[] = $menu_object;
645 + }
646 + }
647 +
648 + return $endpoints;
649 + }
650 +
651 + /**
274 652 * Return the current endpoint.
275 653 *
276 654 * @since 1.5.0
655 + * @version 1.8.9.1
277 656 *
278 657 * @return string|false String if we're on one of our endpoints. False otherwise.
279 658 */
280 659 public function get_current_endpoint() {
@@ -279,13 +658,33 @@
279 658 */
280 659 public function get_current_endpoint() {
281 660 if ( ! isset( $this->current_endpoint ) ) {
282 661
283 - foreach ( $this->endpoints as $endpoint_id => $endpoint ) {
284 - if ( $this->is_page( $endpoint_id, array( 'strict' => true ) ) ) {
285 - $this->current_endpoint = $endpoint_id;
662 + ksort( $this->endpoints_prioritized, SORT_NUMERIC );
286 663
287 - return $this->current_endpoint;
664 + foreach ( $this->endpoints_prioritized as $priority => $endpoint_ids ) {
665 + foreach ( $endpoint_ids as $endpoint_id ) {
666 +
667 + /* Sanity check to ensure the endpoint was properly registered. */
668 + if ( ! isset( $this->endpoints[ $endpoint_id ] ) ) {
669 + if ( charitable_is_debug() ) {
670 + error_log( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
671 + sprintf(
672 + /* translators: %s: endpoint id */
673 + __( 'Endpoint %s was incorrectly registered.', 'charitable' ),
674 + $endpoint_id
675 + )
676 + );
677 + }
678 +
679 + continue;
680 + }
681 +
682 + if ( $this->is_page( $endpoint_id, array( 'strict' => true ) ) ) {
683 + $this->current_endpoint = $endpoint_id;
684 +
685 + return $this->current_endpoint;
686 + }
288 687 }
289 688 }
290 689
291 690 $this->current_endpoint = false;
@@ -322,8 +721,20 @@
322 721 * @return boolean
323 722 */
324 723 public function endpoint_exists( $endpoint ) {
325 724 return array_key_exists( $endpoint, $this->endpoints );
725 + }
726 +
727 + /**
728 + * Returns an endpoint.
729 + *
730 + * @since 1.6.14
731 + *
732 + * @param string $endpoint The endpoint ID.
733 + * @return Charitable_Endpoint|false False if no endpoint exists, or the object.
734 + */
735 + public function get_endpoint( $endpoint ) {
736 + return $this->endpoint_exists( $endpoint ) ? $this->endpoints[ $endpoint ] : false;
326 737 }
327 738
328 739 /**
329 740 * Remove _page from the endpoint (required for backwards compatibility)