PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
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 1.7.0.9 All 211 releases
charitable / charitable.php

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

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