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