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 +609 -86 1.6.60 → 1.8.13 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
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
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';
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,78 @@
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 . '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';
192 301 }
193 302
194 303 /**
195 304 * Load the template functions after theme is loaded.
@@ -202,10 +311,10 @@
202 311 */
203 312 public function load_template_files() {
204 313 $includes_path = $this->get_path( 'includes' );
205 314
206 - require_once( $includes_path . 'public/charitable-template-functions.php' );
207 - require_once( $includes_path . 'public/charitable-template-hooks.php' );
315 + require_once $includes_path . 'public/charitable-template-functions.php';
316 + require_once $includes_path . 'public/charitable-template-hooks.php';
208 317 }
209 318
210 319 /**
211 320 * Dynamically loads the class attempting to be instantiated elsewhere in the
@@ -228,18 +337,20 @@
228 337 }
229 338
230 339 /* Autogenerated class map. */
231 340 if ( ! isset( $this->classmap ) ) {
232 - $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';
233 345 }
234 346
235 347 $file_path = isset( $this->classmap[ $class_name ] ) ? $this->get_path( 'includes' ) . $this->classmap[ $class_name ] : false;
236 348
237 349 if ( false !== $file_path && file_exists( $file_path ) && is_file( $file_path ) ) {
238 - require_once( $file_path );
350 + require_once $file_path;
239 351 return true;
240 352 }
241 -
242 353 return false;
243 354 }
244 355
245 356 /**
@@ -262,12 +373,13 @@
262 373 $this->registry->register_object( Charitable_Licenses::get_instance() );
263 374 $this->registry->register_object( Charitable_User_Dashboard::get_instance() );
264 375 $this->registry->register_object( Charitable_Locations::get_instance() );
265 376 $this->registry->register_object( Charitable_Currency::get_instance() );
377 + $this->registry->register_object( Charitable_Notifications::get_instance() );
266 378
267 - $this->registry->register_object( new Charitable_Privacy );
268 - $this->registry->register_object( new Charitable_Debugging );
269 - $this->registry->register_object( new Charitable_Locale );
379 + $this->registry->register_object( new Charitable_Privacy() );
380 + $this->registry->register_object( new Charitable_Debugging() );
381 + $this->registry->register_object( new Charitable_Locale() );
270 382 }
271 383
272 384 return $this->registry;
273 385 }
@@ -283,10 +395,10 @@
283 395 add_action( 'wpmu_new_blog', array( $this, 'maybe_activate_charitable_on_new_site' ) );
284 396 add_action( 'plugins_loaded', array( $this, 'charitable_install' ), 100 );
285 397 add_action( 'plugins_loaded', array( $this, 'charitable_start' ), 100 );
286 398 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 );
399 + add_action( 'init', array( $this, 'donation_fields' ), 100 );
400 + add_action( 'init', array( $this, 'campaign_fields' ), 100 );
289 401 add_action( 'plugins_loaded', array( $this, 'register_donormeta_table' ) );
290 402 add_action( 'plugins_loaded', 'charitable_load_compat_functions' );
291 403 add_action( 'setup_theme', array( 'Charitable_Customizer', 'start' ) );
292 404 add_action( 'after_setup_theme', array( $this, 'load_template_files' ) );
@@ -297,11 +409,78 @@
297 409 * We do this on priority 20 so that any functionality that is loaded on init (such
298 410 * as addons) has a chance to run before the event.
299 411 */
300 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;
301 423 }
302 424
425 +
303 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 + /**
304 483 * Checks whether we're in the admin area and if so, loads the admin-only functionality.
305 484 *
306 485 * @since 1.0.0
307 486 *
@@ -311,19 +490,70 @@
311 490 if ( ! is_admin() ) {
312 491 return false;
313 492 }
314 493
315 - require_once( $this->get_path( 'admin' ) . 'class-charitable-admin.php' );
316 - 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';
317 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 +
318 522 $admin = Charitable_Admin::get_instance();
523 + $this->registry->register_object( $admin );
319 524
320 - $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();
321 528
322 529 return $admin;
323 530 }
324 531
325 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 + /**
326 556 * Checks whether we're on the public-facing side and if so, loads the public-facing functionality.
327 557 *
328 558 * @since 1.0.0
329 559 *
@@ -333,10 +563,18 @@
333 563 if ( is_admin() && ! $this->is_ajax() ) {
334 564 return false;
335 565 }
336 566
337 - require_once( $this->get_path( 'public' ) . 'class-charitable-public.php' );
567 + require_once $this->get_path( 'public' ) . 'class-charitable-public.php';
338 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 +
339 577 $public = Charitable_Public::get_instance();
340 578
341 579 $this->registry->register_object( $public );
342 580
@@ -351,25 +589,51 @@
351 589 * @return boolean
352 590 */
353 591 public function maybe_start_qunit() {
354 592 /* Skip out early if ?qunit isn't included in the request. */
355 - if ( ! array_key_exists( 'qunit', $_GET ) ) {
593 + if ( ! array_key_exists( 'qunit', $_GET ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
356 594 return false;
357 595 }
358 596
359 - /* The unit tests have to exist. */
360 - if ( ! file_exists( $this->get_path( 'directory' ) . 'tests/qunit/tests.js' ) ) {
361 - return false;
362 - }
597 + /* The unit tests have to exist. */
598 + if ( ! file_exists( $this->get_path( 'directory' ) . 'tests/qunit/tests.js' ) ) {
599 + return false;
600 + }
363 601
364 - wp_register_script( 'qunit', 'https://code.jquery.com/qunit/qunit-2.3.3.js', array(), '2.3.3', true );
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 + );
365 623
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' );
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' );
369 627
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' );
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' );
372 636 }
373 637
374 638 /**
375 639 * Checks whether the current request is an AJAX request.
@@ -393,10 +657,10 @@
393 657 if ( ! $this->is_ajax() ) {
394 658 return;
395 659 }
396 660
397 - require_once( $this->get_path( 'includes' ) . 'ajax/charitable-ajax-functions.php' );
398 - 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';
399 663 }
400 664
401 665 /**
402 666 * This method is fired after all plugins are loaded and simply fires the charitable_start hook.
@@ -414,9 +678,11 @@
414 678 /**
415 679 * Fires off an action right after Charitable is installed, allowing other
416 680 * plugins/themes to do something at this point.
417 681 *
418 - * @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.
419 685 *
420 686 * @return void
421 687 */
422 688 public function charitable_install() {
@@ -425,9 +691,9 @@
425 691 if ( ! $install ) {
426 692 return;
427 693 }
428 694
429 - require_once( $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php' );
695 + require_once $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php';
430 696
431 697 Charitable_Install::finish_installing();
432 698
433 699 do_action( 'charitable_install' );
@@ -432,8 +698,28 @@
432 698
433 699 do_action( 'charitable_install' );
434 700
435 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' );
436 722 }
437 723
438 724 /**
439 725 * Returns whether we are currently in the start phase of the plugin.
@@ -442,9 +728,9 @@
442 728 *
443 729 * @return boolean
444 730 */
445 731 public function is_start() {
446 - return current_filter() == 'charitable_start';
732 + return current_filter() == 'charitable_start'; // phpcs:ignore
447 733 }
448 734
449 735 /**
450 736 * Returns whether the plugin has already started.
@@ -453,9 +739,9 @@
453 739 *
454 740 * @return boolean
455 741 */
456 742 public function started() {
457 - return did_action( 'charitable_start' ) || current_filter() == 'charitable_start';
743 + return did_action( 'charitable_start' ) || current_filter() == 'charitable_start'; // phpcs:ignore
458 744 }
459 745
460 746 /**
461 747 * Returns whether the plugin is being activated.
@@ -464,9 +750,9 @@
464 750 *
465 751 * @return boolean
466 752 */
467 753 public function is_activation() {
468 - return current_filter() == 'activate_charitable/charitable.php';
754 + return current_filter() == 'activate_charitable/charitable.php'; // phpcs:ignore
469 755 }
470 756
471 757 /**
472 758 * Returns whether the plugin is being deactivated.
@@ -475,9 +761,9 @@
475 761 *
476 762 * @return boolean
477 763 */
478 764 public function is_deactivation() {
479 - return current_filter() == 'deactivate_charitable/charitable.php';
765 + return current_filter() == 'deactivate_charitable/charitable.php'; // phpcs:ignore
480 766 }
481 767
482 768 /**
483 769 * Returns plugin paths.
@@ -511,8 +797,12 @@
511 797 case 'templates':
512 798 $path = $base . 'templates/';
513 799 break;
514 800
801 + case 'plugin-directory':
802 + $path = WP_PLUGIN_DIR;
803 + break;
804 +
515 805 case 'directory':
516 806 $path = $base;
517 807 break;
518 808
@@ -542,8 +832,26 @@
542 832 return $version;
543 833 }
544 834
545 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 + /**
546 854 * Get the campaign fields registry
547 855 *
548 856 * If the registry has not been set up yet, this will also
549 857 * perform the task of setting it up initially.
@@ -560,9 +868,9 @@
560 868
561 869 /* Register it immediately to avoid endless recursion. */
562 870 $this->registry->register_object( $campaign_fields );
563 871
564 - $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';
565 873
566 874 foreach ( $fields as $key => $args ) {
567 875 $campaign_fields->register_field( new Charitable_Campaign_Field( $key, $args ) );
568 876 }
@@ -589,9 +897,9 @@
589 897
590 898 /* Register it immediately to avoid endless recursion. */
591 899 $this->registry->register_object( $donation_fields );
592 900
593 - $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';
594 902
595 903 foreach ( $fields as $key => $args ) {
596 904 $donation_fields->register_field( new Charitable_Donation_Field( $key, $args ) );
597 905 }
@@ -623,22 +931,22 @@
623 931 * BEFORE the endpoint it builds on. In other words, move from
624 932 * most specific to least specific.
625 933 */
626 934 $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 );
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() );
641 949
642 950 $this->registry->register_object( $endpoints );
643 951 }
644 952
@@ -652,9 +960,9 @@
652 960 *
653 961 * @param string $class The type of class to be retrieved.
654 962 * @return object
655 963 */
656 - public function get_registered_object( $class ) {
964 + public function get_registered_object( $class ) { // phpcs:ignore
657 965 return $this->registry->get( $class );
658 966 }
659 967
660 968 /**
@@ -705,8 +1013,9 @@
705 1013 /**
706 1014 * Filter the array of available Charitable table classes.
707 1015 *
708 1016 * @since 1.0.0
1017 + * @version 1.8.1
709 1018 *
710 1019 * @param array $tables List of tables as a key=>value array.
711 1020 */
712 1021 return apply_filters(
@@ -711,11 +1020,13 @@
711 1020 */
712 1021 return apply_filters(
713 1022 'charitable_db_tables',
714 1023 array(
715 - 'campaign_donations' => 'Charitable_Campaign_Donations_DB',
716 - 'donors' => 'Charitable_Donors_DB',
717 - 'donormeta' => 'Charitable_Donormeta_DB',
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',
718 1029 )
719 1030 );
720 1031 }
721 1032
@@ -746,14 +1057,14 @@
746 1057 * or just the current site. Multisite only. Default is false.
747 1058 * @return void
748 1059 */
749 1060 public function activate( $network_wide = false ) {
750 - require_once( $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php' );
1061 + require_once $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php';
751 1062
752 1063 if ( is_multisite() && $network_wide ) {
753 1064 global $wpdb;
754 1065
755 - 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
756 1067 switch_to_blog( $blog_id );
757 1068 new Charitable_Install( $this->includes_path );
758 1069 restore_current_blog();
759 1070 }
@@ -759,8 +1070,14 @@
759 1070 }
760 1071 } else {
761 1072 new Charitable_Install( $this->includes_path );
762 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();
763 1080 }
764 1081
765 1082 /**
766 1083 * Runs on plugin deactivation.
@@ -771,9 +1088,9 @@
771 1088 *
772 1089 * @return void
773 1090 */
774 1091 public function deactivate() {
775 - require_once( $this->get_path( 'includes' ) . 'plugin/class-charitable-uninstall.php' );
1092 + require_once $this->get_path( 'includes' ) . 'plugin/class-charitable-uninstall.php';
776 1093 new Charitable_Uninstall();
777 1094 }
778 1095
779 1096 /**
@@ -783,11 +1100,11 @@
783 1100 *
784 1101 * @return void
785 1102 */
786 1103 public function do_charitable_actions() {
787 - if ( isset( $_REQUEST['charitable_action'] ) ) {
1104 + if ( isset( $_REQUEST['charitable_action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
788 1105
789 - $action = $_REQUEST['charitable_action'];
1106 + $action = esc_attr( $_REQUEST['charitable_action'] ); // phpcs:ignore
790 1107
791 1108 /**
792 1109 * Handle Charitable action.
793 1110 *
@@ -863,9 +1180,9 @@
863 1180 *
864 1181 * @param mixed $object Object to be registered.
865 1182 * @return void
866 1183 */
867 - public function register_object( $object ) {
1184 + public function register_object( $object ) { // phpcs:ignore
868 1185 charitable_get_deprecated()->deprecated_function(
869 1186 __METHOD__,
870 1187 '1.5.0',
871 1188 'charitable()->registry()->register_object()'
@@ -955,8 +1272,214 @@
955 1272 */
956 1273 public function get_location_helper() {
957 1274 charitable_get_deprecated()->deprecated_function( __METHOD__, '1.2.0', 'charitable_get_location_helper' );
958 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 + }
959 1482 }
960 1483 }
961 1484
962 1485 $charitable = new Charitable();