問題描述

我正在嘗試輸出音樂標題列表,並希望排序忽略 (但仍然顯示) 標題的初始文章。

例如,如果我有一個樂隊列表,它將按照字母順序在 WordPress 中顯示,如下所示:

  • 黑安息日

  • 齊柏林飛艇

  • 平克·弗洛伊德 (樂隊名

  • 披頭士

  • 扭結

  • 滾石樂隊

  • 薄麗的

相反,我想按字母順序顯示,忽略最初的文章’The’,像這樣:

  • 披頭士

  • 黑安息日

  • 扭結

  • 齊柏林飛艇

  • 平克·弗洛伊德 (樂隊名

  • 滾石樂隊

  • 薄麗的

我在 a blog entry from last year 中遇到了一個解決方案,建議 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 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

我不會撒謊,我對 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();
?>

最佳解決方案

問題

我認為那裏有一個錯字:

過濾器的名稱是 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 );

您現在可以使用_custom orderby 參數激活自定義排序:

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

我們可以在這裏構建遞歸函數:

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

次佳解決方案

更簡單的方法可能是通過並改變那些需要它的帖子 (在帖子寫作屏幕上的標題下),然後只是使用它來排序而不是標題。

即。使用 post_name 不用 post_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);
            }
        }
    }
}

第三種解決方案

EDIT

我已經改進了一些代碼。所有代碼塊都相應更新。在剛剛進入 「原始答案」 中的更新之前,我已經設置了代碼,使用以下內容

  • 自定義帖子類型 – > 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 );

參考文獻

注:本文內容整合自 Google/Baidu/Bing 輔助翻譯的英文資料結果。如果您對結果不滿意,可以加入我們改善翻譯效果:薇曉朵技術論壇。