Surprisingly, WordPress does not make it very straightforward. Even though lots of people have had similar questions on the wordpress.org forums, I believe yours is unique in that you want to keep the order and sort.
This is by no means quality code, but does exactly what you’re looking for.
class WP_Query_Custom extends WP_Query {
static $filters = array( 'join', 'groupby', 'fields', 'results' );
function get_posts() {
$this->enable_filters();
$posts = parent::get_posts();
$this->disable_filters();
return $posts;
}
function enable_filters() {
foreach( self::$filters as $filter ) {
add_filter( 'posts_' . $filter, array( __CLASS__, 'filter_' . $filter ), 10, 1 );
}
}
function disable_filters() {
foreach( self::$filters as $filter ) {
remove_filter( 'posts_' . $filter, array( __CLASS__, 'filter_' . $filter ), 10, 1 );
}
}
function filter_join($join) {
global $wpdb;
return "{$join} LEFT JOIN {$wpdb->posts} AS post_attachment ON {$wpdb->posts}.ID=post_attachment.post_parent AND post_attachment.post_type="attachment" ";
}
function filter_groupby($groupby) {
global $wpdb;
if ( ! empty($groupby) ) {
$groupby = ",$groupby";
}
return "{$wpdb->posts}.ID{$groupby}";
}
function filter_fields($fields) {
$fields = "GROUP_CONCAT(post_attachment.ID) AS post_attachments, ".$fields;
return $fields;
}
function filter_results($posts) {
$num_posts = count($posts);
$offset = 0;
$new_posts = array();
for ( $i = 0; $i < $num_posts; $i++ ) {
if ( ! empty($posts[$i]->post_attachments) ) {
array_push( $new_posts, $posts[$i] );
unset( $posts[$i] );
}
}
return array_merge( $new_posts, $posts );
}
}
$query = new WP_Query_Custom('post_type=post');