검색어를 정렬 할 때 초기 기사 (예 : '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 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();
?>

1
다른 해결책은 정렬하려는 제목을 게시물 메타 데이터로 저장하고 제목 대신 해당 필드에 순서를 지정하는 것입니다.
Milo

나는 그것을 계속하는 방법에 대해 조금 확신하지 못한다. 새 열에 저장하지 않으면 현재 얻는 것과 비슷한 오류가 발생합니까?
rpbtz

1
해당 코드를 사용하지 않으면 메타 쿼리 매개 변수를 사용하여 포스트 메타를 쿼리하고 정렬 할 수 있습니다 .
Milo

답변:


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

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

이것이 내 솔루션보다 문제를 더 잘 해결해야한다고 생각합니다.
Pieter Goosen

당신은 절대적으로 정확했습니다-post_fields를 posts_fields로 변경하면 문제가 해결되었으며 이제는 내가 원하는 방식으로 정확하게 정렬됩니다. 감사합니다! 나는 그것이 문제라고 생각하는 약간 바보 같은 느낌이 든다. 그것이 오전 4시에 코딩을 위해 얻는 것입니다. 단일 필터 솔루션도 살펴 보겠습니다. 정말 좋은 생각 인 것 같습니다. 다시 감사합니다.
rpbtz

다른 답변도 유효한 솔루션이라고 말할 수있는 한, 이것이 나의 초기 질문과 가장 밀접한 관련이 있으므로 정답으로 표시하겠습니다.
rpbtz

단일 필터 대안도 매력처럼 작동했습니다. 이제 필터 코드를 유지하고 필요할 때 functions.php이를 통해 호출 orderby할 수 있습니다. 훌륭한 솔루션-감사합니다 :-)
rpbtz

1
그것이 당신에게 효과적이라고 들게되어 기쁘다-나는 재귀 방법을 추가했다. @rpbtz
birgire

12

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

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

훌륭한 솔루션-정렬이 매우 간단하고 효율적입니다.
BillK

@birgire의 오타 솔루션은 매력처럼 작동했지만 괜찮은 대안처럼 보입니다. 초기 기사가있는 쿼리 된 게시물이 많고 모든 permalink 슬러그를 변경하는 데 시간이 걸릴 수 있으므로 지금은 다른 기사와 함께 갈 것입니다. 그러나이 솔루션의 단순함을 좋아합니다. 감사합니다 :-)
rpbtz

1
당신이 좋아하기 때문에, 원하거나 필요하면 모든 슬러그를 변경 해야하는 코드를 추가했습니다. :-)
majick

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 나는 단지 이것과 함께 갔다. 왜냐하면 나의 SQL 지식은 교회 마우스처럼 가난하기 때문이다. 오타 주셔서 감사합니다
피터 구센

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 );
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.