ไม่สนใจบทความเริ่มต้น (เช่น 'a', 'an' หรือ 'the') เมื่อเรียงลำดับข้อความค้นหา?


13

ฉันกำลังพยายามส่งออกรายการของชื่อเพลงและต้องการให้ละเว้นการเรียงลำดับ (แต่ยังคงแสดง) บทความเริ่มต้นของชื่อเรื่อง

ตัวอย่างเช่นถ้าฉันมีรายชื่อของวงดนตรีมันจะแสดงตามลำดับตัวอักษรใน WordPress ดังนี้:

  • วันธรรมสวนะดำ
  • Led Zeppelin
  • พิงค์ฟลอยด์
  • เดอะบีทเทิลส์
  • หว่า
  • หินกลิ้ง
  • ผอมลิซซี่

แต่ฉันต้องการให้มันแสดงตามตัวอักษรในขณะที่ละเว้นบทความเริ่มต้น 'The' เช่นนี้

  • เดอะบีทเทิลส์
  • วันธรรมสวนะดำ
  • หว่า
  • Led Zeppelin
  • พิงค์ฟลอยด์
  • หินกลิ้ง
  • ผอมลิซซี่

ฉันเจอวิธีแก้ไขปัญหาในรายการบล็อกจากปีที่แล้วซึ่งแนะนำรหัสต่อไปนี้ใน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: [คอลัมน์ที่ไม่รู้จัก 'title2' ใน 'order clause']

เลือก wp_posts. * จาก wp_posts โดยที่ 1 = 1 และ wp_posts.post_type = 'ปล่อย' และ (wp_posts.post_status = 'เผยแพร่' หรือ 'wp_posts.post_status =' ส่วนตัว ') เรียงตามบนสุด (ชื่อ 2) ASC

ฉันจะไม่โกหกฉันค่อนข้างใหม่กับส่วน php ของ WordPress ดังนั้นฉันจึงไม่แน่ใจว่าทำไมฉันถึงได้รับข้อผิดพลาดนี้ ฉันเห็นว่ามันมีบางอย่างเกี่ยวกับคอลัมน์ '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
คุณจะไม่ใช้ใด ๆ ของรหัสที่คุณสามารถค้นหาและจัดเรียงในโพสต์เมตากับพารามิเตอร์การค้นหา meta
Milo

คำตอบ:


8

ปัญหา

ฉันคิดว่ามีการพิมพ์ผิดในนั้น:

ชื่อของตัวกรองที่ได้คือไม่ได้posts_fieldspost_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()

ลองใช้ความคิด recursive โดยปาสกาล 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:00 คาดเดา ฉันจะดูโซลูชันตัวกรองเดียวเช่นกัน ดูเหมือนว่าความคิดที่ดีจริงๆ ขอบคุณอีกครั้ง.
rpbtz

ฉันจะทำเครื่องหมายคำตอบนี้ว่าเป็นคำตอบที่ถูกต้องเพราะนี่เป็นคำตอบที่เกี่ยวข้องกับคำถามเริ่มต้นของฉันมากที่สุดแม้ว่าฉันจะบอกได้ว่าคำตอบอื่น ๆ นั้นเป็นคำตอบที่ถูกต้องเช่นกัน
rpbtz

ตัวเลือกตัวกรองเดียวทำงานได้อย่างมีเสน่ห์เช่นกัน ตอนนี้ฉันสามารถเก็บรหัสตัวกรองไว้functions.phpและเรียกใช้ผ่านorderbyเมื่อฉันต้องการ ทางออกที่ดีเยี่ยม - ขอบคุณ :-)
rpbtz

1
ดีใจที่ได้ยินว่าเหมาะกับคุณ - ฉันเพิ่มวิธีเรียกซ้ำ @rpbtz
birgire

12

วิธีที่ง่ายกว่าอาจทำได้คือเปลี่ยนบุ้ง Permalink บนโพสต์เหล่านั้นที่ต้องการ (ใต้ชื่อเรื่องบนหน้าจอการเขียนโพสต์) จากนั้นใช้คำสั่งนั้นแทนการเรียงชื่อ

กล่าวคือ ใช้post_nameไม่ได้post_titleสำหรับการจัดเรียง ...

นี่ก็หมายความว่า Permalink ของคุณอาจแตกต่างกันถ้าคุณใช้% 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

แก้ไข

ฉันได้ปรับปรุงโค้ดเล็กน้อย บล็อคโค้ดทั้งหมดได้รับการอัพเดตตามนั้น เพิ่งทราบว่าก่อนที่จะกระโดดเข้าไปในการปรับปรุงในคำตอบเดิมฉันได้ตั้งรหัสให้ทำงานกับต่อไปนี้

  • ประเภทโพสต์ที่กำหนดเอง -> 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 ช้าง hardcoded ;-)
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.