I’m currently trying to output a list of music titles and would like to have the sorting ignore (but still display) the initial article of the title.
For example if I had a list of bands it will be displayed alphabetically in WordPress like this:
- Black Sabbath
- Led Zeppelin
- Pink Floyd
- The Beatles
- The Kinks
- The Rolling Stones
- Thin Lizzy
Instead I would like to have it displayed alphabetically while ignoring the initial article ‘The’, like this:
- The Beatles
- Black Sabbath
- The Kinks
- Led Zeppelin
- Pink Floyd
- The Rolling Stones
- Thin Lizzy
I came across a solution in a blog entry from last year, that suggests the following code in functions.php
:
function wpcf_create_temp_column($fields) {
global $wpdb;
$matches="The";
$has_the = " CASE
WHEN $wpdb->posts.post_title regexp( '^($matches)[[:space:]]' )
THEN trim(substr($wpdb->posts.post_title from 4))
ELSE $wpdb->posts.post_title
END AS title2";
if ($has_the) {
$fields .= ( preg_match( '/^(\s+)?,/', $has_the ) ) ? $has_the : ", $has_the";
}
return $fields;
}
function wpcf_sort_by_temp_column ($orderby) {
$custom_orderby = " UPPER(title2) ASC";
if ($custom_orderby) {
$orderby = $custom_orderby;
}
return $orderby;
}
and then wrapping the query with add_filter
before and remove_filter
after.
I’ve tried this, but I keep getting the following error on my site:
WordPress database error: [Unknown column ‘title2’ in ‘order clause’]
SELECT wp_posts.* FROM wp_posts WHERE 1=1 AND wp_posts.post_type=”release” AND (wp_posts.post_status=”publish” OR
wp_posts.post_status=”private”) ORDER BY UPPER(title2) ASC
I’m not gonna lie, I’m pretty new to the php part of WordPress, so I’m unsure as to why I’m getting this error. I can see it’s got something to do with the ‘title2’ column, but it was my understanding that the first function should take care of that. Also, if there’s a smarter way to do this I’m all ears. I’ve been googling around and searching this site, but I haven’t really found a lot of solutions.
My code using the filters looks like this if it’s any help:
<?php
$args_post = array('post_type' => 'release', 'orderby' => 'title', 'order' => 'ASC', 'posts_per_page' => -1, );
add_filter('post_fields', 'wpcf_create_temp_column'); /* remove initial 'The' from post titles */
add_filter('posts_orderby', 'wpcf_sort_by_temp_column');
$loop = new WP_Query($args_post);
remove_filter('post_fields', 'wpcf_create_temp_column');
remove_filter('posts_orderby', 'wpcf_sort_by_temp_column');
while ($loop->have_posts() ) : $loop->the_post();
?>
The Problem
I think there’s a typo in there:
The name of the filter is posts_fields
not post_fields
.
That could explain why the title2
field is unknown, because it’s definition isn’t added to the generated SQL string.
Alternative – Single filter
We can rewrite it to use only a single filter:
add_filter( 'posts_orderby', function( $orderby, \WP_Query $q )
{
// Do nothing
if( '_custom' !== $q->get( 'orderby' ) )
return $orderby;
global $wpdb;
$matches="The"; // REGEXP is not case sensitive here
// Custom ordering (SQL)
return sprintf(
"
CASE
WHEN {$wpdb->posts}.post_title REGEXP( '^($matches)[[:space:]]+' )
THEN TRIM( SUBSTR( {$wpdb->posts}.post_title FROM %d ))
ELSE {$wpdb->posts}.post_title
END %s
",
strlen( $matches ) + 1,
'ASC' === strtoupper( $q->get( 'order' ) ) ? 'ASC' : 'DESC'
);
}, 10, 2 );
where you can now activate the custom ordering with the _custom
orderby parameter:
$args_post = array
'post_type' => 'release',
'orderby' => '_custom', // Activate the custom ordering
'order' => 'ASC',
'posts_per_page' => -1,
);
$loop = new WP_Query($args_post);
while ($loop->have_posts() ) : $loop->the_post();
Alternative – Recursive TRIM()
Let’s implement the recursive idea by Pascal Birchler, commented here:
add_filter( 'posts_orderby', function( $orderby, \WP_Query $q )
{
if( '_custom' !== $q->get( 'orderby' ) )
return $orderby;
global $wpdb;
// Adjust this to your needs:
$matches = [ 'the ', 'an ', 'a ' ];
return sprintf(
" %s %s ",
wpse_sql( $matches, " LOWER( {$wpdb->posts}.post_title) " ),
'ASC' === strtoupper( $q->get( 'order' ) ) ? 'ASC' : 'DESC'
);
}, 10, 2 );
where we can for example construct the recursive function as:
function wpse_sql( &$matches, $sql )
{
if( empty( $matches ) || ! is_array( $matches ) )
return $sql;
$sql = sprintf( " TRIM( LEADING '%s' FROM ( %s ) ) ", $matches[0], $sql );
array_shift( $matches );
return wpse_sql( $matches, $sql );
}
This means that
$matches = [ 'the ', 'an ', 'a ' ];
echo wpse_sql( $matches, " LOWER( {$wpdb->posts}.post_title) " );
will generate:
TRIM( LEADING 'a ' FROM (
TRIM( LEADING 'an ' FROM (
TRIM( LEADING 'the ' FROM (
LOWER( wp_posts.post_title)
) )
) )
) )
Alternative – MariaDB
In general I like to use MariaDB instead of MySQL. Then it’s much easier because MariaDB 10.0.5 supports REGEXP_REPLACE
:
/**
* Ignore (the,an,a) in post title ordering
*
* @uses MariaDB 10.0.5+
*/
add_filter( 'posts_orderby', function( $orderby, \WP_Query $q )
{
if( '_custom' !== $q->get( 'orderby' ) )
return $orderby;
global $wpdb;
return sprintf(
" REGEXP_REPLACE( {$wpdb->posts}.post_title, '^(the|a|an)[[:space:]]+', '' ) %s",
'ASC' === strtoupper( $q->get( 'order' ) ) ? 'ASC' : 'DESC'
);
}, 10, 2 );