<?php
defined('ABSPATH') || exit;
class WC_Comments
{
public static function init()
{
add_filter('comments_open', array(__CLASS__, 'comments_open'), 10, 2);
add_filter('preprocess_comment', array(__CLASS__, 'check_comment_rating'), 0);
add_action('comment_post', array(__CLASS__, 'add_comment_rating'), 1);
add_action('comment_moderation_recipients', array(__CLASS__, 'comment_moderation_recipients'), 10, 2);
add_action('wp_update_comment_count', array(__CLASS__, 'clear_transients'));
add_filter('comments_clauses', array(__CLASS__, 'exclude_order_comments'), 10, 1);
add_filter('comment_feed_where', array(__CLASS__, 'exclude_order_comments_from_feed_where'));
add_filter('comments_clauses', array(__CLASS__, 'exclude_webhook_comments'), 10, 1);
add_filter('comment_feed_where', array(__CLASS__, 'exclude_webhook_comments_from_feed_where'));
add_filter('wp_count_comments', array(__CLASS__, 'wp_count_comments'), 10, 2);
add_action('wp_insert_comment', array(__CLASS__, 'delete_comments_count_cache'));
add_action('wp_set_comment_status', array(__CLASS__, 'delete_comments_count_cache'));
add_filter('get_avatar_comment_types', array(__CLASS__, 'add_avatar_for_review_comment_type'));
add_action('comment_post', array(__CLASS__, 'add_comment_purchase_verification'));
add_action('preprocess_comment', array(__CLASS__, 'update_comment_type'), 1);
add_action('pre_comment_on_post', array(__CLASS__, 'validate_product_review_verified_owners'));
}
public static function comments_open($open, $post_id)
{
if ('product' === get_post_type($post_id) && !post_type_supports('product', 'comments')) {
$open = false;
}
return $open;
}
public static function exclude_order_comments($clauses)
{
$clauses['where'] .= ($clauses['where'] ? ' AND ' : '') . " comment_type != 'order_note' ";
return $clauses;
}
public static function exclude_order_comments_from_feed_join($join)
{
wc_deprecated_function('WC_Comments::exclude_order_comments_from_feed_join', '3.1');
}
public static function exclude_order_comments_from_feed_where($where)
{
return $where . ($where ? ' AND ' : '') . " comment_type != 'order_note' ";
}
public static function exclude_webhook_comments($clauses)
{
$clauses['where'] .= ($clauses['where'] ? ' AND ' : '') . " comment_type != 'webhook_delivery' ";
return $clauses;
}
public static function exclude_webhook_comments_from_feed_join($join)
{
wc_deprecated_function('WC_Comments::exclude_webhook_comments_from_feed_join', '3.1');
}
public static function exclude_webhook_comments_from_feed_where($where)
{
return $where . ($where ? ' AND ' : '') . " comment_type != 'webhook_delivery' ";
}
public static function check_comment_rating($comment_data)
{
if (!is_admin() && isset($_POST['comment_post_ID'], $_POST['rating'], $comment_data['comment_type']) && 'product' === get_post_type(absint($_POST['comment_post_ID'])) && empty($_POST['rating']) && self::is_default_comment_type($comment_data['comment_type']) && wc_review_ratings_enabled() && wc_review_ratings_required()) {
wp_die(esc_html__('Please rate the product.', 'woocommerce'));
exit;
}
return $comment_data;
}
public static function add_comment_rating($comment_id)
{
if (isset($_POST['rating'], $_POST['comment_post_ID']) && 'product' === get_post_type(absint($_POST['comment_post_ID']))) {
if (!$_POST['rating'] || $_POST['rating'] > 5 || $_POST['rating'] < 0) {
return;
}
add_comment_meta($comment_id, 'rating', intval($_POST['rating']), true);
$post_id = isset($_POST['comment_post_ID']) ? absint($_POST['comment_post_ID']) : 0;
if ($post_id) {
self::clear_transients($post_id);
}
}
}
public static function comment_moderation_recipients($emails, $comment_id)
{
$comment = get_comment($comment_id);
if ($comment && 'product' === get_post_type($comment->comment_post_ID)) {
$emails = array(get_option('admin_email'));
}
return $emails;
}
public static function clear_transients($post_id)
{
if ('product' === get_post_type($post_id)) {
$product = wc_get_product($post_id);
$product->set_rating_counts(self::get_rating_counts_for_product($product));
$product->set_average_rating(self::get_average_rating_for_product($product));
$product->set_review_count(self::get_review_count_for_product($product));
$product->save();
}
}
public static function delete_comments_count_cache()
{
delete_transient('wc_count_comments');
}
public static function wp_count_comments($stats, $post_id)
{
global $wpdb;
if (0 === $post_id) {
$stats = get_transient('wc_count_comments');
if (!$stats) {
$stats = array('total_comments' => 0, 'all' => 0);
$count = $wpdb->get_results("\n\t\t\t\t\tSELECT comment_approved, COUNT(*) AS num_comments\n\t\t\t\t\tFROM {$wpdb->comments}\n\t\t\t\t\tWHERE comment_type NOT IN ('action_log', 'order_note', 'webhook_delivery')\n\t\t\t\t\tGROUP BY comment_approved\n\t\t\t\t\t", ARRAY_A);
$approved = array('0' => 'moderated', '1' => 'approved', 'spam' => 'spam', 'trash' => 'trash', 'post-trashed' => 'post-trashed');
foreach ((array) $count as $row) {
if (!in_array($row['comment_approved'], array('post-trashed', 'trash', 'spam'), true)) {
$stats['all'] += $row['num_comments'];
$stats['total_comments'] += $row['num_comments'];
} elseif (!in_array($row['comment_approved'], array('post-trashed', 'trash'), true)) {
$stats['total_comments'] += $row['num_comments'];
}
if (isset($approved[$row['comment_approved']])) {
$stats[$approved[$row['comment_approved']]] = $row['num_comments'];
}
}
foreach ($approved as $key) {
if (empty($stats[$key])) {
$stats[$key] = 0;
}
}
$stats = (object) $stats;
set_transient('wc_count_comments', $stats);
}
}
return $stats;
}
public static function add_avatar_for_review_comment_type($comment_types)
{
return array_merge($comment_types, array('review'));
}
public static function add_comment_purchase_verification($comment_id)
{
$comment = get_comment($comment_id);
$verified = false;
if ('product' === get_post_type($comment->comment_post_ID)) {
$verified = wc_customer_bought_product($comment->comment_author_email, $comment->user_id, $comment->comment_post_ID);
add_comment_meta($comment_id, 'verified', (int) $verified, true);
}
return $verified;
}
public static function get_average_rating_for_product(&$product)
{
global $wpdb;
$count = $product->get_rating_count();
if ($count) {
$ratings = $wpdb->get_var($wpdb->prepare("\n\t\t\t\tSELECT SUM(meta_value) FROM {$wpdb->commentmeta}\n\t\t\t\tLEFT JOIN {$wpdb->comments} ON {$wpdb->commentmeta}.comment_id = {$wpdb->comments}.comment_ID\n\t\t\t\tWHERE meta_key = 'rating'\n\t\t\t\tAND comment_post_ID = %d\n\t\t\t\tAND comment_approved = '1'\n\t\t\t\tAND meta_value > 0\n\t\t\t\t\t", $product->get_id()));
$average = number_format($ratings / $count, 2, '.', '');
} else {
$average = 0;
}
return $average;
}
public static function get_review_counts_for_product_ids($product_ids)
{
global $wpdb;
if (empty($product_ids)) {
return array();
}
$product_id_string_placeholder = substr(str_repeat(',%s', count($product_ids)), 1);
$review_counts = $wpdb->get_results($wpdb->prepare("\n\t\t\t\t\tSELECT comment_post_ID as product_id, COUNT( comment_post_ID ) as review_count\n\t\t\t\t\tFROM {$wpdb->comments}\n\t\t\t\t\tWHERE\n\t\t\t\t\t\tcomment_parent = 0\n\t\t\t\t\t\tAND comment_post_ID IN ( {$product_id_string_placeholder} )\n\t\t\t\t\t\tAND comment_approved = '1'\n\t\t\t\t\t\tAND comment_type in ( 'review', '', 'comment' )\n\t\t\t\t\tGROUP BY product_id\n\t\t\t\t", $product_ids), ARRAY_A);
$counts = array_replace(array_fill_keys($product_ids, 0), array_column($review_counts, 'review_count', 'product_id'));
return $counts;
}
public static function get_review_count_for_product(&$product)
{
$counts = self::get_review_counts_for_product_ids(array($product->get_id()));
return $counts[$product->get_id()];
}
public static function get_rating_counts_for_product(&$product)
{
global $wpdb;
$counts = array();
$raw_counts = $wpdb->get_results($wpdb->prepare("\n\t\t\tSELECT meta_value, COUNT( * ) as meta_value_count FROM {$wpdb->commentmeta}\n\t\t\tLEFT JOIN {$wpdb->comments} ON {$wpdb->commentmeta}.comment_id = {$wpdb->comments}.comment_ID\n\t\t\tWHERE meta_key = 'rating'\n\t\t\tAND comment_post_ID = %d\n\t\t\tAND comment_approved = '1'\n\t\t\tAND meta_value > 0\n\t\t\tGROUP BY meta_value\n\t\t\t\t", $product->get_id()));
foreach ($raw_counts as $count) {
$counts[$count->meta_value] = absint($count->meta_value_count);
}
return $counts;
}
public static function update_comment_type($comment_data)
{
if (!is_admin() && isset($_POST['comment_post_ID'], $comment_data['comment_type']) && self::is_default_comment_type($comment_data['comment_type']) && 'product' === get_post_type(absint($_POST['comment_post_ID']))) {
$comment_data['comment_type'] = 'review';
}
return $comment_data;
}
public static function validate_product_review_verified_owners($comment_post_id)
{
if ('yes' !== get_option('woocommerce_review_rating_verification_required')) {
return;
}
if ('product' !== get_post_type($comment_post_id)) {
return;
}
if (wc_customer_bought_product('', get_current_user_id(), $comment_post_id)) {
return;
}
wp_die(esc_html__('Only logged in customers who have purchased this product may leave a review.', 'woocommerce'), esc_html__('Reviews can only be left by "verified owners"', 'woocommerce'), array('code' => 403));
}
private static function is_default_comment_type($comment_type)
{
return '' === $comment_type || 'comment' === $comment_type;
}
}
WC_Comments::init();