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 -160 1.7.0.3 → 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.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.1
7 7 * Author: Charitable Donations & Fundraising Team
8 8 * Author URI: https://wpcharitable.com
9 - * Requires at least: 4.1
10 - * Tested up to: 6.0.2
11 - * Stable tag: 1.7.0.3
9 + * Requires at least: 5.9
10 + * Requires PHP: 7.4
11 + * Stable tag: 1.8.13.1
12 + * License: GPLv2 or later
13 + * License URI: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
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.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,21 +338,19 @@
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 - // } else {
297 - // echo '----'; print_r( $class_name );
298 353 }
299 354 return false;
300 355 }
301 356
@@ -321,11 +376,11 @@
321 376 $this->registry->register_object( Charitable_Locations::get_instance() );
322 377 $this->registry->register_object( Charitable_Currency::get_instance() );
323 378 $this->registry->register_object( Charitable_Notifications::get_instance() );
324 379
325 - $this->registry->register_object( new Charitable_Privacy );
326 - $this->registry->register_object( new Charitable_Debugging );
327 - $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() );
328 383 }
329 384
330 385 return $this->registry;
331 386 }
@@ -341,10 +396,10 @@
341 396 add_action( 'wpmu_new_blog', array( $this, 'maybe_activate_charitable_on_new_site' ) );
342 397 add_action( 'plugins_loaded', array( $this, 'charitable_install' ), 100 );
343 398 add_action( 'plugins_loaded', array( $this, 'charitable_start' ), 100 );
344 399 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 );
400 + add_action( 'init', array( $this, 'donation_fields' ), 100 );
401 + add_action( 'init', array( $this, 'campaign_fields' ), 100 );
347 402 add_action( 'plugins_loaded', array( $this, 'register_donormeta_table' ) );
348 403 add_action( 'plugins_loaded', 'charitable_load_compat_functions' );
349 404 add_action( 'setup_theme', array( 'Charitable_Customizer', 'start' ) );
350 405 add_action( 'after_setup_theme', array( $this, 'load_template_files' ) );
@@ -356,19 +411,17 @@
356 411 * as addons) has a chance to run before the event.
357 412 */
358 413 add_action( 'init', array( $this, 'do_charitable_actions' ), 20 );
359 414
360 - If ( $this->load_core_stripe() ) :
415 + if ( $this->load_core_stripe() ) :
361 416
362 417 /**
363 418 * Set up scripts & stylesheets & enqueue them when appropriate.
364 419 */
365 - // add_action( 'wp_enqueue_scripts', array( $this, 'setup_stripe_scripts' ), 11 );
366 420 add_action( 'charitable_form_after_fields', array( $this, 'maybe_setup_stripe_scripts_in_donation_form' ) );
367 421 add_action( 'charitable_campaign_loop_after', array( $this, 'maybe_setup_stripe_scripts_in_campaign_loop' ) );
368 422
369 423 endif;
370 -
371 424 }
372 425
373 426
374 427 /**
@@ -438,25 +491,70 @@
438 491 if ( ! is_admin() ) {
439 492 return false;
440 493 }
441 494
442 - require_once( $this->get_path( 'admin' ) . 'class-charitable-admin.php' );
443 - 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';
444 497
445 - $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';
446 500
447 - $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';
448 510
449 - // Stripe
511 + // connect for upgrade.wpcharitable.com.
512 + require_once $this->get_path( 'admin' ) . 'class-charitable-admin-connect.php';
450 513
451 - 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';
452 518
453 - 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();
454 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 +
455 530 return $admin;
456 531 }
457 532
458 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 + /**
459 557 * Checks whether we're on the public-facing side and if so, loads the public-facing functionality.
460 558 *
461 559 * @since 1.0.0
462 560 *
@@ -466,10 +564,18 @@
466 564 if ( is_admin() && ! $this->is_ajax() ) {
467 565 return false;
468 566 }
469 567
470 - require_once( $this->get_path( 'public' ) . 'class-charitable-public.php' );
568 + require_once $this->get_path( 'public' ) . 'class-charitable-public.php';
471 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 +
472 578 $public = Charitable_Public::get_instance();
473 579
474 580 $this->registry->register_object( $public );
475 581
@@ -484,25 +590,51 @@
484 590 * @return boolean
485 591 */
486 592 public function maybe_start_qunit() {
487 593 /* Skip out early if ?qunit isn't included in the request. */
488 - if ( ! array_key_exists( 'qunit', $_GET ) ) {
594 + if ( ! array_key_exists( 'qunit', $_GET ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
489 595 return false;
490 596 }
491 597
492 - /* The unit tests have to exist. */
493 - if ( ! file_exists( $this->get_path( 'directory' ) . 'tests/qunit/tests.js' ) ) {
494 - return false;
495 - }
598 + /* The unit tests have to exist. */
599 + if ( ! file_exists( $this->get_path( 'directory' ) . 'tests/qunit/tests.js' ) ) {
600 + return false;
601 + }
496 602
497 - 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 + );
498 624
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' );
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' );
502 628
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' );
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' );
505 637 }
506 638
507 639 /**
508 640 * Checks whether the current request is an AJAX request.
@@ -526,10 +658,10 @@
526 658 if ( ! $this->is_ajax() ) {
527 659 return;
528 660 }
529 661
530 - require_once( $this->get_path( 'includes' ) . 'ajax/charitable-ajax-functions.php' );
531 - 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';
532 664 }
533 665
534 666 /**
535 667 * This method is fired after all plugins are loaded and simply fires the charitable_start hook.
@@ -547,9 +679,11 @@
547 679 /**
548 680 * Fires off an action right after Charitable is installed, allowing other
549 681 * plugins/themes to do something at this point.
550 682 *
551 - * @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.
552 686 *
553 687 * @return void
554 688 */
555 689 public function charitable_install() {
@@ -558,9 +692,9 @@
558 692 if ( ! $install ) {
559 693 return;
560 694 }
561 695
562 - require_once( $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php' );
696 + require_once $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php';
563 697
564 698 Charitable_Install::finish_installing();
565 699
566 700 do_action( 'charitable_install' );
@@ -568,14 +702,25 @@
568 702 delete_transient( 'charitable_install' );
569 703
570 704 // Store the date when the initial activation was performed.
571 705 $type = function_exists( 'charitable_is_pro' ) && charitable_is_pro() ? 'pro' : 'lite';
572 - $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 +
573 716 if ( empty( $activated[ $type ] ) ) {
574 717 $activated[ $type ] = time();
575 718 update_option( 'charitable_activated', $activated );
576 719 }
577 720
721 + // Clear notifications.
722 + delete_option( 'charitable_notifications' );
578 723 }
579 724
580 725 /**
581 726 * Returns whether we are currently in the start phase of the plugin.
@@ -584,9 +729,9 @@
584 729 *
585 730 * @return boolean
586 731 */
587 732 public function is_start() {
588 - return current_filter() == 'charitable_start';
733 + return current_filter() == 'charitable_start'; // phpcs:ignore
589 734 }
590 735
591 736 /**
592 737 * Returns whether the plugin has already started.
@@ -595,9 +740,9 @@
595 740 *
596 741 * @return boolean
597 742 */
598 743 public function started() {
599 - return did_action( 'charitable_start' ) || current_filter() == 'charitable_start';
744 + return did_action( 'charitable_start' ) || current_filter() == 'charitable_start'; // phpcs:ignore
600 745 }
601 746
602 747 /**
603 748 * Returns whether the plugin is being activated.
@@ -606,9 +751,9 @@
606 751 *
607 752 * @return boolean
608 753 */
609 754 public function is_activation() {
610 - return current_filter() == 'activate_charitable/charitable.php';
755 + return current_filter() == 'activate_charitable/charitable.php'; // phpcs:ignore
611 756 }
612 757
613 758 /**
614 759 * Returns whether the plugin is being deactivated.
@@ -617,9 +762,9 @@
617 762 *
618 763 * @return boolean
619 764 */
620 765 public function is_deactivation() {
621 - return current_filter() == 'deactivate_charitable/charitable.php';
766 + return current_filter() == 'deactivate_charitable/charitable.php'; // phpcs:ignore
622 767 }
623 768
624 769 /**
625 770 * Returns plugin paths.
@@ -653,8 +798,12 @@
653 798 case 'templates':
654 799 $path = $base . 'templates/';
655 800 break;
656 801
802 + case 'plugin-directory':
803 + $path = WP_PLUGIN_DIR;
804 + break;
805 +
657 806 case 'directory':
658 807 $path = $base;
659 808 break;
660 809
@@ -684,8 +833,26 @@
684 833 return $version;
685 834 }
686 835
687 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 + /**
688 855 * Get the campaign fields registry
689 856 *
690 857 * If the registry has not been set up yet, this will also
691 858 * perform the task of setting it up initially.
@@ -702,9 +869,9 @@
702 869
703 870 /* Register it immediately to avoid endless recursion. */
704 871 $this->registry->register_object( $campaign_fields );
705 872
706 - $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';
707 874
708 875 foreach ( $fields as $key => $args ) {
709 876 $campaign_fields->register_field( new Charitable_Campaign_Field( $key, $args ) );
710 877 }
@@ -731,9 +898,9 @@
731 898
732 899 /* Register it immediately to avoid endless recursion. */
733 900 $this->registry->register_object( $donation_fields );
734 901
735 - $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';
736 903
737 904 foreach ( $fields as $key => $args ) {
738 905 $donation_fields->register_field( new Charitable_Donation_Field( $key, $args ) );
739 906 }
@@ -765,22 +932,22 @@
765 932 * BEFORE the endpoint it builds on. In other words, move from
766 933 * most specific to least specific.
767 934 */
768 935 $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 );
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() );
783 950
784 951 $this->registry->register_object( $endpoints );
785 952 }
786 953
@@ -794,9 +961,9 @@
794 961 *
795 962 * @param string $class The type of class to be retrieved.
796 963 * @return object
797 964 */
798 - public function get_registered_object( $class ) {
965 + public function get_registered_object( $class ) { // phpcs:ignore
799 966 return $this->registry->get( $class );
800 967 }
801 968
802 969 /**
@@ -847,8 +1014,9 @@
847 1014 /**
848 1015 * Filter the array of available Charitable table classes.
849 1016 *
850 1017 * @since 1.0.0
1018 + * @version 1.8.1
851 1019 *
852 1020 * @param array $tables List of tables as a key=>value array.
853 1021 */
854 1022 return apply_filters(
@@ -853,11 +1021,13 @@
853 1021 */
854 1022 return apply_filters(
855 1023 'charitable_db_tables',
856 1024 array(
857 - 'campaign_donations' => 'Charitable_Campaign_Donations_DB',
858 - 'donors' => 'Charitable_Donors_DB',
859 - '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',
860 1030 )
861 1031 );
862 1032 }
863 1033
@@ -888,14 +1058,14 @@
888 1058 * or just the current site. Multisite only. Default is false.
889 1059 * @return void
890 1060 */
891 1061 public function activate( $network_wide = false ) {
892 - require_once( $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php' );
1062 + require_once $this->get_path( 'includes' ) . 'plugin/class-charitable-install.php';
893 1063
894 1064 if ( is_multisite() && $network_wide ) {
895 1065 global $wpdb;
896 1066
897 - 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
898 1068 switch_to_blog( $blog_id );
899 1069 new Charitable_Install( $this->includes_path );
900 1070 restore_current_blog();
901 1071 }
@@ -903,9 +1073,12 @@
903 1073 new Charitable_Install( $this->includes_path );
904 1074 }
905 1075
906 1076 // create or update the install date for future reference.
907 - 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();
908 1081 }
909 1082
910 1083 /**
911 1084 * Runs on plugin deactivation.
@@ -916,9 +1089,9 @@
916 1089 *
917 1090 * @return void
918 1091 */
919 1092 public function deactivate() {
920 - require_once( $this->get_path( 'includes' ) . 'plugin/class-charitable-uninstall.php' );
1093 + require_once $this->get_path( 'includes' ) . 'plugin/class-charitable-uninstall.php';
921 1094 new Charitable_Uninstall();
922 1095 }
923 1096
924 1097 /**
@@ -928,11 +1101,11 @@
928 1101 *
929 1102 * @return void
930 1103 */
931 1104 public function do_charitable_actions() {
932 - if ( isset( $_REQUEST['charitable_action'] ) ) {
1105 + if ( isset( $_REQUEST['charitable_action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
933 1106
934 - $action = $_REQUEST['charitable_action'];
1107 + $action = esc_attr( $_REQUEST['charitable_action'] ); // phpcs:ignore
935 1108
936 1109 /**
937 1110 * Handle Charitable action.
938 1111 *
@@ -1008,9 +1181,9 @@
1008 1181 *
1009 1182 * @param mixed $object Object to be registered.
1010 1183 * @return void
1011 1184 */
1012 - public function register_object( $object ) {
1185 + public function register_object( $object ) { // phpcs:ignore
1013 1186 charitable_get_deprecated()->deprecated_function(
1014 1187 __METHOD__,
1015 1188 '1.5.0',
1016 1189 'charitable()->registry()->register_object()'
@@ -1120,18 +1293,8 @@
1120 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() ) ) {
1121 1294 return false;
1122 1295 }
1123 1296
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 1297 return true;
1135 1298 }
1136 1299
1137 1300 /**
@@ -1142,9 +1305,9 @@
1142 1305 * @return boolean
1143 1306 */
1144 1307 public function is_stripe_connect_addon() {
1145 1308
1146 - 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
1147 1310 return true;
1148 1311 }
1149 1312
1150 1313 return false;
@@ -1154,10 +1317,11 @@
1154 1317 * Action links for the plugins page.
1155 1318 *
1156 1319 * @since 1.7.0
1157 1320 *
1158 - * @param array $actions An array of existing actions.
1159 - * @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.
1160 1324 * @return array An array of action links.
1161 1325 */
1162 1326 public function plugin_action_links( $actions, $plugin_file, $action_links = false ) {
1163 1327
@@ -1164,15 +1328,15 @@
1164 1328 if ( ! charitable_is_pro() ) :
1165 1329
1166 1330 // if this isn't pro, add the upgrade link which is visible on the plugins page until the plugin name.
1167 1331
1168 - $action_links = [
1169 - 'proupgrade' => [
1332 + $action_links = array(
1333 + 'proupgrade' => array(
1170 1334 // Translators: This is an action link users can click to purchase a license for All in One SEO Pro.
1171 1335 'label' => __( 'Upgrade to Pro', 'charitable' ),
1172 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',
1173 - ]
1174 - ];
1337 + ),
1338 + );
1175 1339
1176 1340 endif;
1177 1341
1178 1342 if ( isset( $actions['edit'] ) ) {
@@ -1186,23 +1350,24 @@
1186 1350 * Parse the action links.
1187 1351 *
1188 1352 * @since 1.7.0
1189 1353 *
1190 - * @param array $actions
1191 - * @param string $pluginFile
1192 - * @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.
1193 1358 * @return array
1194 1359 */
1195 - 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' ) {
1196 1361 if ( empty( $this->plugin ) ) {
1197 - $this->plugin = CHARTIABLE_PLUGIN_BASENAME;
1362 + $this->plugin = CHARITABLE_PLUGIN_BASENAME;
1198 1363 }
1199 1364
1200 - if ( $this->plugin === $pluginFile && ! empty( $action_links ) ) {
1365 + if ( $this->plugin === $plugin_file && ! empty( $action_links ) ) {
1201 1366 foreach ( $action_links as $key => $value ) {
1202 - $link = [
1203 - $key => '<a href="' . $value['url'] . '">' . $value['label'] . '</a>'
1204 - ];
1367 + $link = array(
1368 + $key => '<a href="' . $value['url'] . '">' . $value['label'] . '</a>',
1369 + );
1205 1370
1206 1371 $actions = 'after' === $position ? array_merge( $actions, $link ) : array_merge( $link, $actions );
1207 1372 }
1208 1373 }
@@ -1218,11 +1383,11 @@
1218 1383 * @return void
1219 1384 */
1220 1385 public function setup_licensing() {
1221 1386 charitable_get_helper( 'licenses' )->register_licensed_product(
1222 - Charitable::NAME . ' Pro',
1223 - Charitable::AUTHOR,
1224 - Charitable::VERSION,
1387 + self::NAME,
1388 + self::AUTHOR,
1389 + self::VERSION,
1225 1390 __FILE__
1226 1391 );
1227 1392 }
1228 1393
@@ -1229,16 +1394,14 @@
1229 1394 /**
1230 1395 * Deativates and/or warnings about any Charitable verison conflicts.
1231 1396 *
1232 1397 * @since 1.7.0
1233 - *
1234 - * @return null
1235 1398 */
1236 1399 public function check_for_version_conflicts() {
1237 1400
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', '<' ) ) {
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
1241 1404 // manually deactivate plugin.
1242 1405 $found = false;
1243 1406 $current = get_option( 'active_plugins', array() );
1244 1407 $key = array_search( 'charitable-stripe/charitable-stripe.php', $current, true );
@@ -1247,21 +1410,78 @@
1247 1410
1248 1411 unset( $current[ $key ] );
1249 1412 update_option( 'active_plugins', $current );
1250 1413
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' );
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 );
1252 1416
1253 - 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
1254 1418 }
1255 - // charitable_get_notices()->add_error( $message );
1256 - add_option( 'chartiable_version_warning', $message );
1419 + add_option( 'charitable_version_warning', $message );
1257 1420 } else {
1258 - delete_option( 'chartiable_version_warning' );
1421 + delete_option( 'charitable_version_warning' );
1259 1422 }
1260 1423 }
1261 1424 }
1262 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();
1263 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 + }
1264 1484 }
1265 1485
1266 1486 $charitable = new Charitable();
1267 1487