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 | includes/upgrades/class-charitable-upgrade.php +398 -118 1.7.0 → 1.8.13 View file →
@@ -4,9 +4,9 @@
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) 2022, WP Charitable LLC
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 11 * @version 1.6.51
12 12 */
@@ -118,9 +118,18 @@
118 118 */
119 119 if ( strlen( $db_version ) && strlen( $edge_version ) ) {
120 120 return $this->legacy_upgrade_mode( $db_version, $edge_version );
121 121 }
122 + }
122 123
124 + /**
125 + * Initialize the upgrade actions.
126 + *
127 + * @since 1.0.0
128 + *
129 + * @return void
130 + */
131 + public function init() {
123 132 $this->upgrade_actions = array(
124 133 'update_upgrade_system' => array(
125 134 'version' => '1.3.0',
126 135 'message' => __( 'Charitable needs to update the database.', 'charitable' ),
@@ -226,8 +235,37 @@
226 235 'message' => '',
227 236 'prompt' => false,
228 237 'callback' => array( $this, 'remove_campaign_manager_unfiltered_html_cap' ),
229 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 + ),
230 268 );
231 269 }
232 270
233 271 /**
@@ -273,8 +311,10 @@
273 311 'message' => __( 'Charitable was installed.', 'charitable' ),
274 312 ),
275 313 );
276 314
315 + $this->init();
316 +
277 317 foreach ( $this->upgrade_actions as $key => $notes ) {
278 318 $log[ $key ] = array(
279 319 'time' => time(),
280 320 'version' => Charitable::VERSION,
@@ -292,13 +332,13 @@
292 332 *
293 333 * @return void
294 334 */
295 335 public function add_upgrade_notice() {
296 - if ( isset( $_GET['page'] ) && 'charitable-upgrades' == $_GET['page'] ) {
336 + if ( isset( $_GET['page'] ) && 'charitable-upgrades' == $_GET['page'] ) { // phpcs:ignore
297 337 return;
298 338 }
299 339
300 - if ( ! current_user_can( 'manage_charitable_settings' ) ) {
340 + if ( ! current_user_can( 'manage_charitable_settings' ) ) { // phpcs:ignore
301 341 return;
302 342 }
303 343
304 344 /**
@@ -303,13 +343,13 @@
303 343
304 344 /**
305 345 * If an upgrade is still in progress, continue it until it's done.
306 346 */
307 - if ( ! Charitable_Upgrade::get_instance()->notice_shown_for_upgrade_in_progress() ) {
347 + if ( ! self::get_instance()->notice_shown_for_upgrade_in_progress() ) {
308 348 $upgrade_progress = $this->upgrade_is_in_progress();
309 349
310 350 if ( false !== $upgrade_progress ) {
311 - return Charitable_Upgrade::get_instance()->show_upgrade_in_progress_notice( $upgrade_progress );
351 + return self::get_instance()->show_upgrade_in_progress_notice( $upgrade_progress );
312 352 }
313 353 }
314 354
315 355 $this->walk_upgrade_actions( array( $this, 'show_upgrade_notice' ) );
@@ -317,9 +357,10 @@
317 357
318 358 /**
319 359 * Show upgrade notice for a particular upgrade notice.
320 360 *
321 - * @since 1.5.0
361 + * @since 1.5.0
362 + * @version 1.8.1 // added nonce.
322 363 *
323 364 * @param string $action The upgrade action key.
324 365 * @param array $upgrade Upgrade args.
325 366 * @return boolean
@@ -332,8 +373,12 @@
332 373 /* Check if we're just setting a transient to display a notice. */
333 374 if ( array_key_exists( 'notice', $upgrade ) ) {
334 375 return $this->set_update_notice_transient( $upgrade, $action );
335 376 }
377 +
378 + /* Add a nonce */
379 + $action .= '&nonce=' . wp_create_nonce( 'charitable-upgrade' );
380 +
336 381 ?>
337 382 <div class="updated">
338 383 <p>
339 384 <?php
@@ -338,12 +383,12 @@
338 383 <p>
339 384 <?php
340 385 printf(
341 386 '%s %s',
342 - $upgrade['message'],
387 + $upgrade['message'], // phpcs:ignore
343 388 sprintf(
344 389 /* translators: %s: upgrade link */
345 - __( 'Click <a href="%s">here</a> to start the upgrade.', 'charitable' ),
390 + __( 'Click <a href="%s">here</a> to start the upgrade.', 'charitable' ), // phpcs:ignore
346 391 esc_url( admin_url( 'index.php?page=charitable-upgrades&charitable-upgrade=' . $action ) )
347 392 )
348 393 );
349 394 ?>
@@ -348,9 +393,9 @@
348 393 );
349 394 ?>
350 395 </p>
351 396 </div>
352 -<?php
397 + <?php
353 398 }
354 399
355 400 /**
356 401 * Perform any immediate upgrades.
@@ -359,9 +404,9 @@
359 404 *
360 405 * @return void
361 406 */
362 407 public function do_immediate_upgrades() {
363 - if ( is_admin() && isset( $_GET['page'] ) && 'charitable-upgrades' == $_GET['page'] ) {
408 + if ( is_admin() && isset( $_GET['page'] ) && 'charitable-upgrades' == $_GET['page'] ) { // phpcs:ignore
364 409 return;
365 410 }
366 411
367 412 $this->walk_upgrade_actions( array( $this, 'perform_immediate_upgrade' ) );
@@ -377,9 +422,14 @@
377 422 * @return void
378 423 */
379 424 public function perform_immediate_upgrade( $action, $upgrade ) {
380 425 if ( $this->do_upgrade_immediately( $upgrade ) ) {
381 - $ret = call_user_func( $upgrade['callback'], $action );
426 + $callback = $upgrade['callback'];
427 + if ( is_callable( $callback ) ) {
428 + $ret = $callback( $action );
429 + } else {
430 + $ret = false;
431 + }
382 432
383 433 /* If the upgrade succeeded, update the log. */
384 434 if ( $ret ) {
385 435 $this->update_upgrade_log( $action );
@@ -390,9 +440,10 @@
390 440 /**
391 441 * Walk over the array of upgrade actions, performing the callback
392 442 * for any upgrades that have not been completed yet.
393 443 *
394 - * @since 1.5.0
444 + * @since 1.5.0
445 + * @version 1.8.5.6
395 446 *
396 447 * @param callback $callback Callback method to perform if the
397 448 * upgrade has not been completed.
398 449 * @return void
@@ -397,24 +448,33 @@
397 448 * upgrade has not been completed.
398 449 * @return void
399 450 */
400 451 protected function walk_upgrade_actions( $callback ) {
452 +
401 453 if ( ! is_callable( $callback ) ) {
402 454 return;
403 455 }
404 456
457 + if ( $this->upgrade_log_key === 'charitable_upgrade_log' ) {
458 + $this->init();
459 + }
460 +
405 461 foreach ( $this->upgrade_actions as $action => $upgrade ) {
406 462
407 463 if ( ! $this->upgrade_has_been_completed( $action ) ) {
408 464
409 465 /* If the upgrade has an active_callback, check whether the upgrade is required. If not, mark it as done. */
410 - if ( array_key_exists( 'active_callback', $upgrade ) && ! call_user_func( $upgrade['active_callback'] ) ) {
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 ) {
411 469 $this->update_upgrade_log( $action );
412 470 continue;
413 471 }
414 472
415 - call_user_func( $callback, $action, $upgrade );
473 + $callback( $action, $upgrade );
474 +
416 475 }
476 +
417 477 }
418 478 }
419 479
420 480 /**
@@ -427,9 +487,8 @@
427 487 * @return boolean
428 488 */
429 489 public function upgrade_has_been_completed( $action ) {
430 490 $log = get_option( $this->upgrade_log_key );
431 -
432 491 return is_array( $log ) && array_key_exists( $action, $log );
433 492 }
434 493
435 494
@@ -453,24 +512,24 @@
453 512 * @return void
454 513 */
455 514 public function show_upgrade_in_progress_notice( $upgrade_progress ) {
456 515 /* Fixes a bug that incorrectly set the page as charitable-upgrade */
457 - if ( isset( $upgrade_progress['page'] ) && 'charitable-upgrade' == $upgrade_progress['page'] ) {
516 + if ( isset( $upgrade_progress['page'] ) && 'charitable-upgrade' == $upgrade_progress['page'] ) { //phpcs:ignore
458 517 $upgrade_progress['page'] = 'charitable-upgrades';
459 518 }
460 -?>
519 + ?>
461 520 <div class="error">
462 521 <p>
463 522 <?php
464 523 printf(
465 524 /* translators: %s: upgrade link */
466 - __( 'Charitable needs to complete an upgrade that was started earlier. Click <a href="%s">here</a> to continue the upgrade.', 'charitable' ),
525 + __( 'Charitable needs to complete an upgrade that was started earlier. Click <a href="%s">here</a> to continue the upgrade.', 'charitable' ), // phpcs:ignore
467 526 esc_url( add_query_arg( $upgrade_progress, admin_url( 'index.php' ) ) )
468 527 );
469 528 ?>
470 529 </p>
471 530 </div>
472 -<?php
531 + <?php
473 532 $this->notice_shown_for_upgrade_in_progress = true;
474 533 }
475 534
476 535 /**
@@ -540,21 +599,21 @@
540 599 * @return void
541 600 */
542 601 public function update_upgrade_system() {
543 602 if ( ! current_user_can( 'manage_charitable_settings' ) ) {
544 - 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
545 604 }
546 605
547 606 ignore_user_abort( true );
548 607
549 - if ( ! charitable_is_func_disabled( 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) {
550 - @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
551 610 }
552 611
553 612 /**
554 613 * Update the campaign donations table to use DECIMAL for amounts.
555 614 *
556 - * @see https://github.com/Charitable/Charitable/issues/56
615 + * @see https://github.com/Charitable/Charitable/issues/56
557 616 */
558 617 $table = new Charitable_Campaign_Donations_DB();
559 618 $table->create_table();
560 619
@@ -575,21 +634,23 @@
575 634 * @return void
576 635 */
577 636 public function fix_donation_dates() {
578 637 if ( ! current_user_can( 'manage_charitable_settings' ) ) {
579 - 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
580 639 }
581 640
582 641 ignore_user_abort( true );
583 642
584 - if ( ! charitable_is_func_disabled( 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) {
585 - @set_time_limit( 0 );
586 - }
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 + }
587 646
588 - $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
589 650 $number = 20;
590 651
591 - $total = Charitable_Donations::count_all();
652 + $total = Charitable_Donations::count_all();
592 653
593 654 /**
594 655 * If there are no donations to update, go ahead and wrap it up right now.
595 656 */
@@ -596,14 +657,16 @@
596 657 if ( ! $total ) {
597 658 $this->finish_upgrade( 'fix_donation_dates' );
598 659 }
599 660
600 - $donations = get_posts( array(
601 - 'post_type' => Charitable::DONATION_POST_TYPE,
602 - 'posts_per_page' => $number,
603 - 'paged' => $step,
604 - 'post_status' => array_keys( charitable_get_valid_donation_statuses() ),
605 - ) );
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 + );
606 669
607 670 if ( count( $donations ) ) {
608 671
609 672 /**
@@ -634,26 +697,31 @@
634 697 }
635 698
636 699 $date = get_date_from_gmt( $date_gmt );
637 700
638 - wp_update_post( array(
639 - 'ID' => $donation->ID,
640 - 'post_date' => $date,
641 - 'post_date_gmt' => $date_gmt,
642 - ) );
701 + wp_update_post(
702 + array(
703 + 'ID' => $donation->ID,
704 + 'post_date' => $date,
705 + 'post_date_gmt' => $date_gmt,
706 + )
707 + );
643 708 }//end foreach
644 709
645 - $step++;
710 + ++$step;
646 711
647 - $redirect = add_query_arg( array(
648 - 'page' => 'charitable-upgrades',
649 - 'charitable-upgrade' => 'fix_donation_dates',
650 - 'step' => $step,
651 - 'number' => $number,
652 - 'total' => $total,
653 - ), 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 + );
654 722
655 - wp_redirect( $redirect );
723 + wp_safe_redirect( $redirect );
656 724
657 725 exit;
658 726 }//end if
659 727
@@ -677,23 +745,25 @@
677 745 public function remove_duplicate_donors() {
678 746 global $wpdb;
679 747
680 748 if ( ! current_user_can( 'manage_charitable_settings' ) ) {
681 - 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
682 750 }
683 751
684 752 ignore_user_abort( true );
685 753
686 - if ( ! charitable_is_func_disabled( 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) {
687 - @set_time_limit( 0 );
688 - }
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 + }
689 757
690 - $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
691 761 $number = 20;
692 762 $subquery = "SELECT GROUP_CONCAT(donor_id, ':', user_id) AS donor, COUNT(*) AS count
693 763 FROM {$wpdb->prefix}charitable_donors
694 764 GROUP BY email";
695 - $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
696 766
697 767 /**
698 768 * If there are no donors left to remove, go ahead and wrap it up right now.
699 769 */
@@ -700,9 +770,9 @@
700 770 if ( ! $total ) {
701 771 $this->finish_upgrade( 'remove_duplicate_donors' );
702 772 }
703 773
704 - $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
705 775
706 776 if ( count( $donors ) ) {
707 777
708 778 foreach ( $donors as $donor ) {
@@ -742,27 +812,30 @@
742 812 SET donor_id = %d
743 813 WHERE donor_id IN ( {$donor_id_placeholders} )";
744 814
745 815 $parameters = array_merge( array( $canonical ), $duplicates );
746 - $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
747 817
748 818 if ( false !== $updated ) {
749 819 $sql = "DELETE FROM {$wpdb->prefix}charitable_donors WHERE donor_id IN ( {$donor_id_placeholders} );";
750 - $deleted = $wpdb->query( $wpdb->prepare( $sql, $duplicates ) );
820 + $deleted = $wpdb->query( $wpdb->prepare( $sql, $duplicates ) ); // phpcs:ignore
751 821 }
752 822 }//end foreach
753 823
754 - $step++;
824 + ++$step;
755 825
756 - $redirect = add_query_arg( array(
757 - 'charitable-upgrade' => 'remove_duplicate_donors',
758 - 'page' => 'charitable-upgrades',
759 - 'step' => $step,
760 - 'number' => $number,
761 - 'total' => $total,
762 - ), 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 + );
763 836
764 - wp_redirect( $redirect );
837 + wp_safe_redirect( $redirect );
765 838
766 839 exit;
767 840 }//end if
768 841
@@ -833,14 +906,14 @@
833 906 * @global WP_Roles
834 907 * @param string $action The upgrade action.
835 908 * @return true Will always return true.
836 909 */
837 - public function remove_campaign_manager_cap( $action ) {
910 + public function remove_campaign_manager_cap( $action ) { // phpcs:ignore
838 911 global $wp_roles;
839 912
840 913 if ( class_exists( 'WP_Roles' ) ) {
841 914 if ( ! isset( $wp_roles ) ) {
842 - $wp_roles = new WP_Roles();
915 + $wp_roles = new WP_Roles(); // phpcs:ignore
843 916 }
844 917 }
845 918
846 919 if ( is_object( $wp_roles ) ) {
@@ -858,9 +931,9 @@
858 931 * @global WPDB $wpdb
859 932 * @param string $action The upgrade action.
860 933 * @return boolean Whether the query was successfully executed.
861 934 */
862 - public function fix_empty_campaign_end_date_meta( $action ) {
935 + public function fix_empty_campaign_end_date_meta( $action ) { // phpcs:ignore
863 936 global $wpdb;
864 937
865 938 $sql = "UPDATE $wpdb->postmeta
866 939 INNER JOIN $wpdb->posts
@@ -869,9 +942,9 @@
869 942 WHERE $wpdb->postmeta.meta_key = '_campaign_end_date'
870 943 AND $wpdb->postmeta.meta_value = ''
871 944 AND $wpdb->posts.post_type = 'campaign';";
872 945
873 - $ret = $wpdb->query( $sql );
946 + $ret = $wpdb->query( $sql ); // phpcs:ignore
874 947
875 948 return false !== $ret;
876 949 }
877 950
@@ -883,15 +956,15 @@
883 956 * @global WPDB $wpdb
884 957 * @param string $action The upgrade action.
885 958 * @return boolean Whether the event was scheduled.
886 959 */
887 - public function clear_campaign_amount_donated_transient( $action ) {
960 + public function clear_campaign_amount_donated_transient( $action ) { // phpcs:ignore
888 961 global $wpdb;
889 962
890 963 $sql = "DELETE FROM $wpdb->options
891 964 WHERE option_name LIKE '_transient_charitable_campaign_%_donation_amount'";
892 965
893 - $ret = $wpdb->query( $sql );
966 + $ret = $wpdb->query( $sql ); // phpcs:ignore
894 967
895 968 return false !== $ret;
896 969 }
897 970
@@ -991,23 +1064,28 @@
991 1064 /**
992 1065 * Finish an upgrade. This clears the charitable_doing_upgrade setting and updates the log.
993 1066 *
994 1067 * @since 1.3.0
1068 + * @since 1.8.1 Added redirect url for dashboard for create_activity_tables upgrade.
995 1069 *
996 1070 * @param string $upgrade The upgrade action.
997 1071 * @param string $redirect_url Optional URL to redirect to after the upgrade.
998 1072 * @return void
999 1073 */
1000 - protected function finish_upgrade( $upgrade, $redirect_url = '' ) {
1074 + protected function finish_upgrade( $upgrade = '', $redirect_url = '' ) {
1001 1075 delete_option( 'charitable_doing_upgrade' );
1002 1076
1003 1077 $this->update_upgrade_log( $upgrade );
1004 1078
1005 1079 if ( empty( $redirect_url ) ) {
1006 - $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 + }
1007 1085 }
1008 1086
1009 - wp_redirect( $redirect_url );
1087 + wp_safe_redirect( $redirect_url );
1010 1088
1011 1089 exit();
1012 1090 }
1013 1091
@@ -1093,9 +1171,9 @@
1093 1171 global $wp_roles;
1094 1172
1095 1173 if ( class_exists( 'WP_Roles' ) ) {
1096 1174 if ( ! isset( $wp_roles ) ) {
1097 - $wp_roles = new WP_Roles();
1175 + $wp_roles = new WP_Roles(); // phpcs:ignore
1098 1176 }
1099 1177 }
1100 1178
1101 1179 if ( is_object( $wp_roles ) ) {
@@ -1136,9 +1214,9 @@
1136 1214 FROM {$wpdb->prefix}charitable_campaign_donations
1137 1215 WHERE donor_id = 0
1138 1216 AND donation_id NOT IN ( $placeholders )";
1139 1217
1140 - return $wpdb->get_var( $wpdb->prepare( $sql, $skipped ) );
1218 + return $wpdb->get_var( $wpdb->prepare( $sql, $skipped ) ); // phpcs:ignore
1141 1219 }
1142 1220
1143 1221 /**
1144 1222 * Get donations with empty donor ids.
@@ -1152,21 +1230,32 @@
1152 1230 protected function get_empty_donor_id_donations( $skipped = array(), $number = 20 ) {
1153 1231 global $wpdb;
1154 1232
1155 1233 if ( empty( $skipped ) ) {
1156 - return $wpdb->get_col( "SELECT DISTINCT donation_id
1234 + return $wpdb->get_col( // phpcs:ignore
1235 + "SELECT DISTINCT donation_id
1157 1236 FROM {$wpdb->prefix}charitable_campaign_donations
1158 1237 WHERE donor_id = 0
1159 - LIMIT $number;" );
1238 + LIMIT $number;" // phpcs:ignore
1239 + );
1160 1240 }
1161 1241
1162 - $placeholders = charitable_get_query_placeholders( count( $skipped ), '%d' );
1242 + $placeholders = charitable_get_query_placeholders( count( $skipped ), '%d' );
1163 1243
1164 - return $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT donation_id
1165 - FROM {$wpdb->prefix}charitable_campaign_donations
1166 - WHERE donor_id = 0
1167 - AND donation_id NOT IN ( $placeholders )
1168 - 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
1169 1258 }
1170 1259
1171 1260 /**
1172 1261 * Fix donations with missing donor ids.
@@ -1177,19 +1266,21 @@
1177 1266 */
1178 1267 public function fix_empty_donor_ids() {
1179 1268 global $wpdb;
1180 1269
1181 - if ( ! current_user_can( 'manage_charitable_settings' ) ) {
1182 - 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
1183 1272 }
1184 1273
1185 1274 ignore_user_abort( true );
1186 1275
1187 - if ( ! charitable_is_func_disabled( 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) {
1188 - @set_time_limit( 0 );
1189 - }
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 + }
1190 1279
1191 - $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
1192 1283 $number = 20;
1193 1284 $skipped = get_option( 'charitable_skipped_donations_with_empty_donor_id', array() );
1194 1285 $total = $this->count_empty_donor_id_donations( $skipped );
1195 1286
@@ -1218,20 +1309,26 @@
1218 1309
1219 1310 $donor_id = $donors_table->get_donor_id_by_email( $data['email'] );
1220 1311
1221 1312 if ( ! $donor_id ) {
1222 - $donor_id = $donors_table->insert( array(
1223 - 'email' => $data['email'],
1224 - 'first_name' => array_key_exists( 'first_name', $data ) ? $data['first_name'] : '',
1225 - 'last_name' => array_key_exists( 'last_name', $data ) ? $data['last_name'] : '',
1226 - 'date_joined' => get_post_field( 'post_date_gmt', $donation_id ),
1227 - ) );
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 + );
1228 1321 }
1229 1322
1230 1323 if ( $donor_id ) {
1231 - $donations_table->update( $donation_id, array(
1232 - 'donor_id' => $donor_id,
1233 - ), 'donation_id' );
1324 + $donations_table->update(
1325 + $donation_id,
1326 + array(
1327 + 'donor_id' => $donor_id,
1328 + ),
1329 + 'donation_id'
1330 + );
1234 1331 } else {
1235 1332 $skipped[] = $donation_id;
1236 1333 }
1237 1334 }//end foreach
@@ -1237,19 +1334,22 @@
1237 1334 }//end foreach
1238 1335
1239 1336 update_option( 'charitable_skipped_donations_with_empty_donor_id', $skipped );
1240 1337
1241 - $step++;
1338 + ++$step;
1242 1339
1243 - $redirect = add_query_arg( array(
1244 - 'charitable-upgrade' => 'fix_empty_donor_ids',
1245 - 'page' => 'charitable-upgrades',
1246 - 'step' => $step,
1247 - 'number' => $number,
1248 - 'total' => $total,
1249 - ), 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 + );
1250 1350
1251 - wp_redirect( $redirect );
1351 + wp_safe_redirect( $redirect );
1252 1352
1253 1353 exit;
1254 1354 }//end if
1255 1355
@@ -1258,8 +1358,73 @@
1258 1358 $this->finish_upgrade( 'fix_empty_donor_ids' );
1259 1359 }
1260 1360
1261 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 + /**
1262 1427 * HERE BE DEPRECATED FUNCTIONS...
1263 1428 *
1264 1429 * We're keeping these functions since Charitable add-ons extend this
1265 1430 * class and don't know whether those have upgraded.
@@ -1292,15 +1457,17 @@
1292 1457 * Before Charitable 1.3, upgrades were in a simple key=>value
1293 1458 * array format.
1294 1459 *
1295 1460 * $upgrade_actions = array(
1296 - * '1.0.1' => 'flush_permalinks',
1297 - * '1.1.0' => 'upgrade_1_1_0',
1298 - * '1.1.3' => 'flush_permalinks',
1299 - * '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',
1300 1465 * );
1301 1466 */
1302 1467
1468 + $this->init();
1469 +
1303 1470 if ( empty( $this->upgrade_actions ) || ! is_array( $this->upgrade_actions ) ) {
1304 1471 return;
1305 1472 }
1306 1473
@@ -1316,9 +1483,11 @@
1316 1483 }
1317 1484
1318 1485 if ( self::requires_upgrade( $this->db_version, $version ) ) {
1319 1486
1320 - call_user_func( array( $this, $method ) );
1487 + if ( method_exists( $this, $method ) ) {
1488 + $this->$method();
1489 + }
1321 1490
1322 1491 }
1323 1492 }
1324 1493 }
@@ -1337,11 +1506,11 @@
1337 1506 $log = array();
1338 1507 }
1339 1508
1340 1509 $log[] = array(
1341 - 'timestamp' => time(),
1342 - 'from' => $this->db_version,
1343 - 'to' => $this->edge_version,
1510 + 'timestamp' => time(),
1511 + 'from' => $this->db_version,
1512 + 'to' => $this->edge_version,
1344 1513 );
1345 1514
1346 1515 update_option( $this->upgrade_log_key, $log );
1347 1516 }
@@ -1354,8 +1523,119 @@
1354 1523 * @return void
1355 1524 */
1356 1525 protected function update_db_version() {
1357 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;
1358 1638 }
1359 1639 }
1360 1640
1361 1641 endif;