PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.12.2
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.12.2
1.8.13.1 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 All 212 releases
charitable / includes / endpoints / class-charitable-endpoints.php

class-charitable-endpoints.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.12.2, at includes/endpoints/class-charitable-endpoints.php

760 lines 20.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The endpoint registry class, providing a clean way to access details about individual endpoints.
4 *
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
11 */
12
13 // Exit if accessed directly.
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 if ( ! class_exists( 'Charitable_Endpoints' ) ) :
19
20 /**
21 * Charitable_Endpoints
22 *
23 * @since 1.5.0
24 */
25 class Charitable_Endpoints {
26
27 /**
28 * Registered endpoints.
29 *
30 * @since 1.5.0
31 *
32 * @var Charitable_Endpoint[]
33 */
34 protected $endpoints;
35
36 /**
37 * Endpoints ordered by priority.
38 *
39 * @since 1.6.37
40 *
41 * @var array
42 */
43 protected $endpoints_prioritized = array();
44
45 /**
46 * Current endpoint.
47 *
48 * @since 1.5.0
49 *
50 * @var string
51 */
52 protected $current_endpoint;
53
54 /**
55 * Create class object.
56 *
57 * @since 1.5.0
58 */
59 public function __construct() {
60 $this->endpoints = array();
61
62 add_action( 'wp', array( $this, 'disable_endpoint_cache' ) );
63 add_filter( 'pre_handle_404', array( $this, 'block_404_on_endpoints' ) );
64 add_action( 'init', array( $this, 'setup_rewrite_rules' ) );
65 add_filter( 'query_vars', array( $this, 'add_query_vars' ) );
66 add_action( 'template_redirect', array( $this, 'maybe_redirect' ) );
67 add_filter( 'template_include', array( $this, 'template_loader' ), 12 );
68 add_filter( 'home_template_hierarchy', array( $this, 'home_template_hierarchy' ) );
69 add_filter( 'the_content', array( $this, 'get_content' ) );
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 );
79 }
80
81 /**
82 * Register an endpoint.
83 *
84 * @since 1.5.0
85 *
86 * @param Charitable_Endpoint $endpoint The endpoint object.
87 * @return boolean True if the endpoint was registered. False if it was already registered.
88 */
89 public function register( Charitable_Endpoint $endpoint ) {
90 $endpoint_id = $endpoint->get_endpoint_id();
91
92 if ( $this->endpoint_exists( $endpoint_id ) ) {
93 charitable_get_deprecated()->doing_it_wrong(
94 __METHOD__,
95 /* translators: %s: endpoint id */
96 sprintf( __( 'Endpoint %s has already been registered.', 'charitable' ), $endpoint_id ),
97 '1.5.0'
98 );
99
100 return false;
101 }
102
103 $this->endpoints[ $endpoint_id ] = $endpoint;
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
112 return true;
113 }
114
115 /**
116 * Get the permalink/URL of a particular endpoint.
117 *
118 * @since 1.5.0
119 *
120 * @param string $endpoint The endpoint id.
121 * @param array $args Optional array of arguments.
122 * @return string|false
123 */
124 public function get_page_url( $endpoint, $args = array() ) {
125 $endpoint = $this->sanitize_endpoint( $endpoint );
126 $default = '';
127
128 if ( $this->endpoint_exists( $endpoint ) ) {
129 $default = $this->endpoints[ $endpoint ]->get_page_url( $args );
130 }
131
132 /**
133 * Filter the URL of a particular endpoint.
134 *
135 * The hook takes the format of charitable_permalink_{endpoint}_page. For example,
136 * for the campaign_donation endpoint, the hook is:
137 *
138 * charitable_permalink_campaign_donation_page
139 *
140 * @since 1.0.0
141 *
142 * @param string $default The endpoint's URL.
143 * @param array $args Mixed set of arguments.
144 */
145 return apply_filters( 'charitable_permalink_' . $endpoint . '_page', $default, $args );
146 }
147
148 /**
149 * Checks if we're currently viewing a particular endpoint/page.
150 *
151 * @since 1.5.0
152 *
153 * @param string $endpoint The endpoint id.
154 * @param array $args Optional array of arguments.
155 * @return boolean
156 */
157 public function is_page( $endpoint, $args = array() ) {
158 $endpoint = $this->sanitize_endpoint( $endpoint );
159 $default = '';
160
161 if ( $this->endpoint_exists( $endpoint ) ) {
162 $default = $this->endpoints[ $endpoint ]->is_page( $args );
163 }
164
165 /**
166 * Return whether we are currently viewing a particular endpoint.
167 *
168 * The hook takes the format of charitable_is_page_{endpoint}_page. For example,
169 * for the campaign_donation endpoint, the hook is:
170 *
171 * charitable_is_page_campaign_donation_page
172 *
173 * @since 1.0.0
174 *
175 * @param boolean $default Whether we are currently on the endpoint.
176 * @param array $args Mixed set of arguments.
177 */
178 return apply_filters( 'charitable_is_page_' . $endpoint . '_page', $default, $args );
179 }
180
181 /**
182 * Set up the template for an endpoint.
183 *
184 * @since 1.5.0
185 *
186 * @param string $endpoint The endpoint id.
187 * @param string $default_template The default template to be used if the endpoint doesn't return its own.
188 * @return string $template
189 */
190 public function get_endpoint_template( $endpoint, $default_template ) {
191 $endpoint = $this->sanitize_endpoint( $endpoint );
192
193 if ( ! $this->endpoint_exists( $endpoint ) ) {
194 charitable_get_deprecated()->doing_it_wrong(
195 __METHOD__,
196 sprintf(
197 /* translators: %s: endpoint id */
198 __( 'Endpoint %s has not been registered.', 'charitable' ),
199 $endpoint
200 ),
201 '1.5.0'
202 );
203
204 return $default_template;
205 }
206
207 return $this->endpoints[ $endpoint ]->get_template( $default_template );
208 }
209
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 /**
297 * Set up the rewrite rules for the site.
298 *
299 * @since 1.5.0
300 *
301 * @return void
302 */
303 public function setup_rewrite_rules() {
304 foreach ( $this->endpoints as $endpoint ) {
305 $endpoint->setup_rewrite_rules();
306 }
307
308 /* Set up any common rewrite tags */
309 add_rewrite_tag( '%donation_id%', '([0-9]+)' );
310 }
311
312 /**
313 * Add custom query vars.
314 *
315 * @since 1.5.0
316 *
317 * @param string[] $vars The query vars.
318 * @return string[]
319 */
320 public function add_query_vars( $vars ) {
321 foreach ( $this->endpoints as $endpoint ) {
322 $vars = $endpoint->add_query_vars( $vars );
323 }
324
325 return array_merge( $vars, array( 'donation_id', 'cancel' ) );
326 }
327
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 /**
354 * Load templates for our endpoints.
355 *
356 * @since 1.5.0
357 *
358 * @param string $template The default template.
359 * @return string
360 */
361 public function template_loader( $template ) {
362 $current_endpoint = $this->get_current_endpoint();
363
364 if ( ! $current_endpoint ) {
365 return $template;
366 }
367
368 $endpoint = $this->endpoints[ $current_endpoint ];
369
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 ) {
379 return $template_options;
380 }
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 */
389 $template_options = apply_filters( 'charitable_' . $current_endpoint . '_page_template', $template_options );
390
391 return charitable_get_template_path( $template_options, $template );
392 }
393
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 /**
416 * Get the content to display for the endpoint we're viewing.
417 *
418 * @since 1.5.0
419 *
420 * @param string $content The default content.
421 * @param false|string $endpoint Fetch the content for a specific endpoint.
422 * @return string
423 */
424 public function get_content( $content, $endpoint = false ) {
425 if ( ! $endpoint ) {
426 $endpoint = $this->get_current_endpoint();
427 }
428
429 if ( ! $endpoint ) {
430 return $content;
431 }
432
433 return $this->endpoints[ $endpoint ]->get_content( $content );
434 }
435
436 /**
437 * Add any custom body classes defined for the endpoint we're viewing.
438 *
439 * @since 1.5.0
440 *
441 * @param string[] $classes The list of body classes.
442 * @return string[]
443 */
444 public function add_body_classes( $classes ) {
445 $endpoint = $this->get_current_endpoint();
446
447 if ( ! $endpoint ) {
448 return $classes;
449 }
450
451 $classes[] = $this->endpoints[ $endpoint ]->get_body_class();
452
453 return $classes;
454 }
455
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 /**
652 * Return the current endpoint.
653 *
654 * @since 1.5.0
655 * @version 1.8.9.1
656 *
657 * @return string|false String if we're on one of our endpoints. False otherwise.
658 */
659 public function get_current_endpoint() {
660 if ( ! isset( $this->current_endpoint ) ) {
661
662 ksort( $this->endpoints_prioritized, SORT_NUMERIC );
663
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 }
687 }
688 }
689
690 $this->current_endpoint = false;
691 }
692
693 return $this->current_endpoint;
694 }
695
696 /**
697 * Return a list of all endpoints that should not be cached.
698 *
699 * @since 1.5.4
700 *
701 * @return array
702 */
703 public function get_non_cacheable_endpoints() {
704 $endpoints = array();
705
706 foreach ( $this->endpoints as $endpoint_id => $endpoint ) {
707 if ( ! $endpoint->is_cacheable() ) {
708 $endpoints[] = $endpoint_id;
709 }
710 }
711
712 return $endpoints;
713 }
714
715 /**
716 * Checks whether a particular endpoint exists.
717 *
718 * @since 1.5.9
719 *
720 * @param string $endpoint The endpoint ID.
721 * @return boolean
722 */
723 public function endpoint_exists( $endpoint ) {
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;
737 }
738
739 /**
740 * Remove _page from the endpoint (required for backwards compatibility)
741 * and make sure donation_cancel is changed to donation_cancellation.
742 *
743 * @since 1.5.0
744 *
745 * @param string $endpoint The endpoint id.
746 * @return string
747 */
748 protected function sanitize_endpoint( $endpoint ) {
749 $endpoint = str_replace( '_page', '', $endpoint );
750
751 if ( 'donation_cancel' == $endpoint ) {
752 $endpoint = 'donation_cancellation';
753 }
754
755 return $endpoint;
756 }
757 }
758
759 endif;
760