PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13.1
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13.1
1.8.13.1 1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 All 212 releases
charitable / charitable.php

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

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