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

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

734 lines 19.4 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 Eric Daams
7 * @copyright Copyright (c) 2022, Studio 164a
8 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 * @since 1.5.0
10 * @version 1.6.56
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 sprintf( __( 'Endpoint %s has already been registered.', 'charitable' ), $endpoint_id ),
96 '1.5.0'
97 );
98
99 return false;
100 }
101
102 $this->endpoints[ $endpoint_id ] = $endpoint;
103
104 /* Record the endpoint by priority. */
105 if ( ! array_key_exists( $endpoint::PRIORITY, $this->endpoints_prioritized ) ) {
106 $this->endpoints_prioritized[ $endpoint::PRIORITY ] = array();
107 }
108
109 $this->endpoints_prioritized[ $endpoint::PRIORITY ][] = $endpoint_id;
110
111 return true;
112 }
113
114 /**
115 * Get the permalink/URL of a particular endpoint.
116 *
117 * @since 1.5.0
118 *
119 * @param string $endpoint The endpoint id.
120 * @param array $args Optional array of arguments.
121 * @return string|false
122 */
123 public function get_page_url( $endpoint, $args = array() ) {
124 $endpoint = $this->sanitize_endpoint( $endpoint );
125 $default = '';
126
127 if ( $this->endpoint_exists( $endpoint ) ) {
128 $default = $this->endpoints[ $endpoint ]->get_page_url( $args );
129 }
130
131 /**
132 * Filter the URL of a particular endpoint.
133 *
134 * The hook takes the format of charitable_permalink_{endpoint}_page. For example,
135 * for the campaign_donation endpoint, the hook is:
136 *
137 * charitable_permalink_campaign_donation_page
138 *
139 * @since 1.0.0
140 *
141 * @param string $default The endpoint's URL.
142 * @param array $args Mixed set of arguments.
143 */
144 return apply_filters( 'charitable_permalink_' . $endpoint . '_page', $default, $args );
145 }
146
147 /**
148 * Checks if we're currently viewing a particular endpoint/page.
149 *
150 * @since 1.5.0
151 *
152 * @param string $endpoint The endpoint id.
153 * @param array $args Optional array of arguments.
154 * @return boolean
155 */
156 public function is_page( $endpoint, $args = array() ) {
157 $endpoint = $this->sanitize_endpoint( $endpoint );
158 $default = '';
159
160 if ( $this->endpoint_exists( $endpoint ) ) {
161 $default = $this->endpoints[ $endpoint ]->is_page( $args );
162 }
163
164 /**
165 * Return whether we are currently viewing a particular endpoint.
166 *
167 * The hook takes the format of charitable_is_page_{endpoint}_page. For example,
168 * for the campaign_donation endpoint, the hook is:
169 *
170 * charitable_is_page_campaign_donation_page
171 *
172 * @since 1.0.0
173 *
174 * @param boolean $default Whether we are currently on the endpoint.
175 * @param array $args Mixed set of arguments.
176 */
177 return apply_filters( 'charitable_is_page_' . $endpoint . '_page', $default, $args );
178 }
179
180 /**
181 * Set up the template for an endpoint.
182 *
183 * @since 1.5.0
184 *
185 * @param string $endpoint The endpoint id.
186 * @param string $default_template The default template to be used if the endpoint doesn't return its own.
187 * @return string $template
188 */
189 public function get_endpoint_template( $endpoint, $default_template ) {
190 $endpoint = $this->sanitize_endpoint( $endpoint );
191
192 if ( ! $this->endpoint_exists( $endpoint ) ) {
193 charitable_get_deprecated()->doing_it_wrong(
194 __METHOD__,
195 sprintf(
196 /* translators: %s: endpoint id */
197 __( 'Endpoint %s has not been registered.', 'charitable' ), $endpoint
198 ),
199 '1.5.0'
200 );
201
202 return $default_template;
203 }
204
205 return $this->endpoints[ $endpoint ]->get_template( $default_template );
206 }
207
208 /**
209 * Disable page cache on non-cacheable endpoints using the DONOTCACHEPAGE constant.
210 *
211 * @since 1.6.14
212 *
213 * @return void
214 */
215 public function disable_endpoint_cache() {
216 if ( defined( 'DONOTCACHEPAGE' ) ) {
217 return;
218 }
219
220 $endpoint_id = $this->get_current_endpoint();
221
222 if ( ! $endpoint_id ) {
223 return;
224 }
225
226 if ( $this->get_endpoint( $endpoint_id )->is_cacheable() ) {
227 return;
228 }
229
230 define( 'DONOTCACHEPAGE', true );
231
232 /**
233 * Fire action to note that the current page should not be cached.
234 *
235 * @since 1.6.14
236 */
237 do_action( 'charitable_do_not_cache' );
238 }
239
240 /**
241 * If we're viewing a Charitable endpoint, we're not on a 404, even
242 * though WordPress interprets some pages (like the Forgot Password)
243 * as a 404 in certain cases.
244 *
245 * @since 1.6.41
246 *
247 * @param boolean $not_a_404 Whether to preempt WordPress and instruct
248 * it that this is not a 404 request.
249 * @return boolean
250 */
251 public function block_404_on_endpoints( $not_a_404 ) {
252 if ( false !== $this->get_current_endpoint() ) {
253 $not_a_404 = true;
254 } else {
255 /* Some endpoints will return false at this point since
256 * it's so early, so we unset the class property to make
257 * sure they are tested again later on.
258 */
259 unset( $this->current_endpoint );
260 }
261
262 return $not_a_404;
263 }
264
265 /**
266 * Prevent Polylang from changing some rewrite rules.
267 *
268 * @since 1.6.21
269 *
270 * @param boolean $modify Whether to modify or not the rule, defaults to true.
271 * @param array $rule Original rewrite rule.
272 * @return boolean
273 */
274 public function prevent_polylang_rewrite_modification( $modify, $rule ) {
275 /**
276 * Filter the list of endpoint URLs that Polylang won't touch.
277 *
278 * @since 1.6.21
279 *
280 * @param array $protected_rules The protected rules.
281 */
282 $protected_rules = apply_filters(
283 'charitable_polylang_protected_rewrite_rules',
284 array(
285 'charitable-listener(/(.*))?/?$',
286 )
287 );
288
289 return $modify && ! in_array( key( $rule ), $protected_rules );
290 }
291
292 /**
293 * Set up the rewrite rules for the site.
294 *
295 * @since 1.5.0
296 *
297 * @return void
298 */
299 public function setup_rewrite_rules() {
300 foreach ( $this->endpoints as $endpoint ) {
301 $endpoint->setup_rewrite_rules();
302 }
303
304 /* Set up any common rewrite tags */
305 add_rewrite_tag( '%donation_id%', '([0-9]+)' );
306 }
307
308 /**
309 * Add custom query vars.
310 *
311 * @since 1.5.0
312 *
313 * @param string[] $vars The query vars.
314 * @return string[]
315 */
316 public function add_query_vars( $vars ) {
317 foreach ( $this->endpoints as $endpoint ) {
318 $vars = $endpoint->add_query_vars( $vars );
319 }
320
321 return array_merge( $vars, array( 'donation_id', 'cancel' ) );
322 }
323
324 /**
325 * Check the current endpoint to see if we should redirect the user to a different page.
326 *
327 * @since 1.6.26
328 *
329 * @return void
330 */
331 public function maybe_redirect() {
332 $current_endpoint = $this->get_current_endpoint();
333
334 if ( ! $current_endpoint ) {
335 return;
336 }
337
338 $url = $this->endpoints[ $current_endpoint ]->get_redirect();
339
340 if ( ! $url ) {
341 return;
342 }
343
344 wp_safe_redirect( $url );
345
346 exit;
347 }
348
349 /**
350 * Load templates for our endpoints.
351 *
352 * @since 1.5.0
353 *
354 * @param string $template The default template.
355 * @return string
356 */
357 public function template_loader( $template ) {
358 $current_endpoint = $this->get_current_endpoint();
359
360 if ( ! $current_endpoint ) {
361 return $template;
362 }
363
364 $endpoint = $this->endpoints[ $current_endpoint ];
365
366 if ( ! $endpoint->uses_custom_template() ) {
367 return $template;
368 }
369
370 $endpoint->setup_template();
371
372 $template_options = $endpoint->get_template( $template );
373
374 if ( $template_options === $template ) {
375 return $template_options;
376 }
377
378 /**
379 * Filter the list of template options for the given endpoint.
380 *
381 * @since 1.0.0
382 *
383 * @param array|string $template_options
384 */
385 $template_options = apply_filters( 'charitable_' . $current_endpoint . '_page_template', $template_options );
386
387 return charitable_get_template_path( $template_options, $template );
388 }
389
390 /**
391 * When using a block theme, dynamically generated pages like the donation receipt
392 * page are interpreted as using the home template. The following workaround inserts
393 * page.php as the primary template for these page.
394 *
395 * @since since
396 *
397 * @return mixed
398 */
399 public function home_template_hierarchy( $templates ) {
400 $current_endpoint = $this->get_current_endpoint();
401
402 if ( false !== $current_endpoint ) {
403 $templates = $this->endpoints[ $current_endpoint ]->get_template( $templates );
404 }
405
406 return $templates;
407 }
408
409 /**
410 * Get the content to display for the endpoint we're viewing.
411 *
412 * @since 1.5.0
413 *
414 * @param string $content The default content.
415 * @param false|string $endpoint Fetch the content for a specific endpoint.
416 * @return string
417 */
418 public function get_content( $content, $endpoint = false ) {
419 if ( ! $endpoint ) {
420 $endpoint = $this->get_current_endpoint();
421 }
422
423 if ( ! $endpoint ) {
424 return $content;
425 }
426
427 return $this->endpoints[ $endpoint ]->get_content( $content );
428 }
429
430 /**
431 * Add any custom body classes defined for the endpoint we're viewing.
432 *
433 * @since 1.5.0
434 *
435 * @param string[] $classes The list of body classes.
436 * @return string[]
437 */
438 public function add_body_classes( $classes ) {
439 $endpoint = $this->get_current_endpoint();
440
441 if ( ! $endpoint ) {
442 return $classes;
443 }
444
445 $classes[] = $this->endpoints[ $endpoint ]->get_body_class();
446
447 return $classes;
448 }
449
450 /**
451 * If we're on an endpoint where comments should be disabled, do so.
452 *
453 * @since 1.6.36
454 *
455 * @param boolean $open Whether comments are open.
456 * @return boolean
457 */
458 public function maybe_disable_comments( $open ) {
459 if ( ! $open ) {
460 return $open;
461 }
462
463 $endpoint = $this->get_current_endpoint();
464
465 if ( ! $endpoint ) {
466 return $open;
467 }
468
469 return ! $this->endpoints[ $endpoint ]->comments_disabled();
470 }
471
472 /**
473 * If we are on an endpoint where comments are disabled, return an
474 * empty string for the template, so WordPress will not display
475 * anything.
476 *
477 * @since 1.6.36
478 *
479 * @param string $template The path to the theme template file.
480 * @return string
481 */
482 public function maybe_remove_comments_template( $template ) {
483 $endpoint = $this->get_current_endpoint();
484
485 if ( $endpoint && $this->endpoints[ $endpoint ]->comments_disabled() ) {
486 $template = charitable_get_template_path( 'comments/disabled-comments.php' );
487 }
488
489 return $template;
490 }
491
492 /**
493 * Add a "Charitable" menus meta box.
494 *
495 * @since 1.6.29
496 *
497 * @param object $object The meta box object
498 * @return object
499 */
500 public function add_endpoints_menu_meta_box( $object ) {
501 add_meta_box(
502 'add-charitable-endpoints',
503 __( 'Charitable', 'charitable' ),
504 [ $this, 'endpoints_menu_meta_box' ],
505 'nav-menus',
506 'side',
507 'low'
508 );
509
510 return $object;
511 }
512
513 /**
514 * The content of the endpoints menu meta box.
515 *
516 * @since 1.6.29
517 *
518 * @global int|string $nav_menu_selected_id (id, name or slug) of the currently-selected menu.
519 * @return void
520 */
521 public function endpoints_menu_meta_box() {
522 global $nav_menu_selected_id;
523
524 $walker = new Charitable_Walker_Nav_Menu_Checklist();
525
526 $current_tab = 'all';
527 $endpoints = $this->get_endpoints_for_nav_menu();
528
529 $removed_args = array( 'action', 'customlink-tab', 'edit-menu-item', 'menu-item', 'page-tab', '_wpnonce' );
530 ?>
531 <div id="charitable" class="categorydiv">
532 <ul id="charitable-tabs" class="charitable-tabs add-menu-item-tabs">
533 <li <?php echo ( 'all' == $current_tab ? ' class="tabs"' : '' ); ?>>
534 <a class="nav-tab-link" data-type="tabs-panel-charitable-all" href="<?php if ( $nav_menu_selected_id ) echo esc_url( add_query_arg( 'charitable-tab', 'all', remove_query_arg( $removed_args ) ) ); ?>#tabs-panel-charitable-all">
535 <?php _e( 'View All', 'charitable' ); ?>
536 </a>
537 </li><!-- /.tabs -->
538 </ul>
539 <div id="tabs-panel-charitable-all" class="tabs-panel tabs-panel-view-all <?php echo ( 'all' == $current_tab ? 'tabs-panel-active' : 'tabs-panel-inactive' ); ?>">
540 <ul id="charitable-checklist-all" class="categorychecklist form-no-clear">
541 <?php
542 echo walk_nav_menu_tree( array_map( 'wp_setup_nav_menu_item', $endpoints ), 0, (object) array( 'walker' => $walker ) );
543 ?>
544 </ul>
545 </div><!-- /.tabs-panel -->
546 <p class="button-controls wp-clearfix">
547 <span class="list-controls">
548 <a href="<?php echo esc_url( add_query_arg( array( 'charitable-tab' => 'all', 'selectall' => 1, ), remove_query_arg( $removed_args ) ) ); ?>#charitable" class="select-all"><?php _e( 'Select All', 'charitable' ); ?></a>
549 </span>
550 <span class="add-to-menu">
551 <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" />
552 <span class="spinner"></span>
553 </span>
554 </p>
555 </div><!-- /.categorydiv -->
556 <?php
557 }
558
559 /**
560 * Add Charitable menu meta box to the Customizer.
561 *
562 * @since 1.6.29
563 *
564 * @param array $item_types An associative array structured for the customizer.
565 * @return array
566 */
567 public function add_endpoints_menu_meta_box_to_customizer( $item_types ) {
568 return array_merge(
569 $item_types,
570 array(
571 'charitable_nav' => array(
572 'title' => _x( 'Charitable', 'customizer menu section title', 'charitable' ),
573 'type' => 'charitable_nav',
574 'object' => 'charitable_nav',
575 ),
576 )
577 );
578 }
579
580 /**
581 * Add meta box items to the Customizer.
582 *
583 * @since 1.6.29
584 *
585 * @param array $items The array of menu items.
586 * @param string $type The requested type.
587 * @param string $object The requested object name.
588 * @param integer $page The page num being requested.
589 * @return array The paginated Charitable user nav items.
590 */
591 public function add_endpoints_menu_meta_box_items_to_customizer( $items = array(), $type = '', $object = '', $page = 0 ) {
592 if ( 'charitable_nav' !== $object ) {
593 return $items;
594 }
595
596 foreach ( $this->get_endpoints_for_nav_menu() as $item ) {
597 $item = (array) $item;
598 $item['id'] = 'charitable-' . $item['object_id'];
599 $item['classes'] = implode( ' ', $item['classes'] );
600 $item['type_label'] = _x( 'Custom Link', 'customizer menu type label', 'charitable' );
601
602 $items[] = $item;
603 }
604
605 return array_slice( $items, 10 * $page, 10 );
606 }
607
608 /**
609 * Return all endpoints that can be added to navigation menus.
610 *
611 * @since 1.6.29
612 *
613 * @return object[]
614 */
615 public function get_endpoints_for_nav_menu() {
616 $endpoints = [];
617
618 foreach ( $this->endpoints as $endpoint_id => $endpoint ) {
619 $menu_object = $endpoint->nav_menu_object();
620 if ( ! is_null( $menu_object ) ) {
621 $endpoints[] = $menu_object;
622 }
623 }
624
625 return $endpoints;
626 }
627
628 /**
629 * Return the current endpoint.
630 *
631 * @since 1.5.0
632 *
633 * @return string|false String if we're on one of our endpoints. False otherwise.
634 */
635 public function get_current_endpoint() {
636 if ( ! isset( $this->current_endpoint ) ) {
637
638 ksort( $this->endpoints_prioritized, SORT_NUMERIC );
639
640 foreach ( $this->endpoints_prioritized as $priority => $endpoint_ids ) {
641 foreach ( $endpoint_ids as $endpoint_id ) {
642
643 /* Sanity check to ensure the endpoint was properly registered. */
644 if ( ! isset( $this->endpoints[ $endpoint_id ] ) ) {
645 error_log(
646 sprintf(
647 /* translators: %s: endpoint id */
648 __( 'Endpoint %s was incorrectly registered.', 'charitable' ),
649 $endpoint_id
650 )
651 );
652
653 continue;
654 }
655
656 if ( $this->is_page( $endpoint_id, array( 'strict' => true ) ) ) {
657 $this->current_endpoint = $endpoint_id;
658
659 return $this->current_endpoint;
660 }
661 }
662 }
663
664 $this->current_endpoint = false;
665 }
666
667 return $this->current_endpoint;
668 }
669
670 /**
671 * Return a list of all endpoints that should not be cached.
672 *
673 * @since 1.5.4
674 *
675 * @return array
676 */
677 public function get_non_cacheable_endpoints() {
678 $endpoints = array();
679
680 foreach ( $this->endpoints as $endpoint_id => $endpoint ) {
681 if ( ! $endpoint->is_cacheable() ) {
682 $endpoints[] = $endpoint_id;
683 }
684 }
685
686 return $endpoints;
687 }
688
689 /**
690 * Checks whether a particular endpoint exists.
691 *
692 * @since 1.5.9
693 *
694 * @param string $endpoint The endpoint ID.
695 * @return boolean
696 */
697 public function endpoint_exists( $endpoint ) {
698 return array_key_exists( $endpoint, $this->endpoints );
699 }
700
701 /**
702 * Returns an endpoint.
703 *
704 * @since 1.6.14
705 *
706 * @param string $endpoint The endpoint ID.
707 * @return Charitable_Endpoint|false False if no endpoint exists, or the object.
708 */
709 public function get_endpoint( $endpoint ) {
710 return $this->endpoint_exists( $endpoint ) ? $this->endpoints[ $endpoint ] : false;
711 }
712
713 /**
714 * Remove _page from the endpoint (required for backwards compatibility)
715 * and make sure donation_cancel is changed to donation_cancellation.
716 *
717 * @since 1.5.0
718 *
719 * @param string $endpoint The endpoint id.
720 * @return string
721 */
722 protected function sanitize_endpoint( $endpoint ) {
723 $endpoint = str_replace( '_page', '', $endpoint );
724
725 if ( 'donation_cancel' == $endpoint ) {
726 $endpoint = 'donation_cancellation';
727 }
728
729 return $endpoint;
730 }
731 }
732
733 endif;
734