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 -146 1.6.7 → 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,30 +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>
291 - <?php
292 - printf(
293 - /* translators: %s: upgrade link */
294 - __( 'Charitable needs to complete an upgrade that was started earlier. Click <a href="%s">here</a> to continue the upgrade.', 'charitable' ),
295 - esc_url( add_query_arg( $upgrade_progress, admin_url( 'index.php' ) ) )
296 - );
297 - ?>
298 - </p>
299 - </div>
300 -<?php
301 - return;
302 353 }
303 354
304 355 $this->walk_upgrade_actions( array( $this, 'show_upgrade_notice' ) );
305 356 }
@@ -306,9 +357,10 @@
306 357
307 358 /**
308 359 * Show upgrade notice for a particular upgrade notice.
309 360 *
310 - * @since 1.5.0
361 + * @since 1.5.0
362 + * @version 1.8.1 // added nonce.
311 363 *
312 364 * @param string $action The upgrade action key.
313 365 * @param array $upgrade Upgrade args.
314 366 * @return boolean
@@ -322,14 +374,28 @@
322 374 if ( array_key_exists( 'notice', $upgrade ) ) {
323 375 return $this->set_update_notice_transient( $upgrade, $action );
324 376 }
325 377
326 -?>
378 + /* Add a nonce */
379 + $action .= '&nonce=' . wp_create_nonce( 'charitable-upgrade' );
380 +
381 + ?>
327 382 <div class="updated">
328 - <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 + ?>
329 395 </p>
330 396 </div>
331 -<?php
397 + <?php
332 398 }
333 399
334 400 /**
335 401 * Perform any immediate upgrades.
@@ -338,9 +404,9 @@
338 404 *
339 405 * @return void
340 406 */
341 407 public function do_immediate_upgrades() {
342 - if ( is_admin() && isset( $_GET['page'] ) && 'charitable-upgrades' == $_GET['page'] ) {
408 + if ( is_admin() && isset( $_GET['page'] ) && 'charitable-upgrades' == $_GET['page'] ) { // phpcs:ignore
343 409 return;
344 410 }
345 411
346 412 $this->walk_upgrade_actions( array( $this, 'perform_immediate_upgrade' ) );
@@ -350,13 +416,20 @@
350 416 * Perform an immediate upgrade and log the result.
351 417 *
352 418 * @since 1.5.0
353 419 *
420 + * @param string $action The upgrade's action key.
421 + * @param array $upgrade Upgrade details.
354 422 * @return void
355 423 */
356 424 public function perform_immediate_upgrade( $action, $upgrade ) {
357 - if ( $this->do_upgrade_immediately( $upgrade ) ) {
358 - $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 + }
359 432
360 433 /* If the upgrade succeeded, update the log. */
361 434 if ( $ret ) {
362 435 $this->update_upgrade_log( $action );
@@ -367,9 +440,10 @@
367 440 /**
368 441 * Walk over the array of upgrade actions, performing the callback
369 442 * for any upgrades that have not been completed yet.
370 443 *
371 - * @since 1.5.0
444 + * @since 1.5.0
445 + * @version 1.8.5.6
372 446 *
373 447 * @param callback $callback Callback method to perform if the
374 448 * upgrade has not been completed.
375 449 * @return void
@@ -374,25 +448,35 @@
374 448 * upgrade has not been completed.
375 449 * @return void
376 450 */
377 451 protected function walk_upgrade_actions( $callback ) {
452 +
378 453 if ( ! is_callable( $callback ) ) {
379 454 return;
380 455 }
381 456
457 + if ( $this->upgrade_log_key === 'charitable_upgrade_log' ) {
458 + $this->init();
459 + }
460 +
382 461 foreach ( $this->upgrade_actions as $action => $upgrade ) {
383 462
384 - /* If the upgrade has an active_callback, check whether the upgrade is required. If not, mark it as done. */
385 - if ( array_key_exists( 'active_callback', $upgrade ) && ! call_user_func( $upgrade['active_callback'] ) ) {
386 - $this->update_upgrade_log( $action );
387 - 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 +
388 475 }
389 476
390 - if ( ! $this->upgrade_has_been_completed( $action ) ) {
391 - call_user_func( $callback, $action, $upgrade );
392 - }
393 477 }
394 - }
478 + }
395 479
396 480 /**
397 481 * Checks whether an upgrade has been completed.
398 482 *
@@ -403,12 +487,53 @@
403 487 * @return boolean
404 488 */
405 489 public function upgrade_has_been_completed( $action ) {
406 490 $log = get_option( $this->upgrade_log_key );
491 + return is_array( $log ) && array_key_exists( $action, $log );
492 + }
407 493
408 - 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;
409 504 }
505 +
410 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 + /**
411 536 * Evaluates two version numbers and determines whether an upgrade is
412 537 * required for version A to get to version B.
413 538 *
414 539 * @since 1.0.0
@@ -474,21 +599,21 @@
474 599 * @return void
475 600 */
476 601 public function update_upgrade_system() {
477 602 if ( ! current_user_can( 'manage_charitable_settings' ) ) {
478 - 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
479 604 }
480 605
481 606 ignore_user_abort( true );
482 607
483 - if ( ! charitable_is_func_disabled( 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) {
484 - @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
485 610 }
486 611
487 612 /**
488 613 * Update the campaign donations table to use DECIMAL for amounts.
489 614 *
490 - * @see https://github.com/Charitable/Charitable/issues/56
615 + * @see https://github.com/Charitable/Charitable/issues/56
491 616 */
492 617 $table = new Charitable_Campaign_Donations_DB();
493 618 $table->create_table();
494 619
@@ -509,21 +634,23 @@
509 634 * @return void
510 635 */
511 636 public function fix_donation_dates() {
512 637 if ( ! current_user_can( 'manage_charitable_settings' ) ) {
513 - 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
514 639 }
515 640
516 641 ignore_user_abort( true );
517 642
518 - if ( ! charitable_is_func_disabled( 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) {
519 - @set_time_limit( 0 );
520 - }
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 + }
521 646
522 - $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
523 650 $number = 20;
524 651
525 - $total = Charitable_Donations::count_all();
652 + $total = Charitable_Donations::count_all();
526 653
527 654 /**
528 655 * If there are no donations to update, go ahead and wrap it up right now.
529 656 */
@@ -530,14 +657,16 @@
530 657 if ( ! $total ) {
531 658 $this->finish_upgrade( 'fix_donation_dates' );
532 659 }
533 660
534 - $donations = get_posts( array(
535 - 'post_type' => Charitable::DONATION_POST_TYPE,
536 - 'posts_per_page' => $number,
537 - 'paged' => $step,
538 - 'post_status' => array_keys( charitable_get_valid_donation_statuses() ),
539 - ) );
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 + );
540 669
541 670 if ( count( $donations ) ) {
542 671
543 672 /**
@@ -568,26 +697,31 @@
568 697 }
569 698
570 699 $date = get_date_from_gmt( $date_gmt );
571 700
572 - wp_update_post( array(
573 - 'ID' => $donation->ID,
574 - 'post_date' => $date,
575 - 'post_date_gmt' => $date_gmt,
576 - ) );
701 + wp_update_post(
702 + array(
703 + 'ID' => $donation->ID,
704 + 'post_date' => $date,
705 + 'post_date_gmt' => $date_gmt,
706 + )
707 + );
577 708 }//end foreach
578 709
579 - $step++;
710 + ++$step;
580 711
581 - $redirect = add_query_arg( array(
582 - 'page' => 'charitable-upgrades',
583 - 'charitable-upgrade' => 'fix_donation_dates',
584 - 'step' => $step,
585 - 'number' => $number,
586 - 'total' => $total,
587 - ), 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 + );
588 722
589 - wp_redirect( $redirect );
723 + wp_safe_redirect( $redirect );
590 724
591 725 exit;
592 726 }//end if
593 727
@@ -611,23 +745,25 @@
611 745 public function remove_duplicate_donors() {
612 746 global $wpdb;
613 747
614 748 if ( ! current_user_can( 'manage_charitable_settings' ) ) {
615 - 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
616 750 }
617 751
618 752 ignore_user_abort( true );
619 753
620 - if ( ! charitable_is_func_disabled( 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) {
621 - @set_time_limit( 0 );
622 - }
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 + }
623 757
624 - $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
625 761 $number = 20;
626 762 $subquery = "SELECT GROUP_CONCAT(donor_id, ':', user_id) AS donor, COUNT(*) AS count
627 763 FROM {$wpdb->prefix}charitable_donors
628 764 GROUP BY email";
629 - $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
630 766
631 767 /**
632 768 * If there are no donors left to remove, go ahead and wrap it up right now.
633 769 */
@@ -634,9 +770,9 @@
634 770 if ( ! $total ) {
635 771 $this->finish_upgrade( 'remove_duplicate_donors' );
636 772 }
637 773
638 - $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
639 775
640 776 if ( count( $donors ) ) {
641 777
642 778 foreach ( $donors as $donor ) {
@@ -676,27 +812,30 @@
676 812 SET donor_id = %d
677 813 WHERE donor_id IN ( {$donor_id_placeholders} )";
678 814
679 815 $parameters = array_merge( array( $canonical ), $duplicates );
680 - $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
681 817
682 818 if ( false !== $updated ) {
683 819 $sql = "DELETE FROM {$wpdb->prefix}charitable_donors WHERE donor_id IN ( {$donor_id_placeholders} );";
684 - $deleted = $wpdb->query( $wpdb->prepare( $sql, $duplicates ) );
820 + $deleted = $wpdb->query( $wpdb->prepare( $sql, $duplicates ) ); // phpcs:ignore
685 821 }
686 822 }//end foreach
687 823
688 - $step++;
824 + ++$step;
689 825
690 - $redirect = add_query_arg( array(
691 - 'charitable-upgrade' => 'remove_duplicate_donors',
692 - 'page' => 'charitable-upgrades',
693 - 'step' => $step,
694 - 'number' => $number,
695 - 'total' => $total,
696 - ), 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 + );
697 836
698 - wp_redirect( $redirect );
837 + wp_safe_redirect( $redirect );
699 838
700 839 exit;
701 840 }//end if
702 841
@@ -759,9 +898,9 @@
759 898 }
760 899 }
761 900
762 901 /**
763 - * Remove the 'manage_charitable_settings' cap from the Campaign Manager role.
902 + * Remove the 'manage_charitable_settings' cap from the Campaign Manager role.
764 903 *
765 904 * @since 1.4.5
766 905 *
767 906 * @global WP_Roles
@@ -767,14 +906,14 @@
767 906 * @global WP_Roles
768 907 * @param string $action The upgrade action.
769 908 * @return true Will always return true.
770 909 */
771 - public function remove_campaign_manager_cap( $action ) {
910 + public function remove_campaign_manager_cap( $action ) { // phpcs:ignore
772 911 global $wp_roles;
773 912
774 913 if ( class_exists( 'WP_Roles' ) ) {
775 914 if ( ! isset( $wp_roles ) ) {
776 - $wp_roles = new WP_Roles();
915 + $wp_roles = new WP_Roles(); // phpcs:ignore
777 916 }
778 917 }
779 918
780 919 if ( is_object( $wp_roles ) ) {
@@ -792,9 +931,9 @@
792 931 * @global WPDB $wpdb
793 932 * @param string $action The upgrade action.
794 933 * @return boolean Whether the query was successfully executed.
795 934 */
796 - public function fix_empty_campaign_end_date_meta( $action ) {
935 + public function fix_empty_campaign_end_date_meta( $action ) { // phpcs:ignore
797 936 global $wpdb;
798 937
799 938 $sql = "UPDATE $wpdb->postmeta
800 939 INNER JOIN $wpdb->posts
@@ -803,9 +942,9 @@
803 942 WHERE $wpdb->postmeta.meta_key = '_campaign_end_date'
804 943 AND $wpdb->postmeta.meta_value = ''
805 944 AND $wpdb->posts.post_type = 'campaign';";
806 945
807 - $ret = $wpdb->query( $sql );
946 + $ret = $wpdb->query( $sql ); // phpcs:ignore
808 947
809 948 return false !== $ret;
810 949 }
811 950
@@ -817,15 +956,15 @@
817 956 * @global WPDB $wpdb
818 957 * @param string $action The upgrade action.
819 958 * @return boolean Whether the event was scheduled.
820 959 */
821 - public function clear_campaign_amount_donated_transient( $action ) {
960 + public function clear_campaign_amount_donated_transient( $action ) { // phpcs:ignore
822 961 global $wpdb;
823 962
824 963 $sql = "DELETE FROM $wpdb->options
825 964 WHERE option_name LIKE '_transient_charitable_campaign_%_donation_amount'";
826 965
827 - $ret = $wpdb->query( $sql );
966 + $ret = $wpdb->query( $sql ); // phpcs:ignore
828 967
829 968 return false !== $ret;
830 969 }
831 970
@@ -925,23 +1064,28 @@
925 1064 /**
926 1065 * Finish an upgrade. This clears the charitable_doing_upgrade setting and updates the log.
927 1066 *
928 1067 * @since 1.3.0
1068 + * @since 1.8.1 Added redirect url for dashboard for create_activity_tables upgrade.
929 1069 *
930 - * @param string $upgrade The upgrade action.
1070 + * @param string $upgrade The upgrade action.
931 1071 * @param string $redirect_url Optional URL to redirect to after the upgrade.
932 1072 * @return void
933 1073 */
934 - protected function finish_upgrade( $upgrade, $redirect_url = '' ) {
1074 + protected function finish_upgrade( $upgrade = '', $redirect_url = '' ) {
935 1075 delete_option( 'charitable_doing_upgrade' );
936 1076
937 1077 $this->update_upgrade_log( $upgrade );
938 1078
939 1079 if ( empty( $redirect_url ) ) {
940 - $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 + }
941 1085 }
942 1086
943 - wp_redirect( $redirect_url );
1087 + wp_safe_redirect( $redirect_url );
944 1088
945 1089 exit();
946 1090 }
947 1091
@@ -1010,16 +1154,37 @@
1010 1154 */
1011 1155 public function fix_donor_role_caps() {
1012 1156 remove_role( 'donor' );
1013 1157
1014 - add_role( 'donor', __( 'Donor', 'charitable' ), array(
1015 - 'read' => true,
1016 - ) );
1158 + add_role( 'donor', __( 'Donor', 'charitable' ), array( 'read' => true ) );
1017 1159
1018 1160 return true;
1019 1161 }
1020 1162
1021 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 + /**
1022 1187 * Checks whether the site has donations missing a donor id.
1023 1188 *
1024 1189 * @since 1.6.5
1025 1190 *
@@ -1049,9 +1214,9 @@
1049 1214 FROM {$wpdb->prefix}charitable_campaign_donations
1050 1215 WHERE donor_id = 0
1051 1216 AND donation_id NOT IN ( $placeholders )";
1052 1217
1053 - return $wpdb->get_var( $wpdb->prepare( $sql, $skipped ) );
1218 + return $wpdb->get_var( $wpdb->prepare( $sql, $skipped ) ); // phpcs:ignore
1054 1219 }
1055 1220
1056 1221 /**
1057 1222 * Get donations with empty donor ids.
@@ -1065,21 +1230,32 @@
1065 1230 protected function get_empty_donor_id_donations( $skipped = array(), $number = 20 ) {
1066 1231 global $wpdb;
1067 1232
1068 1233 if ( empty( $skipped ) ) {
1069 - return $wpdb->get_col( "SELECT DISTINCT donation_id
1070 - FROM {$wpdb->prefix}charitable_campaign_donations
1071 - WHERE donor_id = 0
1072 - 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 + );
1073 1240 }
1074 1241
1075 - $placeholders = charitable_get_query_placeholders( count( $skipped ), '%d' );
1242 + $placeholders = charitable_get_query_placeholders( count( $skipped ), '%d' );
1076 1243
1077 - return $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT donation_id
1078 - FROM {$wpdb->prefix}charitable_campaign_donations
1079 - WHERE donor_id = 0
1080 - AND donation_id NOT IN ( $placeholders )
1081 - 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
1082 1258 }
1083 1259
1084 1260 /**
1085 1261 * Fix donations with missing donor ids.
@@ -1090,19 +1266,21 @@
1090 1266 */
1091 1267 public function fix_empty_donor_ids() {
1092 1268 global $wpdb;
1093 1269
1094 - if ( ! current_user_can( 'manage_charitable_settings' ) ) {
1095 - 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
1096 1272 }
1097 1273
1098 1274 ignore_user_abort( true );
1099 1275
1100 - if ( ! charitable_is_func_disabled( 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) {
1101 - @set_time_limit( 0 );
1102 - }
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 + }
1103 1279
1104 - $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
1105 1283 $number = 20;
1106 1284 $skipped = get_option( 'charitable_skipped_donations_with_empty_donor_id', array() );
1107 1285 $total = $this->count_empty_donor_id_donations( $skipped );
1108 1286
@@ -1131,20 +1309,26 @@
1131 1309
1132 1310 $donor_id = $donors_table->get_donor_id_by_email( $data['email'] );
1133 1311
1134 1312 if ( ! $donor_id ) {
1135 - $donor_id = $donors_table->insert( array(
1136 - 'email' => $data['email'],
1137 - 'first_name' => array_key_exists( 'first_name', $data ) ? $data['first_name'] : '',
1138 - 'last_name' => array_key_exists( 'last_name', $data ) ? $data['last_name'] : '',
1139 - 'date_joined' => get_post_field( 'post_date_gmt', $donation_id ),
1140 - ) );
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 + );
1141 1321 }
1142 1322
1143 1323 if ( $donor_id ) {
1144 - $donations_table->update( $donation_id, array(
1145 - 'donor_id' => $donor_id,
1146 - ), 'donation_id' );
1324 + $donations_table->update(
1325 + $donation_id,
1326 + array(
1327 + 'donor_id' => $donor_id,
1328 + ),
1329 + 'donation_id'
1330 + );
1147 1331 } else {
1148 1332 $skipped[] = $donation_id;
1149 1333 }
1150 1334 }//end foreach
@@ -1150,19 +1334,22 @@
1150 1334 }//end foreach
1151 1335
1152 1336 update_option( 'charitable_skipped_donations_with_empty_donor_id', $skipped );
1153 1337
1154 - $step++;
1338 + ++$step;
1155 1339
1156 - $redirect = add_query_arg( array(
1157 - 'charitable-upgrade' => 'fix_empty_donor_ids',
1158 - 'page' => 'charitable-upgrades',
1159 - 'step' => $step,
1160 - 'number' => $number,
1161 - 'total' => $total,
1162 - ), 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 + );
1163 1350
1164 - wp_redirect( $redirect );
1351 + wp_safe_redirect( $redirect );
1165 1352
1166 1353 exit;
1167 1354 }//end if
1168 1355
@@ -1171,8 +1358,73 @@
1171 1358 $this->finish_upgrade( 'fix_empty_donor_ids' );
1172 1359 }
1173 1360
1174 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 + /**
1175 1427 * HERE BE DEPRECATED FUNCTIONS...
1176 1428 *
1177 1429 * We're keeping these functions since Charitable add-ons extend this
1178 1430 * class and don't know whether those have upgraded.
@@ -1205,15 +1457,17 @@
1205 1457 * Before Charitable 1.3, upgrades were in a simple key=>value
1206 1458 * array format.
1207 1459 *
1208 1460 * $upgrade_actions = array(
1209 - * '1.0.1' => 'flush_permalinks',
1210 - * '1.1.0' => 'upgrade_1_1_0',
1211 - * '1.1.3' => 'flush_permalinks',
1212 - * '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',
1213 1465 * );
1214 1466 */
1215 1467
1468 + $this->init();
1469 +
1216 1470 if ( empty( $this->upgrade_actions ) || ! is_array( $this->upgrade_actions ) ) {
1217 1471 return;
1218 1472 }
1219 1473
@@ -1229,9 +1483,11 @@
1229 1483 }
1230 1484
1231 1485 if ( self::requires_upgrade( $this->db_version, $version ) ) {
1232 1486
1233 - call_user_func( array( $this, $method ) );
1487 + if ( method_exists( $this, $method ) ) {
1488 + $this->$method();
1489 + }
1234 1490
1235 1491 }
1236 1492 }
1237 1493 }
@@ -1250,11 +1506,11 @@
1250 1506 $log = array();
1251 1507 }
1252 1508
1253 1509 $log[] = array(
1254 - 'timestamp' => time(),
1255 - 'from' => $this->db_version,
1256 - 'to' => $this->edge_version,
1510 + 'timestamp' => time(),
1511 + 'from' => $this->db_version,
1512 + 'to' => $this->edge_version,
1257 1513 );
1258 1514
1259 1515 update_option( $this->upgrade_log_key, $log );
1260 1516 }
@@ -1267,8 +1523,119 @@
1267 1523 * @return void
1268 1524 */
1269 1525 protected function update_db_version() {
1270 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;
1271 1638 }
1272 1639 }
1273 1640
1274 1641 endif;