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 +380 -158 1.7.0.11 → 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.11
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.2.0
11 - * Stable tag: 1.7.0.11
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.11';
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,63 +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' );
228 - require_once( $includes_path . 'stripe/gateway/class-charitable-stripe-plan.php' );
229 - require_once( $includes_path . 'stripe/gateway/class-charitable-stripe-product.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';
230 285
231 - require_once( $includes_path . 'stripe/connect/charitable-stripe-connect-functions.php' );
232 - require_once( $includes_path . 'stripe/gateway/class-charitable-stripe-gateway-processor-checkout.php' );
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';
233 288
234 289 /* Functions & Hooks */
235 - require_once( $includes_path . 'stripe/charitable-stripe-core-functions.php' );
236 - require_once( $includes_path . 'stripe/donations/charitable-stripe-donation-functions.php' );
237 - 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';
238 293
239 294 /* webhooks */
240 - require_once( $includes_path . 'stripe/gateway/class-charitable-stripe-webhook-api.php' );
241 - 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';
242 297
243 298 endif;
244 299
300 + /* Load PayPal Commerce Gateway */
301 + require_once $includes_path . 'gateways/paypal-commerce/charitable-paypal-commerce.php';
245 302 }
246 303
247 304 /**
248 305 * Load the template functions after theme is loaded.
@@ -255,10 +312,10 @@
255 312 */
256 313 public function load_template_files() {
257 314 $includes_path = $this->get_path( 'includes' );
258 315
259 - require_once( $includes_path . 'public/charitable-template-functions.php' );
260 - 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';
261 318 }
262 319
263 320 /**
264 321 * Dynamically loads the class attempting to be instantiated elsewhere in the
@@ -281,18 +338,18 @@
281 338 }
282 339
283 340 /* Autogenerated class map. */
284 341 if ( ! isset( $this->classmap ) ) {
285 - $this->classmap = include( 'includes/autoloader/charitable-class-map.php' );
342 + $this->classmap = include 'includes/autoloader/charitable-class-map.php';
286 343
287 - $this->classmap[ 'Charitable_Gateway_Stripe_AM' ] = 'gateways/class-charitable-gateway-stripe-am.php';
288 - $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';
289 346 }
290 347
291 348 $file_path = isset( $this->classmap[ $class_name ] ) ? $this->get_path( 'includes' ) . $this->classmap[ $class_name ] : false;
292 349
293 350 if ( false !== $file_path && file_exists( $file_path ) && is_file( $file_path ) ) {
294 - require_once( $file_path );
351 + require_once $file_path;
295 352 return true;
296 353 }
297 354 return false;
298 355 }
@@ -319,11 +376,11 @@
319 376 $this->registry->register_object( Charitable_Locations::get_instance() );
320 377 $this->registry->register_object( Charitable_Currency::get_instance() );
321 378 $this->registry->register_object( Charitable_Notifications::get_instance() );
322 379
323 - $this->registry->register_object( new Charitable_Privacy );
324 - $this->registry->register_object( new Charitable_Debugging );
325 - $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() );
326 383 }
327 384
328 385 return $this->registry;
329 386 }
@@ -339,10 +396,10 @@
339 396 add_action( 'wpmu_new_blog', array( $this, 'maybe_activate_charitable_on_new_site' ) );
340 397 add_action( 'plugins_loaded', array( $this, 'charitable_install' ), 100 );
341 398 add_action( 'plugins_loaded', array( $this, 'charitable_start' ), 100 );
342 399 add_action( 'plugins_loaded', array( $this, 'endpoints' ), 100 );
343 - add_action( 'plugins_loaded', array( $this, 'donation_fields' ), 100 );
344 - 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 );
345 402 add_action( 'plugins_loaded', array( $this, 'register_donormeta_table' ) );
346 403 add_action( 'plugins_loaded', 'charitable_load_compat_functions' );
347 404 add_action( 'setup_theme', array( 'Charitable_Customizer', 'start' ) );
348 405 add_action( 'after_setup_theme', array( $this, 'load_template_files' ) );
@@ -354,19 +411,17 @@
354 411 * as addons) has a chance to run before the event.
355 412 */
356 413 add_action( 'init', array( $this, 'do_charitable_actions' ), 20 );
357 414
358 - If ( $this->load_core_stripe() ) :
415 + if ( $this->load_core_stripe() ) :
359 416
360 417 /**
361 418 * Set up scripts & stylesheets & enqueue them when appropriate.
362 419 */
363 - // add_action( 'wp_enqueue_scripts', array( $this, 'setup_stripe_scripts' ), 11 );
364 420 add_action( 'charitable_form_after_fields', array( $this, 'maybe_setup_stripe_scripts_in_donation_form' ) );
365 421 add_action( 'charitable_campaign_loop_after', array( $this, 'maybe_setup_stripe_scripts_in_campaign_loop' ) );
366 422
367 423 endif;
368 -
369 424 }
370 425
371 426
372 427 /**
@@ -436,25 +491,70 @@
436 491 if ( ! is_admin() ) {
437 492 return false;
438 493 }
439 494
440 - require_once( $this->get_path( 'admin' ) . 'class-charitable-admin.php' );
441 - 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';
442 497
443 - $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';
444 500
445 - $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';
446 510
447 - // Stripe
511 + // connect for upgrade.wpcharitable.com.
512 + require_once $this->get_path( 'admin' ) . 'class-charitable-admin-connect.php';
448 513
449 - 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';
450 518
451 - 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();
452 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 +
453 530 return $admin;
454 531 }
455 532
456 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 + /**
457 557 * Checks whether we're on the public-facing side and if so, loads the public-facing functionality.
458 558 *
459 559 * @since 1.0.0
460 560 *
@@ -464,10 +564,18 @@
464 564 if ( is_admin() && ! $this->is_ajax() ) {
465 565 return false;
466 566 }
467 567
468 - require_once( $this->get_path( 'public' ) . 'class-charitable-public.php' );
568 + require_once $this->get_path( 'public' ) . 'class-charitable-public.php';
469 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 +
470 578 $public = Charitable_Public::get_instance();
471 579
472 580 $this->registry->register_object( $public );
473 581
@@ -482,25 +590,51 @@
482 590 * @return boolean
483 591 */
484 592 public function maybe_start_qunit() {
485 593 /* Skip out early if ?qunit isn't included in the request. */
486 - if ( ! array_key_exists( 'qunit', $_GET ) ) {
594 + if ( ! array_key_exists( 'qunit', $_GET ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
487 595 return false;
488 596 }
489 597
490 - /* The unit tests have to exist. */
491 - if ( ! file_exists( $this->get_path( 'directory' ) . 'tests/qunit/tests.js' ) ) {
492 - return false;
493 - }
598 + /* The unit tests have to exist. */
599 + if ( ! file_exists( $this->get_path( 'directory' ) . 'tests/qunit/tests.js' ) ) {
600 + return false;
601 + }
494 602
495 - 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 + );
496 624
497 - /* Version: '20170615-15:44' */
498 - wp_register_script( 'qunit-tests', $this->get_path( 'directory', false ) . 'tests/qunit/tests.js', array( 'jquery', 'qunit' ), time(), true );
499 - 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' );
500 628
501 - wp_register_style( 'qunit', 'https://code.jquery.com/qunit/qunit-2.3.3.css', array(), '2.3.3', 'all' );
502 - 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' );
503 637 }
504 638
505 639 /**
506 640 * Checks whether the current request is an AJAX request.
@@ -524,10 +658,10 @@
524 658 if ( ! $this->is_ajax() ) {
525 659 return;
526 660 }
527 661
528 - require_once( $this->get_path( 'includes' ) . 'ajax/charitable-ajax-functions.php' );
529 - 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';
530 664 }
531 665
532 666 /**
533 667 * This method is fired after all plugins are loaded and simply fires the charitable_start hook.
@@ -545,9 +679,11 @@
545 679 /**
546 680 * Fires off an action right after Charitable is installed, allowing other
547 681 * plugins/themes to do something at this point.
548 682 *
549 - * @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.
550 686 *
551 687 * @return void
552 688 */
553 689 public function charitable_install() {
@@ -556,9 +692,9 @@
556 692 if ( ! $install ) {
557 693 return;
558 694 }
559 695
560 - require_once( $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php' );
696 + require_once $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php';
561 697
562 698 Charitable_Install::finish_installing();
563 699
564 700 do_action( 'charitable_install' );
@@ -566,14 +702,25 @@
566 702 delete_transient( 'charitable_install' );
567 703
568 704 // Store the date when the initial activation was performed.
569 705 $type = function_exists( 'charitable_is_pro' ) && charitable_is_pro() ? 'pro' : 'lite';
570 - $activated = get_option( 'charitable_activated', array() );
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 +
571 716 if ( empty( $activated[ $type ] ) ) {
572 717 $activated[ $type ] = time();
573 718 update_option( 'charitable_activated', $activated );
574 719 }
575 720
721 + // Clear notifications.
722 + delete_option( 'charitable_notifications' );
576 723 }
577 724
578 725 /**
579 726 * Returns whether we are currently in the start phase of the plugin.
@@ -582,9 +729,9 @@
582 729 *
583 730 * @return boolean
584 731 */
585 732 public function is_start() {
586 - return current_filter() == 'charitable_start';
733 + return current_filter() == 'charitable_start'; // phpcs:ignore
587 734 }
588 735
589 736 /**
590 737 * Returns whether the plugin has already started.
@@ -593,9 +740,9 @@
593 740 *
594 741 * @return boolean
595 742 */
596 743 public function started() {
597 - return did_action( 'charitable_start' ) || current_filter() == 'charitable_start';
744 + return did_action( 'charitable_start' ) || current_filter() == 'charitable_start'; // phpcs:ignore
598 745 }
599 746
600 747 /**
601 748 * Returns whether the plugin is being activated.
@@ -604,9 +751,9 @@
604 751 *
605 752 * @return boolean
606 753 */
607 754 public function is_activation() {
608 - return current_filter() == 'activate_charitable/charitable.php';
755 + return current_filter() == 'activate_charitable/charitable.php'; // phpcs:ignore
609 756 }
610 757
611 758 /**
612 759 * Returns whether the plugin is being deactivated.
@@ -615,9 +762,9 @@
615 762 *
616 763 * @return boolean
617 764 */
618 765 public function is_deactivation() {
619 - return current_filter() == 'deactivate_charitable/charitable.php';
766 + return current_filter() == 'deactivate_charitable/charitable.php'; // phpcs:ignore
620 767 }
621 768
622 769 /**
623 770 * Returns plugin paths.
@@ -651,8 +798,12 @@
651 798 case 'templates':
652 799 $path = $base . 'templates/';
653 800 break;
654 801
802 + case 'plugin-directory':
803 + $path = WP_PLUGIN_DIR;
804 + break;
805 +
655 806 case 'directory':
656 807 $path = $base;
657 808 break;
658 809
@@ -682,8 +833,26 @@
682 833 return $version;
683 834 }
684 835
685 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 + /**
686 855 * Get the campaign fields registry
687 856 *
688 857 * If the registry has not been set up yet, this will also
689 858 * perform the task of setting it up initially.
@@ -700,9 +869,9 @@
700 869
701 870 /* Register it immediately to avoid endless recursion. */
702 871 $this->registry->register_object( $campaign_fields );
703 872
704 - $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';
705 874
706 875 foreach ( $fields as $key => $args ) {
707 876 $campaign_fields->register_field( new Charitable_Campaign_Field( $key, $args ) );
708 877 }
@@ -729,9 +898,9 @@
729 898
730 899 /* Register it immediately to avoid endless recursion. */
731 900 $this->registry->register_object( $donation_fields );
732 901
733 - $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';
734 903
735 904 foreach ( $fields as $key => $args ) {
736 905 $donation_fields->register_field( new Charitable_Donation_Field( $key, $args ) );
737 906 }
@@ -763,22 +932,22 @@
763 932 * BEFORE the endpoint it builds on. In other words, move from
764 933 * most specific to least specific.
765 934 */
766 935 $endpoints = new Charitable_Endpoints();
767 - $endpoints->register( new Charitable_Campaign_Donation_Endpoint );
768 - $endpoints->register( new Charitable_Campaign_Widget_Endpoint );
769 - $endpoints->register( new Charitable_Campaign_Endpoint );
770 - $endpoints->register( new Charitable_Donation_Cancellation_Endpoint );
771 - $endpoints->register( new Charitable_Donation_Processing_Endpoint );
772 - $endpoints->register( new Charitable_Donation_Receipt_Endpoint );
773 - $endpoints->register( new Charitable_Email_Preview_Endpoint );
774 - $endpoints->register( new Charitable_Email_Verification_Endpoint );
775 - $endpoints->register( new Charitable_Forgot_Password_Endpoint );
776 - $endpoints->register( new Charitable_Reset_Password_Endpoint );
777 - $endpoints->register( new Charitable_Registration_Endpoint );
778 - $endpoints->register( new Charitable_Login_Endpoint );
779 - $endpoints->register( new Charitable_Profile_Endpoint );
780 - $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() );
781 950
782 951 $this->registry->register_object( $endpoints );
783 952 }
784 953
@@ -792,9 +961,9 @@
792 961 *
793 962 * @param string $class The type of class to be retrieved.
794 963 * @return object
795 964 */
796 - public function get_registered_object( $class ) {
965 + public function get_registered_object( $class ) { // phpcs:ignore
797 966 return $this->registry->get( $class );
798 967 }
799 968
800 969 /**
@@ -845,8 +1014,9 @@
845 1014 /**
846 1015 * Filter the array of available Charitable table classes.
847 1016 *
848 1017 * @since 1.0.0
1018 + * @version 1.8.1
849 1019 *
850 1020 * @param array $tables List of tables as a key=>value array.
851 1021 */
852 1022 return apply_filters(
@@ -851,11 +1021,13 @@
851 1021 */
852 1022 return apply_filters(
853 1023 'charitable_db_tables',
854 1024 array(
855 - 'campaign_donations' => 'Charitable_Campaign_Donations_DB',
856 - 'donors' => 'Charitable_Donors_DB',
857 - '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',
858 1030 )
859 1031 );
860 1032 }
861 1033
@@ -886,14 +1058,14 @@
886 1058 * or just the current site. Multisite only. Default is false.
887 1059 * @return void
888 1060 */
889 1061 public function activate( $network_wide = false ) {
890 - require_once( $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php' );
1062 + require_once $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php';
891 1063
892 1064 if ( is_multisite() && $network_wide ) {
893 1065 global $wpdb;
894 1066
895 - 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
896 1068 switch_to_blog( $blog_id );
897 1069 new Charitable_Install( $this->includes_path );
898 1070 restore_current_blog();
899 1071 }
@@ -901,9 +1073,12 @@
901 1073 new Charitable_Install( $this->includes_path );
902 1074 }
903 1075
904 1076 // create or update the install date for future reference.
905 - 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();
906 1081 }
907 1082
908 1083 /**
909 1084 * Runs on plugin deactivation.
@@ -914,9 +1089,9 @@
914 1089 *
915 1090 * @return void
916 1091 */
917 1092 public function deactivate() {
918 - require_once( $this->get_path( 'includes' ) . 'plugin/class-charitable-uninstall.php' );
1093 + require_once $this->get_path( 'includes' ) . 'plugin/class-charitable-uninstall.php';
919 1094 new Charitable_Uninstall();
920 1095 }
921 1096
922 1097 /**
@@ -926,11 +1101,11 @@
926 1101 *
927 1102 * @return void
928 1103 */
929 1104 public function do_charitable_actions() {
930 - if ( isset( $_REQUEST['charitable_action'] ) ) {
1105 + if ( isset( $_REQUEST['charitable_action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
931 1106
932 - $action = $_REQUEST['charitable_action'];
1107 + $action = esc_attr( $_REQUEST['charitable_action'] ); // phpcs:ignore
933 1108
934 1109 /**
935 1110 * Handle Charitable action.
936 1111 *
@@ -1006,9 +1181,9 @@
1006 1181 *
1007 1182 * @param mixed $object Object to be registered.
1008 1183 * @return void
1009 1184 */
1010 - public function register_object( $object ) {
1185 + public function register_object( $object ) { // phpcs:ignore
1011 1186 charitable_get_deprecated()->deprecated_function(
1012 1187 __METHOD__,
1013 1188 '1.5.0',
1014 1189 'charitable()->registry()->register_object()'
@@ -1118,18 +1293,8 @@
1118 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() ) ) {
1119 1294 return false;
1120 1295 }
1121 1296
1122 - // check for stripe addon
1123 -
1124 - // if ( in_array('charitable-stripe/charitable-stripe.php', apply_filters( 'active_plugins', get_option('active_plugins' ) ) ) ) {
1125 - // return false;
1126 - // }
1127 -
1128 - // if ( class_exists( 'Charitable_Stripe' ) ) {
1129 - // return false;
1130 - // }
1131 -
1132 1297 return true;
1133 1298 }
1134 1299
1135 1300 /**
@@ -1140,9 +1305,9 @@
1140 1305 * @return boolean
1141 1306 */
1142 1307 public function is_stripe_connect_addon() {
1143 1308
1144 - 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
1145 1310 return true;
1146 1311 }
1147 1312
1148 1313 return false;
@@ -1152,10 +1317,11 @@
1152 1317 * Action links for the plugins page.
1153 1318 *
1154 1319 * @since 1.7.0
1155 1320 *
1156 - * @param array $actions An array of existing actions.
1157 - * @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.
1158 1324 * @return array An array of action links.
1159 1325 */
1160 1326 public function plugin_action_links( $actions, $plugin_file, $action_links = false ) {
1161 1327
@@ -1162,15 +1328,15 @@
1162 1328 if ( ! charitable_is_pro() ) :
1163 1329
1164 1330 // if this isn't pro, add the upgrade link which is visible on the plugins page until the plugin name.
1165 1331
1166 - $action_links = [
1167 - 'proupgrade' => [
1332 + $action_links = array(
1333 + 'proupgrade' => array(
1168 1334 // Translators: This is an action link users can click to purchase a license for All in One SEO Pro.
1169 1335 'label' => __( 'Upgrade to Pro', 'charitable' ),
1170 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',
1171 - ]
1172 - ];
1337 + ),
1338 + );
1173 1339
1174 1340 endif;
1175 1341
1176 1342 if ( isset( $actions['edit'] ) ) {
@@ -1184,23 +1350,24 @@
1184 1350 * Parse the action links.
1185 1351 *
1186 1352 * @since 1.7.0
1187 1353 *
1188 - * @param array $actions
1189 - * @param string $pluginFile
1190 - * @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.
1191 1358 * @return array
1192 1359 */
1193 - 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' ) {
1194 1361 if ( empty( $this->plugin ) ) {
1195 - $this->plugin = CHARTIABLE_PLUGIN_BASENAME;
1362 + $this->plugin = CHARITABLE_PLUGIN_BASENAME;
1196 1363 }
1197 1364
1198 - if ( $this->plugin === $pluginFile && ! empty( $action_links ) ) {
1365 + if ( $this->plugin === $plugin_file && ! empty( $action_links ) ) {
1199 1366 foreach ( $action_links as $key => $value ) {
1200 - $link = [
1201 - $key => '<a href="' . $value['url'] . '">' . $value['label'] . '</a>'
1202 - ];
1367 + $link = array(
1368 + $key => '<a href="' . $value['url'] . '">' . $value['label'] . '</a>',
1369 + );
1203 1370
1204 1371 $actions = 'after' === $position ? array_merge( $actions, $link ) : array_merge( $link, $actions );
1205 1372 }
1206 1373 }
@@ -1216,11 +1383,11 @@
1216 1383 * @return void
1217 1384 */
1218 1385 public function setup_licensing() {
1219 1386 charitable_get_helper( 'licenses' )->register_licensed_product(
1220 - Charitable::NAME, // . ' Pro',
1221 - Charitable::AUTHOR,
1222 - Charitable::VERSION,
1387 + self::NAME,
1388 + self::AUTHOR,
1389 + self::VERSION,
1223 1390 __FILE__
1224 1391 );
1225 1392 }
1226 1393
@@ -1227,16 +1394,14 @@
1227 1394 /**
1228 1395 * Deativates and/or warnings about any Charitable verison conflicts.
1229 1396 *
1230 1397 * @since 1.7.0
1231 - *
1232 - * @return null
1233 1398 */
1234 1399 public function check_for_version_conflicts() {
1235 1400
1236 - // Check for Charitable Stripe < 1.5.0
1237 - if ( in_array( 'charitable-stripe/charitable-stripe.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) {
1238 - if ( class_exists( 'Charitable_Stripe') && 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
1239 1404 // manually deactivate plugin.
1240 1405 $found = false;
1241 1406 $current = get_option( 'active_plugins', array() );
1242 1407 $key = array_search( 'charitable-stripe/charitable-stripe.php', $current, true );
@@ -1245,21 +1410,78 @@
1245 1410
1246 1411 unset( $current[ $key ] );
1247 1412 update_option( 'active_plugins', $current );
1248 1413
1249 - $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 );
1250 1416
1251 - 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
1252 1418 }
1253 - // charitable_get_notices()->add_error( $message );
1254 - add_option( 'chartiable_version_warning', $message );
1419 + add_option( 'charitable_version_warning', $message );
1255 1420 } else {
1256 - delete_option( 'chartiable_version_warning' );
1421 + delete_option( 'charitable_version_warning' );
1257 1422 }
1258 1423 }
1259 1424 }
1260 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();
1261 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 + }
1262 1484 }
1263 1485
1264 1486 $charitable = new Charitable();
1265 1487