File: /var/www/html/wpmuhibbah_err/wp-content/plugins/give/includes/donors/class-give-donor-wall.php
<?php
/**
* Donor Wall
*
* @package Give
* @subpackage Classes/Give_Donor_Wall
* @copyright Copyright (c) 2020, GiveWP
* @license https://opensource.org/licenses/gpl-license GNU Public License
* @since 1.0
*/
// Exit if accessed directly.
use Give\Donations\ValueObjects\DonationMetaKeys;
if (!defined('ABSPATH')) {
exit;
}
/**
* Give_Donor_Wall Class
*
* This class handles donors.
*
* @since 2.2.0
*/
class Give_Donor_Wall {
/**
* Instance.
*
* @since 2.2.0
* @access private
* @var Give_Donor_Wall
*/
private static $instance;
/**
* Singleton pattern.
*
* @since 2.2.0
* @access private
*/
private function __construct() {
}
/**
* Get instance.
*
* @since 2.2.0
* @access public
* @return Give_Donor_Wall
*/
public static function get_instance() {
if ( null === static::$instance ) {
self::$instance = new static();
self::$instance->setup_actions();
}
return self::$instance;
}
/**
* Setup the default hooks and actions
*
* @since 2.2.0
*
* @return void
*/
public function setup_actions() {
add_shortcode( 'give_donor_wall', [ $this, 'render_shortcode' ] );
add_action( 'wp_ajax_give_get_donor_comments', [ $this, 'ajax_handler' ] );
add_action( 'wp_ajax_nopriv_give_get_donor_comments', [ $this, 'ajax_handler' ] );
}
/**
* Displays donors in a grid layout.
*
* @since 4.3.1 remove redundant _give_redirect_form_id() function.
* @since 3.7.0 Sanitize attributes
* @since 2.27.0 Moved AJAX nonce verification to ajax_handler method.
* @since 2.2.0
*
* @param array $atts {
* Optional. Attributes of the donor wall shortcode.
*
* @type int $donors_per_page Number of donors per page. Default '20'.
* @type int $form_id The donation form to filter donors by. Default is all forms (no filter).
* @type bool $paged Whether to paginate donors. Default 'true'.
* @type string $ids A comma-separated list of donor IDs to display. Default empty.
* @type string $columns Maximum columns to display. Default 'best-fit'.
* Accepts 'best-fit', '1', '2', '3', '4'.
* @type bool $show_avatar Whether to display the donor's gravatar image if available. Default 'true'.
* @type bool $show_name Whether to display the donor's full name, first and last. Default 'true'.
* @type bool $show_company_name Whether to display the donor's company name. Default 'false'.
* @type bool $show_total Whether to display the donor's donation amount. Default 'true'.
* @type bool $show_comments Whether to display the donor's comment if they left one. Default 'true'.
* @type int $comment_length The number of words to display for the comments before a "Read more" field
* @type int $only_comments Whether to display the donors only with comment. Default 'false'.
* @type bool $show_time Whether to display date of the last donation. Default 'true'.
*
* @type string $readmore_text Link label for modal in which donor can read full comment.
* @type string $loadmore_text Button label which will load more donor comments.
* @type int $avatar_size Avatar image size in pixels without the "px". Default "75"
* @type string $orderby The order in which you want the donations to appear.
* Currently we are using this attribute internally and, it will sort donations by created date.
* @type string $order The order in which you want the donors to appear. Accepts "ASC". "DESC".
*
* }
* @return string|bool The markup of the form grid or false.
*/
public function render_shortcode( $atts ) {
$atts = give_clean($atts);
$give_settings = give_get_settings();
$atts = $this->parse_atts( $atts );
$donations = $this->get_donation_data( $atts );
$html = '';
if ( $donations ) {
ob_start();
foreach ( $donations as $donation ) {
$donor = new Give_Donor($donation['_give_payment_donor_id']);
// Give/templates/shortcode-donor-wall.php.
give_get_template(
'shortcode-donor-wall',
[
$donation,
$give_settings,
$atts,
$donor
]
);
}
$html = ob_get_clean();
// Return only donor html.
if (
isset( $atts['only_donor_html'] )
&& wp_doing_ajax()
&& $atts['only_donor_html']
) {
return $html;
}
}
$temp_atts = $atts;
$temp_atts['paged'] = $atts['paged'] + 1;
$more_btn_html = sprintf(
'<input type="hidden" class="give-donor-wall-shortcode-attrs" data-shortcode="%s" data-nonce="%s">',
rawurlencode( http_build_query( $atts ) ),
wp_create_nonce( 'givewp-donor-wall-more' )
);
if ( $this->has_donations( $temp_atts ) ) {
$more_btn_html .= sprintf(
'<button class="give-donor__load_more give-button-with-loader"><span class="give-loading-animation"></span>%1$s</button>',
$atts['loadmore_text']
);
}
$html = $html
? sprintf(
'<div class="give-wrap give-grid-ie-utility"><div class="give-grid give-grid--%1$s">%2$s</div>%3$s</div>',
esc_attr( $atts['columns'] ),
$html,
$more_btn_html
)
: '';
return $html;
}
/**
* Parse shortcode attributes
*
* @since 2.30.0
* @since 2.2.0
* @access public
*
* @param array $atts Shortcode attributes.
*
* @return array
*/
public function parse_atts( $atts ) {
$atts = shortcode_atts(
[
'donors_per_page' => 12,
'form_id' => 0,
'paged' => 1,
'ids' => '',
'cats' => '',
'tags' => '',
'columns' => '3',
'anonymous' => true,
'show_avatar' => true,
'show_name' => true,
'show_company_name' => false,
'show_form' => false,
'show_total' => true,
'show_comments' => true,
'show_tributes' => true,
'comment_length' => 140,
'only_comments' => false,
'readmore_text' => esc_html__( 'Read more', 'give' ),
'loadmore_text' => esc_html__( 'Load more', 'give' ),
'avatar_size' => 75,
'color' => "#219653",
'orderby' => 'post_date',
'order' => 'DESC',
'hide_empty' => true, // Deprecated in 2.3.0
'only_donor_html' => false, // Only for internal use.,
'show_time' => true,
],
$atts,
'give_donor_wall'
);
// Validate boolean attributes.
$boolean_attributes = [
'anonymous',
'show_avatar',
'show_name',
'show_company_name',
'show_total',
'show_comments',
'show_tributes',
'hide_empty',
'only_comments',
'only_donor_html',
'show_time'
];
foreach ( $boolean_attributes as $att ) {
// Convert numeric to boolean.
// It will prevent condition check against boolean value.
if ( is_numeric( $atts[ $att ] ) ) {
$atts[ $att ] = (bool) $atts[ $att ];
}
$atts[ $att ] = filter_var( $atts[ $att ], FILTER_VALIDATE_BOOLEAN );
}
// Validate numeric attributes.
$numeric_attributes = [
'donors_per_page',
'paged',
'comment_length',
'avatar_size',
];
foreach ( $numeric_attributes as $att ) {
// It will prevent condition check against numeric value.
$atts[ $att ] = absint( $atts[ $att ] );
}
// Validate comma separated numeric attributes and keep original data format ( comma separated string).
if ( ! empty( $atts['ids'] ) ) {
$atts['ids'] = implode( ',', $this->split_string($atts['ids'], 'absint') );
}
// Validate Form IDs
if ( ! empty( $atts['form_id'] ) ) {
$atts['form_id'] = implode( ',', $this->split_string($atts['form_id'], 'absint') );
}
// Donation form categories
if ( ! empty( $atts['cats'] ) ) {
$atts['cats'] = $this->split_string($atts['cats']);
}
// Donation form tags
if ( ! empty( $atts['tags'] ) ) {
$atts['tags'] = $this->split_string($atts['tags']);
}
return $atts;
}
/**
* Get donors
*
* @since 2.2.0
* @access public
*
* @param array $donor_query Donor query.
*
* @return array
*/
public function get_donors( $donor_query ) {
$donor_query = new Give_Donors_Query( $donor_query );
return $donor_query->get_donors();
}
/**
* This function should return donor comment for ajax request.
*
* @since 2.27.0 Check nonce for AJAX request to prevent scrapping, see https://github.com/impress-org/givewp/issues/6374.
* @since 2.2.0
* @access public
*/
public function ajax_handler() {
$shortcode_atts = array_map( 'give_clean', wp_parse_args( rawurldecode( $_POST['data'] ) ) ); // @codingStandardsIgnoreLine
// Get next page donor comments.
$shortcode_atts['paged'] = $shortcode_atts['paged'] + 1;
$shortcode_atts['only_donor_html'] = true;
check_ajax_referer( 'givewp-donor-wall-more', 'nonce' );
$donors_comment_html = $this->render_shortcode( $shortcode_atts );
// Check if donor comment remaining.
$temp_atts = $shortcode_atts;
$temp_atts['paged'] = $shortcode_atts['paged'] + 1;
$has_donors = $this->has_donations( $temp_atts ) ? 1 : 0;
// Remove internal shortcode param.
unset( $shortcode_atts['only_donor_html'] );
wp_send_json(
[
'shortcode' => rawurlencode( http_build_query( $shortcode_atts ) ),
'html' => $donors_comment_html,
'remaining' => $has_donors,
]
);
}
/**
* Get query params
*
* @since 2.24.1
* @since 2.3.0
*
* @param array $atts
*
* @return array
*/
private function get_query_param( $atts = [] ) {
$valid_order = [ 'ASC', 'DESC' ];
$valid_orderby = [ 'post_date', 'donation_amount' ];
$query_atts = [];
$query_atts['order'] = in_array( $atts['order'], $valid_order ) ? $atts['order'] : 'DESC';
$query_atts['orderby'] = in_array( $atts['orderby'], $valid_orderby ) ? $atts['orderby'] : 'post_date';
$query_atts['limit'] = absint( $atts['donors_per_page'] );
$query_atts['offset'] = absint( $atts['donors_per_page'] * ( $atts['paged'] - 1 ) );
$query_atts['form_id'] = implode( '\',\'', array_map( 'absint', explode( ',', $atts['form_id'] ) ) );
$query_atts['ids'] = implode( '\',\'', array_map( 'absint', explode( ',', $atts['ids'] ) ) );
$query_atts['cats'] = $atts['cats'];
$query_atts['tags'] = $atts['tags'];
$query_atts['only_comments'] = ( true === $atts['only_comments'] );
$query_atts['anonymous'] = ( true === $atts['anonymous'] );
return $query_atts;
}
/**
* Get donation data.
*
* @since 2.27.0 Change to read comment from donations meta table
* @since 2.3.0
*
* @param array $atts
*
* @return array
*/
private function get_donation_data( $atts = [] ) {
global $wpdb;
// Bailout if donation does not exist.
if ( ! ( $donation_ids = $this->get_donations( $atts ) ) ) {
return [];
}
$donation_ids = ! empty( $donation_ids )
? '\'' . implode( '\',\'', $donation_ids ) . '\''
: '';
// Backward compatibility
$donation_id_col = Give()->payment_meta->get_meta_type() . '_id';
$sql = "SELECT m1.*, p1.post_date as donation_date FROM {$wpdb->donationmeta} as m1
INNER JOIN {$wpdb->posts} as p1 ON (m1.{$donation_id_col}=p1.ID)
WHERE m1.{$donation_id_col} IN ( {$donation_ids} )
ORDER BY FIELD( p1.ID, {$donation_ids} )
";
$results = (array) $wpdb->get_results( $sql );
if ( ! empty( $results ) ) {
$temp = [];
/* @var stdClass $result */
foreach ( $results as $result ) {
$temp[ $result->{$donation_id_col} ][ $result->meta_key ] = maybe_unserialize( $result->meta_value );
// Set donation date.
if ( empty( $temp[ $result->{$donation_id_col} ]['donation_date'] ) ) {
$temp[ $result->{$donation_id_col} ]['donation_date'] = $result->donation_date;
}
}
if ( ! empty( $temp ) ) {
foreach ( $temp as $donation_id => $donation_data ) {
$temp[ $donation_id ]['donation_id'] = $donation_id;
$temp[ $donation_id ]['name_initial'] = give_get_name_initial(
[
'firstname' => $donation_data['_give_donor_billing_first_name'],
'lastname' => $donation_data['_give_donor_billing_last_name'],
]
);
$temp[$donation_id]['donor_comment'] = give_get_payment_meta(
$donation_id,
DonationMetaKeys::COMMENT
);
}
}
$results = ! empty( $temp ) ? $temp : [];
}
return $results;
}
/**
* Get donation list for specific query
*
* @since 3.17.2 fix - filter by only_comments attr
* @since 2.3.0
*
* @param array $atts
*
* @return array
*/
private function get_donations( $atts = [] ) {
global $wpdb;
// Backward compatibility
$donation_id_col = Give()->payment_meta->get_meta_type() . '_id';
$query_params = $this->get_query_param( $atts );
$sql = "SELECT p1.ID FROM {$wpdb->posts} as p1";
$where = " WHERE p1.post_status IN ('publish') AND p1.post_type = 'give_payment'";
// exclude donation with zero amount from result.
$sql .= " INNER JOIN {$wpdb->donationmeta} as m1 ON (p1.ID = m1.{$donation_id_col})";
$where .= " AND m1.meta_key='_give_payment_total' AND m1.meta_value>0";
if ( $query_params['form_id'] ) {
$sql .= " INNER JOIN {$wpdb->donationmeta} as m2 ON (p1.ID = m2.{$donation_id_col})";
$where .= " AND m2.meta_key='_give_payment_form_id' AND m2.meta_value IN ('{$query_params['form_id']}')";
}
// Get donations only from specific donors.
if ( $query_params['ids'] ) {
$sql .= " INNER JOIN {$wpdb->donationmeta} as m3 ON (p1.ID = m3.{$donation_id_col})";
$where .= " AND m3.meta_key='_give_payment_donor_id' AND m3.meta_value IN ('{$query_params['ids']}')";
}
// exclude donations which does not has donor comment.
if ( $query_params['only_comments'] ) {
$sql .= " INNER JOIN {$wpdb->donationmeta} as m4 ON (p1.ID = m4.{$donation_id_col})";
$where .= " AND m4.meta_key='_give_donation_comment'";
}
// exclude anonymous donation form query based on query parameters.
if (
! $query_params['anonymous']
|| $query_params['only_comments']
) {
$where .= " AND p1.ID NOT IN ( SELECT DISTINCT({$donation_id_col}) FROM {$wpdb->donationmeta} WHERE meta_key='_give_anonymous_donation' AND meta_value='1')";
}
// Handle Taxonomy
$args = [
'post_type' => 'give_forms',
'posts_per_page' => -1,
'fields' => 'ids',
'tax_query' => [],
];
// Categories
if ( is_array($atts['cats'])) {
$args['tax_query']['conditions'] = ['relation' => 'OR'];
foreach ($atts['cats'] as $category) {
$args['tax_query']['conditions'][] = [
'operator' => 'IN',
'taxonomy' => 'give_forms_category',
'field' => 'slug',
'terms' => $category,
];
}
}
// Tags
if ( is_array($atts['tags'])) {
if (empty($args['tax_query'])) {
$args['tax_query']['conditions'] = ['relation' => 'OR'];
}
foreach($atts['tags'] as $tag) {
$args['tax_query']['conditions'][] = [
'operator' => 'IN',
'taxonomy' => 'give_forms_tag',
'field' => 'slug',
'terms' => $tag,
];
}
}
if ( ! empty( $args['tax_query'] ) ) {
$query = new WP_Query( $args );
if ( ! empty($query->posts) ) {
$form_ids = implode("','", $query->posts );
$sql .= " INNER JOIN {$wpdb->donationmeta} as m4 ON (p1.ID = m4.{$donation_id_col})";
$where .= " AND m4.meta_key='_give_payment_form_id' AND m4.meta_value IN ('{$form_ids}')";
}
}
// order by query based on parameter.
if ( 'donation_amount' === $query_params['orderby'] ) {
$order = " ORDER BY m1.meta_value+0 {$query_params['order']}";
} else {
$order = " ORDER BY p1.{$query_params['orderby']} {$query_params['order']}, p1.ID {$query_params['order']}";
}
$limit = " LIMIT {$query_params['limit']}";
$offset = " OFFSET {$query_params['offset']}";
$sql .= $where . $order . $limit . $offset;
return $wpdb->get_col( $sql );
}
/**
* Get donor comments
*
* @since 2.3.0
*
* @param array $donations_data
*
* @return array
*/
private function get_donor_comments( $donations_data = [] ) {
global $wpdb;
$comments = [];
// Bailout.
if ( empty( $donations_data ) ) {
return $comments;
}
// Backward compatibility.
if (
! give_has_upgrade_completed( 'v230_move_donor_note' )
|| ! give_has_upgrade_completed( 'v230_move_donation_note' )
) {
foreach ( $donations_data as $id => $data ) {
$comment = give_get_donor_donation_comment( $id, $data['_give_payment_donor_id'] );
$comments[ $id ] = ! empty( $comment ) ? $comment->comment_content : '';
}
return $comments;
}
$sql = "SELECT c1.comment_parent as donation_id, c1.comment_content as comment FROM {$wpdb->give_comments} as c1";
$sql .= " INNER JOIN {$wpdb->give_commentmeta} as cm1 ON (c1.comment_ID=cm1.give_comment_id)";
$where = [];
foreach ( $donations_data as $id => $data ) {
// Do not fetch comment for anonymous donation.
if ( ! empty( $data['_give_anonymous_donation'] ) ) {
continue;
}
$where[] = "(c1.comment_parent={$id} AND cm1.meta_key='_give_donor_id' AND cm1.meta_value={$data['_give_payment_donor_id']})";
}
$where = ' WHERE ' . implode( ' OR ', $where );
$where .= " AND c1.comment_type='donor_donation'";
$sql = $sql . $where;
$comments = (array) $wpdb->get_results( $sql );
if ( ! empty( $comments ) ) {
$comments = array_combine(
wp_list_pluck( $comments, 'donation_id' ),
wp_list_pluck( $comments, 'comment' )
);
}
return $comments;
}
/**
* Check if donation exist or not for specific query
*
* @since 2.3.0
*
* @param array $atts
*
* @return bool
*/
private function has_donations( $atts = [] ) {
return (bool) $this->get_donations( $atts );
}
/**
* @since 2.20.0
*
* @param string $string
* @param null|callable $filter
* @param string $separator
*
* @return array
*/
private function split_string($string, $filter = null, $separator = ',') {
if ( false === strpos( $string, $separator ) ) {
$string = trim( $string );
if (is_callable($filter)) {
$string = $filter($string);
}
return [$string];
}
return array_filter(
array_map(
static function( $value ) use ($filter) {
$value = trim( $value );
if (is_callable($filter)) {
return $filter($value);
}
return $value;
},
explode( $separator, $string )
)
);
}
}
// Initialize shortcode.
Give_Donor_Wall::get_instance();