args[ $key ] ) ? $this->args[ $key ] : $fallback; } /** * Set the query argument for the given key. * * @since 1.0.0 * * @param string $key Key of argument to set. * @param mixed $value Value to be set. * @return void */ public function set( $key, $value ) { $this->args[ $key ] = apply_filters( 'charitable_query_sanitize_argument_' . $key, $value, $this ); } /** * Remove the given query argument. * * @since 1.0.0 * * @param string $key Key of argument to remove. * @return void */ public function remove( $key ) { unset( $this->args[ $key ] ); } /** * Return the results of the query. * * @global WPDB $wpdb * * @since 1.6.8 * * @return object[] */ public function get_query() { if ( ! isset( $this->query ) ) { /** * Fires right before the query is executed. * * @since 1.0.0 * * @param Charitable_Query The `Charitable_Query` instance. */ do_action( 'charitable_pre_query', $this ); $this->query = $this->run_query( $this->get( 'output' ) ); $this->found_items = $this->get_found_items(); /** * Fires right after the query is executed. * * @since 1.0.0 * * @param Charitable_Query The `Charitable_Query` instance. */ do_action( 'charitable_post_query', $this ); }//end if return $this->query; } /** * Return the results. * * @since 1.6.8 * * @return object[] */ public function get_results() { return $this->results; } /** * Return the results of the query. * * @global WPDB $wpdb * * @since 1.0.0 * * @return object[] */ public function query() { return $this->get_query(); } /** * Run query, returning the results. * * @global WPDB $wpdb * * @since 1.5.0 * * @param string $output The output type. * @return array */ public function run_query( $output ) { if ( method_exists( $this, 'run_query_' . $output ) ) { return call_user_func( array( $this, 'run_query_' . $output ) ); } global $wpdb; $sql = "SELECT {$this->fields()} {$this->from()} {$this->join()} {$this->where()} {$this->groupby()} {$this->orderby()} {$this->order()} {$this->limit()} {$this->offset()};"; return $wpdb->get_results( $this->get_prepared_sql( $sql ) ); // phpcs:ignore } /** * Run count query, returning the results. * * @global WPDB $wpdb * * @since 1.5.0 * * @return array */ public function run_query_count() { global $wpdb; $sql = "SELECT {$this->select_count()} {$this->from()} {$this->join()} {$this->where()};"; return array( $wpdb->get_var( $this->get_prepared_sql( $sql ) ) ); // phpcs:ignore } /** * Prepare a query with any passed parameters. * * @global WPDB $wpdb * * @since 1.5.0 * * @param string $sql The SQL query with placeholders. * @return string */ public function get_prepared_sql( $sql ) { global $wpdb; if ( empty( $this->parameters ) ) { return $sql; } return $wpdb->prepare( $sql, $this->parameters ); // phpcs:ignore } /** * After a query has been executed, check how many found rows there are. * * @global WPDB $wpdb * * @since 1.5.0 * * @return int */ public function get_found_items() { global $wpdb; if ( empty( $this->query ) ) { return 0; } if ( 'count' == $this->get( 'output' ) ) { return current( $this->query ); } if ( $this->show_all() ) { return count( $this->query ); } /** * Filters the query used to retrieve the query result count. * * @since 1.5.0 * * @param string $found_customers_query SQL query. Default 'SELECT FOUND_ROWS()'. * @param Charitable_Query $query The `Charitable_Query` instance. */ $found_items_query = apply_filters( 'charitable_found_items_query', 'SELECT FOUND_ROWS()', $this ); return (int) $wpdb->get_var( $found_items_query ); // phpcs:ignore } /** * Return the count query right after the SELECT part of the query. * * This is used instead of Charitable_Query::fields and is only appropriate * when all you want to do with a query is get the number of something. * * @global WPBD $wpdb * * @since 1.5.0 * * @return string */ public function select_count() { global $wpdb; /** * Filter the `SELECT COUNT` statement. * * @since 1.5.0 * * @param string $sql The `SELECT COUNT` statement. * @param Charitable_Query $query The `Charitable_Query` instance. */ return apply_filters( 'charitable_select_count_fields', "COUNT(DISTINCT {$wpdb->posts}.ID)", $this ); } /** * Return the fields right after the SELECT part of the query. * * @global WPBD $wpdb * * @since 1.0.0 * * @return string */ public function fields() { global $wpdb; $found_rows = $this->show_all() ? '' : 'SQL_CALC_FOUND_ROWS'; /** * Filter the `SELECT` statement. * * @since 1.0.0 * * @param string $sql The `SELECT` statement. * @param Charitable_Query $query The `Charitable_Query` instance. */ return apply_filters( 'charitable_query_fields', "$found_rows {$wpdb->posts}.ID", $this ); } /** * Return the FROM part of the query. * * @global WPBD $wpdb * * @since 1.0.0 * * @return string */ public function from() { global $wpdb; /** * Filter the `FROM` statement. * * @since 1.0.0 * * @param string $sql The `FROM` statement. * @param Charitable_Query $query The `Charitable_Query` instance. */ return apply_filters( 'charitable_query_from', "FROM $wpdb->posts", $this ); } /** * Return the JOIN part of the query. * * @since 1.0.0 * * @return string */ public function join() { /** * Filter the `JOIN` statement. * * @since 1.0.0 * * @param string $sql The `JOIN` statement. Empty by default. * @param Charitable_Query $query The `Charitable_Query` instance. */ return apply_filters( 'charitable_query_join', '', $this ); } /** * Return the WHERE part of the query. * * @since 1.0.0 * * @return string */ public function where() { /** * Filter the `WHERE` statement. * * @since 1.0.0 * * @param string $sql The `WHERE` statement. * @param Charitable_Query $query This query object. */ return apply_filters( 'charitable_query_where', 'WHERE 1=1 ', $this ); } /** * Return the GROUPBY part of the query. * * @since 1.0.0 * * @return string */ public function groupby() { /** * Filter the `GROUP BY` statement. * * @since 1.0.0 * * @param string $sql The `GROUP BY` statement. Empty by default. * @param Charitable_Query $query This query object. */ return apply_filters( 'charitable_query_groupby', '', $this ); } /** * Return the ORDER BY part of the query. * * @global WPBD $wpdb * * @since 1.0.0 * * @return string */ public function orderby() { global $wpdb; /** * Filter the `ORDER BY` statement. * * @since 1.0.0 * * @param string $sql The `ORDER BY` statement. * @param Charitable_Query $query The `Charitable_Query` instance. */ return apply_filters( 'charitable_query_orderby', "ORDER BY {$wpdb->posts}.ID", $this ); } /** * Return the ORDER part of the query. * * The returned value is interpolated directly into the SQL statement * assembled by Charitable_Query::run_query(). A sort direction cannot be * passed to $wpdb->prepare() as a placeholder, so it is validated against * a strict whitelist here instead. Any value other than ASC or DESC -- * including anything supplied through a shortcode attribute or the * `charitable_query_order` filter -- falls back to DESC. * * @since 1.0.0 * @version 1.8.12.2 * * @return string */ public function order() { /** * Filter the `ORDER` statement. * * Note that the filtered value is validated against ASC/DESC before it * is used in the query, so returning any other value has no effect. * * @since 1.0.0 * * @param string $sql The `ORDER` statement. DESC by default. * @param Charitable_Query $query The `Charitable_Query` instance. */ $order = apply_filters( 'charitable_query_order', $this->get( 'order', 'DESC' ), $this ); return charitable_validate_sort_direction( $order ); } /** * Return the LIMIT part of the query. * * @since 1.0.0 * * @return string */ public function limit() { if ( $this->show_all() ) { return ''; } /** * Filter the `LIMIT` statement. * * @since 1.0.0 * * @param string $sql The `LIMIT` statement. * @param Charitable_Query $query The `Charitable_Query` instance. */ return apply_filters( 'charitable_query_limit', sprintf( 'LIMIT %d', $this->get( 'number', 20 ) ), $this ); } /** * Return the OFFSET part of the query. * * @since 1.0.0 * * @return string */ public function offset() { if ( $this->show_all() ) { return ''; } $offset = $this->get( 'number' ) * ( $this->get( 'paged', 1 ) - 1 ); /** * Filter the `OFFSET` statement. * * @since 1.0.0 * * @param string $sql The `OFFSET` statement. * @param Charitable_Query $query The `Charitable_Query` instance. */ return apply_filters( 'charitable_query_offset', sprintf( 'OFFSET %d', $offset ), $this ); } /** * Select donor-specific fields. * * @since 1.0.0 * * @param string $select_statement The default select statement. * @return string */ public function donor_fields( $select_statement ) { return $select_statement . ', d.donor_id, d.user_id, d.first_name, d.last_name, d.email, d.date_joined'; } /** * Retrieve the donation ID and campaigns. * * @since 1.0.0 * * @param string $select_statement The default select statement. * @return string */ public function donation_fields( $select_statement ) { return $select_statement . ", cd.donation_id, GROUP_CONCAT(cd.campaign_name SEPARATOR ', ') AS campaigns, GROUP_CONCAT(cd.campaign_id SEPARATOR ',') AS campaign_ids, cd.donor_id"; } /** * Select donation-specific fields. * * @since 1.0.0 * * @param string $select_statement The default select statement. * @return string */ public function donation_calc_fields( $select_statement ) { return $select_statement . ', COUNT(cd.campaign_donation_id) AS donations, SUM(cd.amount) AS amount'; } /** * Select total amount field. * * @since 1.2.0 * * @param string $select_statement The default select statement. * @return string */ public function donation_amount_sum_field( $select_statement ) { return $select_statement . ', SUM(cd.amount) AS amount'; } /** * Return the SQL to be used for meta queries. * * @since 1.6.5 * * @global WPDB $wpdb * * @return false|array { * Array containing JOIN and WHERE SQL clauses to append to the main query. * * @type string $join SQL fragment to append to the main JOIN clause. * @type string $where SQL fragment to append to the main WHERE clause. * } */ public function get_meta_sql() { if ( ! isset( $this->meta_sql ) ) { global $wpdb; $meta_args = $this->get( 'meta_query' ); if ( empty( $meta_args ) ) { return false; } $meta_query = new WP_Meta_Query( $meta_args ); $this->meta_sql = $meta_query->get_sql( 'post', $wpdb->posts, 'ID' ); } return $this->meta_sql; } /** * A helper function used to populate the query parameters ith an item and a return a string * of placeholders that can be used in a MySQL `IN` statement. * * @since 1.5.0 * * @param mixed $item The item. * @param callable $filter_callback A callback to use to filter out invalid items. * @param string $placeholder_type The type of placeholder to use. May be %s, %d or %f. * @return string A string containing the correct number of placeholders. */ public function get_where_in_placeholders( $item, $filter_callback, $placeholder_type ) { if ( ! is_array( $item ) ) { $item = array( $item ); } $item = array_filter( $item, $filter_callback ); $this->add_parameters( $item ); return $this->get_placeholders( count( $item ), $placeholder_type ); } /** * Filter query by donation_plan ID. * * @since 1.6.8 * * @global WPBD $wpdb * @param string $where_statement The default where statement. * @return string */ public function where_donation_plan_is_in( $where_statement ) { global $wpdb; $donation_plan = $this->get( 'donation_plan', false ); if ( ! $donation_plan ) { return $where_statement; } $placeholders = $this->get_where_in_placeholders( $donation_plan, 'charitable_validate_absint', '%d' ); return $where_statement . " AND {$wpdb->posts}.post_parent IN ({$placeholders})"; } /** * Filter query by campaign receiving the donation. * * @since 1.0.0 * * @param string $where_statement The default where statement. * @return string */ public function where_campaign_is_in( $where_statement ) { $campaign = $this->get( 'campaign', 0 ); if ( ! $campaign ) { return $where_statement; } $placeholders = $this->get_where_in_placeholders( $this->get( 'campaign', 0 ), 'charitable_validate_absint', '%d' ); return $where_statement . " AND cd.campaign_id IN ({$placeholders})"; } /** * Filter query by status of the post. * * @global WPBD $wpdb * * @since 1.0.0 * * @param string $where_statement The default where statement. * @return string */ public function where_status_is_in( $where_statement ) { global $wpdb; $status = $this->get( 'status', false ); if ( ! $status ) { return $where_statement; } $placeholders = $this->get_where_in_placeholders( $status, 'charitable_is_valid_donation_status', '%s' ); return $where_statement . " AND {$wpdb->posts}.post_status IN ({$placeholders})"; } /** * Filter query by donor ID. * * @global WPBD $wpdb * @since 1.0.0 * @version 1.7.0.9 * * @param string $where_statement The default where statement. * @return string */ public function where_donor_id_is_in( $where_statement ) { $donor_email_passed = isset( $_GET['donor_email'] ) ? sanitize_email( $_GET['donor_email'] ) : false; // phpcs:ignore $donor_email = $this->get( 'email', $donor_email_passed ); if ( $donor_email ) { // stop searching by ID, we will search by email that was supplied. return $where_statement; } $donor_id = $this->get( 'donor_id', false ); if ( ! $donor_id ) { return $where_statement; } $placeholders = $this->get_where_in_placeholders( $donor_id, 'charitable_validate_absint', '%d' ); return $where_statement . " AND cd.donor_id IN ({$placeholders})"; } /** * Filter query by donor email. * * @global WPBD $wpdb * @since 1.7.0.9 * * @param string $where_statement The default where statement. * @return string */ public function where_donor_email_is_in( $where_statement ) { return $where_statement; } /** * Filter query by user ID. * * @since 1.5.0 * * @global WPBD $wpdb * @param string $where_statement The default where statement. * @return string */ public function where_user_id_is_in( $where_statement ) { global $wpdb; $user_id = $this->get( 'user_id', false ); if ( ! $user_id ) { return $where_statement; } $placeholders = $this->get_where_in_placeholders( $user_id, 'charitable_validate_absint', '%d' ); return $where_statement . " AND {$wpdb->posts}.post_author IN ({$placeholders})"; } /** * Filter query by donation ID(s). * * @since 1.7.0.9 * @version 1.8.8.5 * * @global WPBD $wpdb * @param string $where_statement The default where statement. * @return string */ public function where_donation_id_is_in( $where_statement ) { global $wpdb; $donation_ids_passed = isset( $_GET['donation_ids'] ) ? sanitize_text_field( $_GET['donation_ids'] ) : false; // phpcs:ignore if ( $donation_ids_passed === false ) { return $where_statement; } // this could be a comma delimited list. $donation_ids = explode( ',', $donation_ids_passed ); if ( empty( $donation_ids ) ) { return $where_statement; } // Validate and sanitize each donation ID to ensure they are positive integers $valid_donation_ids = array(); foreach ( $donation_ids as $donation_id ) { $donation_id = trim( $donation_id ); if ( is_numeric( $donation_id ) && intval( $donation_id ) > 0 ) { $valid_donation_ids[] = intval( $donation_id ); } } if ( empty( $valid_donation_ids ) ) { return $where_statement; } // Use proper parameterized query with placeholders $placeholders = $this->get_where_in_placeholders( $valid_donation_ids, 'charitable_validate_absint', '%d' ); return $where_statement . " AND {$wpdb->posts}.ID IN ({$placeholders})"; } /** * Filter a query by date. * * @uses WP_Date_Query * * @since 1.5.0 * * @param string $where_statement The where query. * @return string */ public function where_date( $where_statement ) { $date_args = $this->get( 'date_query' ); if ( empty( $date_args ) ) { return $where_statement; } $date_query = new WP_Date_Query( $date_args ); return $where_statement . $date_query->get_sql(); } /** * Filter a query by meta data. * * @uses WP_Meta_Query * * @since 1.6.5 * * @param string $where_statement The where query. * @return string */ public function where_meta( $where_statement ) { $meta_sql = $this->get_meta_sql(); if ( ! $meta_sql || empty( $meta_sql['where'] ) ) { return $where_statement; } return $where_statement . $meta_sql['where']; } /** * A method used to join the campaign donations table on the campaigns query. * * @global WPBD $wpdb * * @since 1.0.0 * * @param string $join_statement The default join statement. * @return string */ public function join_campaign_donations_table_on_campaign( $join_statement ) { global $wpdb; return $join_statement . " INNER JOIN {$wpdb->prefix}charitable_campaign_donations cd ON cd.campaign_id = $wpdb->posts.ID "; } /** * Add one or more joins to the post meta table. * * @uses WP_Meta_Query * * @since 1.6.5 * * @param string $join_statement The where query. * @return string */ public function join_meta( $join_statement ) { $meta_sql = $this->get_meta_sql(); if ( ! $meta_sql || empty( $meta_sql['join'] ) ) { return $join_statement; } return $join_statement . $meta_sql['join']; } /** * A method used to join the campaign donations table on the campaigns query. * * @global WPBD $wpdb * * @since 1.0.0 * * @param string $join_statement The default join statement. * @return string */ public function join_campaign_donations_table_on_donation( $join_statement ) { global $wpdb; return $join_statement . " INNER JOIN {$wpdb->prefix}charitable_campaign_donations cd ON cd.donation_id = $wpdb->posts.ID "; } /** * A method used to join the campaign donations table on the campaigns query. * * @global WPBD $wpdb * * @since 1.7.0.9 * * @param string $join_statement The default join statement. * @return string */ public function join_campaign_donors_table_on_donation( $join_statement ) { global $wpdb; return $join_statement . " INNER JOIN {$wpdb->prefix}charitable_donors cdn ON cdn.donor_id = cd.donor_id "; } /** * A method used to join the donors table on the query. * * @global WPBD $wpdb * @since 1.5.0 * * @param string $join_statement The default join statement. * @return string */ public function join_post_meta_table_on_donation( $join_statement ) { global $wpdb; return $join_statement . " INNER JOIN $wpdb->postmeta pm ON pm.post_id = $wpdb->posts.ID "; } /** * A method used to join the donors table on the query. * * @global WPBD $wpdb * @since 1.0.0 * * @param string $join_statement The default join statement. * @return string */ public function join_donors_table( $join_statement ) { global $wpdb; return $join_statement . " INNER JOIN {$wpdb->prefix}charitable_donors d ON d.donor_id = cd.donor_id "; } /** * Group results by the ID. * * @global WPBD $wpdb * @since 1.0.0 * * @return string */ public function groupby_ID() { global $wpdb; return "GROUP BY {$wpdb->posts}.ID"; } /** * Group a query by the donor ID. * * @since 1.0.0 * * @return string */ public function groupby_donor_id() { return 'GROUP BY cd.donor_id'; } /** * Group a query by the donation ID. * * @since 1.4.0 * * @return string */ public function groupby_donation_id() { return 'GROUP BY cd.donation_id'; } /** * Order by the date of the post. * * @global WPBD $wpdb * @since 1.0.0 * * @return string */ public function orderby_date() { global $wpdb; return "ORDER BY {$wpdb->posts}.post_date"; } /** * Order by the results count of the ID column. * * This is useful when used in combination with a group statement. * * @since 1.0.0 * * @return string */ public function orderby_count() { return 'ORDER BY COUNT(*)'; } /** * A method used to change the ordering of the campaigns query, to order by the amount donated. * * @since 1.0.0 * * @return string */ public function orderby_donation_amount() { return 'ORDER BY COALESCE(SUM(cd.amount), 0)'; } /** * Return number of results. * * @since 1.0.0 * * @return int */ public function count() { return 'count' == $this->get( 'output' ) ? $this->found_items : count( $this->results ); } /** * Return total result count. * * @since 1.5.0 * * @return int */ public function found_items() { return $this->found_items; } /** * Rewind to first result. * * @since 1.0.0 * * @return void */ #[\ReturnTypeWillChange] public function rewind() { $this->position = 0; } /** * Return current element. * * @since 1.0.0 * * @return object */ #[\ReturnTypeWillChange] public function current() { return isset( $this->results[ $this->position ] ) ? $this->results[ $this->position ] : false; } /** * Return current key. * * @since 1.0.0 * * @return int */ #[\ReturnTypeWillChange] public function key() { return $this->position; } /** * Advance to next item. * * @since 1.0.0 * * @return void */ #[\ReturnTypeWillChange] public function next() { ++$this->position; } /** * Ensure that current position is valid. * * @since 1.0.0 * * @return boolean */ #[\ReturnTypeWillChange] public function valid() { return isset( $this->results[ $this->position ] ); } /** * Add parameters to pass to the prepared query. * * @since 1.0.0 * * @param mixed $parameters Parameters to be set for the query. * @return void */ public function add_parameters( $parameters ) { $this->parameters = array_merge( $this->parameters, $parameters ); } /** * Whether to show all results. * * @since 1.1.0 * * @return boolean */ public function show_all() { return -1 == $this->get( 'number' ); } /** * Return the correct number of placeholders given a symbol and count. * * @since 1.0.0 * * @param int $count Number of placeholders. * @param string $placeholder Placeholder symbol. * @return string */ protected function get_placeholders( $count = 1, $placeholder = '%s' ) { return charitable_get_query_placeholders( $count, $placeholder ); } } endif;