Wordpress - How to return only certain fields using get_posts()
get_posts
passes the heavy lifting off to WP_Query
and if you look at the source of that class you can see that there are only a limited number of options with that fields
argument. There are only three options in that switch
-- ids
, id=>parent
, and the default case, everything.
You can use the posts_fields
filter to alter what fields get returned, though it looks like you need to pass 'suppress_filters => false
in the arguments in order to get that filter to run. It should look something like this:
function alter_fields_wpse_108288($fields) {
return 'ID,post_title'; // etc
}
add_filter('posts_fields','alter_fields_wpse_10888');
However, there is a larger problem. The post objects that get returned are created by a call to get_post
and it doesn't honor the values passed into the original query and I don't see a way to change what gets returned either in get_posts
or in the WP_Post
class itself.
Have a look to this
function get_posts_fields( $args = array() ) {
$valid_fields = array(
'ID'=>'%d', 'post_author'=>'%d',
'post_type'=>'%s', 'post_mime_type'=>'%s',
'post_title'=>false, 'post_name'=>'%s',
'post_date'=>'%s', 'post_modified'=>'%s',
'menu_order'=>'%d', 'post_parent'=>'%d',
'post_excerpt'=>false, 'post_content'=>false,
'post_status'=>'%s', 'comment_status'=>false, 'ping_status'=>false,
'to_ping'=>false, 'pinged'=>false, 'comment_count'=>'%d'
);
$defaults = array(
'post_type' => 'post',
'post_status' => 'publish',
'orderby' => 'post_date',
'order' => 'DESC',
'posts_per_page' => get_option('posts_per_page'),
);
global $wpdb;
$args = wp_parse_args($args, $defaults);
$where = "";
foreach ( $valid_fields as $field => $can_query ) {
if ( isset($args[$field]) && $can_query ) {
if ( $where != "" ) $where .= " AND ";
$where .= $wpdb->prepare( $field . " = " . $can_query, $args[$field] );
}
}
if ( isset($args['search']) && is_string($args['search']) ) {
if ( $where != "" ) $where .= " AND ";
$where .= $wpdb->prepare("post_title LIKE %s", "%" . $args['search'] . "%");
}
if ( isset($args['include']) ) {
if ( is_string($args['include']) ) $args['include'] = explode(',', $args['include']);
if ( is_array($args['include']) ) {
$args['include'] = array_map('intval', $args['include']);
if ( $where != "" ) $where .= " OR ";
$where .= "ID IN (" . implode(',', $args['include'] ). ")";
}
}
if ( isset($args['exclude']) ) {
if ( is_string($args['exclude']) ) $args['exclude'] = explode(',', $args['exclude']);
if ( is_array($args['exclude']) ) {
$args['exclude'] = array_map('intval', $args['exclude']);
if ( $where != "" ) $where .= " AND ";
$where .= "ID NOT IN (" . implode(',', $args['exclude'] ). ")";
}
}
extract($args);
$iscol = false;
if ( isset($fields) ) {
if ( is_string($fields) ) $fields = explode(',', $fields);
if ( is_array($fields) ) {
$fields = array_intersect($fields, array_keys($valid_fields));
if( count($fields) == 1 ) $iscol = true;
$fields = implode(',', $fields);
}
}
if ( empty($fields) ) $fields = '*';
if ( ! in_array($orderby, $valid_fields) ) $orderby = 'post_date';
if ( ! in_array( strtoupper($order), array('ASC','DESC')) ) $order = 'DESC';
if ( ! intval($posts_per_page) && $posts_per_page != -1)
$posts_per_page = $defaults['posts_per_page'];
if ( $where == "" ) $where = "1";
$q = "SELECT $fields FROM $wpdb->posts WHERE " . $where;
$q .= " ORDER BY $orderby $order";
if ( $posts_per_page != -1) $q .= " LIMIT $posts_per_page";
return $iscol ? $wpdb->get_col($q) : $wpdb->get_results($q);
}
It's a function that mimics get_posts but with the ability to get the fields you desire. Be aware: this function is not get_posts and has 2 great limitations: run only in posts table so taxonomy and meta query cannot be run!
However, the query can rely on all the post fields and on some 'special' arguments like include
, exclude
and search
.
The good part is this: the fields you are able to retrieve are all the field of the post table. Just pass a list or an array in the fields
argument.
Bonus: passing only one field is returned a one dimensional array of strings or integers (instead of an array of objects).
List of Available args are:
$available_args = array(
'ID', // int
'post_author', // string
'post_type', // string
'post_mime_type', // string
'post_name', // string
'post_date', // string
'post_modified', // string
'menu_order', // int
'post_parent', // int
'post_status', // string
'comment_status', // string
'comment_count', // int
'orderby', // string, a valid field name
'order', // string 'ASC', or 'DESC',
'posts_per_page', // int
'include', // array (or comma separed string) of post ids
'exclude', // array (or comma separed string) of post ids
'search', // string if passed will search for it in post title
'fields', // array (or comma separed string) of fields to retrieve.
// If only 1 field is passed a 'flat' array is returned
);
Examples of usage
// Retrieve the date and the title of pages having 'Hello' in the title
$pages_hello = get_posts_fields("post_type=page&search=Hello&fields=post_date,post_title");
// another example
$args = array(
'post_type' => 'custom_post',
'posts_per_page' => -1,
'post_parent' => 1,
'include' => array(2,3,4),
'exclude' => '6,8,10',
'fields' => array('post_title', 'comment_status')
);
get_posts_fields($args);
// One more, just for fun ;)
$args = array(
'post_type' => 'attachment', 'posts_per_page' => -1,
'post_status' => 'inherit', 'fields' => 'post_mime_type'
);
foreach ( array_count_values ( get_posts_fields($args) ) as $mime => $count ) {
echo "I have $count media of the type $mime" . PHP_EOL;
}
You can only use 'ids'
or 'id=>parent'
for the parameter fields
.
If you parse something else it will return all fields (this is default).
However, it would be nice if Wordpress could add the following 2 options: 'titles'
and 'ids_and_titles'
.
I am not aware of a way to parse an array for this parameter. I also think it will never happen, since the limits of the answer given by G. M.
More info: http://codex.wordpress.org/Class_Reference/WP_Query#Return_Fields_Parameter