在对查询进行排序时忽略初始文章(例如“ a”,“ an”或“ the”)?


13

我目前正在尝试输出音乐标题列表,并希望排序忽略(但仍显示)该标题的初始文章。

例如,如果我有乐队列表,它将在WordPress中按字母顺序显示,如下所示:

  • 黑色安息日
  • 齐柏林飞艇
  • 平克·弗洛伊德(乐队名
  • 披头士
  • 纠结
  • 滚石乐队
  • 薄丽兹

相反,我希望按字母顺序显示它,而忽略初始文章“ The”,如下所示:

  • 披头士
  • 黑色安息日
  • 纠结
  • 齐柏林飞艇
  • 平克·弗洛伊德(乐队名
  • 滚石乐队
  • 薄丽兹

去年,我在一个博客条目中遇到了一个解决方案,该提议在中提供了以下代码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;
}

然后用add_filter之前和remove_filter之后包装查询。

我已经尝试过了,但是在我的网站上一直出现以下错误:

WordPress数据库错误:['order子句'中的未知列'title2']

从wp_posts中选择wp_posts。*在1 = 1且wp_posts.post_type ='release'AND(wp_posts.post_status ='publish'或wp_posts.post_status ='private')的情况下按顺序排序(title2)ASC

我不会撒谎,我对WordPress的php部分还很陌生,所以我不确定为什么会收到此错误。我可以看到它与'title2'列有关,但是据我了解,第一个功能应该解决这一问题。另外,如果有更聪明的方法可以做到这一点,我全都听不见。我一直在搜寻并搜索该站点,但实际上并没有找到很多解决方案。

如果有帮助,我使用过滤器的代码将如下所示:

<?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();
?>

1
另一种解决方案是将要排序的标题存储为该字段上的发布元数据和顺序,而不是标题。
米洛

我不确定如何进行此操作。不会将其存储在新列中导致与我当前得到的错误类似的错误吗?
rpbtz '16

1
您无需使用任何代码,就可以使用meta查询参数对post meta进行查询和排序。
米洛

Answers:


8

问题

我认为其中有一个错字:

过滤器的名称posts_fields不是post_fields

那可以解释为什么该title2字段未知,因为它的定义没有添加到生成的SQL字符串中。

替代方案-单个过滤器

我们可以将其重写为仅使用单个过滤器:

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 );

现在,您可以在其中使用_customorderby参数激活自定义订购:

$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();

另类-递归 TRIM()

让我们实现Pascal Birchler的递归思想,在这里评论

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 );

例如,我们可以将递归函数构造为:

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 );
}

这意味着

$matches = [ 'the ', 'an ', 'a ' ];
echo wpse_sql( $matches, " LOWER( {$wpdb->posts}.post_title) " );

将产生:

TRIM( LEADING 'a ' FROM ( 
    TRIM( LEADING 'an ' FROM ( 
        TRIM( LEADING 'the ' FROM ( 
            LOWER( wp_posts.post_title) 
        ) )
    ) )
) )

替代方案-MariaDB

通常,我喜欢使用MariaDB代替MySQL。然后,因为MariaDB 10.0.5 支持 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 );

我认为这应该比我的解决方案更好地解决问题
Pieter Goosen

您是完全正确的-将post_fields更改为posts_fields可以解决此问题,并且现在可以按照我想要的方式进行排序。谢谢!我现在觉得有点愚蠢,因为那是问题所在。猜猜,这就是我在凌晨4点进行编码所得到的。我还将研究单个过滤器解决方案。似乎是个好主意。再次感谢。
rpbtz

我将其标记为正确答案,因为这是与我最初提出的问题最相关的答案,尽管据我所知其他答案也是有效的解决方案。
rpbtz '16

单个过滤器替代方案也像魅力一样起作用。现在,我可以保留过滤器代码,functions.phporderby在需要时通过调用它。伟大的解决方案-谢谢您:-)
rpbtz '16

1
很高兴听到它对您有用-我添加了递归方法。@rpbtz
birgire

12

一种更简单的方法可能是遍历并更改需要它的那些帖子的永久链接块(在帖子写作屏幕上的标题下),然后仅使用它来排序而不是标题。

即。使用post_namepost_title进行排序...

这也意味着如果您在永久链接结构中使用%postname%,则永久链接可能会有所不同,这可能会增加额外的好处。

例如。http://example.com/rolling-stones/ 不给http://example.com/the-rolling-stones/

编辑:代码更新现有的子弹,从post_name列中删除不需要的前缀...

global $wpdb;
$posttype = 'release';
$stripprefixes = array('a-','an-','the-');

$results = $wpdb->get_results("SELECT ID, post_name FROM ".$wpdb->prefix."posts" WHERE post_type = '".$posttype."' AND post_status = 'publish');
if (count($results) > 0) {
    foreach ($results as $result) {
        $postid = $result->ID;
        $postslug = $result->post_name;
        foreach ($stripprefixes as $stripprefix) {
            $checkprefix = strtolower(substr($postslug,0,strlen($stripprefix));
            if ($checkprefix == $stripprefix) {
                $newslug = substr($postslug,strlen($stripprefix),strlen($postslug));
                // echo $newslug; // debug point
                $query = $wpdb->prepare("UPDATE ".$wpdb->prefix."posts SET post_name = '%s' WHERE ID = '%d'", $newslug, $postid);
                $wpdb->query($query);
            }
        }
    }
}

很棒的解决方案-非常简单高效的排序。
BillK '16

@birgire的错字解决方案就像一个魅力,但似乎是一个不错的选择。我现在将继续讨论另一篇文章,因为有很多查询的文章带有初始文章,并且更改所有永久链接标记可能需要一些时间。不过,我喜欢这种解决方案的简单性。谢谢:-)
rpbtz '16

1
因为您喜欢,所以添加了一些代码,如果需要,该代码应更改所有子段。:-)
majick '16

6

编辑

我对代码做了一些改进。所有代码块都会相应更新。尽管只是一个注意事项,但在跳至ORIGINAL ANSWER中的更新之前,我已经设置了可与以下代码一起使用的代码

  • 自定义帖子类型-> release

  • 自定义分类法-> game

确保根据您的需要进行设置

原始答案

除了@birgire指出的其他答案和错别字外,这是另一种方法。

首先,我们将标题设置为隐藏的自定义字段,但首先the将要删除的单词删除。在此之前,我们需要首先创建一个辅助函数,以便从术语名称和帖子标题中删除被禁止的单词

/**
 * Function get_name_banned_removed()
 *
 * A helper function to handle removing banned words
 * 
 * @param string $tring  String to remove banned words from
 * @param array  $banned Array of banned words to remove
 * @return string $string
 */
function get_name_banned_removed( $string = '', $banned = [] )
{
    // Make sure we have a $string to handle
    if ( !$string )
        return $string;

    // Sanitize the string
    $string = filter_var( $string, FILTER_SANITIZE_STRING );

    // Make sure we have an array of banned words
    if (    !$banned
         || !is_array( $banned )
    )
        return $string; 

    // Make sure that all banned words is lowercase
    $banned = array_map( 'strtolower', $banned );

    // Trim the string and explode into an array, remove banned words and implode
    $text          = trim( $string );
    $text          = strtolower( $text );
    $text_exploded = explode( ' ', $text );

    if ( in_array( $text_exploded[0], $banned ) )
        unset( $text_exploded[0] );

    $text_as_string = implode( ' ', $text_exploded );

    return $string = $text_as_string;
}

现在我们已经涵盖了这一点,让我们看一下用于设置自定义字段的代码。一旦加载任何页面,您必须立即完全删除此代码。如果您的站点很大,且帖子很多,则可以设置posts_per_page一些内容100并运行脚本几次,直到所有帖子的自定义字段都设置为所有帖子为止

add_action( 'wp', function ()
{
    add_filter( 'posts_fields', function ( $fields, \WP_Query $q ) 
    {
        global $wpdb;

        remove_filter( current_filter(), __FUNCTION__ );

        // Only target a query where the new custom_query parameter is set with a value of custom_meta_1
        if ( 'custom_meta_1' === $q->get( 'custom_query' ) ) {
            // Only get the ID and post title fields to reduce server load
            $fields = "$wpdb->posts.ID, $wpdb->posts.post_title";
        }

        return $fields;
    }, 10, 2);

    $args = [
        'post_type'        => 'release',       // Set according to needs
        'posts_per_page'   => -1,              // Set to execute smaller chucks per page load if necessary
        'suppress_filters' => false,           // Allow the posts_fields filter
        'custom_query'     => 'custom_meta_1', // New parameter to allow that our filter only target this query
        'meta_query'       => [
            [
                'key'      => '_custom_sort_post_title', // Make it a hidden custom field
                'compare'  => 'NOT EXISTS'
            ]
        ]
    ];
    $q = get_posts( $args );

    // Make sure we have posts before we continue, if not, bail
    if ( !$q ) 
        return;

    foreach ( $q as $p ) {
        $new_post_title = strtolower( $p->post_title );

        if ( function_exists( 'get_name_banned_removed' ) )
            $new_post_title = get_name_banned_removed( $new_post_title, ['the'] );

        // Set our custom field value
        add_post_meta( 
            $p->ID,                    // Post ID
            '_custom_sort_post_title', // Custom field name
            $new_post_title            // Custom field value
        );  
    } //endforeach $q
});

现在,自定义字段已设置为所有帖子,并且上面的代码已删除,我们需要确保将此自定义字段设置为所有新帖子,或者在更新帖子标题时进行设置。为此,我们将使用transition_post_status钩子。以下代码可以插入插件(建议使用)或您的functions.php

add_action( 'transition_post_status', function ( $new_status, $old_status, $post )
{
    // Make sure we only run this for the release post type
    if ( 'release' !== $post->post_type )
        return;

    $text = strtolower( $post->post_title );   

    if ( function_exists( 'get_name_banned_removed' ) )
        $text = get_name_banned_removed( $text, ['the'] );

    // Set our custom field value
    update_post_meta( 
        $post->ID,                 // Post ID
        '_custom_sort_post_title', // Custom field name
        $text                      // Custom field value
    );
}, 10, 3 );

查询您的帖子

您可以正常运行查询,而无需任何自定义过滤器。您可以按以下方式查询和排序帖子

$args_post = [
    'post_type'      => 'release', 
    'orderby'        => 'meta_value', 
    'meta_key'       => '_custom_sort_post_title',
    'order'          => 'ASC', 
    'posts_per_page' => -1, 
];
$loop = new WP_Query( $args );

我喜欢这种方法(也许足以从标题开头删除被禁止的单词)
birgire

@birgire我之所以这样做,是因为我的SQL知识像教堂鼠标一样糟糕,哈哈哈哈。感谢您的打字错误
Pieter Goosen

1
机智的鼠标比硬编码的SQL大象要敏捷得多;-)
birgire

0

仅按此字段排序时,birgire的答案会很好。我进行了一些修改,以使其在按多个字段排序时可以正常工作(我不确定当标题排序为主要排序时它是否可以正常工作):

add_filter( 'posts_orderby', function( $orderby, \WP_Query $q )
{
// Do nothing
if( '_custom' !== $q->get( 'orderby' ) && !isset($q->get( 'orderby' )['_custom']) )
    return $orderby;

global $wpdb;

$matches = 'The';   // REGEXP is not case sensitive here

// Custom ordering (SQL)
if (is_array($q->get( 'orderby' ))) {
    return sprintf( 
        " $orderby, 
        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( 'orderby' )['_custom'] ) ? 'ASC' : 'DESC'     
    );
}
else {
    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 );
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.