Concatenate two meta keys? - php

I have a school timetable as a custom post type. Each post is a school class with a post meta box containing two text fields for specifying the hour and minute that a class starts in 24-hour time format:
_start_hour
_start_minute
I am trying to output the posts in order according to time e.g.
// the args
$args = array(
'post_type' => 'my-cpt',
'meta_key' => '???????',
'orderby' => 'meta_value_num',
'order' => 'ASC',
'posts_per_page' => -1,
);
// The Query
$the_query = new WP_Query( $args );
while ( $the_query->have_posts() ) : $the_query->the_post();
// ordered output according to time
endwhile;
In 'meta_key' is there some way I can concatenate the two meta keys?
I have tried 'meta_key' => '_start_hour' && '_start_minute' but this breaks the query.

Unfortunately no, wordpress doesn't support this feature, you will have to sort it by yourself after fetching it from the database and before the loop.
Disclaimer note
This is extremely ugly by design, but this is Wordpress so you have to play with what you get, you can make it less ugly if you fallback to writing the SQL queries yourself, depends on performance in my opinion, as Wordpress can be a performance degrader beast if not handled properly you should consider making it with SQL queries instead.
// Fetch all posts - (1 SQL Query)
$query = new WP_Query(array(
'post_type' => 'my-cpt',
'order' => 'ASC',
'posts_per_page' => -1,
));
foreach ($query->posts as &$post) { // N queries as the number of posts you have - totally inefficient
$post->meta = get_post_meta($post->ID);
}
usort($query->posts, function($a, $b) {
$a_time = strtotime($a->meta['_start_hour'][0] . ':' . $a->meta['_start_minute'][0]);
$b_time = strtotime($b->meta['_start_hour'][0] . ':' . $b->meta['_start_minute'][0]);
if ($a_time > $b_time)
return 1;
else if ($a_time < $b_time)
return -1;
else
return 0;
}); // Sorting by date
... the_loop ...
note that this is totally untested so it should just give you pointers on hour should you do it, and again I say, you should refactor this to join the meta keys in advance, that way you can perhaps already sort it with the SQL instead of the PHP...

Related

Add incremental parameter value to each post in wordpress query loop

I'm trying to combine several loops into one and sort the final results by relevance.
For the former part, I did this:
// set the variables
$author_id = get_the_author_meta('ID');
$tags_id = wp_get_post_tags($post->ID);
$first_tag = $tags_id[0]->term_id;
$categories_id = wp_get_post_categories($post->ID);
// loop for same author
$by_author = new WP_Query (array(
'author' => $author_id,
'posts_per_page' => '5'
));
// add ids to array
if ($by_author->have_posts()) {
while ($by_author->have_posts()) {
$by_author->the_post();
$add[] = get_the_id();
}
}
// loop for same tag
$by_tag = new WP_Query(array(
'tag__in' => $first_tag,
'posts_per_page' => '5'
));
// add ids to array
if ($by_tag->have_posts()) {
while ($by_tag->have_posts()) {
$by_tag->the_post();
$add[] = get_the_id();
}
}
// loop for same category
$by_category = new WP_Query(array(
'category__in' => $categories_id,
'posts_per_page' => '5'
));
// add ids to array
if ($by_category->have_posts()) {
while ($by_category->have_posts()) {
$by_category->the_post();
$add[] = get_the_id();
}
}
// loop array of combined results
$related = new WP_Query(array(
'post__in' => $add,
'post__not_in' => array($post->ID),
'posts_per_page' => '10',
'orderby' => $weight[$post->ID],
'order' => 'DESC'
));
// show them
if ($related->have_posts()) {
while ($related->have_posts()) {
$related->the_post();
// [template]
}
}
This is working nicely combining the loops into one. For the latter part, what I'm trying to do next is to add an incremental "weight" value to each post as they come up so as to later sort them with something like 'orderby' => $weight,.
For example, if a post comes up in "same author" it gets 3 points, if another one comes up in same tag it gets 2 points and so on. If it comes up in more than one loop, it should get the combined points i.e. 3+2+1=6 hence be boosted to the top of the final query.
I have tried to add a counter to each preliminary loop, like $weight = +3 etc, but this only adds everything up for every post, not individually.
I also tried inserting something like this at the end of each preliminary loop...
$weight = 0;
if ($by_author){
foreach ($by_author as $post){
setup_postdata($post);
$weight = +10;
add_post_meta($post->ID, 'incr_number', $weight, true);
update_post_meta($post->ID, 'incr_number', $weight);
}
}
... and this to the final one
echo get_post_meta($post->ID,'incr_number',true);
But it still doesnt do it right. It assigns a global value, while I want them to be different depending on the actual main posts one is reading.
So is there a way to do this?
If I understand your question right, I think your last solution was close. Instead of a global $weight parameter, however, I think you need to build an array of $weights that's unique to each post:
$weights[$post.id] += 10;
Then, you could sort that array and get your most heavily weighted post IDs from there.

Wordpress combine query and output different styling dependant on post type

I have two WordPress post types the normal 'post' and a custom post type called 'notes'. I want to combine both and sort them by date. I managed to find the combine query and that's working fine. The problem is I'd like to have different styling for notes. I thought I could do a wordpress if_singlular('notes') to try and target it.. but I'm guessing it's getting lost when the merge happens.
Does anyone know how I could achieve this functionality?
Many thanks!
My php:
<?php
// An example of creating two separate WP queries, combining the results,
// sorting by date and formatting the results for us in a loop like a regular query.
// order the posts by date in descending order (should go in functions.php to keep things tidy)
function order_by_date( $a, $b )
{
return strcmp( $b->post_date, $a->post_date );
}
// get the posts for the first query
$q1_args = array(
'post_type' => 'post',
'posts_per_page' => -1,
'post_status' => 'publish'
);
$q1_posts = get_posts( $q1_args );
// get the posts for the second query
$q2_args = array(
'post_type' => 'notes',
'posts_per_page' => -1,
'post_status' => 'publish'
);
$q2_posts= get_posts( $q2_args );
// Merge the post arrays together, and sort by date using the order_by_date function
$final_posts = array_merge( $q1_posts, $q2_posts );
usort( $final_posts, 'order_by_date' );
// Loop over the posts and use setup_postdata to format for template tag usage
foreach ( $final_posts as $key => $post ) {
setup_postdata( $post );
// Now we can use template tags as if this was in a normal WP loop
foreach ( $final_posts as $key => $post ) {
setup_postdata( $post );
// Now we can use template tags as if this was in a normal WP loop
echo '
<article class="item shortNote">
<div class="snMeta clearfix">
<img src="'.get_bloginfo('template_url').'/assets/images/sn-icon.png" alt="Short Note" />
<span class="metaDate">'. get_the_date('M / d / Y').'</span>
<strong>Short Note</strong>
</div>
<h2>'.get_the_title().'</h2>
</article>';
}
}
?>
Ok so this should do it:
<?php
// An example of creating two separate WP queries, combining the results,
// sorting by date and formatting the results for us in a loop like a regular query.
// order the posts by date in descending order (should go in functions.php to keep things tidy)
function order_by_date( $a, $b ) {
return strcmp( $b->post_date, $a->post_date );
}
// get the posts for the first query
$q1_args = array(
'post_type' => 'post',
'posts_per_page' => -1,
'post_status' => 'publish'
);
$q1_posts = get_posts( $q1_args );
// get the posts for the second query
$q2_args = array(
'post_type' => 'notes',
'posts_per_page' => -1,
'post_status' => 'publish'
);
$q2_posts= get_posts( $q2_args );
// Merge the post arrays together, and sort by date using the order_by_date function
$final_posts = array_merge( $q1_posts, $q2_posts );
usort( $final_posts, 'order_by_date' );
// Loop over the posts and use setup_postdata to format for template tag usage
foreach ( $final_posts as $key => $post ) {
$post_type = $post->post_type;
setup_postdata( $post );
// Now we can use template tags as if this was in a normal WP loop
<article class="item shortNote ' . $post_type . '">
<div class="snMeta clearfix">
<img src="'.get_bloginfo('template_url').'/assets/images/sn-icon.png" alt="Short Note" />
<span class="metaDate">'. get_the_date('M / d / Y').'</span>
<strong>Short Note</strong>
</div>
<h2>'.get_the_title().'</h2>
</article>';
}
?>
I just had to test it on my server to see if it works. So I added the $post_type variable that should return the post type, and then I just put that in your class in the <article> tag, so you can differentiate :)
Tip:
When in doubt what your loop outputs, always do print_r(). This will show you what you are dealing with (arrays, strings, objects) so you easily know what to target :)
Hope this helps.

Wordpress orderby query sometimes not working

I have been looking for the error for a while and have no idea what could be causing it.
In about 80% of the time the following code works, but sometimes it just orders completely random:
function filter_where($time, $where = '') {
$where .= " AND post_date > '" . date('Y-m-d H:i:s', strtotime($time)) . "'";
$where .= " AND post_status = 'publish' ";
return $where;
}
add_filter('posts_where', 'filter_where');
$array = array(
'cat' => 2,
'meta_key' => 'ratings_score',
'orderby' => 'meta_value_num',
'order' => 'DESC',
'meta_query' => array(
array(
'key' => 'ratings_score',
),
),
'posts_per_page' => 6
);
filter_where($time);
query_posts($array);
The filter function works. I have tested it with different $time values and post_status's.
Could it just be some kind of lag or have I ordered the code in a wrong manner?
some posts do not have a meta_key of ratings_score, but all posts in cat = 2 do have this meta_key.
Maybe this has something to do with the issue?
First of all, never use query_posts
Note: This function isn't meant to be used by plugins or themes. As explained later, there are better, more performant options to alter the main query. query_posts() is overly simplistic and problematic way to modify main query of a page by replacing it with new instance of the query. It is inefficient (re-runs SQL queries) and will outright fail in some circumstances (especially often when dealing with posts pagination).
You should be using WP_Query or get_posts and only when you cannot achieve the results you are after by modifying the main query with pre_get_posts
You are correctly adding your filter before your query arguments, but then you are again adding the function again after the query arguments. I believe this is one of the big problems here. You should remove the filter after the query arguments, not re-adding them
Replace this line
filter_where($time);
with
remove_filter('posts_where', 'filter_where');
One final note, why are you defining $time but then you doesn't use it at all?

Wordpress count_posts() equivalent function non-expensive method

I'm looking to count the number of posts in the last week then group them by a custom taxonomy called 'topic' So that in the next get_posts equation I can get topics by the number of posts to that area in the last week.
It can be done like this with get posts, but I am concerned that this is unnecessarily expensive on the server. Is there another way?
function count_posts_by_taxanomy($since,$taxonomy){
global $sincer;
$sincer = $since;
function filter_post_count($where = ''){
global $sincer;
$where .= " AND post_date > '" . date('Y-m-d', strtotime($sincer)) . "'";
return $where;
}
$args = array(
'numberposts' => -1,
'post_type' => 'post',
'suppress_filters' => false
);
add_filter('posts_where','filter_post_count');
$posts = get_posts($args);
remove_filter('posts_where','filter_post_count');
$count_term = array();
foreach($posts as $post){
foreach(get_the_terms($post->ID,'topic') as $term){
$count_term[$term->slug] += 1;
}
}
print_r($count_term);
}
Called like this:
count_posts_by_taxanomy('-5 days','topic');
You would be better using a custom database query. See here for more info on that: http://codex.wordpress.org/Class_Reference/wpdb
I would then suggest you store the result in a transient. You don't need to run the query on every load.
http://codex.wordpress.org/Transients_API
You can use WP_Query and call $wp_query->found_posts to find count of posts. And then do a loop and cache values.
More at : http://codex.wordpress.org/Class_Reference/WP_Query
$query = new WP_Query( array('posts_per_page'=>-1,
'post_type'=>array('post'),
'date_query' => array(array('after' => 'YOUR DATE')),
'tax_query' => array(
array(
'taxonomy' => 'YOUR TAX'
))));

How to sort a 'query_posts' function by custom field, while limiting posts by another custom field

I'm querying a series of posts in WP with the following function:
<?php
$thirtydays = date('Y/m/d', strtotime('+30 days'));
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
query_posts( array(
'post_type' => array('post', 'real-estate'),
'meta_key' => 'Time Available',
'meta_compare' => '<=',
'meta_value' => $thirtydays,
'paged' => $paged ));
?>
This part is working fine. It's basically pulling all my Real Estate posts, but only returning results that have a 'Time Available' of 30 days or less.
I need this to also order the posts in ascending order from low to high using the data from another custom field, 'Price.'
Whenever I add the standard 'orderby' => 'meta_value', 'meta_key' => 'Price' it no longer shows results within 30 days.
Is there any way I can combine these two? And is it possible to add a button which re-runs the query and sorts by Price, Bedrooms, etc? Or is this too specific for WP?
I believe this will provide you want you need. It's a class called PostsOrderedByMetaQuery that extends WP_Query and accepts new arguments 'orderby_meta_key' and 'orderby_order':
class PostsOrderedByMetaQuery extends WP_Query {
var $posts_ordered_by_meta = true;
var $orderby_order = 'ASC';
var $orderby_meta_key;
function __construct($args=array()) {
add_filter('posts_join',array(&$this,'posts_join'),10,2);
add_filter('posts_orderby',array(&$this,'posts_orderby'),10,2);
$this->posts_ordered_by_meta = true;
$this->orderby_meta_key = $args['orderby_meta_key'];
unset($args['orderby_meta_key']);
if (!empty($args['orderby_order'])) {
$this->orderby_order = $args['orderby_order'];
unset($args['orderby_order']);
}
parent::query($args);
}
function posts_join($join,$query) {
if (isset($query->posts_ordered_by_meta)) {
global $wpdb;
$join .=<<<SQL
INNER JOIN {$wpdb->postmeta} postmeta_price ON postmeta_price.post_id={$wpdb->posts}.ID
AND postmeta_price.meta_key='{$this->orderby_meta_key}'
SQL;
}
return $join;
}
function posts_orderby($orderby,$query) {
if (isset($query->posts_ordered_by_meta)) {
global $wpdb;
$orderby = "postmeta_price.meta_value {$this->orderby_order}";
}
return $orderby;
}
}
You would call it like this:
$thirtydays = date('Y/m/d', strtotime('+30 days'));
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
$query = new PostsOrderedByMetaQuery(array(
'post_type' => array('post', 'real-estate'),
'meta_key' => 'Time Available',
'meta_compare' => '<=',
'meta_value' => $thirtydays,
'paged' => $paged,
'orderby_meta_key' => 'Price',
'orderby_order' => 'DESC',
));
foreach($query->posts as $post) {
echo " {$post->post_title}\n";
}
You can copy the PostsOrderedByMetaQuery class to your theme's functions.php file, or you can use it within a .php file of a plugin you may be writing.
If you want to test it quickly I've posted a self-contained version of the code to Gist which you can download and copy to your web server's root as test.php, modify for your use case, and then request from your browser using a URL like http://example.com/test.php.
Hope this helps.
-Mike
P.S. This answer is very similar to an answer I just gave over at WordPress Answers, which is the sister site of StackOverflow where lots of WordPress enthusiasts like me answer questions daily. You might want to see that answer too because it has a tad more explanation and because you might want to see WordPress Answers. Hope you'll consider posting your WordPress questions over there too in the future?
Because 'orderby' => 'meta_value' requires meta_key, and your meta_key is already in use for a comparison, I don't think you can do this. meta_key only accepts a single value and not an array of options. This is definitely a limitation and I encourage you to open a request if you don't find a solution.
As far as the button to re-run the query, you could simply reload the page and pass a query variable to change the sort. Unfortunately you still have to solve the first part of your question.
UPDATE
You could always sort the returned array yourself using PHP.
Also, not sure what you are checking with time available but you could register a filter that may help you customize the query a bit further add_filter('posts_where', ...); http://codex.wordpress.org/Function_Reference/query_posts

Categories