Mengabaikan artikel awal (seperti 'a', 'an' atau 'the') saat menyortir kueri?

13

Saat ini saya mencoba menampilkan daftar judul musik dan ingin menyortir mengabaikan (tetapi masih menampilkan) artikel awal dari judul.

Sebagai contoh jika saya memiliki daftar band itu akan ditampilkan secara alfabet di WordPress seperti ini:

  • Sabat hitam
  • Dipimpin Zeppelin
  • Pink Floyd
  • The Beatles
  • Kinks
  • Batu berputar
  • Lizzy Tipis

Alih-alih, saya ingin menampilkannya sesuai abjad sambil mengabaikan artikel awal 'The', seperti ini:

  • The Beatles
  • Sabat hitam
  • Kinks
  • Dipimpin Zeppelin
  • Pink Floyd
  • Batu berputar
  • Lizzy Tipis

Saya menemukan solusi dalam entri blog dari tahun lalu , yang menyarankan kode berikut dalam 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;
}

dan kemudian membungkus kueri dengan add_filtersebelum danremove_filter sesudah.

Saya sudah mencoba ini, tetapi saya terus mendapatkan kesalahan berikut di situs saya:

Kesalahan basis data WordPress: [Kolom 'title2' tidak dikenal di 'urutan klausa']

SELECT wp_posts. * FROM wp_posts WHERE 1 = 1 AND wp_posts.post_type = 'rilis' AND (wp_posts.post_status = 'publikasikan' ATAU wp_posts.post_status = 'pribadi') ORDER OLEH DASAR (title2) ASC

Saya tidak akan berbohong, saya cukup baru di bagian php WordPress, jadi saya tidak yakin mengapa saya mendapatkan kesalahan ini. Saya bisa melihat itu ada hubungannya dengan kolom 'title2', tapi itu pemahaman saya bahwa fungsi pertama harus mengurus itu. Juga, jika ada cara yang lebih cerdas untuk melakukan ini saya semua telinga. Saya sudah googling di sekitar dan mencari situs ini, tetapi saya belum benar-benar menemukan banyak solusi.

Kode saya menggunakan filter terlihat seperti ini jika ada bantuan:

<?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();
?>
rpbtz
sumber
1
solusi alternatif bisa dengan menyimpan judul yang ingin Anda urutkan sebagai memposting data meta dan memesan pada bidang itu alih-alih judul.
Milo
Saya sedikit tidak yakin bagaimana melanjutkannya. Tidakkah menyimpan itu di kolom baru menghasilkan kesalahan yang mirip dengan yang saya dapatkan saat ini?
rpbtz
1
Anda tidak akan menggunakan salah satu kode itu, Anda dapat meminta dan mengurutkan pada posting meta dengan parameter permintaan meta .
Milo

Jawaban:

8

Masalah

Saya pikir ada kesalahan ketik di sana:

Nama filternya posts_fieldsbukan post_fields.

Itu bisa menjelaskan mengapa title2bidang tidak diketahui, karena definisi itu tidak ditambahkan ke string SQL yang dihasilkan.

Alternatif - Filter tunggal

Kami dapat menulis ulang untuk menggunakan hanya satu filter:

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

di mana Anda sekarang dapat mengaktifkan pemesanan khusus dengan _customparameter 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();

Alternatif - Rekursif TRIM()

Mari kita terapkan ide rekursif oleh Pascal Birchler , berkomentar di sini :

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

di mana kita dapat misalnya membangun fungsi rekursif sebagai:

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

Ini artinya

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

akan menghasilkan:

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

Alternatif - MariaDB

Secara umum saya suka menggunakan MariaDB, bukan MySQL . Maka itu jauh lebih mudah karena MariaDB 10.0.5 mendukung 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 );
birgire
sumber
Saya pikir ini harus memecahkan masalah lebih baik daripada solusi saya
Pieter Goosen
Anda benar-benar benar - mengubah post_fields ke posts_fields memperbaiki masalah dan sekarang menyortir persis seperti yang saya inginkan. Terima kasih! Saya merasa sedikit bodoh sekarang karena itulah masalahnya. Itu yang saya dapatkan untuk coding jam 4 pagi, tebak. Saya akan melihat ke solusi filter tunggal juga. Sepertinya ide yang sangat bagus. Terima kasih lagi.
rpbtz
Saya akan menandai ini sebagai jawaban yang benar karena ini adalah jawaban yang paling dekat hubungannya dengan pertanyaan awal saya, meskipun sejauh yang saya tahu jawabannya adalah solusi yang valid juga.
rpbtz
Alternatif filter tunggal bekerja seperti pesona juga. Sekarang saya dapat menyimpan kode filter functions.phpdan menyebutnya melalui orderbyketika saya membutuhkannya. Solusi hebat - terima kasih :-)
rpbtz
1
Senang mendengarnya bekerja untuk Anda - saya menambahkan metode rekursif. @rpbtz
birgire
12

Cara yang lebih mudah adalah melalui dan mengubah slug permalink pada postingan yang membutuhkannya (di bawah judul pada layar tulisan postingan) dan kemudian gunakan saja untuk memesan alih-alih judul.

yaitu. gunakan post_namebukan post_titleuntuk menyortir ...

Ini juga berarti bahwa permalink Anda mungkin berbeda jika Anda menggunakan% postname% dalam struktur permalink Anda, yang bisa menjadi bonus tambahan.

misalnya. memberi http://example.com/rolling-stones/ tidakhttp://example.com/the-rolling-stones/

Sunting : kode untuk memperbarui siput yang ada, menghapus awalan yang tidak diinginkan dari post_namekolom ...

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);
            }
        }
    }
}
majik
sumber
Solusi hebat - sangat sederhana dan efisien untuk penyortiran.
BillK
Solusi salah ketik dari @birgire bekerja seperti pesona, tetapi ini sepertinya alternatif yang layak. Saya akan pergi dengan yang lain untuk saat ini karena ada beberapa posting pertanyaan dengan artikel awal dan mengubah semua siput permalink mungkin memakan waktu. Saya suka kesederhanaan dari solusi ini. Terima kasih :-)
rpbtz
1
karena Anda suka, tambahkan beberapa kode yang harus mengubah semua siput jika diinginkan / dibutuhkan. :-)
majick
6

EDIT

Saya sedikit memperbaiki kode. Semua blok kode diperbarui sesuai. Hanya catatan sebelum melompat ke pembaruan di JAWABAN ASLI , saya telah menyiapkan kode untuk bekerja dengan yang berikut ini

  • Jenis pos khusus -> release

  • Taksonomi khusus -> game

Pastikan untuk mengatur ini sesuai dengan kebutuhan Anda

JAWABAN ASLI

Selain jawaban lain dan kesalahan ketik yang ditunjukkan oleh @Birgire, berikut adalah pendekatan lain.

Pertama, kita akan menetapkan judul sebagai bidang khusus tersembunyi, tetapi pertama-tama kita akan menghapus kata-kata seperti theyang ingin kita kecualikan. Sebelum kita melakukan itu, kita harus terlebih dahulu membuat fungsi pembantu untuk menghapus kata-kata yang dilarang dari nama istilah dan memposting judul

/**
 * 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;
}

Sekarang kita sudah membahasnya, mari kita lihat potongan kode untuk mengatur bidang khusus kita. Anda harus menghapus kode ini sepenuhnya begitu Anda telah memuat halaman apa saja sekali. Jika Anda memiliki situs besar dengan banyak posting, Anda dapat mengatur posts_per_pagesesuatu untuk 100dan menjalankan skrip beberapa kali sampai semua posting memiliki bidang khusus telah ditetapkan untuk semua posting

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

Sekarang setelah bidang khusus diatur ke semua posting dan kode di atas dihapus, kami perlu memastikan bahwa kami mengatur bidang khusus ini untuk semua posting baru atau setiap kali kami memperbarui judul posting. Untuk ini kita akan menggunakan transition_post_statuspengait. Kode berikut dapat masuk ke plugin ( yang saya sarankan ) atau difunctions.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 );

MENGUMPULKAN POS ANDA

Anda dapat menjalankan kueri Anda seperti biasa tanpa filter khusus. Anda dapat meminta dan mengurutkan posting Anda sebagai berikut

$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 );
Pieter Goosen
sumber
Saya suka pendekatan ini (mungkin cukup untuk menghapus kata yang dilarang dari awal judul)
birgire
@ Borgire Saya hanya pergi dengan ini karena pengetahuan SQL saya miskin seperti mouse gereja, hahahaha. Terima kasih atas kesalahan ketiknya
Pieter Goosen
1
Tikus jenaka bisa jauh lebih gesit daripada gajah SQL hardcoded ;-)
birgire
0

jawaban birgire berfungsi baik saat memesan hanya dengan bidang ini. Saya membuat beberapa modifikasi agar berfungsi ketika memesan dengan berbagai bidang (Saya tidak yakin itu berfungsi dengan benar ketika pemesanan judul adalah yang utama):

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 );
Yedidel Elhayany
sumber