I'm wanting to order Wordpress posts by the most recent comment. To the best of my knowledge this isn't possible using the WP_Query object, and would require a custom $wpdb query, which I can easily write. However, I then don't know how to setup the loop to run off this object.
Can anyone help?
Assign
select wp_posts.*, max(comment_date) as max_comment_date
from $wpdb->posts wp_posts
right join $wpdb->comments
on id = comment_post_id
group by ID
order by max_comment_date desc
limit 10
to some variable $query. You can fiddle around with the 10 or the query itself. (I'm no SQL optimization ninja.) Then your code will look something like
<?php
$results = $wpdb->get_results($query) or die('!');
foreach ($results as $result):
?>
[insert template here]
<?php endforeach ?>
This pattern is covered in more depth by the Codex.
I used a simpler, portion of a native WP in function. hope it helps and some one can continue to develop. Here is a simplified version that shows the title & excerpt of the post along with the comment content & author from the latest commented posts using get_comments.
$args = array(
'status' => 'approve',
'number' => 6,
'order' => 'DESC'
);
$comments = get_comments($args);
foreach($comments as $comment) : $count++;
$post_args = array(
'post_type' => 'post',
'p' => $comment->comment_post_ID,
'posts_per_page' => 1
);
$posts = get_posts($post_args);
foreach($posts as $post) : setup_postdata($post);
the_title();
the_excerpt();
endforeach;
echo $comment->comment_content;
echo $comment->comment_author;
endforeach;
OK guys,
A lot of great answers here, but obviously nobody's taken the time to test them.
Hao Lian gets the credit for the first best original answer, but unfortunately his code doesn't show posts without comments.
Captain Keytar is on the right track, but his code will display every single post and attachment as a separate result.
Here is a modified version of Captain Keytar but it limits the results to the type 'post'.. that has been published (to avoid getting drafts!!)
select wp_posts.*,
coalesce(
(
select max(comment_date)
from $wpdb->comments wpc
where wpc.comment_post_id = wp_posts.id
),
wp_posts.post_date
) as mcomment_date
from $wpdb->posts wp_posts
where post_type = 'post'
and post_status = 'publish'
order by mcomment_date desc
limit 10
This is an old question, but I had the same issue and found a much cleaner way to do this, so I'm posting it here in case it helps anyone.
If you use the posts_clauses filter you can then just modify the main query and still use The Loop and all the regular loop functions.
function intercept_query_clauses( $pieces ) {
global $wpdb;
$pieces['fields'] = "wp_posts.*,
(
select max(comment_date)
from " . $wpdb->comments ." wpc
where wpc.comment_post_id = wp_posts.id AND wpc.comment_approved = 1
) as mcomment_date";
$pieces['orderby'] = "mcomment_date desc";
return $pieces;
}
add_filter( 'posts_clauses', 'intercept_query_clauses', 20, 1 );
Note that I changed the sql slightly for my own purposes, but the general concept is the same.
As an addendum to Hao Lian's answer, if you use the following query:
select wp_posts.*,
coalesce(
(
select max(comment_date)
from $wpdb->comments wpc
where wpc.comment_post_id = wp_posts.id
),
wp_posts.post_date
) as mcomment_date
from $wpdb->posts wp_posts
order by mcomment_date desc
limit 10
This mixes in posts that don't have comments yet, and sorts them by post_date and max(comment_date).
Code suggested by Hao Lian works perfect except for the fact that we should add the following WHERE clause to avoid pulling POST with comment_count = 0, this situation is caused by spam comments.
The WHERE clause to add is as follows:
WHERE comment_approved = '1' AND comment_type = '' AND post_password = ''
Complete code after adding the where clause shoud look like following:
select wp_posts.*, max(comment_date) as comment_date
from wp_posts
right join wp_comments on id = comment_post_id
WHERE comment_approved = '1' AND comment_type = '' AND post_password = ''
group by ID
order by comment_date desc
limit 6
This can be done by combining WP_Comment_Query with WP_Query, like this:
// For performance, limit the number of queried comments,
// but make it be something big enough to account for "duplicate" posts.
$comments_query = new WP_Comment_Query;
$comments = $comments_query->query( array( 'number' => '100' ) );
if ( $comments ) {
foreach ( $comments as $comment ) {
// You'll want to convert the dates from string to integer so you can sort them out later
$comment_utf = strtotime($comment->comment_date);
// Build an array of post IDs with the date of the last published comment
$latest_comments[$comment->comment_post_ID] = $comment_utf;
}}
// Sort the array by date
arsort($latest_comments); foreach ($latest_comments as $key => $value) { $posts_ordered[] = $key; }
// The nice thing is that WP_Query will remove duplicates by default
$args = array ( 'posts_per_page' => '10', 'post__in' => $posts_ordered, 'orderby' => 'post__in');
$query = new WP_Query( $args );
if ( $query->have_posts() ) {
while ( $query->have_posts() ) {
$query->the_post();
// Do your stuff (add the template or whatever)
// If you want to add the comment itself, use this:
$comments = get_comments(array('number' => '1', 'post_id' => $post->ID));
foreach($comments as $comment) :
echo $comment->comment_content;
endforeach;
// That's about it
}}
wp_reset_postdata();
I'm thinking that adding in the max function will screw up your results. MySQL isn't going to pull the max from each one. It's going to pull the max from the full set. This is the query that'll get you your results:
select wp_posts.*, comment_date
from $wpdb->posts wp_posts
right join $wpdb->comments
on id = comment_post_id
group by ID
order by comment_date desc
limit 10
After that, if you want to follow WP convention, use this, and then you can use the functions that most of your templates are using (based on the loop):
$results = $wpdb->get_results($query) or die('!');
foreach ($results as $post):
setup_postdata($post);
Get 3 newest comments for custom post type 'question' regardless of approvement:
global $wpdb;
$results = $wpdb->get_results(
"
SELECT wp_posts.ID, MAX(comment_date) AS max_comment_date
FROM wp_posts
RIGHT JOIN wp_comments
ON id = comment_post_id
WHERE wp_posts.post_type = 'question'
AND wp_posts.post_status = 'publish'
GROUP BY ID
ORDER BY max_comment_date DESC
LIMIT 3
"
);
foreach ($results as $result) {
$posts_arr[] = $result->ID;
}
$args = array(
'post_type' => 'question',
'post__in' => $posts_arr,
'orderby' => 'post__in',
);
$the_query = new WP_Query( $args );
Using Lucian's BEAUTIFUL solution, I needed to alter/filter the existing WP_Query to sort posts by the latest comment. Here's the code, tested & works perfectly:
$comments_query = new WP_Comment_Query;
$comments = $comments_query->query( array( 'number' => '100' ) );
if ( $comments ) {
foreach ( $comments as $comment ) {
$comment_utf = strtotime($comment->comment_date);
$latest_comments[$comment->comment_post_ID] = $comment_utf;
}
// Sort the array by date
arsort( $latest_comments );
foreach( $latest_comments as $key => $value ) {
$posts_ordered[] = $key;
}
$query->set( 'post__in', $posts_ordered );
$query->set( 'orderby', 'post__in' );
}
Related
I'm trying to get the ranking the number of meta_key at specific custom post type.
But I'm not great at SQL so therefor not great with using $wpdb.
For example, if I write something like this,
<?php
$tarms = array( ‘sweet, sour’ );
echo get_count_ranking( $tarms );
?>
Then I would like to display rankings from comment("custom post of reply") of "custom post of fluits" with "term of sweet" and "term of sour" in order of "meta_key of count".
Here is my code:
function get_count_ranking( $tarms ){
global $wpdb;
$counts = $wpdb->get_results( $wpdb->prepare( "
SELECT p.post_author AS user_id, sum(m.meta_value) AS SumUser
FROM $wpdb->posts AS p, $wpdb->postmeta AS m
WHERE p.ID = m.post_ID
AND p.ID IN (
SELECT tr.object_id
FROM $wpdb->term_relationships AS tr, $wpdb->posts AS p, $wpdb->term_taxonomy AS tt
WHERE p.post_type = 'reply'
AND tt.term_id = %s
AND p.id = tr.object_id
AND tr.term_taxonomy_id = tt.term_taxonomy_id
)
AND p.post_status = 'publish'
AND m.meta_key = 'count'
GROUP BY p.post_author
ORDER BY m.meta_value DESC LIMIT 10
", $tarms ) );
$result = '';
foreach ( $counts as $count ) {
$result .= '<li><img>'.get_avatar($count->user_id, 30).'<span></span></li>';
}
return $result;
}
I am sorry that my English is so bad.
So I attach that image for your reference.
Thanks.
enter image description here
--
Updated code:
function get_count_ranking( $tarms ){
$customPostArg = array(
'posts_per_page' => 5,
'post_type' => 'fluits',
'tax_query' => array(
array(
'taxonomy' => 'taste-tag',
'field' => 'slug',
'terms' => $tarms
)
)
);
$array_with_post_ids = get_posts($customPostArg);
$argsp = array(
'post__in' => $array_with_post_ids
);
$commentsp = get_comments( $argsp );
$needed_data_array = array();
foreach ($comments as $key => $comment) {
$ranking = get_comment_meta($comment->ID, 'count', $return_single_value = true);
$author_id = $comment->user_id;
// make sure we have an author id
if($author_id) {
$needed_data_array[$author_id][] = $ranking;
}
}
}
$tarms = array( ‘sweet, sour’ );
echo get_count_ranking( $tarms );
I would not use $wpdb for this.
Also, it looks like you save rankings to a POST meta field, I would use a comment meta field.
Please NOTE, i'm not going to help you write everything, but here are
some pointers how I would accomplish this.
Register the custom post types with comment capability.
Extend the add comment to include a ranking setter. Save this ranking data in a comment_meta field. Now the comment and ranking meta are linked. When you remove the comment, the ranking meta data is also removed from the DB.
Now post comments and rankings are saved.
Collecting data
WP has a get_comments() function, this function accepts many arguments. Sadly, I miss an argument to get comments from posts with a certain taxonomy. So we have to collect all the posts first:
Collect all the posts with 'sweet' and/or 'sour' taxonomy, use get_posts().
Build an array with post id's.
Use get_comments() to get all comments connected to the posts.
Example:
$args = array(
'post__in' => $array_with_post_ids,
);
$comments = get_comments( $args );
Now you have all the comments you need for creating overviews, I would loop (iterate) through them and build an array with author_names and their rankings.
Example:
$needed_data_array = array();
foreach ($comments as $key => $comment) {
$ranking = get_comment_meta($comment->ID, 'ranking_meta_key', $return_single_value = true);
$author_id = $comment->user_id;
// make sure we have an author id
if($author_id) {
$needed_data_array[$author_id][] = $ranking;
}
}
//
// Now the $needed_data_array holds all authors
// and their post rankings, you can count them
// to get ranking totals for each comment-author.
//
I am trying to get ordering working in wp_query, but posts are still being ordered with default settings (just tag__in is working). SQL query for posts looks like this:
string(379) "SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts INNER JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id) WHERE 1=1 AND ( wp_term_relationships.term_taxonomy_id IN (81) ) AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'private') GROUP BY wp_posts.ID ORDER BY wp_posts.post_date DESC LIMIT 0, 3"
Here is code snippet:
remove_all_filters('posts_orderby');
$tag = get_term_by('name', 'title_post', 'post_tag');
$args=array(
'order'=>'ID',
'orderby'=>'ASC',
'tag__in' => $tag,
'posts_per_page'=>3, // Number of related posts to display.
'caller_get_posts'=>1
);
$my_query = new wp_query( $args );
var_dump($my_query->request);
Thanks!
I have checked your code you have to pass wrong arguments.
Can you please check below code?
Wrong
'order'=>'ID',
'orderby'=>'ASC',
Right
'order'=>'ASC',
'orderby'=>'ID',
Ok, I switched order and orderby ...
So, correct arguments are 'orderby'=>'ID','order'=>'ASC',
Is there any way to see if a post exists by a meta value?
For instance, lets say I want to see if another post has a unique meta value of "pictureID", and if so do something else.
Is there a way I could write that clause in php?
Thank you
if you dont know the post id then
you can use custom wordpress query to check post meta according to key like
global $wpdb;
$wpdb->get_results( "select * from $wpdb->postmeta where meta_key = 'pictureID' " );
And then you can get all results with post id and then get that post data.
Hope this helps ;)
You can use a standard WP_Query to return posts by meta_key using the meta_query argument and EXISTS compare type.
// query for all posts with the pictureID meta key set
$args = array(
'post_type' => 'post', // or your_custom_post_type
'meta_query' => array(
array(
'key' => 'pictureID',
'compare' => 'EXISTS',
),
),
}
// create a custom query
$my_query = new WP_Query( $args );
// loop over your query, creating a custom The Loop
if ( $my_query->have_posts() ): while ( $my_query->have_posts() ): $my_query->the_post();
// $post is now posts that have a pictureId meta value
endwhile; endif;
// reset $post
wp_reset_postdata();
If you want to quickly grab a random post_id that has this meta_key set you can go to the database directly (bypassing caching, etc).
global $wpdb;
// SQL statement to fetch the post_id using a meta_key and a published post
$sql = <<<SQL
SELECT post_id
FROM {$wpdb->postmeta} pm
JOIN {$wpdb->posts} p
ON p.ID = pm.post_id
AND post_status = 'publish'
AND post_type = 'post'
WHERE meta_key = 'pictureID'
AND meta_value != ''
AND post_id != %d
ORDER BY RAND()
LIMIT 1
SQL;
// exclude the current post by replacing %d with the current ID
$sql = $wpdb->prepare( $sql, $post->ID );
// use get_var() to return the post_id
$post_id = $wpdb->get_var( $sql );
first try to get meta value for post get_post_meta()
$postMetaValue=get_post_meta($postId,"meta_key",true);
if($postMetaValue=='pictureID')
{
//do as you want
}
i would like to get date gmt of latest comment for post ID. Result i would like to get as a string.
Can somebody help me how to set result into string:
function GetLastCommentDate( $postId ) {
global $wpdb;
$dateOutput = '0000-00-00 00:00:00';
$commentRes= $wpdb->get_results("SELECT DISTINCT `comment_date_gmt` FROM `wp_comments` WHERE `comment_approved` ='1' AND `comment_post_ID` = '". $postId. "' ORDER BY `comment_date_gmt` DESC LIMIT 1");
if(!empty($commentRes)) {
$dateOutput = ...........
}
return $dateOutput;
}
One answer is like this:
$commentRes= $wpdb->get_results("SELECT DISTINCT `comment_date_gmt` as `comment_date_gmt` FROM `wp_comments` WHERE `comment_approved` ='1' AND `comment_post_ID` = '". $postId. "' ORDER BY `comment_date_gmt` DESC LIMIT 1");
if(!empty($commentRes)) {
foreach($commentRes as $comment) {
$dateOutput=$comment->comment_date_gmt;
}
}
return $dateOutput;
But how to avoid foreach loop? There is only one row (sql limit set to 1).
You need not query wordpress database directly. WP provides an API to retrieve this.
$comments = get_comments( array(
'post_id' => $post->ID,
'orderby' => 'comment_date_gmt',
'status' => 'approve',
'number' => 1
) );
Check out this API reference. Specifying number as 1 returns only the last comment.
The date value for last comment can be retrieved as $comments[0]['date']
Now that you want to use this from outside, include the following at top of your php code
require('/the/path/to/your/wp-blog-header.php');
Check out this wordpress doumentation
If you get an out of loop error try adding this code.
The loop starts here:
<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>
and ends here:
<?php endwhile; else: ?>
<p><?php _e('Sorry, no posts matched your criteria.'); ?></p>
<?php endif; ?>
I think you want something like this;
$commentRes= $wpdb->get_row(
"SELECT `comment_date_gmt` FROM `wp_comments` ".
"WHERE `comment_approved` ='1' AND `comment_post_ID` = '$postId' ".
"ORDER BY `comment_date_gmt` DESC LIMIT 1");
if(!empty($commentRes))
$dateOutput = date("Y-m-d H:i:s", $commentRes->comment_date_gmt);
I've stumbled upon this while searching for something else. I know that the question is old, and the aswers too, but somebody can find it like I did, and I'd like to add another updated solution.
function GetLastCommentDate( $postId ) {
$dateOutput = false; //this will make easier to check if there are no comments
//get_comments() args https://codex.wordpress.org/Function_Reference/get_comments
$args = array(
'post_id' => $postId, //just comments of this post
'number' => 1, //just one comment
'order_by' => 'comment_date_gmt', //order by comment date gmt
'order' => 'DESC', //latest first
);
//retrieve comments
$comments = get_comments($args);
if ($comments){
$dateOutput = $comments[0]->comment_date;
}
return $dateOutput;
}
And you can use it wherever you want like this:
$postId = '12345';
$lastCommentDate = GetLastCommentDate($postId) ?: 'Never';
echo $lastCommentDate;
I created a custom taxonomy named 'technologies' but cannot query multiple terms like I can with categories or tags.
These querys DO work:
query_posts('tag=goldfish,airplanes');
query_posts('technologies=php');
However, neither of the following work correctly:
query_posts('technologies=php,sql');
query_posts('technologies=php&technologies=sql');
My objective: Show all posts with a technology of 'php' and all posts with a technology of 'sql'
Any ideas? Is this even possible? Thanks!
Apparently query_posts cannot help in this specific situation. (Hopefully it will be added in future versions of Wordpress!) The solution is to use a custom select query like the following:
SELECT *
FROM $wpdb->posts
LEFT JOIN $wpdb->term_relationships ON($wpdb->posts.ID = $wpdb->term_relationships.object_id)
LEFT JOIN $wpdb->term_taxonomy ON($wpdb->term_relationships.term_taxonomy_id = $wpdb->term_taxonomy.term_taxonomy_id)
LEFT JOIN $wpdb->terms ON($wpdb->term_taxonomy.term_id = $wpdb->terms.term_id)
WHERE $wpdb->posts.post_type = 'post'
AND $wpdb->posts.post_status = 'publish'
AND $wpdb->term_taxonomy.taxonomy = 'technologies'
AND $wpdb->terms.slug = 'php' OR $wpdb->terms.slug = 'css'
ORDER BY $wpdb->posts.post_date DESC
More information can be found at the Wordpress Codex:
http://codex.wordpress.org/Displaying_Posts_Using_a_Custom_Select_Query
This is a bit of a delayed reply, but it's first on Google at the moment for "wordpress related posts by multiple terms" so thought I'd contribute my findings.
Since this question was posted Wordpress has been changed to allow for this type of query. This will give you a list of posts related by any of the custom taxonomy terms assigned to an object:
$post_cats = wp_get_object_terms(get_the_ID(), 'video_category', array('fields' => 'ids'));
$args=array(
"tax_query" => array(
array(
"taxonomy" => "video_category",
"field" => "id",
"terms" => $post_cats
)
),
'post__not_in' => array(get_the_ID()),
'post_type' => 'video',
'posts_per_page' => 8,
'caller_get_posts' => 1
);
$related_by_cats = new WP_Query($args);
This is my first contribution to SO, I hope it's up to standards.
You can use this plugin:
http://scribu.net/wordpress/query-multiple-taxonomies/
Does this work? query_posts('tag=bread+baking+recipe')
From: http://codex.wordpress.org/Template_Tags/query_posts
OK, so here is my crack at this. It's a little hacky, but it works. The big downside is that any other query variables need to be re-added, as when multiple terms are invoked, the fail strips out all of the query vars.
Also, I did not test this against querying across multiple taxonomies. This only works within a specific taxonomy. Use at your own risk.
function multi_tax_terms($where) {
global $wp_query;
if ( strpos($wp_query->query_vars['term'], ',') !== false && strpos($where, "AND 0") !== false ) {
// it's failing because taxonomies can't handle multiple terms
//first, get the terms
$term_arr = explode(",", $wp_query->query_vars['term']);
foreach($term_arr as $term_item) {
$terms[] = get_terms($wp_query->query_vars['taxonomy'], array('slug' => $term_item));
}
//next, get the id of posts with that term in that tax
foreach ( $terms as $term ) {
$term_ids[] = $term[0]->term_id;
}
$post_ids = get_objects_in_term($term_ids, $wp_query->query_vars['taxonomy']);
if ( !is_wp_error($post_ids) && count($post_ids) ) {
// build the new query
$new_where = " AND wp_posts.ID IN (" . implode(', ', $post_ids) . ") ";
// re-add any other query vars via concatenation on the $new_where string below here
// now, sub out the bad where with the good
$where = str_replace("AND 0", $new_where, $where);
} else {
// give up
}
}
return $where;
}
add_filter("posts_where", "multi_tax_terms");
It's somehow silly that after implementing custom taxonomies in WP there are no built-in functions to use them at will, and the documentation is close to non-existent. I was looking for a solution, this query solves it (and made my day). Thanks.
Still, sadly I'm too dumb (OOP blind) to make it into a function so I don't repeat it all over.
I get: **Fatal error**: Call to a member function get_results() on a non-object
I guess I don't know how to call $wpdb from within a function.
it should be like this:
global $wp_query;
query_posts(
array_merge(
array('taxonomy' => 'technologies', 'term' => array('sql', 'php')),
$wp_query->query
)
);
that works for custom post_types, at least.
Hey, I also faced the same problem once. If you don't have many multiple values, then you can do it in the following way, rather than writing a raw SQL query:
$loop = new WP_Query(array('technologies' => 'php','technologies' => 'sql'));
Then you can loop through the wp_query object created here.
Though this is a very old post and am sure you have already solved the problem. :)
//equivalent to get_posts
function brand_get_posts($args=array()){
global $wpdb;
$sql = "SELECT p.* ";
$sql.= " FROM $wpdb->posts p";
$sql.= " LEFT JOIN $wpdb->term_relationships term_r ON(p.ID = term_r.object_id)";
$sql.= " LEFT JOIN $wpdb->term_taxonomy term_t ON(term_r.term_taxonomy_id = term_t.term_taxonomy_id)";
$sql.= " LEFT JOIN $wpdb->terms terms ON(term_t.term_id = terms.term_id)";
$sql.= " WHERE 1=1 ";
if(!empty($args['post_type'])){
$sql.= " AND p.post_type = '".$args['post_type']."'";
}
$sql.= " AND p.post_status = 'publish'";
if(!empty($args['taxonomy'])){
$sql.= " AND term_t.taxonomy = '".$args['taxonomy']."'";
}
if(!empty($args['terms'])&&is_array($args['terms'])){
$sql.= " AND terms.slug IN ('".implode(",",$args['terms'])."')";
}
$sql.= " ORDER BY p.post_date DESC";
if(!empty($args['posts_per_page'])){
$sql.=" LIMIT ".$args['posts_per_page'];
}
if(!empty($args['offset'])){
$sql.=" OFFSET ".$args['offset'];
}
//echo '<h1>'.$sql.'</h1>';
return $wpdb->get_results($sql);
}