PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.8.5
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.8.5
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 / charitable.php

charitable.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.8.5, at charitable.php

1,402 lines 41.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: Charitable
4 * Plugin URI: https://www.wpcharitable.com
5 * Description: The best WordPress donation plugin. Fundraising with recurring donations, and powerful features to help you raise more money online.
6 * Version: 1.8.8.5
7 * Author: Charitable Donations & Fundraising Team
8 * Author URI: https://wpcharitable.com
9 * Requires at least: 5.0
10 * Stable tag: 1.8.8.5
11 * License: GPLv2 or later
12 * License URI: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
13 *
14 * Text Domain: charitable
15 * Domain Path: /i18n/languages/
16 *
17 * @package Charitable
18 * @author David Bisset
19 * @copyright Copyright (c) 2024, WP Charitable LLC
20 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
21 */
22
23 // Exit if accessed directly.
24 if ( ! defined( 'ABSPATH' ) ) {
25 exit;
26 }
27
28 if ( ! class_exists( 'Charitable' ) ) :
29
30 /**
31 * Main Charitable class.
32 */
33 class Charitable {
34
35 /** The product name. */
36 const NAME = 'Charitable';
37
38 /** The plugin author name. */
39 const AUTHOR = 'WP Charitable';
40
41 /* Plugin version. */
42 const VERSION = '1.8.8.5';
43
44 /* Version of database schema. */
45 const DB_VERSION = '20180522';
46
47 /* Campaign post type. */
48 const CAMPAIGN_POST_TYPE = 'campaign';
49
50 /* Donation post type. */
51 const DONATION_POST_TYPE = 'donation';
52
53 /**
54 * Single instance of this class.
55 *
56 * @since 1.0.0
57 *
58 * @var Charitable
59 */
60 private static $instance = null;
61
62 /**
63 * The absolute path to this plugin's directory.
64 *
65 * @since 1.0.0
66 *
67 * @var string
68 */
69 private $directory_path;
70
71 /**
72 * The URL of this plugin's directory.
73 *
74 * @since 1.0.0
75 *
76 * @var string
77 */
78 private $directory_url;
79
80 /**
81 * Directory path for the includes folder of the plugin.
82 *
83 * @since 1.0.0
84 *
85 * @var string
86 */
87 private $includes_path;
88
89 /**
90 * Store of registered objects.
91 *
92 * @since 1.0.0
93 * @since 1.5.0 Changed to Charitable_Registry object. Previously it was an array.
94 *
95 * @var Charitable_Registry
96 */
97 private $registry;
98
99 /**
100 * Classmap.
101 *
102 * @since 1.5.0
103 *
104 * @var array
105 */
106 private $classmap;
107
108 /**
109 * Plugin.
110 *
111 * @since 1.8.0
112 *
113 * @var array
114 */
115 private $plugin;
116
117 /**
118 * Create class instance.
119 *
120 * @since 1.0.0
121 */
122 public function __construct() {
123 $this->directory_path = plugin_dir_path( __FILE__ );
124 $this->directory_url = plugin_dir_url( __FILE__ );
125 $this->includes_path = $this->directory_path . 'includes/';
126
127 define( 'CHARITABLE_DIRECTORY_PATH', plugin_dir_path( __FILE__ ) );
128 define( 'CHARITABLE_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
129 define( 'CHARITABLE_MARKETING_URL', 'https://wpcharitable.com/' );
130
131 define( 'CHARITABLE_BUILDER_SHOW_LEGACY_EDIT_LINKS', true ); // 1.8.0 specific.
132
133 spl_autoload_register( array( $this, 'autoloader' ) );
134
135 $this->load_dependencies();
136
137 register_activation_hook( __FILE__, array( $this, 'activate' ) );
138 register_deactivation_hook( __FILE__, array( $this, 'deactivate' ) );
139
140 add_filter( 'plugin_action_links_' . CHARITABLE_PLUGIN_BASENAME, array( $this, 'plugin_action_links' ), 99, 2 );
141 add_action( 'plugins_loaded', array( $this, 'start' ), 3 );
142
143 add_action( 'plugins_loaded', array( $this, 'check_for_version_conflicts' ), 2 );
144
145 // Initialize error handler early.
146 add_action(
147 'plugins_loaded',
148 function () {
149 require_once $this->get_path( 'includes' ) . 'class-charitable-error-handler.php';
150 $error_handler = new Charitable_Error_Handler();
151 $error_handler->init();
152 },
153 1
154 );
155 }
156
157 /**
158 * Returns the original instance of this class.
159 *
160 * @since 1.0.0
161 *
162 * @return Charitable
163 */
164 public static function get_instance() {
165 return self::$instance;
166 }
167
168 /**
169 * Run the startup sequence.
170 *
171 * This is only ever executed once.
172 *
173 * @since 1.0.0
174 *
175 * @return void
176 */
177 public function start() {
178 /* If we've already started (i.e. run this function once before), do not pass go. */
179 if ( $this->started() ) {
180 return;
181 }
182
183 /* Set static instance. */
184 self::$instance = $this;
185
186 /* Set up the registry and instantiate objects we need straight away. */
187 $this->registry();
188
189 $this->setup_licensing();
190
191 $this->maybe_start_ajax();
192
193 $this->attach_hooks_and_filters();
194
195 $this->maybe_start_admin();
196
197 $this->maybe_start_public();
198
199 Charitable_Addons::load( $this );
200 }
201
202 /**
203 * Include necessary files.
204 *
205 * @since 1.0.0
206 *
207 * @return void
208 */
209 private function load_dependencies() {
210 $includes_path = $this->get_path( 'includes' );
211
212 /* Load files with hooks & functions. Classes are autoloaded. */
213 require_once $includes_path . 'charitable-core-functions.php';
214 require_once $includes_path . 'api/charitable-api-functions.php';
215 require_once $includes_path . 'campaigns/charitable-campaign-functions.php';
216 require_once $includes_path . 'campaigns/charitable-campaign-hooks.php';
217 require_once $includes_path . 'compat/charitable-compat-functions.php';
218 require_once $includes_path . 'currency/charitable-currency-functions.php';
219 require_once $includes_path . 'deprecated/charitable-deprecated-functions.php';
220 require_once $includes_path . 'donations/charitable-donation-hooks.php';
221 require_once $includes_path . 'donations/charitable-donation-functions.php';
222 require_once $includes_path . 'emails/charitable-email-hooks.php';
223 require_once $includes_path . 'endpoints/charitable-endpoints-functions.php';
224 require_once $includes_path . 'privacy/charitable-privacy-functions.php';
225 require_once $includes_path . 'public/charitable-template-helpers.php';
226 require_once $includes_path . 'shortcodes/charitable-shortcodes-hooks.php';
227 require_once $includes_path . 'upgrades/charitable-upgrade-hooks.php';
228 require_once $includes_path . 'users/charitable-user-functions.php';
229 require_once $includes_path . 'user-management/charitable-user-management-hooks.php';
230 require_once $includes_path . 'utilities/charitable-utility-functions.php';
231 require_once $includes_path . 'elementor/charitable-elementor-hooks.php';
232
233 /* Load vendor/autoload.php - our modified platform_check.php and autoload_real.php will handle conditional loading */
234 require_once $this->get_path( 'directory' ) . 'vendor/autoload.php';
235
236 /* Square Compatibility Wrapper - always load this regardless of PHP version */
237 require_once $includes_path . 'square/class-charitable-square-compatibility.php';
238
239 /* Initialize Square conditionally - will check for compatibility internally */
240 Charitable_Square_Compatibility::init();
241
242 if ( $this->load_core_stripe() ) :
243
244 /* Interfaces */
245 require_once $includes_path . 'stripe/interfaces/interface-charitable-stripe-gateway-processor.php';
246
247 /* Abstract classes */
248 require_once $includes_path . 'stripe/abstracts/class-charitable-stripe-gateway-processor.php';
249
250 /* Classes */
251 require_once $includes_path . 'stripe/donations/class-charitable-stripe-donation-log.php';
252 require_once $includes_path . 'stripe/donations/class-charitable-stripe-recurring-donation-log.php';
253 require_once $includes_path . 'stripe/i18n/class-charitable-stripe-i18n.php';
254 require_once $includes_path . 'stripe/gateway/class-charitable-stripe-connected-customer.php';
255 require_once $includes_path . 'stripe/gateway/class-charitable-stripe-customer.php';
256 require_once $includes_path . 'stripe/gateway/class-charitable-stripe-gateway-processor-payment-intents.php';
257 require_once $includes_path . 'stripe/gateway/class-charitable-stripe-payment-intent.php';
258 require_once $includes_path . 'stripe/gateway/class-charitable-stripe-plan.php';
259 require_once $includes_path . 'stripe/gateway/class-charitable-stripe-product.php';
260
261 require_once $includes_path . 'stripe/connect/charitable-stripe-connect-functions.php';
262 require_once $includes_path . 'stripe/gateway/class-charitable-stripe-gateway-processor-checkout.php';
263
264 /* Functions & Hooks */
265 require_once $includes_path . 'stripe/charitable-stripe-core-functions.php';
266 require_once $includes_path . 'stripe/donations/charitable-stripe-donation-functions.php';
267 require_once $includes_path . 'stripe/gateway/charitable-stripe-gateway-hooks.php';
268
269 /* webhooks */
270 require_once $includes_path . 'stripe/gateway/class-charitable-stripe-webhook-api.php';
271 require_once $includes_path . 'stripe/gateway/class-charitable-stripe-webhook-processor.php';
272
273 endif;
274 }
275
276 /**
277 * Load the template functions after theme is loaded.
278 *
279 * This gives themes time to override the functions.
280 *
281 * @since 1.6.10
282 *
283 * @return void
284 */
285 public function load_template_files() {
286 $includes_path = $this->get_path( 'includes' );
287
288 require_once $includes_path . 'public/charitable-template-functions.php';
289 require_once $includes_path . 'public/charitable-template-hooks.php';
290 }
291
292 /**
293 * Dynamically loads the class attempting to be instantiated elsewhere in the
294 * plugin by looking at the $class_name parameter being passed as an argument.
295 *
296 * @since 1.5.0
297 *
298 * @param string $class_name The fully-qualified name of the class to load.
299 * @return boolean
300 */
301 public function autoloader( $class_name ) {
302 /* If the specified $class_name already exists, bail. */
303 if ( class_exists( $class_name ) ) {
304 return false;
305 }
306
307 /* If the specified $class_name does not include our namespace, duck out. */
308 if ( false === strpos( $class_name, 'Charitable_' ) ) {
309 return false;
310 }
311
312 /* Autogenerated class map. */
313 if ( ! isset( $this->classmap ) ) {
314 $this->classmap = include 'includes/autoloader/charitable-class-map.php';
315
316 $this->classmap['Charitable_Gateway_Stripe_AM'] = 'gateways/class-charitable-gateway-stripe-am.php';
317 $this->classmap['Charitable_Gateway_Stripe'] = 'gateways/class-charitable-gateway-stripe-am.php';
318 }
319
320 $file_path = isset( $this->classmap[ $class_name ] ) ? $this->get_path( 'includes' ) . $this->classmap[ $class_name ] : false;
321
322 if ( false !== $file_path && file_exists( $file_path ) && is_file( $file_path ) ) {
323 require_once $file_path;
324 return true;
325 }
326 return false;
327 }
328
329 /**
330 * Returns a registered class object.
331 *
332 * @since 1.5.0
333 *
334 * @return Charitable_Registry
335 */
336 public function registry() {
337 if ( ! isset( $this->registry ) ) {
338 $this->registry = new Charitable_Registry();
339 $this->registry->register_object( Charitable_Emails::get_instance() );
340 $this->registry->register_object( Charitable_Request::get_instance() );
341 $this->registry->register_object( Charitable_Gateways::get_instance() );
342 $this->registry->register_object( Charitable_i18n::get_instance() );
343 $this->registry->register_object( Charitable_Post_Types::get_instance() );
344 $this->registry->register_object( Charitable_Cron::get_instance() );
345 $this->registry->register_object( Charitable_Widgets::get_instance() );
346 $this->registry->register_object( Charitable_Licenses::get_instance() );
347 $this->registry->register_object( Charitable_User_Dashboard::get_instance() );
348 $this->registry->register_object( Charitable_Locations::get_instance() );
349 $this->registry->register_object( Charitable_Currency::get_instance() );
350 $this->registry->register_object( Charitable_Notifications::get_instance() );
351
352 $this->registry->register_object( new Charitable_Privacy() );
353 $this->registry->register_object( new Charitable_Debugging() );
354 $this->registry->register_object( new Charitable_Locale() );
355 }
356
357 return $this->registry;
358 }
359
360 /**
361 * Set up hook and filter callback functions.
362 *
363 * @since 1.0.0
364 *
365 * @return void
366 */
367 private function attach_hooks_and_filters() {
368 add_action( 'wpmu_new_blog', array( $this, 'maybe_activate_charitable_on_new_site' ) );
369 add_action( 'plugins_loaded', array( $this, 'charitable_install' ), 100 );
370 add_action( 'plugins_loaded', array( $this, 'charitable_start' ), 100 );
371 add_action( 'plugins_loaded', array( $this, 'endpoints' ), 100 );
372 add_action( 'init', array( $this, 'donation_fields' ), 100 );
373 add_action( 'init', array( $this, 'campaign_fields' ), 100 );
374 add_action( 'plugins_loaded', array( $this, 'register_donormeta_table' ) );
375 add_action( 'plugins_loaded', 'charitable_load_compat_functions' );
376 add_action( 'setup_theme', array( 'Charitable_Customizer', 'start' ) );
377 add_action( 'after_setup_theme', array( $this, 'load_template_files' ) );
378 add_action( 'wp_enqueue_scripts', array( $this, 'maybe_start_qunit' ), 100 );
379 add_action( 'rest_api_init', 'charitable_register_api_routes' );
380
381 /**
382 * We do this on priority 20 so that any functionality that is loaded on init (such
383 * as addons) has a chance to run before the event.
384 */
385 add_action( 'init', array( $this, 'do_charitable_actions' ), 20 );
386
387 if ( $this->load_core_stripe() ) :
388
389 /**
390 * Set up scripts & stylesheets & enqueue them when appropriate.
391 */
392 add_action( 'charitable_form_after_fields', array( $this, 'maybe_setup_stripe_scripts_in_donation_form' ) );
393 add_action( 'charitable_campaign_loop_after', array( $this, 'maybe_setup_stripe_scripts_in_campaign_loop' ) );
394
395 endif;
396 }
397
398
399 /**
400 * Load Stripe JS, as well as our handling scripts.
401 *
402 * @since 1.4.0
403 *
404 * @return boolean
405 */
406 public function enqueue_stripe_scripts() {
407 if ( ! Charitable_Gateways::get_instance()->is_active_gateway( Charitable_Gateway_Stripe_AM::get_gateway_id() ) ) {
408 return false;
409 }
410
411 wp_enqueue_script( 'charitable-stripe' );
412
413 return true;
414 }
415
416 /**
417 * Load Stripe JS or Stripe Checkout, as well as our handling script.
418 *
419 * @uses Charitable_Stripe::enqueue_scripts()
420 *
421 * @since 1.4.0
422 *
423 * @param Charitable_Donation_Form $form The current form object.
424 * @return boolean
425 */
426 public function maybe_setup_stripe_scripts_in_donation_form( $form ) {
427 if ( ! is_a( $form, 'Charitable_Donation_Form' ) ) {
428 return false;
429 }
430
431 if ( 'make_donation' !== $form->get_form_action() ) {
432 return false;
433 }
434
435 return $this->enqueue_stripe_scripts();
436 }
437
438 /**
439 * Enqueue the Stripe JS/Checkout scripts after a campaign loop if modal donations are in use.
440 *
441 * @uses Charitable_Stripe::enqueue_scripts()
442 *
443 * @since 1.4.0
444 *
445 * @return boolean
446 */
447 public function maybe_setup_stripe_scripts_in_campaign_loop() {
448 if ( 'modal' !== charitable_get_option( 'donation_form_display', 'separate_page' ) ) {
449 return false;
450 }
451
452 return $this->enqueue_stripe_scripts();
453 }
454
455 /**
456 * Checks whether we're in the admin area and if so, loads the admin-only functionality.
457 *
458 * @since 1.0.0
459 *
460 * @return Charitable_Admin|false
461 */
462 private function maybe_start_admin() {
463 if ( ! is_admin() ) {
464 return false;
465 }
466
467 // load blocks/Gutenberg related.
468 require_once $this->get_path( 'admin' ) . 'class-charitable-admin-blocks.php';
469
470 require_once $this->get_path( 'admin' ) . 'class-charitable-admin.php';
471 require_once $this->get_path( 'admin' ) . 'charitable-admin-hooks.php';
472
473 // load campaign builder.
474 require_once $this->get_path( 'admin' ) . 'campaign-builder/util.php';
475 require_once $this->get_path( 'admin' ) . 'campaign-builder/functions.php';
476 require_once $this->get_path( 'admin' ) . 'campaign-builder/ajax-actions.php';
477 require_once $this->get_path( 'admin' ) . 'campaign-builder/functions-legacy.php';
478 require_once $this->get_path( 'admin' ) . 'campaign-builder/debug.php';
479 require_once $this->get_path( 'admin' ) . 'campaign-builder/access.php';
480 require_once $this->get_path( 'admin' ) . 'campaign-builder/class-builder-fields.php';
481 require_once $this->get_path( 'admin' ) . 'campaign-builder/class-builder-form-fields.php';
482
483 // connect for upgrade.wpcharitable.com.
484 require_once $this->get_path( 'admin' ) . 'class-charitable-admin-connect.php';
485
486 // misc.
487 require_once $this->get_path( 'admin' ) . 'class-charitable-admin-pointers.php';
488 require_once $this->get_path( 'admin' ) . 'plugins/charitable-admin-plugin-hooks.php';
489 require_once $this->get_path( 'admin' ) . 'class-charitable-admin-getting-started.php';
490
491 $admin = Charitable_Admin::get_instance();
492 $this->registry->register_object( $admin );
493
494 // Stripe.
495 require_once $this->get_path( 'includes' ) . 'stripe/admin/class-charitable-stripe-admin.php';
496 new Charitable_Stripe_Admin();
497
498 return $admin;
499 }
500
501 /**
502 * Checks whether we're on the public-facing side and if so, loads the public-facing functionality.
503 *
504 * @since 1.0.0
505 *
506 * @return Charitable_Public|false
507 */
508 private function maybe_start_public() {
509 if ( is_admin() && ! $this->is_ajax() ) {
510 return false;
511 }
512
513 require_once $this->get_path( 'public' ) . 'class-charitable-public.php';
514
515 // Campaign Builder related (v1.8.0).
516 require_once $this->get_path( 'admin' ) . 'campaign-builder/class-campaign-builder-preview.php';
517 require_once $this->get_path( 'admin' ) . 'campaign-builder/class-builder-fields.php';
518 require_once $this->get_path( 'admin' ) . 'campaign-builder/class-builder-form-fields.php';
519
520 // load blocks/Gutenberg related.
521 require_once $this->get_path( 'admin' ) . 'class-charitable-admin-blocks.php';
522
523 $public = Charitable_Public::get_instance();
524
525 $this->registry->register_object( $public );
526
527 return $public;
528 }
529
530 /**
531 * Load the QUnit tests if ?qunit is appended to the request.
532 *
533 * @since 1.4.17
534 *
535 * @return boolean
536 */
537 public function maybe_start_qunit() {
538 /* Skip out early if ?qunit isn't included in the request. */
539 if ( ! array_key_exists( 'qunit', $_GET ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
540 return false;
541 }
542
543 /* The unit tests have to exist. */
544 if ( ! file_exists( $this->get_path( 'directory' ) . 'tests/qunit/tests.js' ) ) {
545 return false;
546 }
547
548 wp_register_script( 'qunit', 'https://code.jquery.com/qunit/qunit-2.3.3.js', array(), '2.3.3', true );
549
550 /* Version: '20170615-15:44' */
551 wp_register_script( 'qunit-tests', $this->get_path( 'directory', false ) . 'tests/qunit/tests.js', array( 'jquery', 'qunit' ), time(), true );
552 wp_enqueue_script( 'qunit-tests' );
553
554 wp_register_style( 'qunit', 'https://code.jquery.com/qunit/qunit-2.3.3.css', array(), '2.3.3', 'all' );
555 wp_enqueue_style( 'qunit' );
556 }
557
558 /**
559 * Checks whether the current request is an AJAX request.
560 *
561 * @since 1.5.0
562 *
563 * @return boolean
564 */
565 private function is_ajax() {
566 return false !== ( defined( 'DOING_AJAX' ) && DOING_AJAX );
567 }
568
569 /**
570 * Checks whether we're executing an AJAX hook and if so, loads some AJAX functionality.
571 *
572 * @since 1.0.0
573 *
574 * @return void
575 */
576 private function maybe_start_ajax() {
577 if ( ! $this->is_ajax() ) {
578 return;
579 }
580
581 require_once $this->get_path( 'includes' ) . 'ajax/charitable-ajax-functions.php';
582 require_once $this->get_path( 'includes' ) . 'ajax/charitable-ajax-hooks.php';
583 }
584
585 /**
586 * This method is fired after all plugins are loaded and simply fires the charitable_start hook.
587 *
588 * Extensions can use the charitable_start event to load their own functionality.
589 *
590 * @since 1.0.0
591 *
592 * @return void
593 */
594 public function charitable_start() {
595 do_action( 'charitable_start', $this );
596 }
597
598 /**
599 * Fires off an action right after Charitable is installed, allowing other
600 * plugins/themes to do something at this point.
601 *
602 * @since 1.0.1
603 * @version 1.8.1.12 - Added transient for activation redirect.
604 * @version 1.8.3 - Added call to add version and delete notifications.
605 *
606 * @return void
607 */
608 public function charitable_install() {
609 $install = get_transient( 'charitable_install' );
610
611 if ( ! $install ) {
612 return;
613 }
614
615 require_once $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php';
616
617 Charitable_Install::finish_installing();
618
619 do_action( 'charitable_install' );
620
621 delete_transient( 'charitable_install' );
622
623 // Store the date when the initial activation was performed.
624 $type = function_exists( 'charitable_is_pro' ) && charitable_is_pro() ? 'pro' : 'lite';
625 $activated = (array) get_option( 'charitable_activated', array() );
626
627 // If no activation date is set, assume it's a new install and set the activation redirect transient.
628 if ( false === $activated || empty( $activated ) ) {
629 // Add transient to trigger redirect to the Welcome / Getting Started screen.
630 set_transient( 'charitable_activation_redirect', true, 30 );
631 }
632
633 $this->add_version_to_upgraded_from();
634
635 if ( empty( $activated[ $type ] ) ) {
636 $activated[ $type ] = time();
637 update_option( 'charitable_activated', $activated );
638 }
639
640 // Clear notifications.
641 delete_option( 'charitable_notifications' );
642 }
643
644 /**
645 * Returns whether we are currently in the start phase of the plugin.
646 *
647 * @since 1.0.0
648 *
649 * @return boolean
650 */
651 public function is_start() {
652 return current_filter() == 'charitable_start'; // phpcs:ignore
653 }
654
655 /**
656 * Returns whether the plugin has already started.
657 *
658 * @since 1.0.0
659 *
660 * @return boolean
661 */
662 public function started() {
663 return did_action( 'charitable_start' ) || current_filter() == 'charitable_start'; // phpcs:ignore
664 }
665
666 /**
667 * Returns whether the plugin is being activated.
668 *
669 * @since 1.0.0
670 *
671 * @return boolean
672 */
673 public function is_activation() {
674 return current_filter() == 'activate_charitable/charitable.php'; // phpcs:ignore
675 }
676
677 /**
678 * Returns whether the plugin is being deactivated.
679 *
680 * @since 1.0.0
681 *
682 * @return boolean
683 */
684 public function is_deactivation() {
685 return current_filter() == 'deactivate_charitable/charitable.php'; // phpcs:ignore
686 }
687
688 /**
689 * Returns plugin paths.
690 *
691 * @since 1.0.0
692 *
693 * @param string $type If empty, returns the path to the plugin.
694 * @param boolean $absolute_path If true, returns the file system path. If false, returns it as a URL.
695 * @return string
696 */
697 public function get_path( $type = '', $absolute_path = true ) {
698 $base = $absolute_path ? $this->directory_path : $this->directory_url;
699
700 switch ( $type ) {
701 case 'includes':
702 $path = $base . 'includes/';
703 break;
704
705 case 'admin':
706 $path = $base . 'includes/admin/';
707 break;
708
709 case 'public':
710 $path = $base . 'includes/public/';
711 break;
712
713 case 'assets':
714 $path = $base . 'assets/';
715 break;
716
717 case 'templates':
718 $path = $base . 'templates/';
719 break;
720
721 case 'plugin-directory':
722 $path = WP_PLUGIN_DIR;
723 break;
724
725 case 'directory':
726 $path = $base;
727 break;
728
729 default:
730 $path = __FILE__;
731
732 }//end switch
733
734 return $path;
735 }
736
737 /**
738 * Returns the plugin's version number.
739 *
740 * @since 1.0.0
741 *
742 * @return string
743 */
744 public function get_version() {
745 $version = self::VERSION;
746
747 if ( false !== strpos( $version, '-' ) ) {
748 $parts = explode( '-', $version );
749 $version = $parts[0];
750 }
751
752 return $version;
753 }
754
755 /**
756 * Adds previous versions to the upgraded_from option.
757 *
758 * @since 1.8.3
759 */
760 public function add_version_to_upgraded_from() {
761
762 $upgraded_from = get_option( 'charitable_version_upgraded_from', array() );
763
764 if ( ! is_array( $upgraded_from ) ) {
765 $upgraded_from = array();
766 }
767
768 $upgraded_from[] = $this->get_version();
769
770 update_option( 'charitable_version_upgraded_from', $upgraded_from, false );
771 }
772
773 /**
774 * Get the campaign fields registry
775 *
776 * If the registry has not been set up yet, this will also
777 * perform the task of setting it up initially.
778 *
779 * This method is called on the plugins_loaded hook.
780 *
781 * @since 1.6.0
782 *
783 * @return Charitable_Campaign_Field_Registry
784 */
785 public function campaign_fields() {
786 if ( ! $this->registry->has( 'campaign_field_registry' ) ) {
787 $campaign_fields = new Charitable_Campaign_Field_Registry();
788
789 /* Register it immediately to avoid endless recursion. */
790 $this->registry->register_object( $campaign_fields );
791
792 $fields = include $this->get_path( 'includes' ) . 'fields/default-fields/campaign-fields.php';
793
794 foreach ( $fields as $key => $args ) {
795 $campaign_fields->register_field( new Charitable_Campaign_Field( $key, $args ) );
796 }
797 }
798
799 return $this->registry->get( 'campaign_field_registry' );
800 }
801
802 /**
803 * Get the donation fields registry
804 *
805 * If the registry has not been set up yet, this will also
806 * perform the task of setting it up initially.
807 *
808 * This method is called on the plugins_loaded hook.
809 *
810 * @since 1.5.0
811 *
812 * @return Charitable_Donation_Field_Registry
813 */
814 public function donation_fields() {
815 if ( ! $this->registry->has( 'donation_field_registry' ) ) {
816 $donation_fields = new Charitable_Donation_Field_Registry();
817
818 /* Register it immediately to avoid endless recursion. */
819 $this->registry->register_object( $donation_fields );
820
821 $fields = include $this->get_path( 'includes' ) . 'fields/default-fields/donation-fields.php';
822
823 foreach ( $fields as $key => $args ) {
824 $donation_fields->register_field( new Charitable_Donation_Field( $key, $args ) );
825 }
826 }
827
828 return $this->registry->get( 'donation_field_registry' );
829 }
830
831 /**
832 * Return the Endpoints API object.
833 *
834 * If the Endpoints API has not been set up yet, this will also
835 * perform the task of setting it up initially.
836 *
837 * This method is called on the plugins_loaded hook.
838 *
839 * @since 1.5.0
840 *
841 * @return Charitable_Endpoints
842 */
843 public function endpoints() {
844 if ( ! $this->registry->has( 'endpoints' ) ) {
845 /**
846 * The order in which we register endpoints is important, because
847 * it determines the order in which the endpoints are checked to
848 * find whether they are the current page.
849 *
850 * Any endpoint that builds on another endpoint should be registered
851 * BEFORE the endpoint it builds on. In other words, move from
852 * most specific to least specific.
853 */
854 $endpoints = new Charitable_Endpoints();
855 $endpoints->register( new Charitable_Campaign_Donation_Endpoint() );
856 $endpoints->register( new Charitable_Campaign_Widget_Endpoint() );
857 $endpoints->register( new Charitable_Campaign_Endpoint() );
858 $endpoints->register( new Charitable_Donation_Cancellation_Endpoint() );
859 $endpoints->register( new Charitable_Donation_Processing_Endpoint() );
860 $endpoints->register( new Charitable_Donation_Receipt_Endpoint() );
861 $endpoints->register( new Charitable_Email_Preview_Endpoint() );
862 $endpoints->register( new Charitable_Email_Verification_Endpoint() );
863 $endpoints->register( new Charitable_Forgot_Password_Endpoint() );
864 $endpoints->register( new Charitable_Reset_Password_Endpoint() );
865 $endpoints->register( new Charitable_Registration_Endpoint() );
866 $endpoints->register( new Charitable_Login_Endpoint() );
867 $endpoints->register( new Charitable_Profile_Endpoint() );
868 $endpoints->register( new Charitable_Webhook_Listener_Endpoint() );
869
870 $this->registry->register_object( $endpoints );
871 }
872
873 return $this->registry->get( 'endpoints' );
874 }
875
876 /**
877 * Returns a registered object.
878 *
879 * @since 1.0.0
880 *
881 * @param string $class The type of class to be retrieved.
882 * @return object
883 */
884 public function get_registered_object( $class ) { // phpcs:ignore
885 return $this->registry->get( $class );
886 }
887
888 /**
889 * Registers our donormeta table as a meta data table.
890 *
891 * @since 1.6.0
892 *
893 * @global WPDB $wpdb
894 * @return void
895 */
896 public function register_donormeta_table() {
897 global $wpdb;
898
899 $wpdb->donormeta = $wpdb->prefix . 'charitable_donormeta';
900 }
901
902 /**
903 * Returns the model for one of Charitable's database tables.
904 *
905 * @since 1.0.0
906 *
907 * @param string $table The database table to retrieve.
908 * @return Charitable_DB
909 */
910 public function get_db_table( $table ) {
911 $tables = $this->get_tables();
912
913 if ( ! isset( $tables[ $table ] ) ) {
914 charitable_get_deprecated()->doing_it_wrong(
915 __METHOD__,
916 sprintf( 'Invalid table %s passed', $table ),
917 '1.0.0'
918 );
919 return null;
920 }
921
922 return $this->registry->get( $tables[ $table ] );
923 }
924
925 /**
926 * Return the filtered list of registered tables.
927 *
928 * @since 1.0.0
929 *
930 * @return string[]
931 */
932 private function get_tables() {
933 /**
934 * Filter the array of available Charitable table classes.
935 *
936 * @since 1.0.0
937 * @version 1.8.1
938 *
939 * @param array $tables List of tables as a key=>value array.
940 */
941 return apply_filters(
942 'charitable_db_tables',
943 array(
944 'campaign_donations' => 'Charitable_Campaign_Donations_DB',
945 'donors' => 'Charitable_Donors_DB',
946 'donormeta' => 'Charitable_Donormeta_DB',
947 'donation_activities' => 'Charitable_Donation_Activities_DB',
948 'campaign_activities' => 'Charitable_Campaign_Activities_DB',
949 )
950 );
951 }
952
953 /**
954 * Maybe activate Charitable when a new site is added in a multisite network.
955 *
956 * @since 1.4.6
957 *
958 * @param int $blog_id The blog to activate Charitable on.
959 * @return void
960 */
961 public function maybe_activate_charitable_on_new_site( $blog_id ) {
962 if ( is_plugin_active_for_network( basename( $this->directory_path ) . '/charitable.php' ) ) {
963 switch_to_blog( $blog_id );
964 $this->activate( false );
965 restore_current_blog();
966 }
967 }
968
969 /**
970 * Runs on plugin activation.
971 *
972 * @see register_activation_hook
973 *
974 * @since 1.0.0
975 *
976 * @param boolean $network_wide Whether to enable the plugin for all sites in the network
977 * or just the current site. Multisite only. Default is false.
978 * @return void
979 */
980 public function activate( $network_wide = false ) {
981 require_once $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php';
982
983 if ( is_multisite() && $network_wide ) {
984 global $wpdb;
985
986 foreach ( $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" ) as $blog_id ) { // phpcs:ignore
987 switch_to_blog( $blog_id );
988 new Charitable_Install( $this->includes_path );
989 restore_current_blog();
990 }
991 } else {
992 new Charitable_Install( $this->includes_path );
993 }
994
995 // create or update the install date for future reference.
996 update_option( 'wpcharitable_activated_datetime', current_time( 'timestamp' ) ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
997
998 // Set initial timestamps for rotating menu items if they're already installed
999 $this->set_initial_rotating_menu_timestamps();
1000 }
1001
1002 /**
1003 * Runs on plugin deactivation.
1004 *
1005 * @see register_deactivation_hook
1006 *
1007 * @since 1.0.0
1008 *
1009 * @return void
1010 */
1011 public function deactivate() {
1012 require_once $this->get_path( 'includes' ) . 'plugin/class-charitable-uninstall.php';
1013 new Charitable_Uninstall();
1014 }
1015
1016 /**
1017 * If a charitable_action event is triggered, delegate the event using do_action.
1018 *
1019 * @since 1.0.0
1020 *
1021 * @return void
1022 */
1023 public function do_charitable_actions() {
1024 if ( isset( $_REQUEST['charitable_action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1025
1026 $action = esc_attr( $_REQUEST['charitable_action'] ); // phpcs:ignore
1027
1028 /**
1029 * Handle Charitable action.
1030 *
1031 * @since 1.0.0
1032 */
1033 do_action( 'charitable_' . $action );
1034 }
1035 }
1036
1037 /**
1038 * Throw error on object clone.
1039 *
1040 * This class is specifically designed to be instantiated once. You can retrieve the instance using charitable()
1041 *
1042 * @since 1.0.0
1043 *
1044 * @return void
1045 */
1046 public function __clone() {
1047 charitable_get_deprecated()->doing_it_wrong(
1048 __FUNCTION__,
1049 __( 'Cloning this object is forbidden.', 'charitable' ),
1050 '1.0.0'
1051 );
1052 }
1053
1054 /**
1055 * Disable unserializing of the class.
1056 *
1057 * @since 1.0.0
1058 *
1059 * @return void
1060 */
1061 public function __wakeup() {
1062 charitable_get_deprecated()->doing_it_wrong(
1063 __FUNCTION__,
1064 __( 'Unserializing instances of this class is forbidden.', 'charitable' ),
1065 '1.0.0'
1066 );
1067 }
1068
1069 /**
1070 * DEPRECATED METHODS
1071 */
1072
1073 /**
1074 * Load plugin compatibility files on plugins_loaded hook.
1075 *
1076 * @deprecated 1.8.0
1077 *
1078 * @since 1.4.18
1079 * @since 1.5.0 Deprecated.
1080 *
1081 * @return void
1082 */
1083 public function load_plugin_compat_files() {
1084 charitable_get_deprecated()->deprecated_function(
1085 __METHOD__,
1086 '1.5.0.',
1087 'charitable_load_compat_functions'
1088 );
1089
1090 charitable_load_compat_functions();
1091 }
1092
1093 /**
1094 * Stores an object in the plugin's registry.
1095 *
1096 * @deprecated 1.8.0
1097 *
1098 * @since 1.0.0
1099 * @since 1.5.0 Deprecated.
1100 *
1101 * @param mixed $object Object to be registered.
1102 * @return void
1103 */
1104 public function register_object( $object ) { // phpcs:ignore
1105 charitable_get_deprecated()->deprecated_function(
1106 __METHOD__,
1107 '1.5.0',
1108 'charitable()->registry()->register_object()'
1109 );
1110
1111 $this->registry->register_object( $object );
1112 }
1113
1114 /**
1115 * Returns the public class.
1116 *
1117 * @deprecated 1.8.0
1118 *
1119 * @since 1.0.0
1120 * @since 1.5.0 Deprecated.
1121 *
1122 * @return Charitable_Public
1123 */
1124 public function get_public() {
1125 charitable_get_deprecated()->deprecated_function(
1126 __METHOD__,
1127 '1.5.0',
1128 'charitable_get_helper'
1129 );
1130
1131 return $this->registry->get( 'Charitable_Public' );
1132 }
1133
1134 /**
1135 * Returns the admin class.
1136 *
1137 * @deprecated 1.8.0
1138 *
1139 * @since 1.0.0
1140 * @since 1.5.0 Deprecated.
1141 *
1142 * @return Charitable_Admin
1143 */
1144 public function get_admin() {
1145 charitable_get_deprecated()->deprecated_function(
1146 __METHOD__,
1147 '1.5.0',
1148 'charitable_get_helper'
1149 );
1150
1151 return $this->registry->get( 'Charitable_Admin' );
1152 }
1153
1154 /**
1155 * Return the current request object.
1156 *
1157 * @deprecated 1.8.0
1158 *
1159 * @since 1.0.0
1160 * @since 1.5.0 Deprecated.
1161 *
1162 * @return Charitable_Request
1163 */
1164 public function get_request() {
1165 charitable_get_deprecated()->deprecated_function(
1166 __METHOD__,
1167 '1.5.0',
1168 'charitable_get_helper'
1169 );
1170
1171 return $this->registry->get( 'Charitable_Request' );
1172 }
1173
1174 /**
1175 * Returns the instance of Charitable_Currency.
1176 *
1177 * @deprecated 1.7.0
1178 *
1179 * @since 1.4.0 Deprecated.
1180 */
1181 public function get_currency_helper() {
1182 charitable_get_deprecated()->deprecated_function( __METHOD__, '1.4.0', 'charitable_get_currency_helper' );
1183 return charitable_get_currency_helper();
1184 }
1185
1186 /**
1187 * Returns the instance of Charitable_Locations.
1188 *
1189 * @deprecated 1.6.0
1190 *
1191 * @since 1.2.0 Deprecated.
1192 */
1193 public function get_location_helper() {
1194 charitable_get_deprecated()->deprecated_function( __METHOD__, '1.2.0', 'charitable_get_location_helper' );
1195 return charitable_get_location_helper();
1196 }
1197
1198 /**
1199 * Determine if we use Stripe core files or use an addon if that's installed and active. Checks for USE_NEW_STRIPE first.
1200 *
1201 * @since 1.7.0
1202 *
1203 * @return boolean
1204 */
1205 public function load_core_stripe() {
1206
1207 if ( false !== ( defined( 'USE_NEW_STRIPE' ) && USE_NEW_STRIPE ) ) {
1208 return true;
1209 }
1210
1211 // check and see if the core Stripe AM gateway is an active gateway, and if it is not, don't load the core stripe JS, etc.
1212 if ( class_exists( 'Charitable_Gateways' ) && class_exists( 'Charitable_Gateway_Stripe_AM' ) && ! Charitable_Gateways::get_instance()->is_active_gateway( Charitable_Gateway_Stripe_AM::get_gateway_id() ) ) {
1213 return false;
1214 }
1215
1216 return true;
1217 }
1218
1219 /**
1220 * Determine Stripe Connect is active.
1221 *
1222 * @since 1.7.0
1223 *
1224 * @return boolean
1225 */
1226 public function is_stripe_connect_addon() {
1227
1228 if ( in_array( 'charitable-stripe/charitable-stripe.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) { // phpcs:ignore
1229 return true;
1230 }
1231
1232 return false;
1233 }
1234
1235 /**
1236 * Action links for the plugins page.
1237 *
1238 * @since 1.7.0
1239 *
1240 * @param array $actions An array of existing actions.
1241 * @param string $plugin_file The plugin file we are modifying.
1242 * @param boolean $action_links An array of action links to add.
1243 * @return array An array of action links.
1244 */
1245 public function plugin_action_links( $actions, $plugin_file, $action_links = false ) {
1246
1247 if ( ! charitable_is_pro() ) :
1248
1249 // if this isn't pro, add the upgrade link which is visible on the plugins page until the plugin name.
1250
1251 $action_links = array(
1252 'proupgrade' => array(
1253 // Translators: This is an action link users can click to purchase a license for All in One SEO Pro.
1254 'label' => __( 'Upgrade to Pro', 'charitable' ),
1255 'url' => 'https://wpcharitable.com/lite-vs-pro/?utm_source=WordPress&utm_campaign=WP+Charitable&utm_medium=Action+Link+Plugins+Page&utm_content=Upgrade+to+Pro',
1256 ),
1257 );
1258
1259 endif;
1260
1261 if ( isset( $actions['edit'] ) ) {
1262 unset( $actions['edit'] );
1263 }
1264
1265 return $this->parse_action_links( $actions, $plugin_file, $action_links, 'before' );
1266 }
1267
1268 /**
1269 * Parse the action links.
1270 *
1271 * @since 1.7.0
1272 *
1273 * @param array $actions An array of existing actions.
1274 * @param string $plugin_file The plugin file we are modifying.
1275 * @param array $action_links An array of action links to add.
1276 * @param string $position The position to add the action links.
1277 * @return array
1278 */
1279 protected function parse_action_links( $actions, $plugin_file, $action_links = array(), $position = 'after' ) {
1280 if ( empty( $this->plugin ) ) {
1281 $this->plugin = CHARITABLE_PLUGIN_BASENAME;
1282 }
1283
1284 if ( $this->plugin === $plugin_file && ! empty( $action_links ) ) {
1285 foreach ( $action_links as $key => $value ) {
1286 $link = array(
1287 $key => '<a href="' . $value['url'] . '">' . $value['label'] . '</a>',
1288 );
1289
1290 $actions = 'after' === $position ? array_merge( $actions, $link ) : array_merge( $link, $actions );
1291 }
1292 }
1293
1294 return $actions;
1295 }
1296
1297 /**
1298 * Set up licensing for pro.
1299 *
1300 * @since 1.7.0
1301 *
1302 * @return void
1303 */
1304 public function setup_licensing() {
1305 charitable_get_helper( 'licenses' )->register_licensed_product(
1306 self::NAME,
1307 self::AUTHOR,
1308 self::VERSION,
1309 __FILE__
1310 );
1311 }
1312
1313 /**
1314 * Deativates and/or warnings about any Charitable verison conflicts.
1315 *
1316 * @since 1.7.0
1317 */
1318 public function check_for_version_conflicts() {
1319
1320 // Check for Charitable Stripe < 1.5.0.
1321 if ( in_array( 'charitable-stripe/charitable-stripe.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) { // phpcs:ignore
1322 if ( class_exists( 'Charitable_Stripe' ) && version_compare( Charitable_Stripe::VERSION, '1.5.0', '<' ) ) { // phpcs:ignore
1323 // manually deactivate plugin.
1324 $found = false;
1325 $current = get_option( 'active_plugins', array() );
1326 $key = array_search( 'charitable-stripe/charitable-stripe.php', $current, true );
1327 if ( false !== $key ) {
1328 $found = true;
1329
1330 unset( $current[ $key ] );
1331 update_option( 'active_plugins', $current );
1332
1333 // translators: %s is the version number.
1334 $message = sprintf( __( '<p><strong style="color:red;">NOTICE:</strong> <strong>Charitable</strong> detected an out-of-date and incompatible version of <strong>Charitable Stripe</strong> addon. To avoid issues with Stripe donations, <strong>Charitable Stripe</strong> %s was deactivated. Please update <strong>Charitable Stripe</strong> to version 1.5.0 or higher.</p><p>You can get the latest version of <strong>Charitable Stripe</strong> through your <a href="https://www.wpcharitable.com/account/" target="_blank">Charitable account at wpcharitable.com</a>. All active licenses are accessible in the "My Downloads" tab.</p>', 'charitable' ), Charitable_Stripe::VERSION );
1335
1336 wp_die( $message . ' <a href="' . admin_url( 'plugins.php' ) . '">Click here to return to the plugins screen.</a>' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1337 }
1338 add_option( 'charitable_version_warning', $message );
1339 } else {
1340 delete_option( 'charitable_version_warning' );
1341 }
1342 }
1343 }
1344
1345 /**
1346 * Set initial timestamps for rotating menu items if they're already installed.
1347 * This ensures the 7-day countdown starts immediately for already-installed plugins.
1348 *
1349 * @since 1.8.8
1350 *
1351 * @return void
1352 */
1353 private function set_initial_rotating_menu_timestamps() {
1354 // Get the rotating menu items configuration
1355 $admin_pages = Charitable_Admin_Pages::get_instance();
1356 $reflection = new ReflectionClass( $admin_pages );
1357 $method = $reflection->getMethod( 'get_marketing_rotation_items' );
1358 $method->setAccessible( true );
1359 $items = $method->invoke( $admin_pages );
1360
1361 $current_time = time();
1362
1363 foreach ( $items as $item ) {
1364 $option_name = $item['option_name'] ?? '';
1365 $option_key = $item['option_key'] ?? '';
1366 $plugin_file = $item['plugin_file'] ?? '';
1367
1368 if ( empty( $option_name ) || empty( $plugin_file ) ) {
1369 continue;
1370 }
1371
1372 // Check if the plugin is installed (file exists in plugins directory)
1373 $plugin_path = WP_PLUGIN_DIR . '/' . $plugin_file;
1374 if ( file_exists( $plugin_path ) ) {
1375 // Plugin is installed, check if we already have a timestamp
1376 $existing_option = get_option( $option_name );
1377 $has_timestamp = false;
1378
1379 if ( is_array( $existing_option ) ) {
1380 $has_timestamp = isset( $existing_option[ $option_key ] ) && is_numeric( $existing_option[ $option_key ] );
1381 } elseif ( is_numeric( $existing_option ) ) {
1382 $has_timestamp = true;
1383 }
1384
1385 // Only set timestamp if one doesn't already exist
1386 if ( ! $has_timestamp ) {
1387 if ( is_array( $existing_option ) ) {
1388 $existing_option[ $option_key ] = $current_time;
1389 update_option( $option_name, $existing_option );
1390 } else {
1391 update_option( $option_name, array( $option_key => $current_time ) );
1392 }
1393 }
1394 }
1395 }
1396 }
1397 }
1398
1399 $charitable = new Charitable();
1400
1401 endif;
1402