'donors', 'status' => array( 'charitable-completed', 'charitable-preapproved' ), 'orderby' => 'date', 'order' => 'DESC', 'number' => 20, 'paged' => 1, 'fields' => 'all', 'campaign' => 0, 'distinct_donors' => true, 'donor_id' => 0, 'include_erased' => 1, 'date_query' => array(), 'meta_query' => array(), ) ); $this->args = wp_parse_args( $args, $defaults ); $this->args['campaign'] = $this->sanitize_campaign(); $this->position = 0; $this->prepare_query(); $this->results = $this->get_donors(); } /** * Return list of donor IDs together with the number of donations they have made. * * @since 1.0.0 * * @return object[] */ public function get_donors() { $records = $this->query(); if ( 'donors' != $this->get( 'output' ) ) { return $records; } return array_map( array( $this, 'get_donor_object' ), $records ); } /** * Returns a Charitable_Donor object for a database record. * * @since 1.5.0 * * @param object $record Database record for the donor. * @return Charitable_Donor */ public function get_donor_object( $record ) { $donation_id = isset( $record->donation_id ) ? $record->donation_id : false; $donor = new Charitable_Donor( $record->donor_id, $donation_id ); /** * Filter the returned object. * * Note that this should always return a Charitable_Donor object. * * @since 1.5.7 * * @param Charitable_Donor $donor The instance of `Charitable_Donor`. * @param object $record Database record for the donor. * @param Charitable_Donor_Query $query This query object. */ return apply_filters( 'charitable_donor_query_donor_object', $donor, $record, $this ); } /** * When retrieving the donor count, get distinct donor IDs. * * @since 1.5.4 * * @return string */ public function select_donor_count() { return 'COUNT(DISTINCT d.donor_id)'; } /** * Set up fields query argument. * * @since 1.0.0 * * @return void */ public function setup_fields() { if ( ! $this->get( 'distinct_donors', true ) ) { add_filter( 'charitable_query_fields', array( $this, 'donation_fields' ), 4 ); } $fields = $this->get( 'fields', 'all' ); if ( 'all' == $fields ) { add_filter( 'charitable_query_fields', array( $this, 'donation_calc_fields' ), 5 ); add_filter( 'charitable_query_fields', array( $this, 'donor_fields' ), 6 ); } if ( is_array( $fields ) ? in_array( 'amount', $fields ) : 'amount' == $fields ) { add_filter( 'charitable_query_fields', array( $this, 'donation_amount_sum_field' ), 6 ); } } /** * Set up orderby query argument. * * @since 1.0.0 * * @return void */ public function setup_orderby() { $orderby = $this->get( 'orderby', false ); if ( ! $orderby ) { return; } switch ( $orderby ) { case 'date': add_filter( 'charitable_query_orderby', array( $this, 'orderby_date' ) ); break; case 'donations': add_filter( 'charitable_query_orderby', array( $this, 'orderby_count' ) ); break; case 'amount': add_filter( 'charitable_query_orderby', array( $this, 'orderby_donation_amount' ) ); break; case 'name': add_filter( 'charitable_query_orderby', array( $this, 'orderby_name' ) ); break; } } /** * Order the results by name. * * @since 1.5.0 * * @return string */ public function orderby_name() { return 'ORDER BY d.last_name, d.first_name'; } /** * Set up query grouping. * * @since 1.0.0 * * @return void */ public function setup_grouping() { if ( ! $this->get( 'distinct_donors', false ) ) { add_filter( 'charitable_query_groupby', array( $this, 'groupby_ID' ) ); return; } add_filter( 'charitable_query_groupby', array( $this, 'groupby_donor_id' ) ); } /** * Do not include donors that have had their data erased. * * @since 1.6.0 * * @param string $where The WHERE SQL query part. * @return string */ public function where_donor_is_not_erased( $where ) { if ( ! $this->get( 'include_erased' ) ) { $where .= ' AND d.data_erased = "0000-00-00 00:00:00"'; } return $where; } /** * Remove any hooks that have been attached by the class to prevent contaminating other queries. * * @since 1.0.0 * @version 1.8.8 * * @return void */ public function unhook_callbacks() { remove_action( 'charitable_pre_query', array( $this, 'setup_fields' ) ); remove_action( 'charitable_pre_query', array( $this, 'setup_orderby' ) ); remove_action( 'charitable_pre_query', array( $this, 'setup_grouping' ) ); remove_filter( 'charitable_select_count_fields', array( $this, 'select_donor_count' ) ); remove_filter( 'charitable_query_fields', array( $this, 'donation_fields' ), 4 ); remove_filter( 'charitable_query_fields', array( $this, 'donation_calc_fields' ), 5 ); remove_filter( 'charitable_query_fields', array( $this, 'donor_fields' ), 6 ); remove_filter( 'charitable_query_fields', array( $this, 'donation_amount_sum_field' ), 6 ); remove_filter( 'charitable_query_join', array( $this, 'join_campaign_donations_table_on_donation' ), 5 ); remove_filter( 'charitable_query_join', array( $this, 'join_donors_table' ), 6 ); remove_filter( 'charitable_query_join', array( $this, 'join_meta' ), 7 ); // Check if we should skip donation-based filters when including donors without donations. $skip_donation_filters = apply_filters( 'charitable_donor_query_skip_donation_filters', false, $this ); // Only remove donation-based filters if they were added. if ( ! $this->get( 'include_donors_without_donations' ) || ! $skip_donation_filters ) { remove_filter( 'charitable_query_where', array( $this, 'where_status_is_in' ), 5 ); remove_filter( 'charitable_query_where', array( $this, 'where_campaign_is_in' ), 6 ); remove_filter( 'charitable_query_where', array( $this, 'where_date' ), 9 ); remove_filter( 'charitable_query_where', array( $this, 'where_meta' ), 10 ); } remove_filter( 'charitable_query_where', array( $this, 'where_donor_id_is_in' ), 7 ); remove_filter( 'charitable_query_where', array( $this, 'where_donor_is_not_erased' ), 8 ); remove_filter( 'charitable_query_groupby', array( $this, 'groupby_donor_id' ) ); remove_filter( 'charitable_query_orderby', array( $this, 'orderby_date' ) ); remove_filter( 'charitable_query_orderby', array( $this, 'orderby_count' ) ); remove_filter( 'charitable_query_orderby', array( $this, 'orderby_donation_amount' ) ); remove_filter( 'charitable_query_orderby', array( $this, 'orderby_name' ) ); remove_action( 'charitable_post_query', array( $this, 'unhook_callbacks' ) ); } /** * Set up callbacks for WP_Query filters. * * @since 1.0.0 * @version 1.8.8 * * @return void */ protected function prepare_query() { add_action( 'charitable_pre_query', array( $this, 'setup_fields' ) ); add_action( 'charitable_pre_query', array( $this, 'setup_orderby' ) ); add_action( 'charitable_pre_query', array( $this, 'setup_grouping' ) ); add_filter( 'charitable_select_count_fields', array( $this, 'select_donor_count' ) ); add_filter( 'charitable_query_join', array( $this, 'join_campaign_donations_table_on_donation' ), 5 ); add_filter( 'charitable_query_join', array( $this, 'join_donors_table' ), 6 ); add_filter( 'charitable_query_join', array( $this, 'join_meta' ), 7 ); /** * Check if we should skip donation-based filters when including donors without donations. * * This filter allows you to opt into the new behavior where donation-based filters * are skipped when querying donors without donations. By default, this is false * to maintain backward compatibility. * * Usage: * add_filter( 'charitable_donor_query_skip_donation_filters', '__return_true' ); * * @since 1.8.8 * * @param boolean $skip_donation_filters Whether to skip donation-based filters. * @param Charitable_Donor_Query $query The current query instance. */ $skip_donation_filters = apply_filters( 'charitable_donor_query_skip_donation_filters', false, $this ); // Only apply donation-based filters when not including donors without donations // or when the skip filter is not enabled. if ( ! $this->get( 'include_donors_without_donations' ) || ! $skip_donation_filters ) { add_filter( 'charitable_query_where', array( $this, 'where_status_is_in' ), 5 ); add_filter( 'charitable_query_where', array( $this, 'where_campaign_is_in' ), 6 ); add_filter( 'charitable_query_where', array( $this, 'where_date' ), 9 ); add_filter( 'charitable_query_where', array( $this, 'where_meta' ), 10 ); } add_filter( 'charitable_query_where', array( $this, 'where_donor_id_is_in' ), 7 ); add_filter( 'charitable_query_where', array( $this, 'where_donor_is_not_erased' ), 8 ); add_action( 'charitable_post_query', array( $this, 'unhook_callbacks' ) ); } /** * Sanitize the campaign argument. * * @since 1.5.0 * * @return int */ protected function sanitize_campaign() { switch ( $this->args['campaign'] ) { case '': case 'all': return 0; case 'current': return charitable_get_current_campaign_id(); default: return $this->args['campaign']; } } } endif;