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 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 | includes/upgrades/class-charitable-upgrade.php +513 -139 1.6.6 → 1.8.13 View file →
@@ -4,12 +4,12 @@
4 4 *
5 5 * The responsibility of this class is to manage migrations between versions of Charitable.
6 6 *
7 7 * @package Charitable/Classes/Charitable_Upgrade
8 - * @copyright Copyright (c) 2018, Eric Daams
8 + * @copyright Copyright (c) 2023, WP Charitable LLC
9 9 * @license http://opensource.org/licenses/gpl-1.0.0.php GNU Public License
10 10 * @since 1.0.0
11 - * @version 1.6.0
11 + * @version 1.6.51
12 12 */
13 13
14 14 // Exit if accessed directly.
15 15 if ( ! defined( 'ABSPATH' ) ) {
@@ -34,8 +34,17 @@
34 34 */
35 35 private static $instance = null;
36 36
37 37 /**
38 + * Whether the "upgrade in progress" notice has been shown.
39 + *
40 + * @since 1.6.14
41 + *
42 + * @var boolean
43 + */
44 + private $notice_shown_for_upgrade_in_progress = false;
45 +
46 + /**
38 47 * Current database version.
39 48 *
40 49 * @since 1.0.0
41 50 *
@@ -109,9 +118,18 @@
109 118 */
110 119 if ( strlen( $db_version ) && strlen( $edge_version ) ) {
111 120 return $this->legacy_upgrade_mode( $db_version, $edge_version );
112 121 }
122 + }
113 123
124 + /**
125 + * Initialize the upgrade actions.
126 + *
127 + * @since 1.0.0
128 + *
129 + * @return void
130 + */
131 + public function init() {
114 132 $this->upgrade_actions = array(
115 133 'update_upgrade_system' => array(
116 134 'version' => '1.3.0',
117 135 'message' => __( 'Charitable needs to update the database.', 'charitable' ),
@@ -199,8 +217,55 @@
199 217 'message' => __( 'Charitable needs to fix donations saved without a donor ID.', 'charitable' ),
200 218 'prompt' => true,
201 219 'active_callback' => array( $this, 'has_empty_donor_ids' ),
202 220 ),
221 + 'flush_permalinks_1614' => array(
222 + 'version' => '1.6.14',
223 + 'message' => '',
224 + 'prompt' => false,
225 + 'callback' => array( $this, 'flush_permalinks' ),
226 + ),
227 + 'flush_permalinks_1621' => array(
228 + 'version' => '1.6.21',
229 + 'message' => '',
230 + 'prompt' => false,
231 + 'callback' => array( $this, 'flush_permalinks' ),
232 + ),
233 + 'remove_campaign_manager_unfiltered_html_cap' => array(
234 + 'version' => '1.6.51',
235 + 'message' => '',
236 + 'prompt' => false,
237 + 'callback' => array( $this, 'remove_campaign_manager_unfiltered_html_cap' ),
238 + ),
239 + 'create_activity_tables' => array(
240 + 'version' => '1.8.1',
241 + 'message' => __( 'Charitable needs to add additional database tables.', 'charitable' ),
242 + 'prompt' => true,
243 + ),
244 + 'detect_paypal_user_tier' => array(
245 + 'version' => '1.8.11',
246 + 'message' => '',
247 + 'prompt' => false,
248 + 'callback' => array( $this, 'detect_paypal_user_tier' ),
249 + ),
250 + 'create_logs_table' => array(
251 + 'version' => '1.8.11',
252 + 'message' => __( 'Create the logs table.', 'charitable' ),
253 + 'prompt' => false,
254 + 'callback' => array( $this, 'create_logs_table' ),
255 + ),
256 + 'verify_logs_table' => array(
257 + 'version' => '1.8.11',
258 + 'message' => '',
259 + 'prompt' => false,
260 + 'callback' => array( $this, 'verify_logs_table' ),
261 + ),
262 + 'sync_paypal_license_to_middleware' => array(
263 + 'version' => '1.8.11.2',
264 + 'message' => '',
265 + 'prompt' => false,
266 + 'callback' => array( $this, 'sync_paypal_license_to_middleware' ),
267 + ),
203 268 );
204 269 }
205 270
206 271 /**
@@ -246,8 +311,10 @@
246 311 'message' => __( 'Charitable was installed.', 'charitable' ),
247 312 ),
248 313 );
249 314
315 + $this->init();
316 +
250 317 foreach ( $this->upgrade_actions as $key => $notes ) {
251 318 $log[ $key ] = array(
252 319 'time' => time(),
253 320 'version' => Charitable::VERSION,
@@ -265,13 +332,13 @@
265 332 *
266 333 * @return void
267 334 */
268 335 public function add_upgrade_notice() {
269 - if ( isset( $_GET['page'] ) && 'charitable-upgrades' == $_GET['page'] ) {
336 + if ( isset( $_GET['page'] ) && 'charitable-upgrades' == $_GET['page'] ) { // phpcs:ignore
270 337 return;
271 338 }
272 339
273 - if ( ! current_user_can( 'manage_charitable_settings' ) ) {
340 + if ( ! current_user_can( 'manage_charitable_settings' ) ) { // phpcs:ignore
274 341 return;
275 342 }
276 343
277 344 /**
@@ -276,23 +343,14 @@
276 343
277 344 /**
278 345 * If an upgrade is still in progress, continue it until it's done.
279 346 */
280 - $upgrade_progress = $this->upgrade_is_in_progress();
347 + if ( ! self::get_instance()->notice_shown_for_upgrade_in_progress() ) {
348 + $upgrade_progress = $this->upgrade_is_in_progress();
281 349
282 - if ( false !== $upgrade_progress ) {
283 -
284 - /* Fixes a bug that incorrectly set the page as charitable-upgrade */
285 - if ( isset( $upgrade_progress['page'] ) && 'charitable-upgrade' == $upgrade_progress['page'] ) {
286 - $upgrade_progress['page'] = 'charitable-upgrades';
350 + if ( false !== $upgrade_progress ) {
351 + return self::get_instance()->show_upgrade_in_progress_notice( $upgrade_progress );
287 352 }
288 -?>
289 - <div class="error">
290 - <p><?php printf( __( 'Charitable needs to complete an upgrade that was started earlier. Click <a href="%s">here</a> to continue the upgrade.', 'charitable' ), esc_url( add_query_arg( $upgrade_progress, admin_url( 'index.php' ) ) ) ) ?>
291 - </p>
292 - </div>
293 -<?php
294 - return;
295 353 }
296 354
297 355 $this->walk_upgrade_actions( array( $this, 'show_upgrade_notice' ) );
298 356 }
@@ -299,9 +357,10 @@
299 357
300 358 /**
301 359 * Show upgrade notice for a particular upgrade notice.
302 360 *
303 - * @since 1.5.0
361 + * @since 1.5.0
362 + * @version 1.8.1 // added nonce.
304 363 *
305 364 * @param string $action The upgrade action key.
306 365 * @param array $upgrade Upgrade args.
307 366 * @return boolean
@@ -315,14 +374,28 @@
315 374 if ( array_key_exists( 'notice', $upgrade ) ) {
316 375 return $this->set_update_notice_transient( $upgrade, $action );
317 376 }
318 377
319 -?>
378 + /* Add a nonce */
379 + $action .= '&nonce=' . wp_create_nonce( 'charitable-upgrade' );
380 +
381 + ?>
320 382 <div class="updated">
321 - <p><?php printf( '%s %s', $upgrade['message'], sprintf( __( 'Click <a href="%s">here</a> to start the upgrade.', 'charitable' ), esc_url( admin_url( 'index.php?page=charitable-upgrades&charitable-upgrade=' . $action ) ) ) ) ?>
383 + <p>
384 + <?php
385 + printf(
386 + '%s %s',
387 + $upgrade['message'], // phpcs:ignore
388 + sprintf(
389 + /* translators: %s: upgrade link */
390 + __( 'Click <a href="%s">here</a> to start the upgrade.', 'charitable' ), // phpcs:ignore
391 + esc_url( admin_url( 'index.php?page=charitable-upgrades&charitable-upgrade=' . $action ) )
392 + )
393 + );
394 + ?>
322 395 </p>
323 396 </div>
324 -<?php
397 + <?php
325 398 }
326 399
327 400 /**
328 401 * Perform any immediate upgrades.
@@ -331,9 +404,9 @@
331 404 *
332 405 * @return void
333 406 */
334 407 public function do_immediate_upgrades() {
335 - if ( is_admin() && isset( $_GET['page'] ) && 'charitable-upgrades' == $_GET['page'] ) {
408 + if ( is_admin() && isset( $_GET['page'] ) && 'charitable-upgrades' == $_GET['page'] ) { // phpcs:ignore
336 409 return;
337 410 }
338 411
339 412 $this->walk_upgrade_actions( array( $this, 'perform_immediate_upgrade' ) );
@@ -343,13 +416,20 @@
343 416 * Perform an immediate upgrade and log the result.
344 417 *
345 418 * @since 1.5.0
346 419 *
420 + * @param string $action The upgrade's action key.
421 + * @param array $upgrade Upgrade details.
347 422 * @return void
348 423 */
349 424 public function perform_immediate_upgrade( $action, $upgrade ) {
350 - if ( $this->do_upgrade_immediately( $upgrade ) ) {
351 - $ret = call_user_func( $upgrade['callback'], $action );
425 + if ( $this->do_upgrade_immediately( $upgrade ) ) {
426 + $callback = $upgrade['callback'];
427 + if ( is_callable( $callback ) ) {
428 + $ret = $callback( $action );
429 + } else {
430 + $ret = false;
431 + }
352 432
353 433 /* If the upgrade succeeded, update the log. */
354 434 if ( $ret ) {
355 435 $this->update_upgrade_log( $action );
@@ -360,9 +440,10 @@
360 440 /**
361 441 * Walk over the array of upgrade actions, performing the callback
362 442 * for any upgrades that have not been completed yet.
363 443 *
364 - * @since 1.5.0
444 + * @since 1.5.0
445 + * @version 1.8.5.6
365 446 *
366 447 * @param callback $callback Callback method to perform if the
367 448 * upgrade has not been completed.
368 449 * @return void
@@ -367,25 +448,35 @@
367 448 * upgrade has not been completed.
368 449 * @return void
369 450 */
370 451 protected function walk_upgrade_actions( $callback ) {
452 +
371 453 if ( ! is_callable( $callback ) ) {
372 454 return;
373 455 }
374 456
457 + if ( $this->upgrade_log_key === 'charitable_upgrade_log' ) {
458 + $this->init();
459 + }
460 +
375 461 foreach ( $this->upgrade_actions as $action => $upgrade ) {
376 462
377 - /* If the upgrade has an active_callback, check whether the upgrade is required. If not, mark it as done. */
378 - if ( array_key_exists( 'active_callback', $upgrade ) && ! call_user_func( $upgrade['active_callback'] ) ) {
379 - $this->update_upgrade_log( $action );
380 - continue;
463 + if ( ! $this->upgrade_has_been_completed( $action ) ) {
464 +
465 + /* If the upgrade has an active_callback, check whether the upgrade is required. If not, mark it as done. */
466 + $active_callback = $upgrade['active_callback'] ?? null;
467 + $is_active = $active_callback && is_callable( $active_callback ) ? $active_callback() : true;
468 + if ( array_key_exists( 'active_callback', $upgrade ) && ! $is_active ) {
469 + $this->update_upgrade_log( $action );
470 + continue;
471 + }
472 +
473 + $callback( $action, $upgrade );
474 +
381 475 }
382 476
383 - if ( ! $this->upgrade_has_been_completed( $action ) ) {
384 - call_user_func( $callback, $action, $upgrade );
385 - }
386 477 }
387 - }
478 + }
388 479
389 480 /**
390 481 * Checks whether an upgrade has been completed.
391 482 *
@@ -396,12 +487,53 @@
396 487 * @return boolean
397 488 */
398 489 public function upgrade_has_been_completed( $action ) {
399 490 $log = get_option( $this->upgrade_log_key );
491 + return is_array( $log ) && array_key_exists( $action, $log );
492 + }
400 493
401 - return is_array( $log ) && array_key_exists( $action, $log );
494 +
495 + /**
496 + * Check whether a notice has been shown to say that an upgrade is in progress.
497 + *
498 + * @since 1.6.14
499 + *
500 + * @return boolean
501 + */
502 + public function notice_shown_for_upgrade_in_progress() {
503 + return $this->notice_shown_for_upgrade_in_progress;
402 504 }
505 +
403 506 /**
507 + * Show the "upgrade in progress" notice.
508 + *
509 + * @since 1.6.14
510 + *
511 + * @param array $upgrade_progress Array detailing the upgrade progress.
512 + * @return void
513 + */
514 + public function show_upgrade_in_progress_notice( $upgrade_progress ) {
515 + /* Fixes a bug that incorrectly set the page as charitable-upgrade */
516 + if ( isset( $upgrade_progress['page'] ) && 'charitable-upgrade' == $upgrade_progress['page'] ) { //phpcs:ignore
517 + $upgrade_progress['page'] = 'charitable-upgrades';
518 + }
519 + ?>
520 + <div class="error">
521 + <p>
522 + <?php
523 + printf(
524 + /* translators: %s: upgrade link */
525 + __( 'Charitable needs to complete an upgrade that was started earlier. Click <a href="%s">here</a> to continue the upgrade.', 'charitable' ), // phpcs:ignore
526 + esc_url( add_query_arg( $upgrade_progress, admin_url( 'index.php' ) ) )
527 + );
528 + ?>
529 + </p>
530 + </div>
531 + <?php
532 + $this->notice_shown_for_upgrade_in_progress = true;
533 + }
534 +
535 + /**
404 536 * Evaluates two version numbers and determines whether an upgrade is
405 537 * required for version A to get to version B.
406 538 *
407 539 * @since 1.0.0
@@ -467,21 +599,21 @@
467 599 * @return void
468 600 */
469 601 public function update_upgrade_system() {
470 602 if ( ! current_user_can( 'manage_charitable_settings' ) ) {
471 - wp_die( __( 'You do not have permission to do Charitable upgrades', 'charitable' ), __( 'Error', 'charitable' ), array( 'response' => 403 ) );
603 + wp_die( __( 'You do not have permission to do Charitable upgrades', 'charitable' ), __( 'Error', 'charitable' ), array( 'response' => 403 ) ); // phpcs:ignore
472 604 }
473 605
474 606 ignore_user_abort( true );
475 607
476 - if ( ! charitable_is_func_disabled( 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) {
477 - @set_time_limit( 0 );
608 + if ( ! charitable_is_func_disabled( 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) { // phpcs:ignore
609 + @set_time_limit( 0 ); // phpcs:ignore
478 610 }
479 611
480 612 /**
481 613 * Update the campaign donations table to use DECIMAL for amounts.
482 614 *
483 - * @see https://github.com/Charitable/Charitable/issues/56
615 + * @see https://github.com/Charitable/Charitable/issues/56
484 616 */
485 617 $table = new Charitable_Campaign_Donations_DB();
486 618 $table->create_table();
487 619
@@ -502,21 +634,23 @@
502 634 * @return void
503 635 */
504 636 public function fix_donation_dates() {
505 637 if ( ! current_user_can( 'manage_charitable_settings' ) ) {
506 - wp_die( __( 'You do not have permission to do Charitable upgrades', 'charitable' ), __( 'Error', 'charitable' ), array( 'response' => 403 ) );
638 + wp_die( __( 'You do not have permission to do Charitable upgrades', 'charitable' ), __( 'Error', 'charitable' ), array( 'response' => 403 ) ); // phpcs:ignore
507 639 }
508 640
509 641 ignore_user_abort( true );
510 642
511 - if ( ! charitable_is_func_disabled( 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) {
512 - @set_time_limit( 0 );
513 - }
643 + if ( ! charitable_is_func_disabled( 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) {
644 + @set_time_limit( 0 ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged
645 + }
514 646
515 - $step = isset( $_GET['step'] ) ? absint( $_GET['step'] ) : 1;
647 + // phpcs:disable WordPress.Security.NonceVerification.Recommended
648 + $step = isset( $_GET['step'] ) ? absint( sanitize_text_field( wp_unslash( $_GET['step'] ) ) ) : 1;
649 + // phpcs:enable WordPress.Security.NonceVerification.Recommended
516 650 $number = 20;
517 651
518 - $total = Charitable_Donations::count_all();
652 + $total = Charitable_Donations::count_all();
519 653
520 654 /**
521 655 * If there are no donations to update, go ahead and wrap it up right now.
522 656 */
@@ -523,14 +657,16 @@
523 657 if ( ! $total ) {
524 658 $this->finish_upgrade( 'fix_donation_dates' );
525 659 }
526 660
527 - $donations = get_posts( array(
528 - 'post_type' => Charitable::DONATION_POST_TYPE,
529 - 'posts_per_page' => $number,
530 - 'paged' => $step,
531 - 'post_status' => array_keys( charitable_get_valid_donation_statuses() ),
532 - ) );
661 + $donations = get_posts(
662 + array(
663 + 'post_type' => Charitable::DONATION_POST_TYPE,
664 + 'posts_per_page' => $number,
665 + 'paged' => $step,
666 + 'post_status' => array_keys( charitable_get_valid_donation_statuses() ),
667 + )
668 + );
533 669
534 670 if ( count( $donations ) ) {
535 671
536 672 /**
@@ -561,26 +697,31 @@
561 697 }
562 698
563 699 $date = get_date_from_gmt( $date_gmt );
564 700
565 - wp_update_post( array(
566 - 'ID' => $donation->ID,
567 - 'post_date' => $date,
568 - 'post_date_gmt' => $date_gmt,
569 - ) );
701 + wp_update_post(
702 + array(
703 + 'ID' => $donation->ID,
704 + 'post_date' => $date,
705 + 'post_date_gmt' => $date_gmt,
706 + )
707 + );
570 708 }//end foreach
571 709
572 - $step++;
710 + ++$step;
573 711
574 - $redirect = add_query_arg( array(
575 - 'page' => 'charitable-upgrades',
576 - 'charitable-upgrade' => 'fix_donation_dates',
577 - 'step' => $step,
578 - 'number' => $number,
579 - 'total' => $total,
580 - ), admin_url( 'index.php' ) );
712 + $redirect = add_query_arg(
713 + array(
714 + 'page' => 'charitable-upgrades',
715 + 'charitable-upgrade' => 'fix_donation_dates',
716 + 'step' => $step,
717 + 'number' => $number,
718 + 'total' => $total,
719 + ),
720 + admin_url( 'index.php' )
721 + );
581 722
582 - wp_redirect( $redirect );
723 + wp_safe_redirect( $redirect );
583 724
584 725 exit;
585 726 }//end if
586 727
@@ -604,23 +745,25 @@
604 745 public function remove_duplicate_donors() {
605 746 global $wpdb;
606 747
607 748 if ( ! current_user_can( 'manage_charitable_settings' ) ) {
608 - wp_die( __( 'You do not have permission to do Charitable upgrades', 'charitable' ), __( 'Error', 'charitable' ), array( 'response' => 403 ) );
749 + wp_die( __( 'You do not have permission to do Charitable upgrades', 'charitable' ), __( 'Error', 'charitable' ), array( 'response' => 403 ) ); // phpcs:ignore
609 750 }
610 751
611 752 ignore_user_abort( true );
612 753
613 - if ( ! charitable_is_func_disabled( 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) {
614 - @set_time_limit( 0 );
615 - }
754 + if ( ! charitable_is_func_disabled( 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) {
755 + @set_time_limit( 0 ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged
756 + }
616 757
617 - $step = array_key_exists( 'step', $_GET ) ? absint( $_GET['step'] ) : 1;
758 + // phpcs:disable WordPress.Security.NonceVerification.Recommended
759 + $step = array_key_exists( 'step', $_GET ) ? absint( sanitize_text_field( wp_unslash( $_GET['step'] ) ) ) : 1;
760 + // phpcs:enable WordPress.Security.NonceVerification.Recommended
618 761 $number = 20;
619 762 $subquery = "SELECT GROUP_CONCAT(donor_id, ':', user_id) AS donor, COUNT(*) AS count
620 763 FROM {$wpdb->prefix}charitable_donors
621 764 GROUP BY email";
622 - $total = $wpdb->get_var( "SELECT COUNT( d.donor ) FROM ( {$subquery} ) AS d WHERE d.count > 1" );
765 + $total = $wpdb->get_var( "SELECT COUNT( d.donor ) FROM ( {$subquery} ) AS d WHERE d.count > 1" ); // phpcs:ignore
623 766
624 767 /**
625 768 * If there are no donors left to remove, go ahead and wrap it up right now.
626 769 */
@@ -627,9 +770,9 @@
627 770 if ( ! $total ) {
628 771 $this->finish_upgrade( 'remove_duplicate_donors' );
629 772 }
630 773
631 - $donors = $wpdb->get_col( "SELECT d.donor FROM ( {$subquery} ) AS d WHERE d.count > 1" );
774 + $donors = $wpdb->get_col( "SELECT d.donor FROM ( {$subquery} ) AS d WHERE d.count > 1" ); // phpcs:ignore
632 775
633 776 if ( count( $donors ) ) {
634 777
635 778 foreach ( $donors as $donor ) {
@@ -669,27 +812,30 @@
669 812 SET donor_id = %d
670 813 WHERE donor_id IN ( {$donor_id_placeholders} )";
671 814
672 815 $parameters = array_merge( array( $canonical ), $duplicates );
673 - $updated = $wpdb->query( $wpdb->prepare( $sql, array_merge( array( $canonical ), $duplicates ) ) );
816 + $updated = $wpdb->query( $wpdb->prepare( $sql, array_merge( array( $canonical ), $duplicates ) ) ); // phpcs:ignore
674 817
675 818 if ( false !== $updated ) {
676 819 $sql = "DELETE FROM {$wpdb->prefix}charitable_donors WHERE donor_id IN ( {$donor_id_placeholders} );";
677 - $deleted = $wpdb->query( $wpdb->prepare( $sql, $duplicates ) );
820 + $deleted = $wpdb->query( $wpdb->prepare( $sql, $duplicates ) ); // phpcs:ignore
678 821 }
679 822 }//end foreach
680 823
681 - $step++;
824 + ++$step;
682 825
683 - $redirect = add_query_arg( array(
684 - 'charitable-upgrade' => 'remove_duplicate_donors',
685 - 'page' => 'charitable-upgrades',
686 - 'step' => $step,
687 - 'number' => $number,
688 - 'total' => $total,
689 - ), admin_url( 'index.php' ) );
826 + $redirect = add_query_arg(
827 + array(
828 + 'charitable-upgrade' => 'remove_duplicate_donors',
829 + 'page' => 'charitable-upgrades',
830 + 'step' => $step,
831 + 'number' => $number,
832 + 'total' => $total,
833 + ),
834 + admin_url( 'index.php' )
835 + );
690 836
691 - wp_redirect( $redirect );
837 + wp_safe_redirect( $redirect );
692 838
693 839 exit;
694 840 }//end if
695 841
@@ -752,9 +898,9 @@
752 898 }
753 899 }
754 900
755 901 /**
756 - * Remove the 'manage_charitable_settings' cap from the Campaign Manager role.
902 + * Remove the 'manage_charitable_settings' cap from the Campaign Manager role.
757 903 *
758 904 * @since 1.4.5
759 905 *
760 906 * @global WP_Roles
@@ -760,14 +906,14 @@
760 906 * @global WP_Roles
761 907 * @param string $action The upgrade action.
762 908 * @return true Will always return true.
763 909 */
764 - public function remove_campaign_manager_cap( $action ) {
910 + public function remove_campaign_manager_cap( $action ) { // phpcs:ignore
765 911 global $wp_roles;
766 912
767 913 if ( class_exists( 'WP_Roles' ) ) {
768 914 if ( ! isset( $wp_roles ) ) {
769 - $wp_roles = new WP_Roles();
915 + $wp_roles = new WP_Roles(); // phpcs:ignore
770 916 }
771 917 }
772 918
773 919 if ( is_object( $wp_roles ) ) {
@@ -785,9 +931,9 @@
785 931 * @global WPDB $wpdb
786 932 * @param string $action The upgrade action.
787 933 * @return boolean Whether the query was successfully executed.
788 934 */
789 - public function fix_empty_campaign_end_date_meta( $action ) {
935 + public function fix_empty_campaign_end_date_meta( $action ) { // phpcs:ignore
790 936 global $wpdb;
791 937
792 938 $sql = "UPDATE $wpdb->postmeta
793 939 INNER JOIN $wpdb->posts
@@ -796,9 +942,9 @@
796 942 WHERE $wpdb->postmeta.meta_key = '_campaign_end_date'
797 943 AND $wpdb->postmeta.meta_value = ''
798 944 AND $wpdb->posts.post_type = 'campaign';";
799 945
800 - $ret = $wpdb->query( $sql );
946 + $ret = $wpdb->query( $sql ); // phpcs:ignore
801 947
802 948 return false !== $ret;
803 949 }
804 950
@@ -810,15 +956,15 @@
810 956 * @global WPDB $wpdb
811 957 * @param string $action The upgrade action.
812 958 * @return boolean Whether the event was scheduled.
813 959 */
814 - public function clear_campaign_amount_donated_transient( $action ) {
960 + public function clear_campaign_amount_donated_transient( $action ) { // phpcs:ignore
815 961 global $wpdb;
816 962
817 963 $sql = "DELETE FROM $wpdb->options
818 964 WHERE option_name LIKE '_transient_charitable_campaign_%_donation_amount'";
819 965
820 - $ret = $wpdb->query( $sql );
966 + $ret = $wpdb->query( $sql ); // phpcs:ignore
821 967
822 968 return false !== $ret;
823 969 }
824 970
@@ -918,23 +1064,28 @@
918 1064 /**
919 1065 * Finish an upgrade. This clears the charitable_doing_upgrade setting and updates the log.
920 1066 *
921 1067 * @since 1.3.0
1068 + * @since 1.8.1 Added redirect url for dashboard for create_activity_tables upgrade.
922 1069 *
923 - * @param string $upgrade The upgrade action.
1070 + * @param string $upgrade The upgrade action.
924 1071 * @param string $redirect_url Optional URL to redirect to after the upgrade.
925 1072 * @return void
926 1073 */
927 - protected function finish_upgrade( $upgrade, $redirect_url = '' ) {
1074 + protected function finish_upgrade( $upgrade = '', $redirect_url = '' ) {
928 1075 delete_option( 'charitable_doing_upgrade' );
929 1076
930 1077 $this->update_upgrade_log( $upgrade );
931 1078
932 1079 if ( empty( $redirect_url ) ) {
933 - $redirect_url = admin_url( 'index.php' );
1080 + if ( 'create_activity_tables' === $upgrade ) {
1081 + $redirect_url = admin_url( 'admin.php?page=charitable-dashboard' );
1082 + } else {
1083 + $redirect_url = admin_url( 'index.php' );
1084 + }
934 1085 }
935 1086
936 - wp_redirect( $redirect_url );
1087 + wp_safe_redirect( $redirect_url );
937 1088
938 1089 exit();
939 1090 }
940 1091
@@ -1003,16 +1154,37 @@
1003 1154 */
1004 1155 public function fix_donor_role_caps() {
1005 1156 remove_role( 'donor' );
1006 1157
1007 - add_role( 'donor', __( 'Donor', 'charitable' ), array(
1008 - 'read' => true,
1009 - ) );
1158 + add_role( 'donor', __( 'Donor', 'charitable' ), array( 'read' => true ) );
1010 1159
1011 1160 return true;
1012 1161 }
1013 1162
1014 1163 /**
1164 + * Update the unfiltered_html cap for Campaign Managers.
1165 + *
1166 + * @since 1.6.51
1167 + *
1168 + * @return true
1169 + */
1170 + public function remove_campaign_manager_unfiltered_html_cap() {
1171 + global $wp_roles;
1172 +
1173 + if ( class_exists( 'WP_Roles' ) ) {
1174 + if ( ! isset( $wp_roles ) ) {
1175 + $wp_roles = new WP_Roles(); // phpcs:ignore
1176 + }
1177 + }
1178 +
1179 + if ( is_object( $wp_roles ) ) {
1180 + $wp_roles->remove_cap( 'campaign_manager', 'unfiltered_html' );
1181 + }
1182 +
1183 + return true;
1184 + }
1185 +
1186 + /**
1015 1187 * Checks whether the site has donations missing a donor id.
1016 1188 *
1017 1189 * @since 1.6.5
1018 1190 *
@@ -1042,9 +1214,9 @@
1042 1214 FROM {$wpdb->prefix}charitable_campaign_donations
1043 1215 WHERE donor_id = 0
1044 1216 AND donation_id NOT IN ( $placeholders )";
1045 1217
1046 - return $wpdb->get_var( $wpdb->prepare( $sql, $skipped ) );
1218 + return $wpdb->get_var( $wpdb->prepare( $sql, $skipped ) ); // phpcs:ignore
1047 1219 }
1048 1220
1049 1221 /**
1050 1222 * Get donations with empty donor ids.
@@ -1058,21 +1230,32 @@
1058 1230 protected function get_empty_donor_id_donations( $skipped = array(), $number = 20 ) {
1059 1231 global $wpdb;
1060 1232
1061 1233 if ( empty( $skipped ) ) {
1062 - return $wpdb->get_col( "SELECT DISTINCT donation_id
1063 - FROM {$wpdb->prefix}charitable_campaign_donations
1064 - WHERE donor_id = 0
1065 - LIMIT $number;" );
1234 + return $wpdb->get_col( // phpcs:ignore
1235 + "SELECT DISTINCT donation_id
1236 + FROM {$wpdb->prefix}charitable_campaign_donations
1237 + WHERE donor_id = 0
1238 + LIMIT $number;" // phpcs:ignore
1239 + );
1066 1240 }
1067 1241
1068 - $placeholders = charitable_get_query_placeholders( count( $skipped ), '%d' );
1242 + $placeholders = charitable_get_query_placeholders( count( $skipped ), '%d' );
1069 1243
1070 - return $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT donation_id
1071 - FROM {$wpdb->prefix}charitable_campaign_donations
1072 - WHERE donor_id = 0
1073 - AND donation_id NOT IN ( $placeholders )
1074 - LIMIT $number;", $skipped ) );
1244 + // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
1245 + // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- $placeholders is built by charitable_get_query_placeholders() which returns safe placeholder strings like '%d, %d, %d'. Values are passed to prepare().
1246 + return $wpdb->get_col(
1247 + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $placeholders is dynamically built and sanitized
1248 + $wpdb->prepare(
1249 + "SELECT DISTINCT donation_id
1250 + FROM {$wpdb->prefix}charitable_campaign_donations
1251 + WHERE donor_id = 0
1252 + AND donation_id NOT IN ( $placeholders )
1253 + LIMIT %d;",
1254 + ...array_merge( $skipped, array( $number ) )
1255 + )
1256 + );
1257 + // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
1075 1258 }
1076 1259
1077 1260 /**
1078 1261 * Fix donations with missing donor ids.
@@ -1083,19 +1266,21 @@
1083 1266 */
1084 1267 public function fix_empty_donor_ids() {
1085 1268 global $wpdb;
1086 1269
1087 - if ( ! current_user_can( 'manage_charitable_settings' ) ) {
1088 - wp_die( __( 'You do not have permission to do Charitable upgrades', 'charitable' ), __( 'Error', 'charitable' ), array( 'response' => 403 ) );
1270 + if ( ! current_user_can( 'manage_charitable_settings' ) ) { // phpcs:ignore
1271 + wp_die( __( 'You do not have permission to do Charitable upgrades', 'charitable' ), __( 'Error', 'charitable' ), array( 'response' => 403 ) ); // phpcs:ignore
1089 1272 }
1090 1273
1091 1274 ignore_user_abort( true );
1092 1275
1093 - if ( ! charitable_is_func_disabled( 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) {
1094 - @set_time_limit( 0 );
1095 - }
1276 + if ( ! charitable_is_func_disabled( 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) {
1277 + @set_time_limit( 0 ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged
1278 + }
1096 1279
1097 - $step = array_key_exists( 'step', $_GET ) ? absint( $_GET['step'] ) : 1;
1280 + // phpcs:disable WordPress.Security.NonceVerification.Recommended
1281 + $step = array_key_exists( 'step', $_GET ) ? absint( sanitize_text_field( wp_unslash( $_GET['step'] ) ) ) : 1;
1282 + // phpcs:enable WordPress.Security.NonceVerification.Recommended
1098 1283 $number = 20;
1099 1284 $skipped = get_option( 'charitable_skipped_donations_with_empty_donor_id', array() );
1100 1285 $total = $this->count_empty_donor_id_donations( $skipped );
1101 1286
@@ -1124,20 +1309,26 @@
1124 1309
1125 1310 $donor_id = $donors_table->get_donor_id_by_email( $data['email'] );
1126 1311
1127 1312 if ( ! $donor_id ) {
1128 - $donor_id = $donors_table->insert( array(
1129 - 'email' => $data['email'],
1130 - 'first_name' => array_key_exists( 'first_name', $data ) ? $data['first_name'] : '',
1131 - 'last_name' => array_key_exists( 'last_name', $data ) ? $data['last_name'] : '',
1132 - 'date_joined' => get_post_field( 'post_date_gmt', $donation_id ),
1133 - ) );
1313 + $donor_id = $donors_table->insert(
1314 + array(
1315 + 'email' => $data['email'],
1316 + 'first_name' => array_key_exists( 'first_name', $data ) ? $data['first_name'] : '',
1317 + 'last_name' => array_key_exists( 'last_name', $data ) ? $data['last_name'] : '',
1318 + 'date_joined' => get_post_field( 'post_date_gmt', $donation_id ),
1319 + )
1320 + );
1134 1321 }
1135 1322
1136 1323 if ( $donor_id ) {
1137 - $donations_table->update( $donation_id, array(
1138 - 'donor_id' => $donor_id,
1139 - ), 'donation_id' );
1324 + $donations_table->update(
1325 + $donation_id,
1326 + array(
1327 + 'donor_id' => $donor_id,
1328 + ),
1329 + 'donation_id'
1330 + );
1140 1331 } else {
1141 1332 $skipped[] = $donation_id;
1142 1333 }
1143 1334 }//end foreach
@@ -1143,19 +1334,22 @@
1143 1334 }//end foreach
1144 1335
1145 1336 update_option( 'charitable_skipped_donations_with_empty_donor_id', $skipped );
1146 1337
1147 - $step++;
1338 + ++$step;
1148 1339
1149 - $redirect = add_query_arg( array(
1150 - 'charitable-upgrade' => 'fix_empty_donor_ids',
1151 - 'page' => 'charitable-upgrades',
1152 - 'step' => $step,
1153 - 'number' => $number,
1154 - 'total' => $total,
1155 - ), admin_url( 'index.php' ) );
1340 + $redirect = add_query_arg(
1341 + array(
1342 + 'charitable-upgrade' => 'fix_empty_donor_ids',
1343 + 'page' => 'charitable-upgrades',
1344 + 'step' => $step,
1345 + 'number' => $number,
1346 + 'total' => $total,
1347 + ),
1348 + admin_url( 'index.php' )
1349 + );
1156 1350
1157 - wp_redirect( $redirect );
1351 + wp_safe_redirect( $redirect );
1158 1352
1159 1353 exit;
1160 1354 }//end if
1161 1355
@@ -1164,8 +1358,73 @@
1164 1358 $this->finish_upgrade( 'fix_empty_donor_ids' );
1165 1359 }
1166 1360
1167 1361 /**
1362 + * Create donation and campaign activity tables.
1363 + *
1364 + * @since 1.8.1
1365 + *
1366 + * @return boolean Whether tables were successfully updated.
1367 + */
1368 + public function create_activity_tables() {
1369 + try {
1370 +
1371 + // test and verify nonce in the $_GET array.
1372 + if ( ! isset( $_GET['nonce'] ) || ! wp_verify_nonce( $_GET['nonce'], 'charitable-upgrade' ) ) { // phpcs:ignore
1373 + return false;
1374 + }
1375 +
1376 + charitable_get_table( 'donation_activities' )->create_table();
1377 + charitable_get_table( 'campaign_activities' )->create_table();
1378 +
1379 + $this->finish_upgrade( 'create_activity_tables' );
1380 +
1381 + return true;
1382 + } catch ( Exception $e ) {
1383 + return false;
1384 + }
1385 + }
1386 +
1387 + /**
1388 + * Create the charitable_logs table.
1389 + *
1390 + * Returns true so perform_immediate_upgrade() logs completion. We
1391 + * deliberately do NOT call finish_upgrade() here because it forces a
1392 + * wp_safe_redirect to the dashboard, which would interrupt whatever
1393 + * admin page the user was loading on the init-priority-5 firing.
1394 + *
1395 + * @since 1.8.11
1396 + *
1397 + * @return bool
1398 + */
1399 + public function create_logs_table() {
1400 + $db = new Charitable_Log_DB();
1401 + $db->create_table();
1402 +
1403 + return true;
1404 + }
1405 +
1406 + /**
1407 + * Defensive follow-up to create_logs_table for fresh installs where
1408 + * the upgrade flag was pre-marked but the table was never created.
1409 + *
1410 + * Same no-finish_upgrade rationale as create_logs_table.
1411 + *
1412 + * @since 1.8.11
1413 + *
1414 + * @return bool
1415 + */
1416 + public function verify_logs_table() {
1417 + $db = new Charitable_Log_DB();
1418 +
1419 + if ( ! $db->table_exists() ) {
1420 + $db->create_table();
1421 + }
1422 +
1423 + return true;
1424 + }
1425 +
1426 + /**
1168 1427 * HERE BE DEPRECATED FUNCTIONS...
1169 1428 *
1170 1429 * We're keeping these functions since Charitable add-ons extend this
1171 1430 * class and don't know whether those have upgraded.
@@ -1198,15 +1457,17 @@
1198 1457 * Before Charitable 1.3, upgrades were in a simple key=>value
1199 1458 * array format.
1200 1459 *
1201 1460 * $upgrade_actions = array(
1202 - * '1.0.1' => 'flush_permalinks',
1203 - * '1.1.0' => 'upgrade_1_1_0',
1204 - * '1.1.3' => 'flush_permalinks',
1205 - * '1.2.0' => 'flush_permalinks',
1461 + * '1.0.1' => 'flush_permalinks',
1462 + * '1.1.0' => 'upgrade_1_1_0',
1463 + * '1.1.3' => 'flush_permalinks',
1464 + * '1.2.0' => 'flush_permalinks',
1206 1465 * );
1207 1466 */
1208 1467
1468 + $this->init();
1469 +
1209 1470 if ( empty( $this->upgrade_actions ) || ! is_array( $this->upgrade_actions ) ) {
1210 1471 return;
1211 1472 }
1212 1473
@@ -1222,9 +1483,11 @@
1222 1483 }
1223 1484
1224 1485 if ( self::requires_upgrade( $this->db_version, $version ) ) {
1225 1486
1226 - call_user_func( array( $this, $method ) );
1487 + if ( method_exists( $this, $method ) ) {
1488 + $this->$method();
1489 + }
1227 1490
1228 1491 }
1229 1492 }
1230 1493 }
@@ -1243,11 +1506,11 @@
1243 1506 $log = array();
1244 1507 }
1245 1508
1246 1509 $log[] = array(
1247 - 'timestamp' => time(),
1248 - 'from' => $this->db_version,
1249 - 'to' => $this->edge_version,
1510 + 'timestamp' => time(),
1511 + 'from' => $this->db_version,
1512 + 'to' => $this->edge_version,
1250 1513 );
1251 1514
1252 1515 update_option( $this->upgrade_log_key, $log );
1253 1516 }
@@ -1260,8 +1523,119 @@
1260 1523 * @return void
1261 1524 */
1262 1525 protected function update_db_version() {
1263 1526 update_option( $this->version_key, $this->edge_version );
1527 + }
1528 +
1529 + /**
1530 + * Get the upgrade log key.
1531 + *
1532 + * @since 1.8.1
1533 + *
1534 + * @return string
1535 + */
1536 + public function get_upgrade_log_key() {
1537 + return $this->upgrade_log_key;
1538 + }
1539 +
1540 + /**
1541 + * Detect and lock the PayPal user tier on first 1.8.11 upgrade.
1542 + *
1543 + * Runs once at the 1.8.11 upgrade. Writes the option `charitable_paypal_user_tier`
1544 + * with value `legacy` or `commerce`. The value is permanent — future donations,
1545 + * gateway state changes, or imports do NOT re-tag the site.
1546 + *
1547 + * Tagging rule:
1548 + * - `legacy` → legacy `paypal` gateway is currently active OR
1549 + * any donation/recurring-donation post has
1550 + * `donation_gateway = 'paypal'` postmeta.
1551 + * - `commerce` → otherwise.
1552 + *
1553 + * The result is escapable at runtime via the `pre_option_charitable_paypal_user_tier`
1554 + * filter or by direct UPDATE on `wp_options`.
1555 + *
1556 + * @since 1.8.11
1557 + *
1558 + * @return boolean True after the upgrade is recorded.
1559 + */
1560 + public function detect_paypal_user_tier() {
1561 + $existing = get_option( 'charitable_paypal_user_tier', null );
1562 +
1563 + if ( null === $existing ) {
1564 + $tier = 'commerce';
1565 +
1566 + // Read active_gateways directly from charitable_settings — bypassing
1567 + // `Charitable_Gateways::is_active_gateway()` because that path runs
1568 + // `apply_paypal_tier_visibility()`, which strips `paypal` from the
1569 + // active set when the tier option is unset (default `commerce`).
1570 + // Without this direct read, the active-gateway detection branch
1571 + // would never fire, defeating the primary detection signal for
1572 + // sites with the legacy gateway active but no donation history.
1573 + $settings = get_option( 'charitable_settings', array() );
1574 + $active_gateways = ( ! empty( $settings['active_gateways'] ) && is_array( $settings['active_gateways'] ) )
1575 + ? $settings['active_gateways']
1576 + : array();
1577 +
1578 + if ( isset( $active_gateways['paypal'] ) ) {
1579 + $tier = 'legacy';
1580 + } else {
1581 + global $wpdb;
1582 +
1583 + $has_legacy_history = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
1584 + "SELECT 1 FROM {$wpdb->postmeta} pm
1585 + JOIN {$wpdb->posts} p ON pm.post_id = p.ID
1586 + WHERE pm.meta_key = 'donation_gateway'
1587 + AND pm.meta_value = 'paypal'
1588 + AND p.post_type IN ( 'donation', 'recurring_donation' )
1589 + LIMIT 1"
1590 + );
1591 +
1592 + if ( $has_legacy_history ) {
1593 + $tier = 'legacy';
1594 + }
1595 + }
1596 +
1597 + add_option( 'charitable_paypal_user_tier', $tier, '', 'yes' );
1598 + }
1599 +
1600 + $this->finish_upgrade( 'detect_paypal_user_tier' );
1601 +
1602 + return true;
1603 + }
1604 +
1605 + /**
1606 + * One-time push of the Charitable license_key so already-connected
1607 + * licensed Lite sites self-heal. Skips finish_upgrade() to avoid a
1608 + * redirect on init priority 5.
1609 + *
1610 + * @since 1.8.11.2
1611 + *
1612 + * @return bool
1613 + */
1614 + public function sync_paypal_license_to_middleware() {
1615 +
1616 + if ( ! function_exists( 'charitable_paypal_get_license_key' )
1617 + || ! function_exists( 'charitable_paypal_send_license_to_connected_modes' ) ) {
1618 + return true;
1619 + }
1620 +
1621 + $license_key = charitable_paypal_get_license_key();
1622 + if ( '' === $license_key ) {
1623 + return true;
1624 + }
1625 +
1626 + $settings = get_option( 'charitable_settings', array() );
1627 + $gw = isset( $settings['gateways_paypal_commerce'] ) && is_array( $settings['gateways_paypal_commerce'] )
1628 + ? $settings['gateways_paypal_commerce']
1629 + : array();
1630 + $has_seller = ! empty( $gw['live_seller_merchant_id'] ) || ! empty( $gw['sandbox_seller_merchant_id'] );
1631 + if ( ! $has_seller ) {
1632 + return true;
1633 + }
1634 +
1635 + charitable_paypal_send_license_to_connected_modes( $license_key );
1636 +
1637 + return true;
1264 1638 }
1265 1639 }
1266 1640
1267 1641 endif;