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

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