File: /var/www/html/wpmuhibbah_err/wp-content/plugins/give/includes/payments/class-payments-query.php
<?php
/**
* Payments Query
*
* @package Give
* @subpackage Classes/Stats
* @copyright Copyright (c) 2016, GiveWP
* @license https://opensource.org/licenses/gpl-license GNU Public License
* @since 1.0
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Give_Payments_Query Class
*
* This class is for retrieving payments data.
*
* Payments can be retrieved for date ranges and pre-defined periods.
*
* @since 1.0
*/
class Give_Payments_Query extends Give_Stats {
/**
* Preserve args
*
* @since 1.8.17
* @access public
*
* @var array
*/
public $_args = [];
/**
* The args to pass to the give_get_payments() query
*
* @since 1.0
* @access public
*
* @var array
*/
public $args = [];
/**
* The payments found based on the criteria set
*
* @since 1.0
* @access public
*
* @var array
*/
public $payments = [];
/**
* Default query arguments.
*
* Not all of these are valid arguments that can be passed to WP_Query. The ones that are not, are modified before
* the query is run to convert them to the proper syntax.
*
* @since 1.0
* @access public
*
* @param $args array The array of arguments that can be passed in and used for setting up this payment query.
*/
public function __construct( $args = [] ) {
$defaults = [
'output' => 'payments',
'post_type' => [ 'give_payment' ],
'start_date' => false,
'end_date' => false,
'number' => 20,
'page' => null,
'orderby' => 'ID',
'order' => 'DESC',
'user' => null, // deprecated, use donor
'donor' => null,
'status' => give_get_payment_status_keys(),
'meta_key' => null,
'year' => null,
'month' => null,
'day' => null,
's' => null,
'search_in_notes' => false,
'children' => false,
'fields' => null,
'gateway' => null,
'give_forms' => null,
'offset' => null,
// Currently these params only works with get_payment_by_group
'group_by' => '',
'count' => false,
];
// We do not want WordPress to handle meta cache because WordPress stores in under `post_meta` key and cache object while we want it under `donation_meta`.
// Similar for term cache
$args['update_post_meta_cache'] = false;
$this->args = $this->_args = wp_parse_args( $args, $defaults );
$this->init();
}
/**
* Set a query variable.
*
* @since 1.0
* @access public
*
* @param $query_var
* @param $value
*/
public function __set( $query_var, $value ) {
if ( in_array( $query_var, [ 'meta_query', 'tax_query' ] ) ) {
$this->args[ $query_var ][] = $value;
} else {
$this->args[ $query_var ] = $value;
}
}
/**
* Unset a query variable.
*
* @since 1.0
* @access public
*
* @param $query_var
*/
public function __unset( $query_var ) {
unset( $this->args[ $query_var ] );
}
/**
* Modify the query/query arguments before we retrieve payments.
*
* @since 1.0
* @access public
*
* @return void
*/
public function init() {
}
/**
* Set query filter.
*
* @since 1.8.9
* @access private
*/
private function set_filters() {
// Reset param to apply filters.
// While set filters $args will get override and multiple get_payments call will not work.
$this->args = $this->_args;
// Whitelist order.
$this->args['order'] = in_array( strtoupper( $this->args['order'] ), [ 'ASC', 'DESC' ] ) ? $this->args['order'] : 'DESC';
$this->date_filter_pre();
$this->orderby();
$this->status();
$this->month();
$this->per_page();
$this->page();
$this->user();
$this->donor();
$this->search();
$this->mode();
$this->children();
$this->give_forms();
$this->gateway_filter();
add_filter( 'posts_orderby', [ $this, 'custom_orderby' ], 10, 2 );
/**
* Fires after setup filters.
*
* @since 1.0
*
* @param Give_Payments_Query $this Payments query object.
*/
do_action( 'give_pre_get_payments', $this );
}
/**
* Unset query filter.
*
* @since 1.8.9
* @access private
*/
private function unset_filters() {
remove_filter( 'posts_orderby', [ $this, 'custom_orderby' ] );
/**
* Fires after retrieving payments.
*
* @since 1.0
*
* @param Give_Payments_Query $this Payments query object.
*/
do_action( 'give_post_get_payments', $this );
}
/**
* Retrieve payments.
*
* The query can be modified in two ways; either the action before the
* query is run, or the filter on the arguments (existing mainly for backwards
* compatibility).
*
* @since 1.0
* @since 2.9.6 Normalize post IDs from either an array of IDs or Post objects.
*
* @access public
*
* @return array
*/
public function get_payments() {
global $post;
$results = [];
$this->payments = [];
$cache_key = Give_Cache::get_key( 'give_payment_query', $this->args, false );
$this->payments = Give_Cache::get_db_query( $cache_key );
// Return cached result.
if ( ! is_null( $this->payments ) ) {
return $this->payments;
}
// Modify the query/query arguments before we retrieve payments.
$this->set_filters();
/* @var WP_Query $query */
$query = new WP_Query( $this->args );
$custom_output = [
'payments',
'give_payments',
];
if ( $query->have_posts() ) {
// Update meta cache only if query is not for all donations.
// @see https://github.com/impress-org/give/issues/4104
if (
( isset( $this->args['nopaging'] ) && true !== (bool) $this->args['nopaging'] )
|| ( isset( $this->args['posts_per_page'] ) && 0 < $this->args['posts_per_page'] )
) {
$postIDs = array_map(
function( $postOrID ) {
return is_object( $postOrID ) ? $postOrID->ID : $postOrID;
},
$query->posts
);
self::update_meta_cache( $postIDs );
}
if ( ! in_array( $this->args['output'], $custom_output ) ) {
$results = $query->posts;
} else {
$previous_post = $post;
while ( $query->have_posts() ) {
$query->the_post();
$payment_id = get_post()->ID;
$payment = new Give_Payment( $payment_id );
$this->payments[] = apply_filters( 'give_payment', $payment, $payment_id, $this );
}
wp_reset_postdata();
// Prevent nest loop from producing unexpected results.
if ( $previous_post instanceof WP_Post ) {
$post = $previous_post;
setup_postdata( $post );
}
$results = $this->payments;
}
}
Give_Cache::set_db_query( $cache_key, $results );
// Remove query filters after we retrieve payments.
$this->unset_filters();
return $results;
}
/**
* Get payments by group
*
* @since 1.8.17
* @access public
*
* @return array
*/
public function get_payment_by_group() {
global $wpdb;
$allowed_groups = [ 'post_status' ];
$result = [];
if ( in_array( $this->args['group_by'], $allowed_groups ) ) {
// Set only count in result.
if ( $this->args['count'] ) {
$this->set_filters();
$new_results = $wpdb->get_results( $this->get_sql(), ARRAY_N );
$this->unset_filters();
foreach ( $new_results as $results ) {
$result[ $results[0] ] = $results[1];
}
switch ( $this->args['group_by'] ) {
case 'post_status':
/* @var Give_Payment $donation */
foreach ( give_get_payment_status_keys() as $status ) {
if ( ! isset( $result[ $status ] ) ) {
$result[ $status ] = 0;
}
}
break;
}
} else {
$donations = $this->get_payments();
/* @var $donation Give_Payment */
foreach ( $donations as $donation ) {
$result[ $donation->{$this->args['group_by']} ][] = $donation;
}
}
}
/**
* Filter the result
*
* @since 1.8.17
*/
return apply_filters( 'give_get_payment_by_group', $result, $this );
}
/**
* If querying a specific date, add the proper filters.
*
* @since 1.0
* @access public
*
* @return void
*/
public function date_filter_pre() {
if ( ! ( $this->args['start_date'] || $this->args['end_date'] ) ) {
return;
}
$this->setup_dates( $this->args['start_date'], $this->args['end_date'] );
$is_start_date = property_exists( __CLASS__, 'start_date' );
$is_end_date = property_exists( __CLASS__, 'end_date' );
if ( $is_start_date || $is_end_date ) {
$date_query = [];
if ( $is_start_date && ! is_wp_error( $this->start_date ) ) {
$date_query['after'] = date( 'Y-m-d H:i:s', $this->start_date );
}
if ( $is_end_date && ! is_wp_error( $this->end_date ) ) {
$date_query['before'] = date( 'Y-m-d H:i:s', $this->end_date );
}
// Include Start Date and End Date while querying.
$date_query['inclusive'] = true;
$this->__set( 'date_query', $date_query );
}
}
/**
* Post Status
*
* @since 1.0
* @access public
*
* @return void
*/
public function status() {
if ( ! isset( $this->args['status'] ) ) {
return;
}
$this->__set( 'post_status', $this->args['status'] );
$this->__unset( 'status' );
}
/**
* Current Page
*
* @since 1.0
* @access public
*
* @return void
*/
public function page() {
if ( ! isset( $this->args['page'] ) ) {
return;
}
$this->__set( 'paged', $this->args['page'] );
$this->__unset( 'page' );
}
/**
* Posts Per Page
*
* @since 1.0
* @access public
*
* @return void
*/
public function per_page() {
if ( ! isset( $this->args['number'] ) ) {
return;
}
if ( $this->args['number'] == - 1 ) {
$this->__set( 'nopaging', true );
} else {
$this->__set( 'posts_per_page', $this->args['number'] );
}
$this->__unset( 'number' );
}
/**
* Current Month
*
* @since 1.0
* @access public
*
* @return void
*/
public function month() {
if ( ! isset( $this->args['month'] ) ) {
return;
}
$this->__set( 'monthnum', $this->args['month'] );
$this->__unset( 'month' );
}
/**
* Order by
*
* @since 1.0
* @access public
*
* @return void
*/
public function orderby() {
switch ( $this->args['orderby'] ) {
case 'amount':
$this->__set( 'orderby', 'meta_value_num' );
$this->__set( 'meta_key', '_give_payment_total' );
break;
case 'status':
$this->__set( 'orderby', 'post_status' );
break;
case 'donation_form':
$this->__set( 'orderby', 'meta_value' );
$this->__set( 'meta_key', '_give_payment_form_title' );
break;
default:
$this->__set( 'orderby', $this->args['orderby'] );
break;
}
}
/**
* Custom orderby.
* Note: currently custom sorting is only used for donation listing page.
*
* @since 1.8
* @access public
*
* @param string $order
* @param WP_Query $query
*
* @return mixed
*/
public function custom_orderby( $order, $query ) {
if ( ! empty( $query->query['post_type'] ) ) {
$post_types = is_array( $query->query['post_type'] ) ? $query->query['post_type'] : [ $query->query['post_type'] ];
if ( ! in_array( 'give_payment', $post_types ) || ! isset( $query->query['orderby'] ) || is_array( $query->query['orderby'] ) ) {
return $order;
}
global $wpdb;
switch ( $query->query['orderby'] ) {
case 'post_status':
$order = $wpdb->posts . '.post_status ' . strtoupper( $query->query['order'] );
break;
}
}
return $order;
}
/**
* Specific User
*
* @since 1.0
* @access public
*
* @return void
*/
public function user() {
if ( is_null( $this->args['user'] ) ) {
return;
}
$args = [];
if ( is_numeric( $this->args['user'] ) ) {
// Backward compatibility: user donor param to get payment attached to donor instead of user
$donor_id = Give()->donors->get_column_by( 'id', 'user_id', $this->args['user'] );
$args = [
'key' => '_give_payment_donor_id',
'value' => $donor_id ?: -1,
];
} elseif ( is_email( $this->args['user'] ) ) {
$args = [
'key' => '_give_payment_donor_email',
'value' => $this->args['user'],
];
}
$this->__set( 'meta_query', $args );
}
/**
* Specific donor id
*
* @access public
* @since 1.8.9
* @return void
*/
public function donor() {
if ( is_null( $this->args['donor'] ) || ! is_numeric( $this->args['donor'] ) ) {
return;
}
$donor_meta_type = Give()->donor_meta->meta_type;
$this->__set(
'meta_query',
[
'key' => "_give_payment_{$donor_meta_type}_id",
'value' => (int) $this->args['donor'],
]
);
}
/**
* Search
*
* @since 1.0
* @access public
*
* @return void
*/
public function search() {
if ( ! isset( $this->args['s'] ) ) {
return;
}
$search = trim( $this->args['s'] );
if ( empty( $search ) ) {
return;
}
$is_email = is_email( $search ) || strpos( $search, '@' ) !== false;
$is_user = strpos( $search, strtolower( 'user:' ) ) !== false;
if ( ! empty( $this->args['search_in_notes'] ) ) {
$notes = give_get_payment_notes( 0, $search );
if ( ! empty( $notes ) ) {
$payment_ids = wp_list_pluck( (array) $notes, 'comment_post_ID' );
$this->__set( 'post__in', $payment_ids );
}
$this->__unset( 's' );
} elseif ( $is_email || strlen( $search ) == 32 ) {
$key = $is_email ? '_give_payment_donor_email' : '_give_payment_purchase_key';
$search_meta = [
'key' => $key,
'value' => $search,
'compare' => 'LIKE',
];
$this->__set( 'meta_query', $search_meta );
$this->__unset( 's' );
} elseif ( $is_user ) {
$search_meta = [
'key' => '_give_payment_donor_id',
'value' => trim( str_replace( 'user:', '', strtolower( $search ) ) ),
];
$this->__set( 'meta_query', $search_meta );
$this->__unset( 's' );
} elseif ( is_numeric( $search ) ) {
$post = get_post( $search );
if ( is_object( $post ) && $post->post_type == 'give_payment' ) {
$arr = [];
$arr[] = $search;
$this->__set( 'post__in', $arr );
$this->__unset( 's' );
}
} elseif ( '#' == substr( $search, 0, 1 ) ) {
$search = str_replace( '#:', '', $search );
$search = str_replace( '#', '', $search );
$this->__set( 'give_forms', $search );
$this->__unset( 's' );
} elseif ( ! empty( $search ) ) {
$search_parts = preg_split( '/\s+/', $search );
if ( is_array( $search_parts ) && 2 === count( $search_parts ) ) {
$search_meta = [
'relation' => 'AND',
[
'key' => '_give_donor_billing_first_name',
'value' => $search_parts[0],
'compare' => 'LIKE',
],
[
'key' => '_give_donor_billing_last_name',
'value' => $search_parts[1],
'compare' => 'LIKE',
],
];
} else {
$search_meta = [
'relation' => 'OR',
[
'key' => '_give_donor_billing_first_name',
'value' => $search,
'compare' => 'LIKE',
],
[
'key' => '_give_donor_billing_last_name',
'value' => $search,
'compare' => 'LIKE',
],
];
}
$this->__set( 'meta_query', $search_meta );
$this->__unset( 's' );
} else {
$this->__set( 's', $search );
}
}
/**
* Payment Mode
*
* @since 1.0
* @access public
*
* @return void
*/
public function mode() {
if ( empty( $this->args['mode'] ) || $this->args['mode'] == 'all' ) {
$this->__unset( 'mode' );
return;
}
$this->__set(
'meta_query',
[
'key' => '_give_payment_mode',
'value' => $this->args['mode'],
]
);
}
/**
* Children
*
* @since 1.0
* @access public
*
* @return void
*/
public function children() {
if ( empty( $this->args['children'] ) ) {
$this->__set( 'post_parent', 0 );
}
$this->__unset( 'children' );
}
/**
* Specific Give Form
*
* @since 1.0
* @access public
*
* @return void
*/
public function give_forms() {
if ( empty( $this->args['give_forms'] ) ) {
return;
}
$compare = '=';
if ( is_array( $this->args['give_forms'] ) ) {
$compare = 'IN';
}
$this->__set(
'meta_query',
[
'key' => '_give_payment_form_id',
'value' => $this->args['give_forms'],
'compare' => $compare,
]
);
$this->__unset( 'give_forms' );
}
/**
* Specific Gateway
*
* @since 1.8.17
* @access public
*
* @return void
*/
public function gateway_filter() {
if ( empty( $this->args['gateway'] ) ) {
return;
}
$compare = '=';
if ( is_array( $this->args['gateway'] ) ) {
$compare = 'IN';
}
$this->__set(
'meta_query',
[
'key' => '_give_payment_gateway',
'value' => $this->args['gateway'],
'compare' => $compare,
]
);
$this->__unset( 'gateway' );
}
/**
* Get sql query
*
* Note: Internal purpose only. We are developing on this fn.
*
* @since 1.8.18
* @access public
* @global $wpdb
*
* @return string
*/
private function get_sql() {
global $wpdb;
$allowed_keys = [
'post_name',
'post_author',
'post_date',
'post_title',
'post_status',
'post_modified',
'post_parent',
'post_type',
'menu_order',
'comment_count',
];
$this->args['orderby'] = 'post_parent__in';
// Whitelist orderby.
if ( ! in_array( $this->args['orderby'], $allowed_keys ) ) {
$this->args['orderby'] = 'ID';
}
$where = "WHERE {$wpdb->posts}.post_type = 'give_payment'";
$where .= " AND {$wpdb->posts}.post_status IN ('" . implode( "','", $this->args['post_status'] ) . "')";
if ( is_numeric( $this->args['post_parent'] ) ) {
$where .= " AND {$wpdb->posts}.post_parent={$this->args['post_parent']}";
}
// Set orderby.
$orderby = "ORDER BY {$wpdb->posts}.{$this->args['orderby']}";
$group_by = '';
// Set group by.
if ( ! empty( $this->args['group_by'] ) ) {
$group_by = "GROUP BY {$wpdb->posts}.{$this->args['group_by']}";
}
// Set offset.
if (
empty( $this->args['nopaging'] ) &&
empty( $this->args['offset'] ) &&
( ! empty( $this->args['page'] ) && 0 < $this->args['page'] )
) {
$this->args['offset'] = $this->args['posts_per_page'] * ( $this->args['page'] - 1 );
}
// Set fields.
$fields = "{$wpdb->posts}.*";
if ( ! empty( $this->args['fields'] ) && 'all' !== $this->args['fields'] ) {
if ( is_string( $this->args['fields'] ) ) {
$fields = "{$wpdb->posts}.{$this->args['fields']}";
} elseif ( is_array( $this->args['fields'] ) ) {
$fields = "{$wpdb->posts}." . implode( " , {$wpdb->posts}.", $this->args['fields'] );
}
}
// Set count.
if ( ! empty( $this->args['count'] ) ) {
$fields = "COUNT({$wpdb->posts}.ID)";
if ( ! empty( $this->args['group_by'] ) ) {
$fields = "{$wpdb->posts}.{$this->args['group_by']}, {$fields}";
}
}
// Date query.
if ( ! empty( $this->args['date_query'] ) ) {
$date_query_obj = new WP_Date_Query( $this->args['date_query'] );
$where .= str_replace(
[
"\n",
'( (',
'))',
],
[
'',
'( (',
') )',
],
$date_query_obj->get_sql()
);
}
// Meta query.
if ( ! empty( $this->args['meta_query'] ) ) {
$meta_query_obj = new WP_Meta_Query( $this->args['meta_query'] );
$where = implode( ' ', $meta_query_obj->get_sql( 'post', $wpdb->posts, 'ID' ) ) . " {$where}";
$where = Give()->payment_meta->__rename_meta_table_name( $where, 'posts_where' );
}
// Set sql query.
$sql = $wpdb->prepare(
"SELECT {$fields} FROM {$wpdb->posts} LIMIT %d,%d;",
absint( $this->args['offset'] ),
( empty( $this->args['nopaging'] ) ? absint( $this->args['posts_per_page'] ) : 99999999999 )
);
// $where, $orderby and order already prepared query they can generate notice if you re prepare them in above.
// WordPress consider LIKE condition as placeholder if start with s,f, or d.
$sql = str_replace( 'LIMIT', "{$where} {$group_by} {$orderby} {$this->args['order']} LIMIT", $sql );
return $sql;
}
/**
* Update donations meta cache
*
* @since 2.5.0
* @access private
*
* @param $donation_ids
*/
public static function update_meta_cache( $donation_ids ) {
// Exit.
if ( empty( $donation_ids ) ) {
return;
}
update_meta_cache( Give()->payment_meta->get_meta_type(), $donation_ids );
}
}