PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 1.7.0.9 All 211 releases
← All changes | charitable.php +379 -160 1.7.0.3 → 1.8.13 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.3
5 + * Description: The best WordPress donation plugin. Fundraising with recurring donations, and powerful features to help you raise more money online.
6 + * Version: 1.8.13
7 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.3
9 + * Requires at least: 5.9
10 + * Requires PHP: 7.4
11 + * Stable tag: 1.8.13
12 + * License: GPLv2 or later
13 + * License URI: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
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.3';
43 + const VERSION = '1.8.13';
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,78 @@
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 . 'abilities/charitable-abilities-hooks.php'; // 1.8.13 - MUST be outside the admin gate: abilities register on REST and WP-CLI requests, neither of which is is_admin().
248 + require_once $includes_path . 'upgrades/charitable-upgrade-hooks.php';
249 + require_once $includes_path . 'users/charitable-user-functions.php';
250 + require_once $includes_path . 'user-management/charitable-user-management-hooks.php';
251 + require_once $includes_path . 'utilities/charitable-utility-functions.php';
252 + require_once $includes_path . 'elementor/charitable-elementor-hooks.php';
208 253
209 - If ( $this->load_core_stripe() ) :
254 + // Load security hooks on both admin and frontend (CAPTCHA needs to work on frontend).
255 + require_once $this->get_path( 'includes', true ) . 'admin/security/charitable-security-hooks.php';
210 256
211 - /* Load Stripe SDK */
212 - require_once( $this->get_path( 'directory' ) . 'vendor/autoload.php' );
257 + /* Load vendor/autoload.php - our modified platform_check.php and autoload_real.php will handle conditional loading */
258 + require_once $this->get_path( 'directory' ) . 'vendor/autoload.php';
213 259
260 + /* Square Compatibility Wrapper - always load this regardless of PHP version */
261 + require_once $includes_path . 'square/class-charitable-square-compatibility.php';
262 +
263 + /* Initialize Square conditionally - will check for compatibility internally */
264 + Charitable_Square_Compatibility::init();
265 +
266 + if ( $this->load_core_stripe() ) :
267 +
214 268 /* Interfaces */
215 - require_once( $includes_path . 'stripe/interfaces/interface-charitable-stripe-gateway-processor.php' );
269 + require_once $includes_path . 'stripe/interfaces/interface-charitable-stripe-gateway-processor.php';
216 270
217 271 /* Abstract classes */
218 - require_once( $includes_path . 'stripe/abstracts/class-charitable-stripe-gateway-processor.php' );
272 + require_once $includes_path . 'stripe/abstracts/class-charitable-stripe-gateway-processor.php';
219 273
220 274 /* 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' );
275 + require_once $includes_path . 'stripe/donations/class-charitable-stripe-donation-log.php';
276 + require_once $includes_path . 'stripe/donations/class-charitable-stripe-recurring-donation-log.php';
277 + require_once $includes_path . 'stripe/i18n/class-charitable-stripe-i18n.php';
278 + require_once $includes_path . 'stripe/gateway/class-charitable-stripe-connected-customer.php';
279 + require_once $includes_path . 'stripe/gateway/class-charitable-stripe-customer.php';
280 + require_once $includes_path . 'stripe/gateway/class-charitable-stripe-gateway-processor-payment-intents.php';
281 + require_once $includes_path . 'stripe/gateway/class-charitable-stripe-payment-intent.php';
282 + require_once $includes_path . 'stripe/gateway/class-charitable-stripe-plan.php';
283 + require_once $includes_path . 'stripe/gateway/class-charitable-stripe-product.php';
230 284
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' );
285 + require_once $includes_path . 'stripe/connect/charitable-stripe-connect-functions.php';
286 + require_once $includes_path . 'stripe/gateway/class-charitable-stripe-gateway-processor-checkout.php';
233 287
234 288 /* 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' );
289 + require_once $includes_path . 'stripe/charitable-stripe-core-functions.php';
290 + require_once $includes_path . 'stripe/donations/charitable-stripe-donation-functions.php';
291 + require_once $includes_path . 'stripe/gateway/charitable-stripe-gateway-hooks.php';
238 292
239 293 /* 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' );
294 + require_once $includes_path . 'stripe/gateway/class-charitable-stripe-webhook-api.php';
295 + require_once $includes_path . 'stripe/gateway/class-charitable-stripe-webhook-processor.php';
242 296
243 297 endif;
244 298
299 + /* Load PayPal Commerce Gateway */
300 + require_once $includes_path . 'gateways/paypal-commerce/charitable-paypal-commerce.php';
245 301 }
246 302
247 303 /**
248 304 * Load the template functions after theme is loaded.
@@ -255,10 +311,10 @@
255 311 */
256 312 public function load_template_files() {
257 313 $includes_path = $this->get_path( 'includes' );
258 314
259 - require_once( $includes_path . 'public/charitable-template-functions.php' );
260 - require_once( $includes_path . 'public/charitable-template-hooks.php' );
315 + require_once $includes_path . 'public/charitable-template-functions.php';
316 + require_once $includes_path . 'public/charitable-template-hooks.php';
261 317 }
262 318
263 319 /**
264 320 * Dynamically loads the class attempting to be instantiated elsewhere in the
@@ -281,21 +337,19 @@
281 337 }
282 338
283 339 /* Autogenerated class map. */
284 340 if ( ! isset( $this->classmap ) ) {
285 - $this->classmap = include( 'includes/autoloader/charitable-class-map.php' );
341 + $this->classmap = include 'includes/autoloader/charitable-class-map.php';
286 342
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';
343 + $this->classmap['Charitable_Gateway_Stripe_AM'] = 'gateways/class-charitable-gateway-stripe-am.php';
344 + $this->classmap['Charitable_Gateway_Stripe'] = 'gateways/class-charitable-gateway-stripe-am.php';
289 345 }
290 346
291 347 $file_path = isset( $this->classmap[ $class_name ] ) ? $this->get_path( 'includes' ) . $this->classmap[ $class_name ] : false;
292 348
293 349 if ( false !== $file_path && file_exists( $file_path ) && is_file( $file_path ) ) {
294 - require_once( $file_path );
350 + require_once $file_path;
295 351 return true;
296 - // } else {
297 - // echo '----'; print_r( $class_name );
298 352 }
299 353 return false;
300 354 }
301 355
@@ -321,11 +375,11 @@
321 375 $this->registry->register_object( Charitable_Locations::get_instance() );
322 376 $this->registry->register_object( Charitable_Currency::get_instance() );
323 377 $this->registry->register_object( Charitable_Notifications::get_instance() );
324 378
325 - $this->registry->register_object( new Charitable_Privacy );
326 - $this->registry->register_object( new Charitable_Debugging );
327 - $this->registry->register_object( new Charitable_Locale );
379 + $this->registry->register_object( new Charitable_Privacy() );
380 + $this->registry->register_object( new Charitable_Debugging() );
381 + $this->registry->register_object( new Charitable_Locale() );
328 382 }
329 383
330 384 return $this->registry;
331 385 }
@@ -341,10 +395,10 @@
341 395 add_action( 'wpmu_new_blog', array( $this, 'maybe_activate_charitable_on_new_site' ) );
342 396 add_action( 'plugins_loaded', array( $this, 'charitable_install' ), 100 );
343 397 add_action( 'plugins_loaded', array( $this, 'charitable_start' ), 100 );
344 398 add_action( 'plugins_loaded', array( $this, 'endpoints' ), 100 );
345 - add_action( 'plugins_loaded', array( $this, 'donation_fields' ), 100 );
346 - add_action( 'plugins_loaded', array( $this, 'campaign_fields' ), 100 );
399 + add_action( 'init', array( $this, 'donation_fields' ), 100 );
400 + add_action( 'init', array( $this, 'campaign_fields' ), 100 );
347 401 add_action( 'plugins_loaded', array( $this, 'register_donormeta_table' ) );
348 402 add_action( 'plugins_loaded', 'charitable_load_compat_functions' );
349 403 add_action( 'setup_theme', array( 'Charitable_Customizer', 'start' ) );
350 404 add_action( 'after_setup_theme', array( $this, 'load_template_files' ) );
@@ -356,19 +410,17 @@
356 410 * as addons) has a chance to run before the event.
357 411 */
358 412 add_action( 'init', array( $this, 'do_charitable_actions' ), 20 );
359 413
360 - If ( $this->load_core_stripe() ) :
414 + if ( $this->load_core_stripe() ) :
361 415
362 416 /**
363 417 * Set up scripts & stylesheets & enqueue them when appropriate.
364 418 */
365 - // add_action( 'wp_enqueue_scripts', array( $this, 'setup_stripe_scripts' ), 11 );
366 419 add_action( 'charitable_form_after_fields', array( $this, 'maybe_setup_stripe_scripts_in_donation_form' ) );
367 420 add_action( 'charitable_campaign_loop_after', array( $this, 'maybe_setup_stripe_scripts_in_campaign_loop' ) );
368 421
369 422 endif;
370 -
371 423 }
372 424
373 425
374 426 /**
@@ -438,25 +490,70 @@
438 490 if ( ! is_admin() ) {
439 491 return false;
440 492 }
441 493
442 - require_once( $this->get_path( 'admin' ) . 'class-charitable-admin.php' );
443 - require_once( $this->get_path( 'admin' ) . 'charitable-admin-hooks.php' );
494 + // load blocks/Gutenberg related.
495 + require_once $this->get_path( 'admin' ) . 'class-charitable-admin-blocks.php';
444 496
445 - $admin = Charitable_Admin::get_instance();
497 + require_once $this->get_path( 'admin' ) . 'class-charitable-admin.php';
498 + require_once $this->get_path( 'admin' ) . 'charitable-admin-hooks.php';
446 499
447 - $this->registry->register_object( $admin );
500 + // load campaign builder.
501 + require_once $this->get_path( 'admin' ) . 'campaign-builder/util.php';
502 + require_once $this->get_path( 'admin' ) . 'campaign-builder/functions.php';
503 + require_once $this->get_path( 'admin' ) . 'campaign-builder/ajax-actions.php';
504 + require_once $this->get_path( 'admin' ) . 'campaign-builder/functions-legacy.php';
505 + require_once $this->get_path( 'admin' ) . 'campaign-builder/debug.php';
506 + require_once $this->get_path( 'admin' ) . 'campaign-builder/access.php';
507 + require_once $this->get_path( 'admin' ) . 'campaign-builder/class-builder-fields.php';
508 + require_once $this->get_path( 'admin' ) . 'campaign-builder/class-builder-form-fields.php';
448 509
449 - // Stripe
510 + // connect for upgrade.wpcharitable.com.
511 + require_once $this->get_path( 'admin' ) . 'class-charitable-admin-connect.php';
450 512
451 - require_once( $this->get_path( 'includes' ) . 'stripe/admin/class-charitable-stripe-admin.php' );
513 + // misc.
514 + require_once $this->get_path( 'admin' ) . 'class-charitable-admin-pointers.php';
515 + require_once $this->get_path( 'admin' ) . 'plugins/charitable-admin-plugin-hooks.php';
516 + require_once $this->get_path( 'admin' ) . 'class-charitable-admin-getting-started.php';
452 517
453 - new Charitable_Stripe_Admin;
518 + // Site Analysis / Recommendations tab.
519 + require_once $this->get_path( 'admin' ) . 'site-analysis/class-charitable-site-analysis.php';
520 + Charitable_Site_Analysis::get_instance();
454 521
522 + $admin = Charitable_Admin::get_instance();
523 + $this->registry->register_object( $admin );
524 +
525 + // Stripe.
526 + require_once $this->get_path( 'includes' ) . 'stripe/admin/class-charitable-stripe-admin.php';
527 + new Charitable_Stripe_Admin();
528 +
455 529 return $admin;
456 530 }
457 531
458 532 /**
533 + * Run the upgrade-server connect callback on init before any redirect (e.g. to login) can run.
534 + * Only registered when CHARITABLE_CONNECT_CALLBACK is defined (request came from connect-callback.php).
535 + *
536 + * @since 1.8.9.6
537 + */
538 + public function run_connect_callback() {
539 + ini_set( 'display_errors', '0' );
540 + require_once ABSPATH . 'wp-admin/includes/plugin.php';
541 + require_once ABSPATH . 'wp-admin/includes/file.php';
542 + require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
543 + $connect_file = $this->get_path( 'admin' ) . 'class-charitable-admin-connect.php';
544 + if ( file_exists( $connect_file ) ) {
545 + require_once $connect_file;
546 + }
547 + if ( ! class_exists( 'Charitable_Admin_Connect' ) ) {
548 + header( 'Content-Type: application/json' );
549 + wp_send_json( array( 'success' => false, 'data' => array( 'message' => 'Charitable not loaded.' ) ) );
550 + }
551 + Charitable_Admin_Connect::get_instance()->process();
552 + exit;
553 + }
554 +
555 + /**
459 556 * Checks whether we're on the public-facing side and if so, loads the public-facing functionality.
460 557 *
461 558 * @since 1.0.0
462 559 *
@@ -466,10 +563,18 @@
466 563 if ( is_admin() && ! $this->is_ajax() ) {
467 564 return false;
468 565 }
469 566
470 - require_once( $this->get_path( 'public' ) . 'class-charitable-public.php' );
567 + require_once $this->get_path( 'public' ) . 'class-charitable-public.php';
471 568
569 + // Campaign Builder related (v1.8.0).
570 + require_once $this->get_path( 'admin' ) . 'campaign-builder/class-campaign-builder-preview.php';
571 + require_once $this->get_path( 'admin' ) . 'campaign-builder/class-builder-fields.php';
572 + require_once $this->get_path( 'admin' ) . 'campaign-builder/class-builder-form-fields.php';
573 +
574 + // load blocks/Gutenberg related.
575 + require_once $this->get_path( 'admin' ) . 'class-charitable-admin-blocks.php';
576 +
472 577 $public = Charitable_Public::get_instance();
473 578
474 579 $this->registry->register_object( $public );
475 580
@@ -484,25 +589,51 @@
484 589 * @return boolean
485 590 */
486 591 public function maybe_start_qunit() {
487 592 /* Skip out early if ?qunit isn't included in the request. */
488 - if ( ! array_key_exists( 'qunit', $_GET ) ) {
593 + if ( ! array_key_exists( 'qunit', $_GET ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
489 594 return false;
490 595 }
491 596
492 - /* The unit tests have to exist. */
493 - if ( ! file_exists( $this->get_path( 'directory' ) . 'tests/qunit/tests.js' ) ) {
494 - return false;
495 - }
597 + /* The unit tests have to exist. */
598 + if ( ! file_exists( $this->get_path( 'directory' ) . 'tests/qunit/tests.js' ) ) {
599 + return false;
600 + }
496 601
497 - wp_register_script( 'qunit', 'https://code.jquery.com/qunit/qunit-2.3.3.js', array(), '2.3.3', true );
602 + /**
603 + * Register QUnit testing library.
604 + *
605 + * Note: These files must be bundled locally for WordPress.org compliance.
606 + * Download from:
607 + * - JS: https://code.jquery.com/qunit/qunit-2.3.3.js
608 + * - CSS: https://code.jquery.com/qunit/qunit-2.3.3.css
609 + * Save to:
610 + * - assets/js/libraries/qunit-2.3.3.js
611 + * - assets/css/libraries/qunit-2.3.3.css
612 + *
613 + * @since 1.0.0
614 + * @version 1.8.9.1
615 + */
616 + wp_register_script(
617 + 'qunit',
618 + $this->get_path( 'directory', false ) . 'assets/js/libraries/qunit-2.3.3.js',
619 + array(),
620 + '2.3.3',
621 + true
622 + );
498 623
499 - /* Version: '20170615-15:44' */
500 - wp_register_script( 'qunit-tests', $this->get_path( 'directory', false ) . 'tests/qunit/tests.js', array( 'jquery', 'qunit' ), time(), true );
501 - wp_enqueue_script( 'qunit-tests' );
624 + /* Version: '20170615-15:44' */
625 + wp_register_script( 'qunit-tests', $this->get_path( 'directory', false ) . 'tests/qunit/tests.js', array( 'jquery', 'qunit' ), time(), true );
626 + wp_enqueue_script( 'qunit-tests' );
502 627
503 - wp_register_style( 'qunit', 'https://code.jquery.com/qunit/qunit-2.3.3.css', array(), '2.3.3', 'all' );
504 - wp_enqueue_style( 'qunit' );
628 + wp_register_style(
629 + 'qunit',
630 + $this->get_path( 'directory', false ) . 'assets/css/libraries/qunit-2.3.3.css',
631 + array(),
632 + '2.3.3',
633 + 'all'
634 + );
635 + wp_enqueue_style( 'qunit' );
505 636 }
506 637
507 638 /**
508 639 * Checks whether the current request is an AJAX request.
@@ -526,10 +657,10 @@
526 657 if ( ! $this->is_ajax() ) {
527 658 return;
528 659 }
529 660
530 - require_once( $this->get_path( 'includes' ) . 'ajax/charitable-ajax-functions.php' );
531 - require_once( $this->get_path( 'includes' ) . 'ajax/charitable-ajax-hooks.php' );
661 + require_once $this->get_path( 'includes' ) . 'ajax/charitable-ajax-functions.php';
662 + require_once $this->get_path( 'includes' ) . 'ajax/charitable-ajax-hooks.php';
532 663 }
533 664
534 665 /**
535 666 * This method is fired after all plugins are loaded and simply fires the charitable_start hook.
@@ -547,9 +678,11 @@
547 678 /**
548 679 * Fires off an action right after Charitable is installed, allowing other
549 680 * plugins/themes to do something at this point.
550 681 *
551 - * @since 1.0.1
682 + * @since 1.0.1
683 + * @version 1.8.1.12 - Added transient for activation redirect.
684 + * @version 1.8.3 - Added call to add version and delete notifications.
552 685 *
553 686 * @return void
554 687 */
555 688 public function charitable_install() {
@@ -558,9 +691,9 @@
558 691 if ( ! $install ) {
559 692 return;
560 693 }
561 694
562 - require_once( $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php' );
695 + require_once $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php';
563 696
564 697 Charitable_Install::finish_installing();
565 698
566 699 do_action( 'charitable_install' );
@@ -568,14 +701,25 @@
568 701 delete_transient( 'charitable_install' );
569 702
570 703 // Store the date when the initial activation was performed.
571 704 $type = function_exists( 'charitable_is_pro' ) && charitable_is_pro() ? 'pro' : 'lite';
572 - $activated = get_option( 'charitable_activated', array() );
705 + $activated = (array) get_option( 'charitable_activated', array() );
706 +
707 + // If no activation date is set, assume it's a new install and set the activation redirect transient.
708 + if ( false === $activated || empty( $activated ) ) {
709 + // Add transient to trigger redirect to the Welcome / Getting Started screen.
710 + set_transient( 'charitable_activation_redirect', true, 30 );
711 + }
712 +
713 + $this->add_version_to_upgraded_from();
714 +
573 715 if ( empty( $activated[ $type ] ) ) {
574 716 $activated[ $type ] = time();
575 717 update_option( 'charitable_activated', $activated );
576 718 }
577 719
720 + // Clear notifications.
721 + delete_option( 'charitable_notifications' );
578 722 }
579 723
580 724 /**
581 725 * Returns whether we are currently in the start phase of the plugin.
@@ -584,9 +728,9 @@
584 728 *
585 729 * @return boolean
586 730 */
587 731 public function is_start() {
588 - return current_filter() == 'charitable_start';
732 + return current_filter() == 'charitable_start'; // phpcs:ignore
589 733 }
590 734
591 735 /**
592 736 * Returns whether the plugin has already started.
@@ -595,9 +739,9 @@
595 739 *
596 740 * @return boolean
597 741 */
598 742 public function started() {
599 - return did_action( 'charitable_start' ) || current_filter() == 'charitable_start';
743 + return did_action( 'charitable_start' ) || current_filter() == 'charitable_start'; // phpcs:ignore
600 744 }
601 745
602 746 /**
603 747 * Returns whether the plugin is being activated.
@@ -606,9 +750,9 @@
606 750 *
607 751 * @return boolean
608 752 */
609 753 public function is_activation() {
610 - return current_filter() == 'activate_charitable/charitable.php';
754 + return current_filter() == 'activate_charitable/charitable.php'; // phpcs:ignore
611 755 }
612 756
613 757 /**
614 758 * Returns whether the plugin is being deactivated.
@@ -617,9 +761,9 @@
617 761 *
618 762 * @return boolean
619 763 */
620 764 public function is_deactivation() {
621 - return current_filter() == 'deactivate_charitable/charitable.php';
765 + return current_filter() == 'deactivate_charitable/charitable.php'; // phpcs:ignore
622 766 }
623 767
624 768 /**
625 769 * Returns plugin paths.
@@ -653,8 +797,12 @@
653 797 case 'templates':
654 798 $path = $base . 'templates/';
655 799 break;
656 800
801 + case 'plugin-directory':
802 + $path = WP_PLUGIN_DIR;
803 + break;
804 +
657 805 case 'directory':
658 806 $path = $base;
659 807 break;
660 808
@@ -684,8 +832,26 @@
684 832 return $version;
685 833 }
686 834
687 835 /**
836 + * Adds previous versions to the upgraded_from option.
837 + *
838 + * @since 1.8.3
839 + */
840 + public function add_version_to_upgraded_from() {
841 +
842 + $upgraded_from = get_option( 'charitable_version_upgraded_from', array() );
843 +
844 + if ( ! is_array( $upgraded_from ) ) {
845 + $upgraded_from = array();
846 + }
847 +
848 + $upgraded_from[] = $this->get_version();
849 +
850 + update_option( 'charitable_version_upgraded_from', $upgraded_from, false );
851 + }
852 +
853 + /**
688 854 * Get the campaign fields registry
689 855 *
690 856 * If the registry has not been set up yet, this will also
691 857 * perform the task of setting it up initially.
@@ -702,9 +868,9 @@
702 868
703 869 /* Register it immediately to avoid endless recursion. */
704 870 $this->registry->register_object( $campaign_fields );
705 871
706 - $fields = include( $this->get_path( 'includes' ) . 'fields/default-fields/campaign-fields.php' );
872 + $fields = include $this->get_path( 'includes' ) . 'fields/default-fields/campaign-fields.php';
707 873
708 874 foreach ( $fields as $key => $args ) {
709 875 $campaign_fields->register_field( new Charitable_Campaign_Field( $key, $args ) );
710 876 }
@@ -731,9 +897,9 @@
731 897
732 898 /* Register it immediately to avoid endless recursion. */
733 899 $this->registry->register_object( $donation_fields );
734 900
735 - $fields = include( $this->get_path( 'includes' ) . 'fields/default-fields/donation-fields.php' );
901 + $fields = include $this->get_path( 'includes' ) . 'fields/default-fields/donation-fields.php';
736 902
737 903 foreach ( $fields as $key => $args ) {
738 904 $donation_fields->register_field( new Charitable_Donation_Field( $key, $args ) );
739 905 }
@@ -765,22 +931,22 @@
765 931 * BEFORE the endpoint it builds on. In other words, move from
766 932 * most specific to least specific.
767 933 */
768 934 $endpoints = new Charitable_Endpoints();
769 - $endpoints->register( new Charitable_Campaign_Donation_Endpoint );
770 - $endpoints->register( new Charitable_Campaign_Widget_Endpoint );
771 - $endpoints->register( new Charitable_Campaign_Endpoint );
772 - $endpoints->register( new Charitable_Donation_Cancellation_Endpoint );
773 - $endpoints->register( new Charitable_Donation_Processing_Endpoint );
774 - $endpoints->register( new Charitable_Donation_Receipt_Endpoint );
775 - $endpoints->register( new Charitable_Email_Preview_Endpoint );
776 - $endpoints->register( new Charitable_Email_Verification_Endpoint );
777 - $endpoints->register( new Charitable_Forgot_Password_Endpoint );
778 - $endpoints->register( new Charitable_Reset_Password_Endpoint );
779 - $endpoints->register( new Charitable_Registration_Endpoint );
780 - $endpoints->register( new Charitable_Login_Endpoint );
781 - $endpoints->register( new Charitable_Profile_Endpoint );
782 - $endpoints->register( new Charitable_Webhook_Listener_Endpoint );
935 + $endpoints->register( new Charitable_Campaign_Donation_Endpoint() );
936 + $endpoints->register( new Charitable_Campaign_Widget_Endpoint() );
937 + $endpoints->register( new Charitable_Campaign_Endpoint() );
938 + $endpoints->register( new Charitable_Donation_Cancellation_Endpoint() );
939 + $endpoints->register( new Charitable_Donation_Processing_Endpoint() );
940 + $endpoints->register( new Charitable_Donation_Receipt_Endpoint() );
941 + $endpoints->register( new Charitable_Email_Preview_Endpoint() );
942 + $endpoints->register( new Charitable_Email_Verification_Endpoint() );
943 + $endpoints->register( new Charitable_Forgot_Password_Endpoint() );
944 + $endpoints->register( new Charitable_Reset_Password_Endpoint() );
945 + $endpoints->register( new Charitable_Registration_Endpoint() );
946 + $endpoints->register( new Charitable_Login_Endpoint() );
947 + $endpoints->register( new Charitable_Profile_Endpoint() );
948 + $endpoints->register( new Charitable_Webhook_Listener_Endpoint() );
783 949
784 950 $this->registry->register_object( $endpoints );
785 951 }
786 952
@@ -794,9 +960,9 @@
794 960 *
795 961 * @param string $class The type of class to be retrieved.
796 962 * @return object
797 963 */
798 - public function get_registered_object( $class ) {
964 + public function get_registered_object( $class ) { // phpcs:ignore
799 965 return $this->registry->get( $class );
800 966 }
801 967
802 968 /**
@@ -847,8 +1013,9 @@
847 1013 /**
848 1014 * Filter the array of available Charitable table classes.
849 1015 *
850 1016 * @since 1.0.0
1017 + * @version 1.8.1
851 1018 *
852 1019 * @param array $tables List of tables as a key=>value array.
853 1020 */
854 1021 return apply_filters(
@@ -853,11 +1020,13 @@
853 1020 */
854 1021 return apply_filters(
855 1022 'charitable_db_tables',
856 1023 array(
857 - 'campaign_donations' => 'Charitable_Campaign_Donations_DB',
858 - 'donors' => 'Charitable_Donors_DB',
859 - 'donormeta' => 'Charitable_Donormeta_DB',
1024 + 'campaign_donations' => 'Charitable_Campaign_Donations_DB',
1025 + 'donors' => 'Charitable_Donors_DB',
1026 + 'donormeta' => 'Charitable_Donormeta_DB',
1027 + 'donation_activities' => 'Charitable_Donation_Activities_DB',
1028 + 'campaign_activities' => 'Charitable_Campaign_Activities_DB',
860 1029 )
861 1030 );
862 1031 }
863 1032
@@ -888,14 +1057,14 @@
888 1057 * or just the current site. Multisite only. Default is false.
889 1058 * @return void
890 1059 */
891 1060 public function activate( $network_wide = false ) {
892 - require_once( $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php' );
1061 + require_once $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php';
893 1062
894 1063 if ( is_multisite() && $network_wide ) {
895 1064 global $wpdb;
896 1065
897 - foreach ( $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" ) as $blog_id ) {
1066 + foreach ( $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" ) as $blog_id ) { // phpcs:ignore
898 1067 switch_to_blog( $blog_id );
899 1068 new Charitable_Install( $this->includes_path );
900 1069 restore_current_blog();
901 1070 }
@@ -903,9 +1072,12 @@
903 1072 new Charitable_Install( $this->includes_path );
904 1073 }
905 1074
906 1075 // create or update the install date for future reference.
907 - update_option( 'wpcharitable_activated_datetime', current_time( 'timestamp' ) );
1076 + update_option( 'wpcharitable_activated_datetime', current_time( 'timestamp' ) ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
1077 +
1078 + // Set initial timestamps for rotating menu items if they're already installed
1079 + $this->set_initial_rotating_menu_timestamps();
908 1080 }
909 1081
910 1082 /**
911 1083 * Runs on plugin deactivation.
@@ -916,9 +1088,9 @@
916 1088 *
917 1089 * @return void
918 1090 */
919 1091 public function deactivate() {
920 - require_once( $this->get_path( 'includes' ) . 'plugin/class-charitable-uninstall.php' );
1092 + require_once $this->get_path( 'includes' ) . 'plugin/class-charitable-uninstall.php';
921 1093 new Charitable_Uninstall();
922 1094 }
923 1095
924 1096 /**
@@ -928,11 +1100,11 @@
928 1100 *
929 1101 * @return void
930 1102 */
931 1103 public function do_charitable_actions() {
932 - if ( isset( $_REQUEST['charitable_action'] ) ) {
1104 + if ( isset( $_REQUEST['charitable_action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
933 1105
934 - $action = $_REQUEST['charitable_action'];
1106 + $action = esc_attr( $_REQUEST['charitable_action'] ); // phpcs:ignore
935 1107
936 1108 /**
937 1109 * Handle Charitable action.
938 1110 *
@@ -1008,9 +1180,9 @@
1008 1180 *
1009 1181 * @param mixed $object Object to be registered.
1010 1182 * @return void
1011 1183 */
1012 - public function register_object( $object ) {
1184 + public function register_object( $object ) { // phpcs:ignore
1013 1185 charitable_get_deprecated()->deprecated_function(
1014 1186 __METHOD__,
1015 1187 '1.5.0',
1016 1188 'charitable()->registry()->register_object()'
@@ -1120,18 +1292,8 @@
1120 1292 if ( class_exists( 'Charitable_Gateways' ) && class_exists( 'Charitable_Gateway_Stripe_AM' ) && ! Charitable_Gateways::get_instance()->is_active_gateway( Charitable_Gateway_Stripe_AM::get_gateway_id() ) ) {
1121 1293 return false;
1122 1294 }
1123 1295
1124 - // check for stripe addon
1125 -
1126 - // if ( in_array('charitable-stripe/charitable-stripe.php', apply_filters( 'active_plugins', get_option('active_plugins' ) ) ) ) {
1127 - // return false;
1128 - // }
1129 -
1130 - // if ( class_exists( 'Charitable_Stripe' ) ) {
1131 - // return false;
1132 - // }
1133 -
1134 1296 return true;
1135 1297 }
1136 1298
1137 1299 /**
@@ -1142,9 +1304,9 @@
1142 1304 * @return boolean
1143 1305 */
1144 1306 public function is_stripe_connect_addon() {
1145 1307
1146 - if ( in_array('charitable-stripe/charitable-stripe.php', apply_filters( 'active_plugins', get_option('active_plugins' ) ) ) ) {
1308 + if ( in_array( 'charitable-stripe/charitable-stripe.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) { // phpcs:ignore
1147 1309 return true;
1148 1310 }
1149 1311
1150 1312 return false;
@@ -1154,10 +1316,11 @@
1154 1316 * Action links for the plugins page.
1155 1317 *
1156 1318 * @since 1.7.0
1157 1319 *
1158 - * @param array $actions An array of existing actions.
1159 - * @param string $plugin_file The plugin file we are modifying.
1320 + * @param array $actions An array of existing actions.
1321 + * @param string $plugin_file The plugin file we are modifying.
1322 + * @param boolean $action_links An array of action links to add.
1160 1323 * @return array An array of action links.
1161 1324 */
1162 1325 public function plugin_action_links( $actions, $plugin_file, $action_links = false ) {
1163 1326
@@ -1164,15 +1327,15 @@
1164 1327 if ( ! charitable_is_pro() ) :
1165 1328
1166 1329 // if this isn't pro, add the upgrade link which is visible on the plugins page until the plugin name.
1167 1330
1168 - $action_links = [
1169 - 'proupgrade' => [
1331 + $action_links = array(
1332 + 'proupgrade' => array(
1170 1333 // Translators: This is an action link users can click to purchase a license for All in One SEO Pro.
1171 1334 'label' => __( 'Upgrade to Pro', 'charitable' ),
1172 1335 'url' => 'https://wpcharitable.com/lite-vs-pro/?utm_source=WordPress&utm_campaign=WP+Charitable&utm_medium=Action+Link+Plugins+Page&utm_content=Upgrade+to+Pro',
1173 - ]
1174 - ];
1336 + ),
1337 + );
1175 1338
1176 1339 endif;
1177 1340
1178 1341 if ( isset( $actions['edit'] ) ) {
@@ -1186,23 +1349,24 @@
1186 1349 * Parse the action links.
1187 1350 *
1188 1351 * @since 1.7.0
1189 1352 *
1190 - * @param array $actions
1191 - * @param string $pluginFile
1192 - * @param
1353 + * @param array $actions An array of existing actions.
1354 + * @param string $plugin_file The plugin file we are modifying.
1355 + * @param array $action_links An array of action links to add.
1356 + * @param string $position The position to add the action links.
1193 1357 * @return array
1194 1358 */
1195 - protected function parse_action_links( $actions, $pluginFile, $action_links = [], $position = 'after' ) {
1359 + protected function parse_action_links( $actions, $plugin_file, $action_links = array(), $position = 'after' ) {
1196 1360 if ( empty( $this->plugin ) ) {
1197 - $this->plugin = CHARTIABLE_PLUGIN_BASENAME;
1361 + $this->plugin = CHARITABLE_PLUGIN_BASENAME;
1198 1362 }
1199 1363
1200 - if ( $this->plugin === $pluginFile && ! empty( $action_links ) ) {
1364 + if ( $this->plugin === $plugin_file && ! empty( $action_links ) ) {
1201 1365 foreach ( $action_links as $key => $value ) {
1202 - $link = [
1203 - $key => '<a href="' . $value['url'] . '">' . $value['label'] . '</a>'
1204 - ];
1366 + $link = array(
1367 + $key => '<a href="' . $value['url'] . '">' . $value['label'] . '</a>',
1368 + );
1205 1369
1206 1370 $actions = 'after' === $position ? array_merge( $actions, $link ) : array_merge( $link, $actions );
1207 1371 }
1208 1372 }
@@ -1218,11 +1382,11 @@
1218 1382 * @return void
1219 1383 */
1220 1384 public function setup_licensing() {
1221 1385 charitable_get_helper( 'licenses' )->register_licensed_product(
1222 - Charitable::NAME . ' Pro',
1223 - Charitable::AUTHOR,
1224 - Charitable::VERSION,
1386 + self::NAME,
1387 + self::AUTHOR,
1388 + self::VERSION,
1225 1389 __FILE__
1226 1390 );
1227 1391 }
1228 1392
@@ -1229,16 +1393,14 @@
1229 1393 /**
1230 1394 * Deativates and/or warnings about any Charitable verison conflicts.
1231 1395 *
1232 1396 * @since 1.7.0
1233 - *
1234 - * @return null
1235 1397 */
1236 1398 public function check_for_version_conflicts() {
1237 1399
1238 - // Check for Charitable Stripe < 1.5.0
1239 - if ( in_array( 'charitable-stripe/charitable-stripe.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) {
1240 - if ( class_exists( 'Charitable_Stripe') && version_compare( Charitable_Stripe::VERSION, '1.5.0', '<' ) ) {
1400 + // Check for Charitable Stripe < 1.5.0.
1401 + if ( in_array( 'charitable-stripe/charitable-stripe.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) { // phpcs:ignore
1402 + if ( class_exists( 'Charitable_Stripe' ) && version_compare( Charitable_Stripe::VERSION, '1.5.0', '<' ) ) { // phpcs:ignore
1241 1403 // manually deactivate plugin.
1242 1404 $found = false;
1243 1405 $current = get_option( 'active_plugins', array() );
1244 1406 $key = array_search( 'charitable-stripe/charitable-stripe.php', $current, true );
@@ -1247,21 +1409,78 @@
1247 1409
1248 1410 unset( $current[ $key ] );
1249 1411 update_option( 'active_plugins', $current );
1250 1412
1251 - $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' );
1413 + // translators: %s is the version number.
1414 + $message = sprintf( __( '<p><strong style="color:red;">NOTICE:</strong> <strong>Charitable</strong> detected an out-of-date and incompatible version of <strong>Charitable Stripe</strong> addon. To avoid issues with Stripe donations, <strong>Charitable Stripe</strong> %s was deactivated. Please update <strong>Charitable Stripe</strong> to version 1.5.0 or higher.</p><p>You can get the latest version of <strong>Charitable Stripe</strong> through your <a href="https://www.wpcharitable.com/account/" target="_blank">Charitable account at wpcharitable.com</a>. All active licenses are accessible in the "My Downloads" tab.</p>', 'charitable' ), Charitable_Stripe::VERSION );
1252 1415
1253 - wp_die( $message . ' <a href="' . admin_url( 'plugins.php' ) . '">Click here to return to the plugins screen.</a>' );
1416 + wp_die( $message . ' <a href="' . admin_url( 'plugins.php' ) . '">Click here to return to the plugins screen.</a>' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1254 1417 }
1255 - // charitable_get_notices()->add_error( $message );
1256 - add_option( 'chartiable_version_warning', $message );
1418 + add_option( 'charitable_version_warning', $message );
1257 1419 } else {
1258 - delete_option( 'chartiable_version_warning' );
1420 + delete_option( 'charitable_version_warning' );
1259 1421 }
1260 1422 }
1261 1423 }
1262 1424
1425 + /**
1426 + * Set initial timestamps for rotating menu items if they're already installed.
1427 + * This ensures the 7-day countdown starts immediately for already-installed plugins.
1428 + *
1429 + * @since 1.8.8
1430 + *
1431 + * @return void
1432 + */
1433 + private function set_initial_rotating_menu_timestamps() {
1434 + // Get the rotating menu items configuration
1435 + $admin_pages = Charitable_Admin_Pages::get_instance();
1263 1436
1437 + try {
1438 + $reflection = new ReflectionClass( $admin_pages );
1439 + $method = $reflection->getMethod( 'get_marketing_rotation_items' );
1440 + $method->setAccessible( true );
1441 + $items = $method->invoke( $admin_pages );
1442 + } catch ( ReflectionException $e ) {
1443 + // Fallback if reflection fails in future PHP versions
1444 + $items = array();
1445 + }
1446 +
1447 + $current_time = time();
1448 +
1449 + foreach ( $items as $item ) {
1450 + $option_name = $item['option_name'] ?? '';
1451 + $option_key = $item['option_key'] ?? '';
1452 + $plugin_file = $item['plugin_file'] ?? '';
1453 +
1454 + if ( empty( $option_name ) || empty( $plugin_file ) ) {
1455 + continue;
1456 + }
1457 +
1458 + // Check if the plugin is installed (file exists in plugins directory)
1459 + $plugin_path = WP_PLUGIN_DIR . '/' . $plugin_file;
1460 + if ( file_exists( $plugin_path ) ) {
1461 + // Plugin is installed, check if we already have a timestamp
1462 + $existing_option = get_option( $option_name );
1463 + $has_timestamp = false;
1464 +
1465 + if ( is_array( $existing_option ) ) {
1466 + $has_timestamp = isset( $existing_option[ $option_key ] ) && is_numeric( $existing_option[ $option_key ] );
1467 + } elseif ( is_numeric( $existing_option ) ) {
1468 + $has_timestamp = true;
1469 + }
1470 +
1471 + // Only set timestamp if one doesn't already exist
1472 + if ( ! $has_timestamp ) {
1473 + if ( is_array( $existing_option ) ) {
1474 + $existing_option[ $option_key ] = $current_time;
1475 + update_option( $option_name, $existing_option );
1476 + } else {
1477 + update_option( $option_name, array( $option_key => $current_time ) );
1478 + }
1479 + }
1480 + }
1481 + }
1482 + }
1264 1483 }
1265 1484
1266 1485 $charitable = new Charitable();
1267 1486