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

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