Update 2015-05-02: Now working for WP 4.2
In a project today we needed to attach a meta value to a comment based on the user commenting, then filter the comments list (both frontend and backend) by this value. The task turned out to be surprisingly difficult and the comment actions/filters are notoriously badly documented in WP so I thought I’d document the process here.
1. Attach a meta value as a visitor comments
A simple comment_post action (not documented) hook does the trick here.
1
2
3
4
5
6
| /**
* Adds a meta value to a comment as one is created
*/
add_action( 'comment_post', function($comment_id) {
add_comment_meta( $comment_id, 'my_meta_key', 'my_meta_value' );
}); |
2. Filter the comments by meta (4.2+)
Until 4.2 this was done a little differently (See the bottom of this post for that code) however due to recent changes we’re now forced to use comments_clauses action like so:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| /**
* Limits displayed comments on front and backend to just those with the
* specified meta key/value pair.
*
* @param array $pieces
* @param WP_Comment_Query $query
* @return array
*/
add_action('comments_clauses', function(array $pieces, WP_Comment_Query $query) {
global $wpdb;
$meta_query = new WP_Meta_Query();
$meta_query->parse_query_vars([
'meta_key' => 'my_meta_key', 'meta_value' => 'my_meta_value', ]);
if ( !empty($meta_query->queries) )
{
$meta_query_clauses = $meta_query->get_sql( 'comment', $wpdb->comments, 'comment_ID', $query );
if ( !empty($meta_query_clauses) )
{
$pieces['join'] .= $meta_query_clauses['join'];
if ( $pieces['where'] )
$pieces['where'] .= ' AND ';
// Strip leading 'AND'.
$pieces['where'] .= preg_replace( '/^\s*AND\s*/', '', $meta_query_clauses['where'] );
if ( !$query->query_vars['count'] )
{
$pieces['groupby'] = "{$wpdb->comments}.comment_ID";
}
}
}
return $pieces;
}, 10, 2); |
3. Making get_comments_number() work
get_comments_number() method commonly used in your themes comments.php file will return the total number of comments associated with a post. We need it filtered by our above meta query. Here’s an inefficient but effective way to make that happen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| /**
* Filter get_comments_number() correctly by our meta query.
*
* @param int $count
* @param int $post_id
* @return int
*/
add_filter('get_comments_number', function($count, $post_id) {
$query = new WP_Comment_Query(['post_id' => $post_id]);
// Frontend users only see approved comments
if ( !is_admin() )
$comment_query['status'] = 'approve';
return sizeof($query->get_comments());
}, 10, 2); |
Conclusion
That should be it! If anything doesn’t work as expected let me know in the comments below.
Bonus: Filter the comments by meta (<4.2)
Filtering comments by meta in <4.2 is done in two steps. One for admin, and one for frontend.
Admin
This can be done with pre_get_comments action hook (again, not documented – but I did find a usage example here).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| /**
* Limit visible comments to just those for the current subdomain.
* Due to a bug in WP this will work for all backend users but only for
* logged out users on the frontend.
*/
add_action('pre_get_comments', function($query) {
// meta_query is already an instance of WP_Comment_Query but other
// query_vars may have already been added, so re-initialize it
$query->meta_query = new WP_Comment_Query();
$query->query_vars['meta_key'] = 'my_meta_key';
$query->query_vars['meta_value'] = 'my_meta_value';
$query->meta_query->parse_query_vars( $query->query_vars );
}); |
Frontend
As the pre_get_comments comment mentions, there is a bug in WP whereby logged in users will not have the above filter applied, so we need to use another hook for those.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| /**
* WP doesn't support pre_get_comments for logged in users, so for those
* we need to manually remove all the comments that don't have our meta
* key
*
* See https://core.trac.wordpress.org/ticket/27018#ticket
*/
add_filter('comments_array', function($comments, $post_id) {
global $wpdb, $user_ID;
// If there's no user_ID and no commenter then the pre_get_comments filter
// applied correctly and we don't need to do anything
// See comments_template() method
$commenter = wp_get_current_commenter();
$comment_author = $commenter['comment_author']; // Escaped by sanitize_comment_cookies()
if ( $user_ID || !empty($comment_author) )
return;
// Grab comment IDs for this current area
$area_comment_ids = $wpdb->get_col($wpdb->prepare("
SELECT $wpdb->comments.comment_ID
FROM $wpdb->comments
JOIN $wpdb->commentmeta ON $wpdb->commentmeta.comment_id=$wpdb->comments.comment_ID
WHERE $wpdb->commentmeta.meta_key=%s AND $wpdb->commentmeta.meta_value=%s
", 'my_meta_key', 'my_meta_value'));
// Strip out any comments not belonging to the current area
foreach ( $comments as $key => $comment )
if ( !in_array($comment->comment_ID, $area_comment_ids) )
unset($comments[$key]);
return $comments;
}, 10, 2); |
The above seems really messy and inefficient to me but I can’t think of a better way. It’ll grab all comments we want to show and filter out any others.
Read More »