검색어를 정렬 할 때 초기 기사 (예 : ‘a’, ‘an’또는 ‘the’)를 무시 하시겠습니까? = ” CASE

현재 음악 제목 목록을 출력하려고하는데 제목의 첫 번째 기사를 정렬에서 무시하고 표시하고 싶습니다.

예를 들어 밴드 목록이 있으면 다음과 같이 알파벳순으로 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 clause’의 ‘title2’열을 알 수 없습니다.]

wp_posts를 선택하십시오. * 1에서 wp_posts로 wp_posts.post_type = ‘release’AND (wp_posts.post_status = ‘publish’또는 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 );

여기서 _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()

의가에 의해 재귀 아이디어를 구현하자 파스칼 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

일반적으로 MySQL 대신 MariaDB 를 사용하고 싶습니다 . 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 );


답변

더 쉬운 방법은 게시물 작성 화면의 제목 아래에 필요한 게시물의 퍼머 링크 슬러그를 변경 한 다음 제목 대신 주문에 사용하는 것입니다.

즉. 사용 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);
            }
        }
    }
}


답변

편집하다

코드를 약간 개선했습니다. 모든 코드 블록이 그에 따라 업데이트됩니다. 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의 답변은이 필드에서만 주문할 때 효과적입니다. 여러 필드로 주문할 때 작동하도록 일부 수정했습니다 (제목 순서가 기본 주문 일 때 올바르게 작동하는지 확실하지 않습니다).

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


답변