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

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

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