| @@ -7,8 +7,9 @@ | ||
| 7 | 7 | |
| 8 | 8 | namespace SureDonation\Inc\Database\Tables; |
| 9 | 9 | |
| 10 | 10 | use SureDonation\Inc\Database\Base; |
| 11 | +use SureDonation\Inc\Helper; | |
| 11 | 12 | use SureDonation\Inc\Traits\Get_Instance; |
| 12 | 13 | |
| 13 | 14 | // Exit if accessed directly. |
| 14 | 15 | defined( 'ABSPATH' ) || exit; |
| @@ -34,9 +35,9 @@ | ||
| 34 | 35 | * |
| 35 | 36 | * @var int |
| 36 | 37 | * @since 0.0.1 |
| 37 | 38 | */ |
| 38 | - protected $table_version = 2; | |
| 39 | + protected $table_version = 4; | |
| 39 | 40 | |
| 40 | 41 | /** |
| 41 | 42 | * Valid donor statuses. |
| 42 | 43 | * |
| @@ -84,8 +85,16 @@ | ||
| 84 | 85 | 'phone' => [ |
| 85 | 86 | 'type' => 'string', |
| 86 | 87 | 'default' => '', |
| 87 | 88 | ], |
| 89 | + 'company' => [ | |
| 90 | + 'type' => 'string', | |
| 91 | + 'default' => '', | |
| 92 | + ], | |
| 93 | + 'address' => [ | |
| 94 | + 'type' => 'string', | |
| 95 | + 'default' => '', | |
| 96 | + ], | |
| 88 | 97 | 'user_id' => [ |
| 89 | 98 | 'type' => 'number', |
| 90 | 99 | 'default' => 0, |
| 91 | 100 | ], |
| @@ -122,8 +131,16 @@ | ||
| 122 | 131 | 'stripe_customer_id' => [ |
| 123 | 132 | 'type' => 'string', |
| 124 | 133 | 'default' => '', |
| 125 | 134 | ], |
| 135 | + 'import_source_id' => [ | |
| 136 | + 'type' => 'number', | |
| 137 | + 'default' => 0, | |
| 138 | + ], | |
| 139 | + 'import_source' => [ | |
| 140 | + 'type' => 'string', | |
| 141 | + 'default' => '', | |
| 142 | + ], | |
| 126 | 143 | 'created_at' => [ |
| 127 | 144 | 'type' => 'datetime', |
| 128 | 145 | ], |
| 129 | 146 | 'updated_at' => [ |
| @@ -150,8 +167,10 @@ | ||
| 150 | 167 | 'donor_tags LONGTEXT', |
| 151 | 168 | 'donor_status VARCHAR(20) NOT NULL', |
| 152 | 169 | 'donor_data LONGTEXT', |
| 153 | 170 | 'stripe_customer_id VARCHAR(255) DEFAULT NULL', |
| 171 | + 'import_source_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0', | |
| 172 | + 'import_source VARCHAR(20) NOT NULL DEFAULT ""', | |
| 154 | 173 | 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP', |
| 155 | 174 | 'updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP', |
| 156 | 175 | 'INDEX idx_email (email)', |
| 157 | 176 | 'INDEX idx_user (user_id)', |
| @@ -156,12 +175,40 @@ | ||
| 156 | 175 | 'INDEX idx_email (email)', |
| 157 | 176 | 'INDEX idx_user (user_id)', |
| 158 | 177 | 'INDEX idx_total (total_donated)', |
| 159 | 178 | 'INDEX idx_status (donor_status)', |
| 179 | + 'INDEX idx_import_source (import_source_id, import_source)', | |
| 160 | 180 | ]; |
| 161 | 181 | } |
| 162 | 182 | |
| 163 | 183 | /** |
| 184 | + * New columns added across versions. | |
| 185 | + * | |
| 186 | + * Version 3 added company/address; version 4 added the | |
| 187 | + * source-agnostic pair `import_source_id` + `import_source` used by | |
| 188 | + * the migration tool. | |
| 189 | + * | |
| 190 | + * {@inheritDoc} | |
| 191 | + * | |
| 192 | + * @since 1.0.0 | |
| 193 | + */ | |
| 194 | + public function get_new_columns_definition() { | |
| 195 | + // Keep migration defaults consistent with the schema's runtime | |
| 196 | + // defaults (the column definitions above use 'default' => ''). Mixing | |
| 197 | + // NOT NULL DEFAULT '' for one column with DEFAULT NULL for another | |
| 198 | + // produces silent divergence at the data layer — a future | |
| 199 | + // `WHERE address = ''` filter would miss legacy rows that landed as | |
| 200 | + // NULL from the migration. | |
| 201 | + return [ | |
| 202 | + 'company VARCHAR(255) NOT NULL DEFAULT "" AFTER phone', | |
| 203 | + 'address TEXT NOT NULL DEFAULT "" AFTER company', | |
| 204 | + 'import_source_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 AFTER stripe_customer_id', | |
| 205 | + 'import_source VARCHAR(20) NOT NULL DEFAULT "" AFTER import_source_id', | |
| 206 | + 'INDEX idx_import_source (import_source_id, import_source)', | |
| 207 | + ]; | |
| 208 | + } | |
| 209 | + | |
| 210 | + /** | |
| 164 | 211 | * Add a new donor record. |
| 165 | 212 | * |
| 166 | 213 | * @param array<mixed> $data Donor data to insert. |
| 167 | 214 | * @return int|false The donor ID on success, false on error. |
| @@ -435,9 +482,9 @@ | ||
| 435 | 482 | return $existing_id > 0 ? $existing_id : false; |
| 436 | 483 | } |
| 437 | 484 | |
| 438 | 485 | // Create new donor. |
| 439 | - return self::add( | |
| 486 | + $donor_id = self::add( | |
| 440 | 487 | [ |
| 441 | 488 | 'email' => sanitize_email( $email ), |
| 442 | 489 | 'name' => sanitize_text_field( $name ), |
| 443 | 490 | 'phone' => sanitize_text_field( $phone ), |
| @@ -443,11 +490,114 @@ | ||
| 443 | 490 | 'phone' => sanitize_text_field( $phone ), |
| 444 | 491 | 'first_donation_date' => current_time( 'mysql' ), |
| 445 | 492 | ] |
| 446 | 493 | ); |
| 494 | + | |
| 495 | + if ( $donor_id ) { | |
| 496 | + // Auto-create or link WP user for this donor. | |
| 497 | + self::maybe_link_wp_user( $donor_id, sanitize_email( $email ), sanitize_text_field( $name ) ); | |
| 498 | + } | |
| 499 | + | |
| 500 | + return $donor_id; | |
| 447 | 501 | } |
| 448 | 502 | |
| 449 | 503 | /** |
| 504 | + * Link a donor to an existing WP user, or create a new WP user if none exists. | |
| 505 | + * | |
| 506 | + * @param int $donor_id Donor ID. | |
| 507 | + * @param string $email Donor email. | |
| 508 | + * @param string $name Donor name. | |
| 509 | + * @return void | |
| 510 | + * @since 1.0.0 | |
| 511 | + */ | |
| 512 | + public static function maybe_link_wp_user( $donor_id, $email, $name = '' ) { | |
| 513 | + if ( empty( $donor_id ) || empty( $email ) ) { | |
| 514 | + return; | |
| 515 | + } | |
| 516 | + | |
| 517 | + // Check if donor already has a linked user. | |
| 518 | + $donor = self::get( $donor_id ); | |
| 519 | + if ( $donor && ! empty( $donor['user_id'] ) && $donor['user_id'] > 0 ) { | |
| 520 | + return; | |
| 521 | + } | |
| 522 | + | |
| 523 | + // Check if a WP user already exists with this email. | |
| 524 | + $existing_user = get_user_by( 'email', $email ); | |
| 525 | + | |
| 526 | + if ( $existing_user ) { | |
| 527 | + self::update( $donor_id, [ 'user_id' => $existing_user->ID ] ); | |
| 528 | + return; | |
| 529 | + } | |
| 530 | + | |
| 531 | + // Creating a brand-new WordPress account for a donor is gated behind an | |
| 532 | + // explicit, default-off setting. On public (nopriv) donation paths this | |
| 533 | + // prevents unsolicited account creation and new-user notification emails | |
| 534 | + // for attacker-supplied emails. Linking to an already-existing user | |
| 535 | + // (handled above) is always allowed. | |
| 536 | + $donor_settings = Helper::get_suredonation_option( 'donor_settings', [] ); | |
| 537 | + if ( empty( $donor_settings['create_wp_user'] ) ) { | |
| 538 | + return; | |
| 539 | + } | |
| 540 | + | |
| 541 | + // Create a new WP user. | |
| 542 | + $username = sanitize_user( $email, true ); | |
| 543 | + $password = wp_generate_password( 24, true, true ); | |
| 544 | + | |
| 545 | + $user_data = [ | |
| 546 | + 'user_login' => $username, | |
| 547 | + 'user_email' => $email, | |
| 548 | + 'user_pass' => $password, | |
| 549 | + 'role' => 'suredonation_donor', | |
| 550 | + ]; | |
| 551 | + | |
| 552 | + // Split name into first/last if provided. | |
| 553 | + if ( ! empty( $name ) ) { | |
| 554 | + $parts = explode( ' ', $name, 2 ); | |
| 555 | + $user_data['first_name'] = $parts[0]; | |
| 556 | + $user_data['last_name'] = $parts[1] ?? ''; | |
| 557 | + $user_data['display_name'] = $name; | |
| 558 | + } | |
| 559 | + | |
| 560 | + /** | |
| 561 | + * Filter the user data before creating a WP user for a donor. | |
| 562 | + * | |
| 563 | + * Return false to prevent user creation. | |
| 564 | + * | |
| 565 | + * @param array $user_data WP user data array for wp_insert_user(). | |
| 566 | + * @param int $donor_id Donor ID. | |
| 567 | + * @param string $email Donor email. | |
| 568 | + * @since 1.0.0 | |
| 569 | + */ | |
| 570 | + $user_data = apply_filters( 'suredonation_new_donor_user_data', $user_data, $donor_id, $email ); | |
| 571 | + | |
| 572 | + if ( false === $user_data || ! is_array( $user_data ) ) { | |
| 573 | + return; | |
| 574 | + } | |
| 575 | + | |
| 576 | + $user_id = wp_insert_user( $user_data ); | |
| 577 | + | |
| 578 | + if ( is_wp_error( $user_id ) ) { | |
| 579 | + return; | |
| 580 | + } | |
| 581 | + | |
| 582 | + // Link the WP user to the donor. | |
| 583 | + self::update( $donor_id, [ 'user_id' => $user_id ] ); | |
| 584 | + | |
| 585 | + // Send new user notification email. | |
| 586 | + wp_new_user_notification( $user_id, null, 'user' ); | |
| 587 | + | |
| 588 | + /** | |
| 589 | + * Fires after a WP user is created and linked to a donor. | |
| 590 | + * | |
| 591 | + * @param int $user_id WP user ID. | |
| 592 | + * @param int $donor_id Donor ID. | |
| 593 | + * @param string $email Donor email. | |
| 594 | + * @since 1.0.0 | |
| 595 | + */ | |
| 596 | + do_action( 'suredonation_donor_user_created', $user_id, $donor_id, $email ); | |
| 597 | + } | |
| 598 | + | |
| 599 | + /** | |
| 450 | 600 | * Update donor statistics after a donation. |
| 451 | 601 | * |
| 452 | 602 | * @param int $donor_id Donor ID. |
| 453 | 603 | * @param float $amount Donation amount. |
| @@ -650,6 +800,222 @@ | ||
| 650 | 800 | |
| 651 | 801 | $result = self::update( $donor_id, [ 'stripe_customer_id' => '' ] ); |
| 652 | 802 | |
| 653 | 803 | return false !== $result; |
| 804 | + } | |
| 805 | + | |
| 806 | + /** | |
| 807 | + * Get donors for admin listing with optional filters. | |
| 808 | + * | |
| 809 | + * @param string $search Search term for name, email, or phone. | |
| 810 | + * @param int $campaign_id Campaign ID filter (0 for no filter). | |
| 811 | + * @param string $status Donor status filter ('all' for no filter). | |
| 812 | + * @param int $limit Number of records to return. | |
| 813 | + * @param int $offset Offset for pagination. | |
| 814 | + * @param string $orderby Column to order by. | |
| 815 | + * @param string $order Order direction (ASC or DESC). | |
| 816 | + * @param string $after Start date filter (Y-m-d). | |
| 817 | + * @param string $before End date filter (Y-m-d). | |
| 818 | + * @return array<mixed> Array of donors. | |
| 819 | + * @since 1.0.0 | |
| 820 | + */ | |
| 821 | + public static function get_admin_list( $search = '', $campaign_id = 0, $status = 'all', $limit = 20, $offset = 0, $orderby = 'created_at', $order = 'DESC', $after = '', $before = '' ) { | |
| 822 | + $instance = self::get_instance(); | |
| 823 | + global $wpdb; | |
| 824 | + | |
| 825 | + $donors_table = $instance->get_tablename(); | |
| 826 | + $donations_table = $wpdb->prefix . 'suredonation_donations'; | |
| 827 | + | |
| 828 | + // Validate orderby column. | |
| 829 | + if ( ! in_array( $orderby, self::$valid_order_columns, true ) ) { | |
| 830 | + $orderby = 'created_at'; | |
| 831 | + } | |
| 832 | + | |
| 833 | + // Validate order direction. | |
| 834 | + $order = strtoupper( $order ); | |
| 835 | + if ( ! in_array( $order, [ 'ASC', 'DESC' ], true ) ) { | |
| 836 | + $order = 'DESC'; | |
| 837 | + } | |
| 838 | + | |
| 839 | + $conditions = self::build_admin_list_conditions( $search, $campaign_id, $status, $after, $before ); | |
| 840 | + $where = $conditions['where']; | |
| 841 | + $query_args = $conditions['args']; | |
| 842 | + | |
| 843 | + if ( $conditions['has_campaign'] ) { | |
| 844 | + $order_col = 'd.' . $orderby; | |
| 845 | + | |
| 846 | + // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- Dynamic query with validated conditions. | |
| 847 | + $results = $wpdb->get_results( | |
| 848 | + $wpdb->prepare( | |
| 849 | + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $where is built with prepare-safe conditions, $order_col and $order are validated against whitelists. | |
| 850 | + "SELECT DISTINCT d.* FROM %i d INNER JOIN %i don ON d.id = don.donor_id {$where} ORDER BY {$order_col} {$order} LIMIT %d, %d", | |
| 851 | + array_merge( [ $donors_table, $donations_table ], $query_args, [ absint( $offset ), absint( $limit ) ] ) | |
| 852 | + ), | |
| 853 | + ARRAY_A | |
| 854 | + ); | |
| 855 | + // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber | |
| 856 | + } else { | |
| 857 | + // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- Dynamic query with validated conditions. | |
| 858 | + $results = $wpdb->get_results( | |
| 859 | + $wpdb->prepare( | |
| 860 | + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $where is built with prepare-safe conditions, $orderby and $order are validated against whitelists. | |
| 861 | + "SELECT * FROM %i {$where} ORDER BY {$orderby} {$order} LIMIT %d, %d", | |
| 862 | + array_merge( [ $donors_table ], $query_args, [ absint( $offset ), absint( $limit ) ] ) | |
| 863 | + ), | |
| 864 | + ARRAY_A | |
| 865 | + ); | |
| 866 | + // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber | |
| 867 | + } | |
| 868 | + | |
| 869 | + if ( ! $results || ! is_array( $results ) ) { | |
| 870 | + return []; | |
| 871 | + } | |
| 872 | + | |
| 873 | + return array_map( [ $instance, 'decode_by_datatype' ], $results ); | |
| 874 | + } | |
| 875 | + | |
| 876 | + /** | |
| 877 | + * Get total donors count with filters. | |
| 878 | + * | |
| 879 | + * @param string $search Search term for name, email, or phone. | |
| 880 | + * @param int $campaign_id Campaign ID filter (0 for no filter). | |
| 881 | + * @param string $status Donor status filter ('all' for no filter). | |
| 882 | + * @param string $after Start date filter (Y-m-d). | |
| 883 | + * @param string $before End date filter (Y-m-d). | |
| 884 | + * @return int Total count. | |
| 885 | + * @since 1.0.0 | |
| 886 | + */ | |
| 887 | + public static function get_total_donors_filtered( $search = '', $campaign_id = 0, $status = 'all', $after = '', $before = '' ) { | |
| 888 | + $instance = self::get_instance(); | |
| 889 | + global $wpdb; | |
| 890 | + | |
| 891 | + $donors_table = $instance->get_tablename(); | |
| 892 | + $donations_table = $wpdb->prefix . 'suredonation_donations'; | |
| 893 | + | |
| 894 | + $conditions = self::build_admin_list_conditions( $search, $campaign_id, $status, $after, $before ); | |
| 895 | + $where = $conditions['where']; | |
| 896 | + $query_args = $conditions['args']; | |
| 897 | + | |
| 898 | + // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- Data changes frequently, caching would show stale counts. | |
| 899 | + if ( $conditions['has_campaign'] ) { | |
| 900 | + $count = $wpdb->get_var( | |
| 901 | + $wpdb->prepare( | |
| 902 | + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $where is built with prepare-safe conditions. | |
| 903 | + "SELECT COUNT(DISTINCT d.id) FROM %i d INNER JOIN %i don ON d.id = don.donor_id {$where}", | |
| 904 | + array_merge( [ $donors_table, $donations_table ], $query_args ) | |
| 905 | + ) | |
| 906 | + ); | |
| 907 | + } elseif ( ! empty( $query_args ) ) { | |
| 908 | + $count = $wpdb->get_var( | |
| 909 | + $wpdb->prepare( | |
| 910 | + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $where is built with prepare-safe conditions. | |
| 911 | + "SELECT COUNT(*) FROM %i {$where}", | |
| 912 | + array_merge( [ $donors_table ], $query_args ) | |
| 913 | + ) | |
| 914 | + ); | |
| 915 | + } else { | |
| 916 | + $count = $wpdb->get_var( | |
| 917 | + $wpdb->prepare( | |
| 918 | + 'SELECT COUNT(*) FROM %i', | |
| 919 | + $donors_table | |
| 920 | + ) | |
| 921 | + ); | |
| 922 | + } | |
| 923 | + // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber | |
| 924 | + | |
| 925 | + return is_numeric( $count ) ? (int) $count : 0; | |
| 926 | + } | |
| 927 | + | |
| 928 | + /** | |
| 929 | + * Get aggregate donor statistics. | |
| 930 | + * | |
| 931 | + * @return array{total_donors: int, total_donated: float, average_donation: float} Aggregate stats. | |
| 932 | + * @since 1.0.0 | |
| 933 | + */ | |
| 934 | + public static function get_aggregate_stats() { | |
| 935 | + $instance = self::get_instance(); | |
| 936 | + global $wpdb; | |
| 937 | + $table = $instance->get_tablename(); | |
| 938 | + | |
| 939 | + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching | |
| 940 | + $row = $wpdb->get_row( | |
| 941 | + $wpdb->prepare( | |
| 942 | + 'SELECT COUNT(*) AS total_donors, COALESCE(SUM(total_donated), 0) AS total_donated, COALESCE(SUM(donation_count), 0) AS total_donation_count FROM %i', | |
| 943 | + $table | |
| 944 | + ), | |
| 945 | + ARRAY_A | |
| 946 | + ); | |
| 947 | + | |
| 948 | + $total_donors = is_numeric( $row['total_donors'] ?? 0 ) ? (int) $row['total_donors'] : 0; | |
| 949 | + $total_donated = is_numeric( $row['total_donated'] ?? 0 ) ? (float) $row['total_donated'] : 0.0; | |
| 950 | + $total_donation_count = is_numeric( $row['total_donation_count'] ?? 0 ) ? (int) $row['total_donation_count'] : 0; | |
| 951 | + $average_donation = $total_donation_count > 0 ? $total_donated / $total_donation_count : 0.0; | |
| 952 | + | |
| 953 | + return [ | |
| 954 | + 'total_donors' => $total_donors, | |
| 955 | + 'total_donated' => $total_donated, | |
| 956 | + 'average_donation' => round( $average_donation, 2 ), | |
| 957 | + ]; | |
| 958 | + } | |
| 959 | + | |
| 960 | + /** | |
| 961 | + * Build WHERE conditions and prepare args for admin list queries. | |
| 962 | + * | |
| 963 | + * @param string $search Search term for name, email, or phone. | |
| 964 | + * @param int $campaign_id Campaign ID filter (0 for no filter). | |
| 965 | + * @param string $status Donor status filter ('all' for no filter). | |
| 966 | + * @param string $after Start date filter (Y-m-d). | |
| 967 | + * @param string $before End date filter (Y-m-d). | |
| 968 | + * @return array{where: string, args: array<mixed>, has_campaign: bool} Query parts. | |
| 969 | + * @since 1.0.0 | |
| 970 | + */ | |
| 971 | + private static function build_admin_list_conditions( $search, $campaign_id, $status, $after = '', $before = '' ) { | |
| 972 | + global $wpdb; | |
| 973 | + | |
| 974 | + $has_search = ! empty( $search ); | |
| 975 | + $has_campaign = $campaign_id > 0; | |
| 976 | + $has_status = 'all' !== $status && ! empty( $status ) && in_array( $status, self::$valid_statuses, true ); | |
| 977 | + | |
| 978 | + $conditions = []; | |
| 979 | + $args = []; | |
| 980 | + | |
| 981 | + if ( $has_campaign ) { | |
| 982 | + $conditions[] = 'don.campaign_id = %d'; | |
| 983 | + $args[] = absint( $campaign_id ); | |
| 984 | + } | |
| 985 | + | |
| 986 | + if ( $has_status ) { | |
| 987 | + $col_prefix = $has_campaign ? 'd.' : ''; | |
| 988 | + $conditions[] = $col_prefix . 'donor_status = %s'; | |
| 989 | + $args[] = sanitize_text_field( $status ); | |
| 990 | + } | |
| 991 | + | |
| 992 | + if ( $has_search ) { | |
| 993 | + $col_prefix = $has_campaign ? 'd.' : ''; | |
| 994 | + $search_term = '%' . $wpdb->esc_like( sanitize_text_field( $search ) ) . '%'; | |
| 995 | + $conditions[] = '(' . $col_prefix . 'name LIKE %s OR ' . $col_prefix . 'email LIKE %s OR ' . $col_prefix . 'phone LIKE %s)'; | |
| 996 | + $args[] = $search_term; | |
| 997 | + $args[] = $search_term; | |
| 998 | + $args[] = $search_term; | |
| 999 | + } | |
| 1000 | + | |
| 1001 | + if ( ! empty( $after ) ) { | |
| 1002 | + $col_prefix = $has_campaign ? 'd.' : ''; | |
| 1003 | + $conditions[] = $col_prefix . 'last_donation_date >= %s'; | |
| 1004 | + $args[] = sanitize_text_field( $after ) . ' 00:00:00'; | |
| 1005 | + } | |
| 1006 | + | |
| 1007 | + if ( ! empty( $before ) ) { | |
| 1008 | + $col_prefix = $has_campaign ? 'd.' : ''; | |
| 1009 | + $conditions[] = $col_prefix . 'last_donation_date <= %s'; | |
| 1010 | + $args[] = sanitize_text_field( $before ) . ' 23:59:59'; | |
| 1011 | + } | |
| 1012 | + | |
| 1013 | + $where = ! empty( $conditions ) ? 'WHERE ' . implode( ' AND ', $conditions ) : ''; | |
| 1014 | + | |
| 1015 | + return [ | |
| 1016 | + 'where' => $where, | |
| 1017 | + 'args' => $args, | |
| 1018 | + 'has_campaign' => $has_campaign, | |
| 1019 | + ]; | |
| 654 | 1020 | } |
| 655 | 1021 | } |