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

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