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 +394 -153 1.7.0 → 1.8.13.1 View file →
@@ -1,15 +1,17 @@
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.7.0
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 7 * Author: Charitable Donations & Fundraising Team
8 8 * Author URI: https://wpcharitable.com
9 - * Requires at least: 4.1
10 - * Tested up to: 6.0.2
11 - * Stable tag: 1.7.0
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
12 14 *
13 15 * Text Domain: charitable
14 16 * Domain Path: /i18n/languages/
15 17 *
@@ -14,9 +16,9 @@
14 16 * Domain Path: /i18n/languages/
15 17 *
16 18 * @package Charitable
17 19 * @author David Bisset
18 - * @copyright Copyright (c) 2022, WP Charitable LLC
20 + * @copyright Copyright (c) 2026, WP Charitable LLC
19 21 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
20 22 */
21 23
22 24 // Exit if accessed directly.
@@ -37,9 +39,9 @@
37 39 /** The plugin author name. */
38 40 const AUTHOR = 'WP Charitable';
39 41
40 42 /* Plugin version. */
41 - const VERSION = '1.7.0';
43 + const VERSION = '1.8.13.1';
42 44
43 45 /* Version of database schema. */
44 46 const DB_VERSION = '20180522';
45 47
@@ -104,8 +106,17 @@
104 106 */
105 107 private $classmap;
106 108
107 109 /**
110 + * Plugin.
111 + *
112 + * @since 1.8.0
113 + *
114 + * @var array
115 + */
116 + private $plugin;
117 +
118 + /**
108 119 * Create class instance.
109 120 *
110 121 * @since 1.0.0
111 122 */
@@ -113,12 +124,26 @@
113 124 $this->directory_path = plugin_dir_path( __FILE__ );
114 125 $this->directory_url = plugin_dir_url( __FILE__ );
115 126 $this->includes_path = $this->directory_path . 'includes/';
116 127
117 - define( "CHARTIABLE_DIRECTORY_PATH", plugin_dir_path( __FILE__ ) );
118 - define( "CHARTIABLE_PLUGIN_BASENAME", plugin_basename( __FILE__ ) );
119 - define( "CHARTIABLE_MARKETING_URL", 'https://wpcharitable.com/' );
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).
120 143
144 + define( 'CHARITABLE_BUILDER_SHOW_LEGACY_EDIT_LINKS', true ); // 1.8.0 specific.
145 +
121 146 spl_autoload_register( array( $this, 'autoloader' ) );
122 147
123 148 $this->load_dependencies();
124 149
@@ -124,12 +149,28 @@
124 149
125 150 register_activation_hook( __FILE__, array( $this, 'activate' ) );
126 151 register_deactivation_hook( __FILE__, array( $this, 'deactivate' ) );
127 152
128 - add_filter( 'plugin_action_links_' . CHARTIABLE_PLUGIN_BASENAME, array( $this, 'plugin_action_links' ), 99, 2 );
153 + add_filter( 'plugin_action_links_' . CHARITABLE_PLUGIN_BASENAME, array( $this, 'plugin_action_links' ), 99, 2 );
129 154 add_action( 'plugins_loaded', array( $this, 'start' ), 3 );
130 155
131 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 + );
132 173 }
133 174
134 175 /**
135 176 * Returns the original instance of this class.
@@ -186,58 +227,79 @@
186 227 private function load_dependencies() {
187 228 $includes_path = $this->get_path( 'includes' );
188 229
189 230 /* Load files with hooks & functions. Classes are autoloaded. */
190 - require_once( $includes_path . 'charitable-core-functions.php' );
191 - require_once( $includes_path . 'api/charitable-api-functions.php' );
192 - require_once( $includes_path . 'campaigns/charitable-campaign-functions.php' );
193 - require_once( $includes_path . 'campaigns/charitable-campaign-hooks.php' );
194 - require_once( $includes_path . 'compat/charitable-compat-functions.php' );
195 - require_once( $includes_path . 'currency/charitable-currency-functions.php' );
196 - require_once( $includes_path . 'deprecated/charitable-deprecated-functions.php' );
197 - require_once( $includes_path . 'donations/charitable-donation-hooks.php' );
198 - require_once( $includes_path . 'donations/charitable-donation-functions.php' );
199 - require_once( $includes_path . 'emails/charitable-email-hooks.php' );
200 - require_once( $includes_path . 'endpoints/charitable-endpoints-functions.php' );
201 - require_once( $includes_path . 'privacy/charitable-privacy-functions.php' );
202 - require_once( $includes_path . 'public/charitable-template-helpers.php' );
203 - require_once( $includes_path . 'shortcodes/charitable-shortcodes-hooks.php' );
204 - require_once( $includes_path . 'upgrades/charitable-upgrade-hooks.php' );
205 - require_once( $includes_path . 'users/charitable-user-functions.php' );
206 - require_once( $includes_path . 'user-management/charitable-user-management-hooks.php' );
207 - 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';
208 254
209 - If ( $this->load_core_stripe() ) :
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';
210 257
211 - /* Load Stripe SDK */
212 - require_once( $this->get_path( 'directory' ) . 'vendor/autoload.php' );
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';
213 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 +
214 269 /* Interfaces */
215 - require_once( $includes_path . 'stripe/interfaces/interface-charitable-stripe-gateway-processor.php' );
270 + require_once $includes_path . 'stripe/interfaces/interface-charitable-stripe-gateway-processor.php';
216 271
217 272 /* Abstract classes */
218 - require_once( $includes_path . 'stripe/abstracts/class-charitable-stripe-gateway-processor.php' );
273 + require_once $includes_path . 'stripe/abstracts/class-charitable-stripe-gateway-processor.php';
219 274
220 275 /* Classes */
221 - require_once( $includes_path . 'stripe/donations/class-charitable-stripe-donation-log.php' );
222 - require_once( $includes_path . 'stripe/donations/class-charitable-stripe-recurring-donation-log.php' );
223 - require_once( $includes_path . 'stripe/i18n/class-charitable-stripe-i18n.php' );
224 - require_once( $includes_path . 'stripe/gateway/class-charitable-stripe-connected-customer.php' );
225 - require_once( $includes_path . 'stripe/gateway/class-charitable-stripe-customer.php' );
226 - require_once( $includes_path . 'stripe/gateway/class-charitable-stripe-gateway-processor-payment-intents.php' );
227 - require_once( $includes_path . 'stripe/gateway/class-charitable-stripe-payment-intent.php' );
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';
228 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 +
229 289 /* Functions & Hooks */
230 - require_once( $includes_path . 'stripe/charitable-stripe-core-functions.php' );
231 - require_once( $includes_path . 'stripe/donations/charitable-stripe-donation-functions.php' );
232 - require_once( $includes_path . 'stripe/gateway/charitable-stripe-gateway-hooks.php' );
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';
233 293
234 294 /* webhooks */
235 - require_once( $includes_path . 'stripe/gateway/class-charitable-stripe-webhook-api.php' );
236 - require_once( $includes_path . 'stripe/gateway/class-charitable-stripe-webhook-processor.php' );
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';
237 297
238 298 endif;
239 299
300 + /* Load PayPal Commerce Gateway */
301 + require_once $includes_path . 'gateways/paypal-commerce/charitable-paypal-commerce.php';
240 302 }
241 303
242 304 /**
243 305 * Load the template functions after theme is loaded.
@@ -250,10 +312,10 @@
250 312 */
251 313 public function load_template_files() {
252 314 $includes_path = $this->get_path( 'includes' );
253 315
254 - require_once( $includes_path . 'public/charitable-template-functions.php' );
255 - 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';
256 318 }
257 319
258 320 /**
259 321 * Dynamically loads the class attempting to be instantiated elsewhere in the
@@ -276,21 +338,20 @@
276 338 }
277 339
278 340 /* Autogenerated class map. */
279 341 if ( ! isset( $this->classmap ) ) {
280 - $this->classmap = include( 'includes/autoloader/charitable-class-map.php' );
342 + $this->classmap = include 'includes/autoloader/charitable-class-map.php';
281 343
282 - $this->classmap[ 'Charitable_Gateway_Stripe_AM' ] = 'gateways/class-charitable-gateway-stripe-am.php';
283 - $this->classmap[ 'Charitable_Gateway_Stripe' ] = 'gateways/class-charitable-gateway-stripe-am.php';
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';
284 346 }
285 347
286 348 $file_path = isset( $this->classmap[ $class_name ] ) ? $this->get_path( 'includes' ) . $this->classmap[ $class_name ] : false;
287 349
288 350 if ( false !== $file_path && file_exists( $file_path ) && is_file( $file_path ) ) {
289 - require_once( $file_path );
351 + require_once $file_path;
290 352 return true;
291 353 }
292 -
293 354 return false;
294 355 }
295 356
296 357 /**
@@ -313,12 +374,13 @@
313 374 $this->registry->register_object( Charitable_Licenses::get_instance() );
314 375 $this->registry->register_object( Charitable_User_Dashboard::get_instance() );
315 376 $this->registry->register_object( Charitable_Locations::get_instance() );
316 377 $this->registry->register_object( Charitable_Currency::get_instance() );
378 + $this->registry->register_object( Charitable_Notifications::get_instance() );
317 379
318 - $this->registry->register_object( new Charitable_Privacy );
319 - $this->registry->register_object( new Charitable_Debugging );
320 - $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() );
321 383 }
322 384
323 385 return $this->registry;
324 386 }
@@ -334,10 +396,10 @@
334 396 add_action( 'wpmu_new_blog', array( $this, 'maybe_activate_charitable_on_new_site' ) );
335 397 add_action( 'plugins_loaded', array( $this, 'charitable_install' ), 100 );
336 398 add_action( 'plugins_loaded', array( $this, 'charitable_start' ), 100 );
337 399 add_action( 'plugins_loaded', array( $this, 'endpoints' ), 100 );
338 - add_action( 'plugins_loaded', array( $this, 'donation_fields' ), 100 );
339 - 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 );
340 402 add_action( 'plugins_loaded', array( $this, 'register_donormeta_table' ) );
341 403 add_action( 'plugins_loaded', 'charitable_load_compat_functions' );
342 404 add_action( 'setup_theme', array( 'Charitable_Customizer', 'start' ) );
343 405 add_action( 'after_setup_theme', array( $this, 'load_template_files' ) );
@@ -349,19 +411,17 @@
349 411 * as addons) has a chance to run before the event.
350 412 */
351 413 add_action( 'init', array( $this, 'do_charitable_actions' ), 20 );
352 414
353 - If ( $this->load_core_stripe() ) :
415 + if ( $this->load_core_stripe() ) :
354 416
355 417 /**
356 418 * Set up scripts & stylesheets & enqueue them when appropriate.
357 419 */
358 - // add_action( 'wp_enqueue_scripts', array( $this, 'setup_stripe_scripts' ), 11 );
359 420 add_action( 'charitable_form_after_fields', array( $this, 'maybe_setup_stripe_scripts_in_donation_form' ) );
360 421 add_action( 'charitable_campaign_loop_after', array( $this, 'maybe_setup_stripe_scripts_in_campaign_loop' ) );
361 422
362 423 endif;
363 -
364 424 }
365 425
366 426
367 427 /**
@@ -431,25 +491,70 @@
431 491 if ( ! is_admin() ) {
432 492 return false;
433 493 }
434 494
435 - require_once( $this->get_path( 'admin' ) . 'class-charitable-admin.php' );
436 - 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';
437 497
438 - $admin = Charitable_Admin::get_instance();
498 + require_once $this->get_path( 'admin' ) . 'class-charitable-admin.php';
499 + require_once $this->get_path( 'admin' ) . 'charitable-admin-hooks.php';
439 500
440 - $this->registry->register_object( $admin );
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';
441 510
442 - // Stripe
511 + // connect for upgrade.wpcharitable.com.
512 + require_once $this->get_path( 'admin' ) . 'class-charitable-admin-connect.php';
443 513
444 - require_once( $this->get_path( 'includes' ) . 'stripe/admin/class-charitable-stripe-admin.php' );
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';
445 518
446 - new Charitable_Stripe_Admin;
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();
447 522
523 + $admin = Charitable_Admin::get_instance();
524 + $this->registry->register_object( $admin );
525 +
526 + // Stripe.
527 + require_once $this->get_path( 'includes' ) . 'stripe/admin/class-charitable-stripe-admin.php';
528 + new Charitable_Stripe_Admin();
529 +
448 530 return $admin;
449 531 }
450 532
451 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 + /**
452 557 * Checks whether we're on the public-facing side and if so, loads the public-facing functionality.
453 558 *
454 559 * @since 1.0.0
455 560 *
@@ -459,10 +564,18 @@
459 564 if ( is_admin() && ! $this->is_ajax() ) {
460 565 return false;
461 566 }
462 567
463 - require_once( $this->get_path( 'public' ) . 'class-charitable-public.php' );
568 + require_once $this->get_path( 'public' ) . 'class-charitable-public.php';
464 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 +
465 578 $public = Charitable_Public::get_instance();
466 579
467 580 $this->registry->register_object( $public );
468 581
@@ -477,25 +590,51 @@
477 590 * @return boolean
478 591 */
479 592 public function maybe_start_qunit() {
480 593 /* Skip out early if ?qunit isn't included in the request. */
481 - if ( ! array_key_exists( 'qunit', $_GET ) ) {
594 + if ( ! array_key_exists( 'qunit', $_GET ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
482 595 return false;
483 596 }
484 597
485 - /* The unit tests have to exist. */
486 - if ( ! file_exists( $this->get_path( 'directory' ) . 'tests/qunit/tests.js' ) ) {
487 - return false;
488 - }
598 + /* The unit tests have to exist. */
599 + if ( ! file_exists( $this->get_path( 'directory' ) . 'tests/qunit/tests.js' ) ) {
600 + return false;
601 + }
489 602
490 - 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 + );
491 624
492 - /* Version: '20170615-15:44' */
493 - wp_register_script( 'qunit-tests', $this->get_path( 'directory', false ) . 'tests/qunit/tests.js', array( 'jquery', 'qunit' ), time(), true );
494 - 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' );
495 628
496 - wp_register_style( 'qunit', 'https://code.jquery.com/qunit/qunit-2.3.3.css', array(), '2.3.3', 'all' );
497 - 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' );
498 637 }
499 638
500 639 /**
501 640 * Checks whether the current request is an AJAX request.
@@ -519,10 +658,10 @@
519 658 if ( ! $this->is_ajax() ) {
520 659 return;
521 660 }
522 661
523 - require_once( $this->get_path( 'includes' ) . 'ajax/charitable-ajax-functions.php' );
524 - 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';
525 664 }
526 665
527 666 /**
528 667 * This method is fired after all plugins are loaded and simply fires the charitable_start hook.
@@ -540,9 +679,11 @@
540 679 /**
541 680 * Fires off an action right after Charitable is installed, allowing other
542 681 * plugins/themes to do something at this point.
543 682 *
544 - * @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.
545 686 *
546 687 * @return void
547 688 */
548 689 public function charitable_install() {
@@ -551,9 +692,9 @@
551 692 if ( ! $install ) {
552 693 return;
553 694 }
554 695
555 - require_once( $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php' );
696 + require_once $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php';
556 697
557 698 Charitable_Install::finish_installing();
558 699
559 700 do_action( 'charitable_install' );
@@ -558,8 +699,28 @@
558 699
559 700 do_action( 'charitable_install' );
560 701
561 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' );
562 723 }
563 724
564 725 /**
565 726 * Returns whether we are currently in the start phase of the plugin.
@@ -568,9 +729,9 @@
568 729 *
569 730 * @return boolean
570 731 */
571 732 public function is_start() {
572 - return current_filter() == 'charitable_start';
733 + return current_filter() == 'charitable_start'; // phpcs:ignore
573 734 }
574 735
575 736 /**
576 737 * Returns whether the plugin has already started.
@@ -579,9 +740,9 @@
579 740 *
580 741 * @return boolean
581 742 */
582 743 public function started() {
583 - return did_action( 'charitable_start' ) || current_filter() == 'charitable_start';
744 + return did_action( 'charitable_start' ) || current_filter() == 'charitable_start'; // phpcs:ignore
584 745 }
585 746
586 747 /**
587 748 * Returns whether the plugin is being activated.
@@ -590,9 +751,9 @@
590 751 *
591 752 * @return boolean
592 753 */
593 754 public function is_activation() {
594 - return current_filter() == 'activate_charitable/charitable.php';
755 + return current_filter() == 'activate_charitable/charitable.php'; // phpcs:ignore
595 756 }
596 757
597 758 /**
598 759 * Returns whether the plugin is being deactivated.
@@ -601,9 +762,9 @@
601 762 *
602 763 * @return boolean
603 764 */
604 765 public function is_deactivation() {
605 - return current_filter() == 'deactivate_charitable/charitable.php';
766 + return current_filter() == 'deactivate_charitable/charitable.php'; // phpcs:ignore
606 767 }
607 768
608 769 /**
609 770 * Returns plugin paths.
@@ -637,8 +798,12 @@
637 798 case 'templates':
638 799 $path = $base . 'templates/';
639 800 break;
640 801
802 + case 'plugin-directory':
803 + $path = WP_PLUGIN_DIR;
804 + break;
805 +
641 806 case 'directory':
642 807 $path = $base;
643 808 break;
644 809
@@ -668,8 +833,26 @@
668 833 return $version;
669 834 }
670 835
671 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 + /**
672 855 * Get the campaign fields registry
673 856 *
674 857 * If the registry has not been set up yet, this will also
675 858 * perform the task of setting it up initially.
@@ -686,9 +869,9 @@
686 869
687 870 /* Register it immediately to avoid endless recursion. */
688 871 $this->registry->register_object( $campaign_fields );
689 872
690 - $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';
691 874
692 875 foreach ( $fields as $key => $args ) {
693 876 $campaign_fields->register_field( new Charitable_Campaign_Field( $key, $args ) );
694 877 }
@@ -715,9 +898,9 @@
715 898
716 899 /* Register it immediately to avoid endless recursion. */
717 900 $this->registry->register_object( $donation_fields );
718 901
719 - $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';
720 903
721 904 foreach ( $fields as $key => $args ) {
722 905 $donation_fields->register_field( new Charitable_Donation_Field( $key, $args ) );
723 906 }
@@ -749,22 +932,22 @@
749 932 * BEFORE the endpoint it builds on. In other words, move from
750 933 * most specific to least specific.
751 934 */
752 935 $endpoints = new Charitable_Endpoints();
753 - $endpoints->register( new Charitable_Campaign_Donation_Endpoint );
754 - $endpoints->register( new Charitable_Campaign_Widget_Endpoint );
755 - $endpoints->register( new Charitable_Campaign_Endpoint );
756 - $endpoints->register( new Charitable_Donation_Cancellation_Endpoint );
757 - $endpoints->register( new Charitable_Donation_Processing_Endpoint );
758 - $endpoints->register( new Charitable_Donation_Receipt_Endpoint );
759 - $endpoints->register( new Charitable_Email_Preview_Endpoint );
760 - $endpoints->register( new Charitable_Email_Verification_Endpoint );
761 - $endpoints->register( new Charitable_Forgot_Password_Endpoint );
762 - $endpoints->register( new Charitable_Reset_Password_Endpoint );
763 - $endpoints->register( new Charitable_Registration_Endpoint );
764 - $endpoints->register( new Charitable_Login_Endpoint );
765 - $endpoints->register( new Charitable_Profile_Endpoint );
766 - $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() );
767 950
768 951 $this->registry->register_object( $endpoints );
769 952 }
770 953
@@ -778,9 +961,9 @@
778 961 *
779 962 * @param string $class The type of class to be retrieved.
780 963 * @return object
781 964 */
782 - public function get_registered_object( $class ) {
965 + public function get_registered_object( $class ) { // phpcs:ignore
783 966 return $this->registry->get( $class );
784 967 }
785 968
786 969 /**
@@ -831,8 +1014,9 @@
831 1014 /**
832 1015 * Filter the array of available Charitable table classes.
833 1016 *
834 1017 * @since 1.0.0
1018 + * @version 1.8.1
835 1019 *
836 1020 * @param array $tables List of tables as a key=>value array.
837 1021 */
838 1022 return apply_filters(
@@ -837,11 +1021,13 @@
837 1021 */
838 1022 return apply_filters(
839 1023 'charitable_db_tables',
840 1024 array(
841 - 'campaign_donations' => 'Charitable_Campaign_Donations_DB',
842 - 'donors' => 'Charitable_Donors_DB',
843 - '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',
844 1030 )
845 1031 );
846 1032 }
847 1033
@@ -872,14 +1058,14 @@
872 1058 * or just the current site. Multisite only. Default is false.
873 1059 * @return void
874 1060 */
875 1061 public function activate( $network_wide = false ) {
876 - require_once( $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php' );
1062 + require_once $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php';
877 1063
878 1064 if ( is_multisite() && $network_wide ) {
879 1065 global $wpdb;
880 1066
881 - 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
882 1068 switch_to_blog( $blog_id );
883 1069 new Charitable_Install( $this->includes_path );
884 1070 restore_current_blog();
885 1071 }
@@ -887,9 +1073,12 @@
887 1073 new Charitable_Install( $this->includes_path );
888 1074 }
889 1075
890 1076 // create or update the install date for future reference.
891 - update_option( 'wpcharitable_activated_datetime', current_time( 'timestamp' ) );
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();
892 1081 }
893 1082
894 1083 /**
895 1084 * Runs on plugin deactivation.
@@ -900,9 +1089,9 @@
900 1089 *
901 1090 * @return void
902 1091 */
903 1092 public function deactivate() {
904 - require_once( $this->get_path( 'includes' ) . 'plugin/class-charitable-uninstall.php' );
1093 + require_once $this->get_path( 'includes' ) . 'plugin/class-charitable-uninstall.php';
905 1094 new Charitable_Uninstall();
906 1095 }
907 1096
908 1097 /**
@@ -912,11 +1101,11 @@
912 1101 *
913 1102 * @return void
914 1103 */
915 1104 public function do_charitable_actions() {
916 - if ( isset( $_REQUEST['charitable_action'] ) ) {
1105 + if ( isset( $_REQUEST['charitable_action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
917 1106
918 - $action = $_REQUEST['charitable_action'];
1107 + $action = esc_attr( $_REQUEST['charitable_action'] ); // phpcs:ignore
919 1108
920 1109 /**
921 1110 * Handle Charitable action.
922 1111 *
@@ -992,9 +1181,9 @@
992 1181 *
993 1182 * @param mixed $object Object to be registered.
994 1183 * @return void
995 1184 */
996 - public function register_object( $object ) {
1185 + public function register_object( $object ) { // phpcs:ignore
997 1186 charitable_get_deprecated()->deprecated_function(
998 1187 __METHOD__,
999 1188 '1.5.0',
1000 1189 'charitable()->registry()->register_object()'
@@ -1099,18 +1288,13 @@
1099 1288 if ( false !== ( defined( 'USE_NEW_STRIPE' ) && USE_NEW_STRIPE ) ) {
1100 1289 return true;
1101 1290 }
1102 1291
1103 - // check for stripe addon
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 + }
1104 1296
1105 - // if ( in_array('charitable-stripe/charitable-stripe.php', apply_filters( 'active_plugins', get_option('active_plugins' ) ) ) ) {
1106 - // return false;
1107 - // }
1108 -
1109 - // if ( class_exists( 'Charitable_Stripe' ) ) {
1110 - // return false;
1111 - // }
1112 -
1113 1297 return true;
1114 1298 }
1115 1299
1116 1300 /**
@@ -1121,9 +1305,9 @@
1121 1305 * @return boolean
1122 1306 */
1123 1307 public function is_stripe_connect_addon() {
1124 1308
1125 - if ( in_array('charitable-stripe/charitable-stripe.php', apply_filters( 'active_plugins', get_option('active_plugins' ) ) ) ) {
1309 + if ( in_array( 'charitable-stripe/charitable-stripe.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) { // phpcs:ignore
1126 1310 return true;
1127 1311 }
1128 1312
1129 1313 return false;
@@ -1133,10 +1317,11 @@
1133 1317 * Action links for the plugins page.
1134 1318 *
1135 1319 * @since 1.7.0
1136 1320 *
1137 - * @param array $actions An array of existing actions.
1138 - * @param string $plugin_file The plugin file we are modifying.
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.
1139 1324 * @return array An array of action links.
1140 1325 */
1141 1326 public function plugin_action_links( $actions, $plugin_file, $action_links = false ) {
1142 1327
@@ -1143,15 +1328,15 @@
1143 1328 if ( ! charitable_is_pro() ) :
1144 1329
1145 1330 // if this isn't pro, add the upgrade link which is visible on the plugins page until the plugin name.
1146 1331
1147 - $action_links = [
1148 - 'proupgrade' => [
1332 + $action_links = array(
1333 + 'proupgrade' => array(
1149 1334 // Translators: This is an action link users can click to purchase a license for All in One SEO Pro.
1150 1335 'label' => __( 'Upgrade to Pro', 'charitable' ),
1151 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',
1152 - ]
1153 - ];
1337 + ),
1338 + );
1154 1339
1155 1340 endif;
1156 1341
1157 1342 if ( isset( $actions['edit'] ) ) {
@@ -1165,23 +1350,24 @@
1165 1350 * Parse the action links.
1166 1351 *
1167 1352 * @since 1.7.0
1168 1353 *
1169 - * @param array $actions
1170 - * @param string $pluginFile
1171 - * @param
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.
1172 1358 * @return array
1173 1359 */
1174 - protected function parse_action_links( $actions, $pluginFile, $action_links = [], $position = 'after' ) {
1360 + protected function parse_action_links( $actions, $plugin_file, $action_links = array(), $position = 'after' ) {
1175 1361 if ( empty( $this->plugin ) ) {
1176 - $this->plugin = CHARTIABLE_PLUGIN_BASENAME;
1362 + $this->plugin = CHARITABLE_PLUGIN_BASENAME;
1177 1363 }
1178 1364
1179 - if ( $this->plugin === $pluginFile && ! empty( $action_links ) ) {
1365 + if ( $this->plugin === $plugin_file && ! empty( $action_links ) ) {
1180 1366 foreach ( $action_links as $key => $value ) {
1181 - $link = [
1182 - $key => '<a href="' . $value['url'] . '">' . $value['label'] . '</a>'
1183 - ];
1367 + $link = array(
1368 + $key => '<a href="' . $value['url'] . '">' . $value['label'] . '</a>',
1369 + );
1184 1370
1185 1371 $actions = 'after' === $position ? array_merge( $actions, $link ) : array_merge( $link, $actions );
1186 1372 }
1187 1373 }
@@ -1197,11 +1383,11 @@
1197 1383 * @return void
1198 1384 */
1199 1385 public function setup_licensing() {
1200 1386 charitable_get_helper( 'licenses' )->register_licensed_product(
1201 - Charitable::NAME . ' Pro',
1202 - Charitable::AUTHOR,
1203 - Charitable::VERSION,
1387 + self::NAME,
1388 + self::AUTHOR,
1389 + self::VERSION,
1204 1390 __FILE__
1205 1391 );
1206 1392 }
1207 1393
@@ -1208,16 +1394,14 @@
1208 1394 /**
1209 1395 * Deativates and/or warnings about any Charitable verison conflicts.
1210 1396 *
1211 1397 * @since 1.7.0
1212 - *
1213 - * @return null
1214 1398 */
1215 1399 public function check_for_version_conflicts() {
1216 1400
1217 - // Check for Charitable Stripe < 1.5.0
1218 - if ( in_array( 'charitable-stripe/charitable-stripe.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) {
1219 - if ( version_compare( Charitable_Stripe::VERSION, '1.5.0', '<' ) ) {
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
1220 1404 // manually deactivate plugin.
1221 1405 $found = false;
1222 1406 $current = get_option( 'active_plugins', array() );
1223 1407 $key = array_search( 'charitable-stripe/charitable-stripe.php', $current, true );
@@ -1226,21 +1410,78 @@
1226 1410
1227 1411 unset( $current[ $key ] );
1228 1412 update_option( 'active_plugins', $current );
1229 1413
1230 - $message = __( '<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> ' . Charitable_Stripe::VERSION . ' 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-stripe' );
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 );
1231 1416
1232 - wp_die( $message . ' <a href="' . admin_url( 'plugins.php' ) . '">Click here to return to the plugins screen.</a>' );
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
1233 1418 }
1234 - // charitable_get_notices()->add_error( $message );
1235 - add_option( 'chartiable_version_warning', $message );
1419 + add_option( 'charitable_version_warning', $message );
1236 1420 } else {
1237 - delete_option( 'chartiable_version_warning' );
1421 + delete_option( 'charitable_version_warning' );
1238 1422 }
1239 1423 }
1240 1424 }
1241 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();
1242 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 + }
1483 + }
1243 1484 }
1244 1485
1245 1486 $charitable = new Charitable();
1246 1487