Allow an array() to be passed as the value for orderby to WP_Query. Allows for an independent order value for each key.

Example: `'orderby' => array( 'title' => 'DESC', 'menu_order' => 'ASC' )`.

Adds docs and unit tests.

Props wonderboymusic, johnbillion, DrewAPicture, dd32, andy.
See #17065.


git-svn-id: https://develop.svn.wordpress.org/trunk@29027 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Scott Taylor 2014-07-08 17:15:53 +00:00
parent c722aca8e5
commit 8ce7c39e44
2 changed files with 206 additions and 52 deletions

View File

@ -2199,6 +2199,97 @@ class WP_Query {
return $search_orderby;
}
/**
* If the passed orderby value is allowed, convert the alias to a
* properly-prefixed orderby value.
*
* @since 4.0.0
* @access protected
*
* @global wpdb $wpdb WordPress database access abstraction object.
*
* @param string $orderby Alias for the field to order by.
* @return string|bool Table-prefixed value to used in the ORDER clause. False otherwise.
*/
protected function parse_orderby( $orderby ) {
global $wpdb;
// Used to filter values.
$allowed_keys = array(
'post_name', 'post_author', 'post_date', 'post_title', 'post_modified',
'post_parent', 'post_type', 'name', 'author', 'date', 'title', 'modified',
'parent', 'type', 'ID', 'menu_order', 'comment_count', 'rand',
);
$meta_key = $this->get( 'meta_key' );
if ( ! empty( $meta_key ) ) {
$allowed_keys[] = $meta_key;
$allowed_keys[] = 'meta_value';
$allowed_keys[] = 'meta_value_num';
}
if ( ! in_array( $orderby, $allowed_keys ) ) {
return false;
}
switch ( $orderby ) {
case 'post_name':
case 'post_author':
case 'post_date':
case 'post_title':
case 'post_modified':
case 'post_parent':
case 'post_type':
case 'ID':
case 'menu_order':
case 'comment_count':
$orderby = "$wpdb->posts.{$orderby}";
break;
case 'rand':
$orderby = 'RAND()';
break;
case $meta_key:
case 'meta_value':
$type = $this->get( 'meta_type' );
if ( ! empty( $type ) ) {
$meta_type = $this->meta_query->get_cast_for_type( $type );
$orderby = "CAST($wpdb->postmeta.meta_value AS {$meta_type})";
} else {
$orderby = "$wpdb->postmeta.meta_value";
}
break;
case 'meta_value_num':
$orderby = "$wpdb->postmeta.meta_value+0";
break;
default:
$orderby = "$wpdb->posts.post_" . $orderby;
break;
}
return $orderby;
}
/**
* Parse an 'order' query variable and cast it to ASC or DESC as necessary.
*
* @since 4.0.0
* @access protected
*
* @param string $order The 'order' query variable.
* @return string The sanitized 'order' query variable.
*/
protected function parse_order( $order ) {
if ( ! is_string( $order ) || empty( $order ) ) {
return 'DESC';
}
if ( 'ASC' === strtoupper( $order ) ) {
return 'ASC';
} else {
return 'DESC';
}
}
/**
* Sets the 404 property and saves whether query is feed.
*
@ -2702,12 +2793,23 @@ class WP_Query {
$where .= $search . $whichauthor . $whichmimetype;
if ( empty($q['order']) || ((strtoupper($q['order']) != 'ASC') && (strtoupper($q['order']) != 'DESC')) )
if ( ! isset( $q['order'] ) ) {
$q['order'] = 'DESC';
} else {
$q['order'] = $this->parse_order( $q['order'] );
}
// Order by
if ( empty($q['orderby']) ) {
$orderby = "$wpdb->posts.post_date " . $q['order'];
// Order by.
if ( empty( $q['orderby'] ) ) {
/*
* Boolean false or empty array blanks out ORDER BY,
* while leaving the value unset or otherwise empty sets the default.
*/
if ( isset( $q['orderby'] ) && ( is_array( $q['orderby'] ) || false === $q['orderby'] ) ) {
$orderby = '';
} else {
$orderby = "$wpdb->posts.post_date " . $q['order'];
}
} elseif ( 'none' == $q['orderby'] ) {
$orderby = '';
} elseif ( $q['orderby'] == 'post__in' && ! empty( $post__in ) ) {
@ -2715,59 +2817,41 @@ class WP_Query {
} elseif ( $q['orderby'] == 'post_parent__in' && ! empty( $post_parent__in ) ) {
$orderby = "FIELD( {$wpdb->posts}.post_parent, $post_parent__in )";
} else {
// Used to filter values
$allowed_keys = array( 'name', 'author', 'date', 'title', 'modified', 'menu_order', 'parent', 'ID', 'rand', 'comment_count', 'type' );
if ( !empty($q['meta_key']) ) {
$allowed_keys[] = $q['meta_key'];
$allowed_keys[] = 'meta_value';
$allowed_keys[] = 'meta_value_num';
}
$q['orderby'] = urldecode($q['orderby']);
$q['orderby'] = addslashes_gpc($q['orderby']);
$orderby_array = array();
foreach ( explode( ' ', $q['orderby'] ) as $i => $orderby ) {
// Only allow certain values for safety
if ( ! in_array($orderby, $allowed_keys) )
continue;
if ( is_array( $q['orderby'] ) ) {
foreach ( $q['orderby'] as $_orderby => $order ) {
$orderby = addslashes_gpc( urldecode( $_orderby ) );
$parsed = $this->parse_orderby( $orderby );
switch ( $orderby ) {
case 'menu_order':
$orderby = "$wpdb->posts.menu_order";
break;
case 'ID':
$orderby = "$wpdb->posts.ID";
break;
case 'rand':
$orderby = 'RAND()';
break;
case $q['meta_key']:
case 'meta_value':
if ( isset( $q['meta_type'] ) ) {
$meta_type = $this->meta_query->get_cast_for_type( $q['meta_type'] );
$orderby = "CAST($wpdb->postmeta.meta_value AS {$meta_type})";
} else {
$orderby = "$wpdb->postmeta.meta_value";
}
break;
case 'meta_value_num':
$orderby = "$wpdb->postmeta.meta_value+0";
break;
case 'comment_count':
$orderby = "$wpdb->posts.comment_count";
break;
default:
$orderby = "$wpdb->posts.post_" . $orderby;
if ( ! $parsed ) {
continue;
}
$orderby_array[] = $parsed . ' ' . $this->parse_order( $order );
}
$orderby = implode( ', ', $orderby_array );
$orderby_array[] = $orderby;
} else {
$q['orderby'] = urldecode( $q['orderby'] );
$q['orderby'] = addslashes_gpc( $q['orderby'] );
foreach ( explode( ' ', $q['orderby'] ) as $i => $orderby ) {
$parsed = $this->parse_orderby( $orderby );
// Only allow certain values for safety.
if ( ! $parsed ) {
continue;
}
$orderby_array[] = $parsed;
}
$orderby = implode( ' ' . $q['order'] . ', ', $orderby_array );
if ( empty( $orderby ) ) {
$orderby = "$wpdb->posts.post_date ".$q['order'];
} else {
$orderby .= " {$q['order']}";
}
}
$orderby = implode( ' ' . $q['order'] . ', ', $orderby_array );
if ( empty( $orderby ) )
$orderby = "$wpdb->posts.post_date ".$q['order'];
else
$orderby .= " {$q['order']}";
}
// Order search results by relevance only when another "orderby" is not specified in the query.

View File

@ -761,4 +761,74 @@ class Tests_Post_Query extends WP_UnitTestCase {
$q3 = new WP_Query( array( 'post_status' => array( 'any', 'auto-draft' ) ) );
$this->assertNotContains( "post_status <> 'auto-draft'", $q3->request );
}
/**
*
* @ticket 17065
*/
function test_orderby_array() {
global $wpdb;
$q1 = new WP_Query( array(
'orderby' => array(
'type' => 'DESC',
'name' => 'ASC'
)
) );
$this->assertContains(
"ORDER BY $wpdb->posts.post_type DESC, $wpdb->posts.post_name ASC",
$q1->request
);
$q2 = new WP_Query( array( 'orderby' => array() ) );
$this->assertNotContains( 'ORDER BY', $q2->request );
$this->assertNotContains( 'ORDER', $q2->request );
$q3 = new WP_Query( array( 'post_type' => 'post' ) );
$this->assertContains(
"ORDER BY $wpdb->posts.post_date DESC",
$q3->request
);
$q4 = new WP_Query( array( 'post_type' => 'post' ) );
$this->assertContains(
"ORDER BY $wpdb->posts.post_date DESC",
$q4->request
);
}
/**
*
* @ticket 17065
*/
function test_order() {
global $wpdb;
$q1 = new WP_Query( array(
'orderby' => array(
'post_type' => 'foo'
)
) );
$this->assertContains(
"ORDER BY $wpdb->posts.post_type DESC",
$q1->request
);
$q2 = new WP_Query( array(
'orderby' => 'title',
'order' => 'foo'
) );
$this->assertContains(
"ORDER BY $wpdb->posts.post_title DESC",
$q2->request
);
$q3 = new WP_Query( array(
'order' => 'asc'
) );
$this->assertContains(
"ORDER BY $wpdb->posts.post_date ASC",
$q3->request
);
}
}