PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 1.7.0.9 All 211 releases
← All changes | charitable.php +645 -115 1.6.7 → 1.8.13 View file →
@@ -1,22 +1,25 @@
1 1 <?php
2 2 /**
3 - * Plugin Name: Charitable
4 - * Plugin URI: https://www.wpcharitable.com
5 - * Description: The WordPress fundraising alternative for non-profits, created to help non-profits raise money on their own website.
6 - * Version: 1.6.7
7 - * Author: WP Charitable
8 - * Author URI: https://wpcharitable.com
9 - * Requires at least: 4.1
10 - * Tested up to: 5.0.0
3 + * Plugin Name: Charitable
4 + * Plugin URI: https://www.wpcharitable.com
5 + * Description: The best WordPress donation plugin. Fundraising with recurring donations, and powerful features to help you raise more money online.
6 + * Version: 1.8.13
7 + * Author: Charitable Donations & Fundraising Team
8 + * Author URI: https://wpcharitable.com
9 + * Requires at least: 5.9
10 + * Requires PHP: 7.4
11 + * Stable tag: 1.8.13
12 + * License: GPLv2 or later
13 + * License URI: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
11 14 *
12 - * Text Domain: charitable
13 - * Domain Path: /i18n/languages/
15 + * Text Domain: charitable
16 + * Domain Path: /i18n/languages/
14 17 *
15 - * @package Charitable
16 - * @author Eric Daams
17 - * @copyright Copyright (c) 2018, Studio 164a
18 - * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
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
19 22 */
20 23
21 24 // Exit if accessed directly.
22 25 if ( ! defined( 'ABSPATH' ) ) {
@@ -25,40 +28,28 @@
25 28
26 29 if ( ! class_exists( 'Charitable' ) ) :
27 30
28 31 /**
29 - * Main Charitable class
30 - *
31 - * @version 1.6.2
32 + * Main Charitable class.
32 33 */
33 34 class Charitable {
34 35
35 - /**
36 - * Plugin version.
37 - *
38 - * @var string
39 - */
40 - const VERSION = '1.6.7';
36 + /** The product name. */
37 + const NAME = 'Charitable';
41 38
42 - /**
43 - * Version of database schema.
44 - *
45 - * @var string A date in the format: YYYYMMDD
46 - */
39 + /** The plugin author name. */
40 + const AUTHOR = 'WP Charitable';
41 +
42 + /* Plugin version. */
43 + const VERSION = '1.8.13';
44 +
45 + /* Version of database schema. */
47 46 const DB_VERSION = '20180522';
48 47
49 - /**
50 - * Campaign post type.
51 - *
52 - * @var string
53 - */
48 + /* Campaign post type. */
54 49 const CAMPAIGN_POST_TYPE = 'campaign';
55 50
56 - /**
57 - * Donation post type.
58 - *
59 - * @var string
60 - */
51 + /* Donation post type. */
61 52 const DONATION_POST_TYPE = 'donation';
62 53
63 54 /**
64 55 * Single instance of this class.
@@ -115,8 +106,17 @@
115 106 */
116 107 private $classmap;
117 108
118 109 /**
110 + * Plugin.
111 + *
112 + * @since 1.8.0
113 + *
114 + * @var array
115 + */
116 + private $plugin;
117 +
118 + /**
119 119 * Create class instance.
120 120 *
121 121 * @since 1.0.0
122 122 */
@@ -124,8 +124,26 @@
124 124 $this->directory_path = plugin_dir_path( __FILE__ );
125 125 $this->directory_url = plugin_dir_url( __FILE__ );
126 126 $this->includes_path = $this->directory_path . 'includes/';
127 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 +
128 146 spl_autoload_register( array( $this, 'autoloader' ) );
129 147
130 148 $this->load_dependencies();
131 149
@@ -131,9 +149,28 @@
131 149
132 150 register_activation_hook( __FILE__, array( $this, 'activate' ) );
133 151 register_deactivation_hook( __FILE__, array( $this, 'deactivate' ) );
134 152
135 - add_action( 'plugins_loaded', array( $this, 'start' ), 1 );
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 + );
136 173 }
137 174
138 175 /**
139 176 * Returns the original instance of this class.
@@ -166,8 +203,10 @@
166 203
167 204 /* Set up the registry and instantiate objects we need straight away. */
168 205 $this->registry();
169 206
207 + $this->setup_licensing();
208 +
170 209 $this->maybe_start_ajax();
171 210
172 211 $this->attach_hooks_and_filters();
173 212
@@ -188,29 +227,97 @@
188 227 private function load_dependencies() {
189 228 $includes_path = $this->get_path( 'includes' );
190 229
191 230 /* Load files with hooks & functions. Classes are autoloaded. */
192 - require_once( $includes_path . 'charitable-core-functions.php' );
193 - require_once( $includes_path . 'api/charitable-api-functions.php' );
194 - require_once( $includes_path . 'campaigns/charitable-campaign-functions.php' );
195 - require_once( $includes_path . 'campaigns/charitable-campaign-hooks.php' );
196 - require_once( $includes_path . 'compat/charitable-compat-functions.php' );
197 - require_once( $includes_path . 'currency/charitable-currency-functions.php' );
198 - require_once( $includes_path . 'deprecated/charitable-deprecated-functions.php' );
199 - require_once( $includes_path . 'donations/charitable-donation-hooks.php' );
200 - require_once( $includes_path . 'donations/charitable-donation-functions.php' );
201 - require_once( $includes_path . 'emails/charitable-email-hooks.php' );
202 - require_once( $includes_path . 'endpoints/charitable-endpoints-functions.php' );
203 - require_once( $includes_path . 'privacy/charitable-privacy-functions.php' );
204 - require_once( $includes_path . 'public/charitable-template-helpers.php' );
205 - require_once( $includes_path . 'shortcodes/charitable-shortcodes-hooks.php' );
206 - require_once( $includes_path . 'upgrades/charitable-upgrade-hooks.php' );
207 - require_once( $includes_path . 'users/charitable-user-functions.php' );
208 - require_once( $includes_path . 'user-management/charitable-user-management-hooks.php' );
209 - require_once( $includes_path . 'utilities/charitable-utility-functions.php' );
231 + require_once $includes_path . 'class-charitable-log-muter.php';
232 + require_once $includes_path . 'charitable-core-functions.php';
233 + require_once $includes_path . 'api/charitable-api-functions.php';
234 + require_once $includes_path . 'campaigns/charitable-campaign-functions.php';
235 + require_once $includes_path . 'campaigns/charitable-campaign-hooks.php';
236 + require_once $includes_path . 'compat/charitable-compat-functions.php';
237 + require_once $includes_path . 'currency/charitable-currency-functions.php';
238 + require_once $includes_path . 'deprecated/charitable-deprecated-functions.php';
239 + require_once $includes_path . 'donations/charitable-donation-hooks.php';
240 + require_once $includes_path . 'donations/charitable-donation-functions.php';
241 + require_once $includes_path . 'emails/charitable-email-hooks.php';
242 + require_once $includes_path . 'endpoints/charitable-endpoints-functions.php';
243 + require_once $includes_path . 'privacy/charitable-privacy-functions.php';
244 + require_once $includes_path . 'public/charitable-template-helpers.php';
245 + require_once $includes_path . 'shortcodes/charitable-shortcodes-hooks.php';
246 + require_once $includes_path . 'tracking/charitable-tracking-hooks.php'; // 1.8.12 - MUST be outside the admin gate; see the file header.
247 + require_once $includes_path . 'abilities/charitable-abilities-hooks.php'; // 1.8.13 - MUST be outside the admin gate: abilities register on REST and WP-CLI requests, neither of which is is_admin().
248 + require_once $includes_path . 'upgrades/charitable-upgrade-hooks.php';
249 + require_once $includes_path . 'users/charitable-user-functions.php';
250 + require_once $includes_path . 'user-management/charitable-user-management-hooks.php';
251 + require_once $includes_path . 'utilities/charitable-utility-functions.php';
252 + require_once $includes_path . 'elementor/charitable-elementor-hooks.php';
253 +
254 + // Load security hooks on both admin and frontend (CAPTCHA needs to work on frontend).
255 + require_once $this->get_path( 'includes', true ) . 'admin/security/charitable-security-hooks.php';
256 +
257 + /* Load vendor/autoload.php - our modified platform_check.php and autoload_real.php will handle conditional loading */
258 + require_once $this->get_path( 'directory' ) . 'vendor/autoload.php';
259 +
260 + /* Square Compatibility Wrapper - always load this regardless of PHP version */
261 + require_once $includes_path . 'square/class-charitable-square-compatibility.php';
262 +
263 + /* Initialize Square conditionally - will check for compatibility internally */
264 + Charitable_Square_Compatibility::init();
265 +
266 + if ( $this->load_core_stripe() ) :
267 +
268 + /* Interfaces */
269 + require_once $includes_path . 'stripe/interfaces/interface-charitable-stripe-gateway-processor.php';
270 +
271 + /* Abstract classes */
272 + require_once $includes_path . 'stripe/abstracts/class-charitable-stripe-gateway-processor.php';
273 +
274 + /* Classes */
275 + require_once $includes_path . 'stripe/donations/class-charitable-stripe-donation-log.php';
276 + require_once $includes_path . 'stripe/donations/class-charitable-stripe-recurring-donation-log.php';
277 + require_once $includes_path . 'stripe/i18n/class-charitable-stripe-i18n.php';
278 + require_once $includes_path . 'stripe/gateway/class-charitable-stripe-connected-customer.php';
279 + require_once $includes_path . 'stripe/gateway/class-charitable-stripe-customer.php';
280 + require_once $includes_path . 'stripe/gateway/class-charitable-stripe-gateway-processor-payment-intents.php';
281 + require_once $includes_path . 'stripe/gateway/class-charitable-stripe-payment-intent.php';
282 + require_once $includes_path . 'stripe/gateway/class-charitable-stripe-plan.php';
283 + require_once $includes_path . 'stripe/gateway/class-charitable-stripe-product.php';
284 +
285 + require_once $includes_path . 'stripe/connect/charitable-stripe-connect-functions.php';
286 + require_once $includes_path . 'stripe/gateway/class-charitable-stripe-gateway-processor-checkout.php';
287 +
288 + /* Functions & Hooks */
289 + require_once $includes_path . 'stripe/charitable-stripe-core-functions.php';
290 + require_once $includes_path . 'stripe/donations/charitable-stripe-donation-functions.php';
291 + require_once $includes_path . 'stripe/gateway/charitable-stripe-gateway-hooks.php';
292 +
293 + /* webhooks */
294 + require_once $includes_path . 'stripe/gateway/class-charitable-stripe-webhook-api.php';
295 + require_once $includes_path . 'stripe/gateway/class-charitable-stripe-webhook-processor.php';
296 +
297 + endif;
298 +
299 + /* Load PayPal Commerce Gateway */
300 + require_once $includes_path . 'gateways/paypal-commerce/charitable-paypal-commerce.php';
210 301 }
211 302
212 303 /**
304 + * Load the template functions after theme is loaded.
305 + *
306 + * This gives themes time to override the functions.
307 + *
308 + * @since 1.6.10
309 + *
310 + * @return void
311 + */
312 + public function load_template_files() {
313 + $includes_path = $this->get_path( 'includes' );
314 +
315 + require_once $includes_path . 'public/charitable-template-functions.php';
316 + require_once $includes_path . 'public/charitable-template-hooks.php';
317 + }
318 +
319 + /**
213 320 * Dynamically loads the class attempting to be instantiated elsewhere in the
214 321 * plugin by looking at the $class_name parameter being passed as an argument.
215 322 *
216 323 * @since 1.5.0
@@ -230,18 +337,20 @@
230 337 }
231 338
232 339 /* Autogenerated class map. */
233 340 if ( ! isset( $this->classmap ) ) {
234 - $this->classmap = include( 'includes/autoloader/charitable-class-map.php' );
341 + $this->classmap = include 'includes/autoloader/charitable-class-map.php';
342 +
343 + $this->classmap['Charitable_Gateway_Stripe_AM'] = 'gateways/class-charitable-gateway-stripe-am.php';
344 + $this->classmap['Charitable_Gateway_Stripe'] = 'gateways/class-charitable-gateway-stripe-am.php';
235 345 }
236 346
237 347 $file_path = isset( $this->classmap[ $class_name ] ) ? $this->get_path( 'includes' ) . $this->classmap[ $class_name ] : false;
238 348
239 349 if ( false !== $file_path && file_exists( $file_path ) && is_file( $file_path ) ) {
240 - require_once( $file_path );
350 + require_once $file_path;
241 351 return true;
242 352 }
243 -
244 353 return false;
245 354 }
246 355
247 356 /**
@@ -263,10 +372,14 @@
263 372 $this->registry->register_object( Charitable_Widgets::get_instance() );
264 373 $this->registry->register_object( Charitable_Licenses::get_instance() );
265 374 $this->registry->register_object( Charitable_User_Dashboard::get_instance() );
266 375 $this->registry->register_object( Charitable_Locations::get_instance() );
376 + $this->registry->register_object( Charitable_Currency::get_instance() );
377 + $this->registry->register_object( Charitable_Notifications::get_instance() );
267 378
268 - $this->registry->register_object( new Charitable_Privacy );
379 + $this->registry->register_object( new Charitable_Privacy() );
380 + $this->registry->register_object( new Charitable_Debugging() );
381 + $this->registry->register_object( new Charitable_Locale() );
269 382 }
270 383
271 384 return $this->registry;
272 385 }
@@ -282,13 +395,14 @@
282 395 add_action( 'wpmu_new_blog', array( $this, 'maybe_activate_charitable_on_new_site' ) );
283 396 add_action( 'plugins_loaded', array( $this, 'charitable_install' ), 100 );
284 397 add_action( 'plugins_loaded', array( $this, 'charitable_start' ), 100 );
285 398 add_action( 'plugins_loaded', array( $this, 'endpoints' ), 100 );
286 - add_action( 'plugins_loaded', array( $this, 'donation_fields' ), 100 );
287 - add_action( 'plugins_loaded', array( $this, 'campaign_fields' ), 100 );
399 + add_action( 'init', array( $this, 'donation_fields' ), 100 );
400 + add_action( 'init', array( $this, 'campaign_fields' ), 100 );
288 401 add_action( 'plugins_loaded', array( $this, 'register_donormeta_table' ) );
289 402 add_action( 'plugins_loaded', 'charitable_load_compat_functions' );
290 403 add_action( 'setup_theme', array( 'Charitable_Customizer', 'start' ) );
404 + add_action( 'after_setup_theme', array( $this, 'load_template_files' ) );
291 405 add_action( 'wp_enqueue_scripts', array( $this, 'maybe_start_qunit' ), 100 );
292 406 add_action( 'rest_api_init', 'charitable_register_api_routes' );
293 407
294 408 /**
@@ -295,11 +409,78 @@
295 409 * We do this on priority 20 so that any functionality that is loaded on init (such
296 410 * as addons) has a chance to run before the event.
297 411 */
298 412 add_action( 'init', array( $this, 'do_charitable_actions' ), 20 );
413 +
414 + if ( $this->load_core_stripe() ) :
415 +
416 + /**
417 + * Set up scripts & stylesheets & enqueue them when appropriate.
418 + */
419 + add_action( 'charitable_form_after_fields', array( $this, 'maybe_setup_stripe_scripts_in_donation_form' ) );
420 + add_action( 'charitable_campaign_loop_after', array( $this, 'maybe_setup_stripe_scripts_in_campaign_loop' ) );
421 +
422 + endif;
299 423 }
300 424
425 +
301 426 /**
427 + * Load Stripe JS, as well as our handling scripts.
428 + *
429 + * @since 1.4.0
430 + *
431 + * @return boolean
432 + */
433 + public function enqueue_stripe_scripts() {
434 + if ( ! Charitable_Gateways::get_instance()->is_active_gateway( Charitable_Gateway_Stripe_AM::get_gateway_id() ) ) {
435 + return false;
436 + }
437 +
438 + wp_enqueue_script( 'charitable-stripe' );
439 +
440 + return true;
441 + }
442 +
443 + /**
444 + * Load Stripe JS or Stripe Checkout, as well as our handling script.
445 + *
446 + * @uses Charitable_Stripe::enqueue_scripts()
447 + *
448 + * @since 1.4.0
449 + *
450 + * @param Charitable_Donation_Form $form The current form object.
451 + * @return boolean
452 + */
453 + public function maybe_setup_stripe_scripts_in_donation_form( $form ) {
454 + if ( ! is_a( $form, 'Charitable_Donation_Form' ) ) {
455 + return false;
456 + }
457 +
458 + if ( 'make_donation' !== $form->get_form_action() ) {
459 + return false;
460 + }
461 +
462 + return $this->enqueue_stripe_scripts();
463 + }
464 +
465 + /**
466 + * Enqueue the Stripe JS/Checkout scripts after a campaign loop if modal donations are in use.
467 + *
468 + * @uses Charitable_Stripe::enqueue_scripts()
469 + *
470 + * @since 1.4.0
471 + *
472 + * @return boolean
473 + */
474 + public function maybe_setup_stripe_scripts_in_campaign_loop() {
475 + if ( 'modal' !== charitable_get_option( 'donation_form_display', 'separate_page' ) ) {
476 + return false;
477 + }
478 +
479 + return $this->enqueue_stripe_scripts();
480 + }
481 +
482 + /**
302 483 * Checks whether we're in the admin area and if so, loads the admin-only functionality.
303 484 *
304 485 * @since 1.0.0
305 486 *
@@ -309,19 +490,70 @@
309 490 if ( ! is_admin() ) {
310 491 return false;
311 492 }
312 493
313 - require_once( $this->get_path( 'admin' ) . 'class-charitable-admin.php' );
314 - require_once( $this->get_path( 'admin' ) . 'charitable-admin-hooks.php' );
494 + // load blocks/Gutenberg related.
495 + require_once $this->get_path( 'admin' ) . 'class-charitable-admin-blocks.php';
315 496
497 + require_once $this->get_path( 'admin' ) . 'class-charitable-admin.php';
498 + require_once $this->get_path( 'admin' ) . 'charitable-admin-hooks.php';
499 +
500 + // load campaign builder.
501 + require_once $this->get_path( 'admin' ) . 'campaign-builder/util.php';
502 + require_once $this->get_path( 'admin' ) . 'campaign-builder/functions.php';
503 + require_once $this->get_path( 'admin' ) . 'campaign-builder/ajax-actions.php';
504 + require_once $this->get_path( 'admin' ) . 'campaign-builder/functions-legacy.php';
505 + require_once $this->get_path( 'admin' ) . 'campaign-builder/debug.php';
506 + require_once $this->get_path( 'admin' ) . 'campaign-builder/access.php';
507 + require_once $this->get_path( 'admin' ) . 'campaign-builder/class-builder-fields.php';
508 + require_once $this->get_path( 'admin' ) . 'campaign-builder/class-builder-form-fields.php';
509 +
510 + // connect for upgrade.wpcharitable.com.
511 + require_once $this->get_path( 'admin' ) . 'class-charitable-admin-connect.php';
512 +
513 + // misc.
514 + require_once $this->get_path( 'admin' ) . 'class-charitable-admin-pointers.php';
515 + require_once $this->get_path( 'admin' ) . 'plugins/charitable-admin-plugin-hooks.php';
516 + require_once $this->get_path( 'admin' ) . 'class-charitable-admin-getting-started.php';
517 +
518 + // Site Analysis / Recommendations tab.
519 + require_once $this->get_path( 'admin' ) . 'site-analysis/class-charitable-site-analysis.php';
520 + Charitable_Site_Analysis::get_instance();
521 +
316 522 $admin = Charitable_Admin::get_instance();
523 + $this->registry->register_object( $admin );
317 524
318 - $this->registry->register_object( $admin );
525 + // Stripe.
526 + require_once $this->get_path( 'includes' ) . 'stripe/admin/class-charitable-stripe-admin.php';
527 + new Charitable_Stripe_Admin();
319 528
320 529 return $admin;
321 530 }
322 531
323 532 /**
533 + * Run the upgrade-server connect callback on init before any redirect (e.g. to login) can run.
534 + * Only registered when CHARITABLE_CONNECT_CALLBACK is defined (request came from connect-callback.php).
535 + *
536 + * @since 1.8.9.6
537 + */
538 + public function run_connect_callback() {
539 + ini_set( 'display_errors', '0' );
540 + require_once ABSPATH . 'wp-admin/includes/plugin.php';
541 + require_once ABSPATH . 'wp-admin/includes/file.php';
542 + require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
543 + $connect_file = $this->get_path( 'admin' ) . 'class-charitable-admin-connect.php';
544 + if ( file_exists( $connect_file ) ) {
545 + require_once $connect_file;
546 + }
547 + if ( ! class_exists( 'Charitable_Admin_Connect' ) ) {
548 + header( 'Content-Type: application/json' );
549 + wp_send_json( array( 'success' => false, 'data' => array( 'message' => 'Charitable not loaded.' ) ) );
550 + }
551 + Charitable_Admin_Connect::get_instance()->process();
552 + exit;
553 + }
554 +
555 + /**
324 556 * Checks whether we're on the public-facing side and if so, loads the public-facing functionality.
325 557 *
326 558 * @since 1.0.0
327 559 *
@@ -331,10 +563,18 @@
331 563 if ( is_admin() && ! $this->is_ajax() ) {
332 564 return false;
333 565 }
334 566
335 - require_once( $this->get_path( 'public' ) . 'class-charitable-public.php' );
567 + require_once $this->get_path( 'public' ) . 'class-charitable-public.php';
336 568
569 + // Campaign Builder related (v1.8.0).
570 + require_once $this->get_path( 'admin' ) . 'campaign-builder/class-campaign-builder-preview.php';
571 + require_once $this->get_path( 'admin' ) . 'campaign-builder/class-builder-fields.php';
572 + require_once $this->get_path( 'admin' ) . 'campaign-builder/class-builder-form-fields.php';
573 +
574 + // load blocks/Gutenberg related.
575 + require_once $this->get_path( 'admin' ) . 'class-charitable-admin-blocks.php';
576 +
337 577 $public = Charitable_Public::get_instance();
338 578
339 579 $this->registry->register_object( $public );
340 580
@@ -349,24 +589,51 @@
349 589 * @return boolean
350 590 */
351 591 public function maybe_start_qunit() {
352 592 /* Skip out early if ?qunit isn't included in the request. */
353 - if ( ! array_key_exists( 'qunit', $_GET ) ) {
593 + if ( ! array_key_exists( 'qunit', $_GET ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
354 594 return false;
355 595 }
356 596
357 - /* The unit tests have to exist. */
358 - if ( ! file_exists( $this->get_path( 'directory' ) . 'tests/qunit/tests.js' ) ) {
359 - return false;
360 - }
597 + /* The unit tests have to exist. */
598 + if ( ! file_exists( $this->get_path( 'directory' ) . 'tests/qunit/tests.js' ) ) {
599 + return false;
600 + }
361 601
362 - wp_register_script( 'qunit', 'https://code.jquery.com/qunit/qunit-2.3.3.js', array(), '2.3.3', true );
363 - /* Version: '20170615-15:44' */
364 - wp_register_script( 'qunit-tests', $this->get_path( 'directory', false ) . 'tests/qunit/tests.js', array( 'jquery-core', 'qunit' ), time(), true );
365 - wp_enqueue_script( 'qunit-tests' );
602 + /**
603 + * Register QUnit testing library.
604 + *
605 + * Note: These files must be bundled locally for WordPress.org compliance.
606 + * Download from:
607 + * - JS: https://code.jquery.com/qunit/qunit-2.3.3.js
608 + * - CSS: https://code.jquery.com/qunit/qunit-2.3.3.css
609 + * Save to:
610 + * - assets/js/libraries/qunit-2.3.3.js
611 + * - assets/css/libraries/qunit-2.3.3.css
612 + *
613 + * @since 1.0.0
614 + * @version 1.8.9.1
615 + */
616 + wp_register_script(
617 + 'qunit',
618 + $this->get_path( 'directory', false ) . 'assets/js/libraries/qunit-2.3.3.js',
619 + array(),
620 + '2.3.3',
621 + true
622 + );
366 623
367 - wp_register_style( 'qunit', 'https://code.jquery.com/qunit/qunit-2.3.3.css', array(), '2.3.3', 'all' );
368 - wp_enqueue_style( 'qunit' );
624 + /* Version: '20170615-15:44' */
625 + wp_register_script( 'qunit-tests', $this->get_path( 'directory', false ) . 'tests/qunit/tests.js', array( 'jquery', 'qunit' ), time(), true );
626 + wp_enqueue_script( 'qunit-tests' );
627 +
628 + wp_register_style(
629 + 'qunit',
630 + $this->get_path( 'directory', false ) . 'assets/css/libraries/qunit-2.3.3.css',
631 + array(),
632 + '2.3.3',
633 + 'all'
634 + );
635 + wp_enqueue_style( 'qunit' );
369 636 }
370 637
371 638 /**
372 639 * Checks whether the current request is an AJAX request.
@@ -390,10 +657,10 @@
390 657 if ( ! $this->is_ajax() ) {
391 658 return;
392 659 }
393 660
394 - require_once( $this->get_path( 'includes' ) . 'ajax/charitable-ajax-functions.php' );
395 - require_once( $this->get_path( 'includes' ) . 'ajax/charitable-ajax-hooks.php' );
661 + require_once $this->get_path( 'includes' ) . 'ajax/charitable-ajax-functions.php';
662 + require_once $this->get_path( 'includes' ) . 'ajax/charitable-ajax-hooks.php';
396 663 }
397 664
398 665 /**
399 666 * This method is fired after all plugins are loaded and simply fires the charitable_start hook.
@@ -411,9 +678,11 @@
411 678 /**
412 679 * Fires off an action right after Charitable is installed, allowing other
413 680 * plugins/themes to do something at this point.
414 681 *
415 - * @since 1.0.1
682 + * @since 1.0.1
683 + * @version 1.8.1.12 - Added transient for activation redirect.
684 + * @version 1.8.3 - Added call to add version and delete notifications.
416 685 *
417 686 * @return void
418 687 */
419 688 public function charitable_install() {
@@ -422,9 +691,9 @@
422 691 if ( ! $install ) {
423 692 return;
424 693 }
425 694
426 - require_once( $this->get_path( 'includes' ) . 'class-charitable-install.php' );
695 + require_once $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php';
427 696
428 697 Charitable_Install::finish_installing();
429 698
430 699 do_action( 'charitable_install' );
@@ -429,8 +698,28 @@
429 698
430 699 do_action( 'charitable_install' );
431 700
432 701 delete_transient( 'charitable_install' );
702 +
703 + // Store the date when the initial activation was performed.
704 + $type = function_exists( 'charitable_is_pro' ) && charitable_is_pro() ? 'pro' : 'lite';
705 + $activated = (array) get_option( 'charitable_activated', array() );
706 +
707 + // If no activation date is set, assume it's a new install and set the activation redirect transient.
708 + if ( false === $activated || empty( $activated ) ) {
709 + // Add transient to trigger redirect to the Welcome / Getting Started screen.
710 + set_transient( 'charitable_activation_redirect', true, 30 );
711 + }
712 +
713 + $this->add_version_to_upgraded_from();
714 +
715 + if ( empty( $activated[ $type ] ) ) {
716 + $activated[ $type ] = time();
717 + update_option( 'charitable_activated', $activated );
718 + }
719 +
720 + // Clear notifications.
721 + delete_option( 'charitable_notifications' );
433 722 }
434 723
435 724 /**
436 725 * Returns whether we are currently in the start phase of the plugin.
@@ -439,9 +728,9 @@
439 728 *
440 729 * @return boolean
441 730 */
442 731 public function is_start() {
443 - return current_filter() == 'charitable_start';
732 + return current_filter() == 'charitable_start'; // phpcs:ignore
444 733 }
445 734
446 735 /**
447 736 * Returns whether the plugin has already started.
@@ -450,9 +739,9 @@
450 739 *
451 740 * @return boolean
452 741 */
453 742 public function started() {
454 - return did_action( 'charitable_start' ) || current_filter() == 'charitable_start';
743 + return did_action( 'charitable_start' ) || current_filter() == 'charitable_start'; // phpcs:ignore
455 744 }
456 745
457 746 /**
458 747 * Returns whether the plugin is being activated.
@@ -461,9 +750,9 @@
461 750 *
462 751 * @return boolean
463 752 */
464 753 public function is_activation() {
465 - return current_filter() == 'activate_charitable/charitable.php';
754 + return current_filter() == 'activate_charitable/charitable.php'; // phpcs:ignore
466 755 }
467 756
468 757 /**
469 758 * Returns whether the plugin is being deactivated.
@@ -472,9 +761,9 @@
472 761 *
473 762 * @return boolean
474 763 */
475 764 public function is_deactivation() {
476 - return current_filter() == 'deactivate_charitable/charitable.php';
765 + return current_filter() == 'deactivate_charitable/charitable.php'; // phpcs:ignore
477 766 }
478 767
479 768 /**
480 769 * Returns plugin paths.
@@ -508,8 +797,12 @@
508 797 case 'templates':
509 798 $path = $base . 'templates/';
510 799 break;
511 800
801 + case 'plugin-directory':
802 + $path = WP_PLUGIN_DIR;
803 + break;
804 +
512 805 case 'directory':
513 806 $path = $base;
514 807 break;
515 808
@@ -539,8 +832,26 @@
539 832 return $version;
540 833 }
541 834
542 835 /**
836 + * Adds previous versions to the upgraded_from option.
837 + *
838 + * @since 1.8.3
839 + */
840 + public function add_version_to_upgraded_from() {
841 +
842 + $upgraded_from = get_option( 'charitable_version_upgraded_from', array() );
843 +
844 + if ( ! is_array( $upgraded_from ) ) {
845 + $upgraded_from = array();
846 + }
847 +
848 + $upgraded_from[] = $this->get_version();
849 +
850 + update_option( 'charitable_version_upgraded_from', $upgraded_from, false );
851 + }
852 +
853 + /**
543 854 * Get the campaign fields registry
544 855 *
545 856 * If the registry has not been set up yet, this will also
546 857 * perform the task of setting it up initially.
@@ -557,9 +868,9 @@
557 868
558 869 /* Register it immediately to avoid endless recursion. */
559 870 $this->registry->register_object( $campaign_fields );
560 871
561 - $fields = include( $this->get_path( 'includes' ) . 'fields/default-fields/campaign-fields.php' );
872 + $fields = include $this->get_path( 'includes' ) . 'fields/default-fields/campaign-fields.php';
562 873
563 874 foreach ( $fields as $key => $args ) {
564 875 $campaign_fields->register_field( new Charitable_Campaign_Field( $key, $args ) );
565 876 }
@@ -586,9 +897,9 @@
586 897
587 898 /* Register it immediately to avoid endless recursion. */
588 899 $this->registry->register_object( $donation_fields );
589 900
590 - $fields = include( $this->get_path( 'includes' ) . 'fields/default-fields/donation-fields.php' );
901 + $fields = include $this->get_path( 'includes' ) . 'fields/default-fields/donation-fields.php';
591 902
592 903 foreach ( $fields as $key => $args ) {
593 904 $donation_fields->register_field( new Charitable_Donation_Field( $key, $args ) );
594 905 }
@@ -620,21 +931,22 @@
620 931 * BEFORE the endpoint it builds on. In other words, move from
621 932 * most specific to least specific.
622 933 */
623 934 $endpoints = new Charitable_Endpoints();
624 - $endpoints->register( new Charitable_Campaign_Donation_Endpoint );
625 - $endpoints->register( new Charitable_Campaign_Widget_Endpoint );
626 - $endpoints->register( new Charitable_Campaign_Endpoint );
627 - $endpoints->register( new Charitable_Donation_Cancellation_Endpoint );
628 - $endpoints->register( new Charitable_Donation_Processing_Endpoint );
629 - $endpoints->register( new Charitable_Donation_Receipt_Endpoint );
630 - $endpoints->register( new Charitable_Email_Preview_Endpoint );
631 - $endpoints->register( new Charitable_Email_Verification_Endpoint );
632 - $endpoints->register( new Charitable_Forgot_Password_Endpoint );
633 - $endpoints->register( new Charitable_Reset_Password_Endpoint );
634 - $endpoints->register( new Charitable_Registration_Endpoint );
635 - $endpoints->register( new Charitable_Login_Endpoint );
636 - $endpoints->register( new Charitable_Profile_Endpoint );
935 + $endpoints->register( new Charitable_Campaign_Donation_Endpoint() );
936 + $endpoints->register( new Charitable_Campaign_Widget_Endpoint() );
937 + $endpoints->register( new Charitable_Campaign_Endpoint() );
938 + $endpoints->register( new Charitable_Donation_Cancellation_Endpoint() );
939 + $endpoints->register( new Charitable_Donation_Processing_Endpoint() );
940 + $endpoints->register( new Charitable_Donation_Receipt_Endpoint() );
941 + $endpoints->register( new Charitable_Email_Preview_Endpoint() );
942 + $endpoints->register( new Charitable_Email_Verification_Endpoint() );
943 + $endpoints->register( new Charitable_Forgot_Password_Endpoint() );
944 + $endpoints->register( new Charitable_Reset_Password_Endpoint() );
945 + $endpoints->register( new Charitable_Registration_Endpoint() );
946 + $endpoints->register( new Charitable_Login_Endpoint() );
947 + $endpoints->register( new Charitable_Profile_Endpoint() );
948 + $endpoints->register( new Charitable_Webhook_Listener_Endpoint() );
637 949
638 950 $this->registry->register_object( $endpoints );
639 951 }
640 952
@@ -648,9 +960,9 @@
648 960 *
649 961 * @param string $class The type of class to be retrieved.
650 962 * @return object
651 963 */
652 - public function get_registered_object( $class ) {
964 + public function get_registered_object( $class ) { // phpcs:ignore
653 965 return $this->registry->get( $class );
654 966 }
655 967
656 968 /**
@@ -701,16 +1013,22 @@
701 1013 /**
702 1014 * Filter the array of available Charitable table classes.
703 1015 *
704 1016 * @since 1.0.0
1017 + * @version 1.8.1
705 1018 *
706 1019 * @param array $tables List of tables as a key=>value array.
707 1020 */
708 - return apply_filters( 'charitable_db_tables', array(
709 - 'campaign_donations' => 'Charitable_Campaign_Donations_DB',
710 - 'donors' => 'Charitable_Donors_DB',
711 - 'donormeta' => 'Charitable_Donormeta_DB',
712 - ) );
1021 + return apply_filters(
1022 + 'charitable_db_tables',
1023 + array(
1024 + 'campaign_donations' => 'Charitable_Campaign_Donations_DB',
1025 + 'donors' => 'Charitable_Donors_DB',
1026 + 'donormeta' => 'Charitable_Donormeta_DB',
1027 + 'donation_activities' => 'Charitable_Donation_Activities_DB',
1028 + 'campaign_activities' => 'Charitable_Campaign_Activities_DB',
1029 + )
1030 + );
713 1031 }
714 1032
715 1033 /**
716 1034 * Maybe activate Charitable when a new site is added in a multisite network.
@@ -739,21 +1057,27 @@
739 1057 * or just the current site. Multisite only. Default is false.
740 1058 * @return void
741 1059 */
742 1060 public function activate( $network_wide = false ) {
743 - require_once( $this->get_path( 'includes' ) . 'class-charitable-install.php' );
1061 + require_once $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php';
744 1062
745 1063 if ( is_multisite() && $network_wide ) {
746 1064 global $wpdb;
747 1065
748 - foreach ( $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" ) as $blog_id ) {
1066 + foreach ( $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" ) as $blog_id ) { // phpcs:ignore
749 1067 switch_to_blog( $blog_id );
750 - new Charitable_Install();
1068 + new Charitable_Install( $this->includes_path );
751 1069 restore_current_blog();
752 1070 }
753 1071 } else {
754 - new Charitable_Install();
1072 + new Charitable_Install( $this->includes_path );
755 1073 }
1074 +
1075 + // create or update the install date for future reference.
1076 + update_option( 'wpcharitable_activated_datetime', current_time( 'timestamp' ) ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
1077 +
1078 + // Set initial timestamps for rotating menu items if they're already installed
1079 + $this->set_initial_rotating_menu_timestamps();
756 1080 }
757 1081
758 1082 /**
759 1083 * Runs on plugin deactivation.
@@ -764,9 +1088,9 @@
764 1088 *
765 1089 * @return void
766 1090 */
767 1091 public function deactivate() {
768 - require_once( $this->get_path( 'includes' ) . 'class-charitable-uninstall.php' );
1092 + require_once $this->get_path( 'includes' ) . 'plugin/class-charitable-uninstall.php';
769 1093 new Charitable_Uninstall();
770 1094 }
771 1095
772 1096 /**
@@ -776,11 +1100,11 @@
776 1100 *
777 1101 * @return void
778 1102 */
779 1103 public function do_charitable_actions() {
780 - if ( isset( $_REQUEST['charitable_action'] ) ) {
1104 + if ( isset( $_REQUEST['charitable_action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
781 1105
782 - $action = $_REQUEST['charitable_action'];
1106 + $action = esc_attr( $_REQUEST['charitable_action'] ); // phpcs:ignore
783 1107
784 1108 /**
785 1109 * Handle Charitable action.
786 1110 *
@@ -856,9 +1180,9 @@
856 1180 *
857 1181 * @param mixed $object Object to be registered.
858 1182 * @return void
859 1183 */
860 - public function register_object( $object ) {
1184 + public function register_object( $object ) { // phpcs:ignore
861 1185 charitable_get_deprecated()->deprecated_function(
862 1186 __METHOD__,
863 1187 '1.5.0',
864 1188 'charitable()->registry()->register_object()'
@@ -948,8 +1272,214 @@
948 1272 */
949 1273 public function get_location_helper() {
950 1274 charitable_get_deprecated()->deprecated_function( __METHOD__, '1.2.0', 'charitable_get_location_helper' );
951 1275 return charitable_get_location_helper();
1276 + }
1277 +
1278 + /**
1279 + * Determine if we use Stripe core files or use an addon if that's installed and active. Checks for USE_NEW_STRIPE first.
1280 + *
1281 + * @since 1.7.0
1282 + *
1283 + * @return boolean
1284 + */
1285 + public function load_core_stripe() {
1286 +
1287 + if ( false !== ( defined( 'USE_NEW_STRIPE' ) && USE_NEW_STRIPE ) ) {
1288 + return true;
1289 + }
1290 +
1291 + // check and see if the core Stripe AM gateway is an active gateway, and if it is not, don't load the core stripe JS, etc.
1292 + if ( class_exists( 'Charitable_Gateways' ) && class_exists( 'Charitable_Gateway_Stripe_AM' ) && ! Charitable_Gateways::get_instance()->is_active_gateway( Charitable_Gateway_Stripe_AM::get_gateway_id() ) ) {
1293 + return false;
1294 + }
1295 +
1296 + return true;
1297 + }
1298 +
1299 + /**
1300 + * Determine Stripe Connect is active.
1301 + *
1302 + * @since 1.7.0
1303 + *
1304 + * @return boolean
1305 + */
1306 + public function is_stripe_connect_addon() {
1307 +
1308 + if ( in_array( 'charitable-stripe/charitable-stripe.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) { // phpcs:ignore
1309 + return true;
1310 + }
1311 +
1312 + return false;
1313 + }
1314 +
1315 + /**
1316 + * Action links for the plugins page.
1317 + *
1318 + * @since 1.7.0
1319 + *
1320 + * @param array $actions An array of existing actions.
1321 + * @param string $plugin_file The plugin file we are modifying.
1322 + * @param boolean $action_links An array of action links to add.
1323 + * @return array An array of action links.
1324 + */
1325 + public function plugin_action_links( $actions, $plugin_file, $action_links = false ) {
1326 +
1327 + if ( ! charitable_is_pro() ) :
1328 +
1329 + // if this isn't pro, add the upgrade link which is visible on the plugins page until the plugin name.
1330 +
1331 + $action_links = array(
1332 + 'proupgrade' => array(
1333 + // Translators: This is an action link users can click to purchase a license for All in One SEO Pro.
1334 + 'label' => __( 'Upgrade to Pro', 'charitable' ),
1335 + 'url' => 'https://wpcharitable.com/lite-vs-pro/?utm_source=WordPress&utm_campaign=WP+Charitable&utm_medium=Action+Link+Plugins+Page&utm_content=Upgrade+to+Pro',
1336 + ),
1337 + );
1338 +
1339 + endif;
1340 +
1341 + if ( isset( $actions['edit'] ) ) {
1342 + unset( $actions['edit'] );
1343 + }
1344 +
1345 + return $this->parse_action_links( $actions, $plugin_file, $action_links, 'before' );
1346 + }
1347 +
1348 + /**
1349 + * Parse the action links.
1350 + *
1351 + * @since 1.7.0
1352 + *
1353 + * @param array $actions An array of existing actions.
1354 + * @param string $plugin_file The plugin file we are modifying.
1355 + * @param array $action_links An array of action links to add.
1356 + * @param string $position The position to add the action links.
1357 + * @return array
1358 + */
1359 + protected function parse_action_links( $actions, $plugin_file, $action_links = array(), $position = 'after' ) {
1360 + if ( empty( $this->plugin ) ) {
1361 + $this->plugin = CHARITABLE_PLUGIN_BASENAME;
1362 + }
1363 +
1364 + if ( $this->plugin === $plugin_file && ! empty( $action_links ) ) {
1365 + foreach ( $action_links as $key => $value ) {
1366 + $link = array(
1367 + $key => '<a href="' . $value['url'] . '">' . $value['label'] . '</a>',
1368 + );
1369 +
1370 + $actions = 'after' === $position ? array_merge( $actions, $link ) : array_merge( $link, $actions );
1371 + }
1372 + }
1373 +
1374 + return $actions;
1375 + }
1376 +
1377 + /**
1378 + * Set up licensing for pro.
1379 + *
1380 + * @since 1.7.0
1381 + *
1382 + * @return void
1383 + */
1384 + public function setup_licensing() {
1385 + charitable_get_helper( 'licenses' )->register_licensed_product(
1386 + self::NAME,
1387 + self::AUTHOR,
1388 + self::VERSION,
1389 + __FILE__
1390 + );
1391 + }
1392 +
1393 + /**
1394 + * Deativates and/or warnings about any Charitable verison conflicts.
1395 + *
1396 + * @since 1.7.0
1397 + */
1398 + public function check_for_version_conflicts() {
1399 +
1400 + // Check for Charitable Stripe < 1.5.0.
1401 + if ( in_array( 'charitable-stripe/charitable-stripe.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) { // phpcs:ignore
1402 + if ( class_exists( 'Charitable_Stripe' ) && version_compare( Charitable_Stripe::VERSION, '1.5.0', '<' ) ) { // phpcs:ignore
1403 + // manually deactivate plugin.
1404 + $found = false;
1405 + $current = get_option( 'active_plugins', array() );
1406 + $key = array_search( 'charitable-stripe/charitable-stripe.php', $current, true );
1407 + if ( false !== $key ) {
1408 + $found = true;
1409 +
1410 + unset( $current[ $key ] );
1411 + update_option( 'active_plugins', $current );
1412 +
1413 + // translators: %s is the version number.
1414 + $message = sprintf( __( '<p><strong style="color:red;">NOTICE:</strong> <strong>Charitable</strong> detected an out-of-date and incompatible version of <strong>Charitable Stripe</strong> addon. To avoid issues with Stripe donations, <strong>Charitable Stripe</strong> %s was deactivated. Please update <strong>Charitable Stripe</strong> to version 1.5.0 or higher.</p><p>You can get the latest version of <strong>Charitable Stripe</strong> through your <a href="https://www.wpcharitable.com/account/" target="_blank">Charitable account at wpcharitable.com</a>. All active licenses are accessible in the "My Downloads" tab.</p>', 'charitable' ), Charitable_Stripe::VERSION );
1415 +
1416 + wp_die( $message . ' <a href="' . admin_url( 'plugins.php' ) . '">Click here to return to the plugins screen.</a>' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1417 + }
1418 + add_option( 'charitable_version_warning', $message );
1419 + } else {
1420 + delete_option( 'charitable_version_warning' );
1421 + }
1422 + }
1423 + }
1424 +
1425 + /**
1426 + * Set initial timestamps for rotating menu items if they're already installed.
1427 + * This ensures the 7-day countdown starts immediately for already-installed plugins.
1428 + *
1429 + * @since 1.8.8
1430 + *
1431 + * @return void
1432 + */
1433 + private function set_initial_rotating_menu_timestamps() {
1434 + // Get the rotating menu items configuration
1435 + $admin_pages = Charitable_Admin_Pages::get_instance();
1436 +
1437 + try {
1438 + $reflection = new ReflectionClass( $admin_pages );
1439 + $method = $reflection->getMethod( 'get_marketing_rotation_items' );
1440 + $method->setAccessible( true );
1441 + $items = $method->invoke( $admin_pages );
1442 + } catch ( ReflectionException $e ) {
1443 + // Fallback if reflection fails in future PHP versions
1444 + $items = array();
1445 + }
1446 +
1447 + $current_time = time();
1448 +
1449 + foreach ( $items as $item ) {
1450 + $option_name = $item['option_name'] ?? '';
1451 + $option_key = $item['option_key'] ?? '';
1452 + $plugin_file = $item['plugin_file'] ?? '';
1453 +
1454 + if ( empty( $option_name ) || empty( $plugin_file ) ) {
1455 + continue;
1456 + }
1457 +
1458 + // Check if the plugin is installed (file exists in plugins directory)
1459 + $plugin_path = WP_PLUGIN_DIR . '/' . $plugin_file;
1460 + if ( file_exists( $plugin_path ) ) {
1461 + // Plugin is installed, check if we already have a timestamp
1462 + $existing_option = get_option( $option_name );
1463 + $has_timestamp = false;
1464 +
1465 + if ( is_array( $existing_option ) ) {
1466 + $has_timestamp = isset( $existing_option[ $option_key ] ) && is_numeric( $existing_option[ $option_key ] );
1467 + } elseif ( is_numeric( $existing_option ) ) {
1468 + $has_timestamp = true;
1469 + }
1470 +
1471 + // Only set timestamp if one doesn't already exist
1472 + if ( ! $has_timestamp ) {
1473 + if ( is_array( $existing_option ) ) {
1474 + $existing_option[ $option_key ] = $current_time;
1475 + update_option( $option_name, $existing_option );
1476 + } else {
1477 + update_option( $option_name, array( $option_key => $current_time ) );
1478 + }
1479 + }
1480 + }
1481 + }
952 1482 }
953 1483 }
954 1484
955 1485 $charitable = new Charitable();