Anfangsartikel (wie 'a', 'an' oder 'the') beim Sortieren von Abfragen ignorieren?

13

Ich versuche gerade, eine Liste von Musiktiteln auszugeben und möchte, dass die Sortierung den ersten Artikel des Titels ignoriert (aber immer noch anzeigt).

Wenn ich zum Beispiel eine Liste von Bands hatte, wird diese in WordPress alphabetisch wie folgt angezeigt:

  • Black Sabbath
  • Led Zeppelin
  • Pink Floyd
  • Die Beatles
  • Die Knicke
  • Die Rolling Stones
  • Dünne Lizzy

Stattdessen möchte ich, dass es alphabetisch angezeigt wird, während der ursprüngliche Artikel 'The' ignoriert wird:

  • Die Beatles
  • Black Sabbath
  • Die Knicke
  • Led Zeppelin
  • Pink Floyd
  • Die Rolling Stones
  • Dünne Lizzy

Ich bin in einem Blogeintrag aus dem letzten Jahr auf eine Lösung gestoßen , die folgenden Code vorschlägt 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;
}

und dann die Abfrage mit add_filtervorher und remove_filternachher umschließen .

Ich habe es versucht, aber auf meiner Website wird immer wieder der folgende Fehler angezeigt:

WordPress-Datenbankfehler: [Unbekannte Spalte '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

Ich werde nicht lügen, ich bin ziemlich neu im PHP-Teil von WordPress, also bin ich mir nicht sicher, warum ich diesen Fehler bekomme. Ich kann sehen, dass es etwas mit der 'title2'-Spalte zu tun hat, aber nach meinem Verständnis sollte sich die erste Funktion darum kümmern. Auch wenn es einen intelligenteren Weg gibt, bin ich ganz Ohr. Ich habe auf dieser Seite herumgegoogelt und gesucht, aber ich habe nicht wirklich viele Lösungen gefunden.

Mein Code, der die Filter verwendet, sieht wie folgt aus:

<?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
quelle
1
Eine alternative Lösung könnte darin bestehen, den Titel, nach dem Sie sortieren möchten, als Post-Metadaten zu speichern und in diesem Feld anstelle des Titels zu sortieren.
Milo
Ich bin ein wenig unsicher, wie ich damit weitermachen soll. Würde das Speichern in einer neuen Spalte nicht zu einem ähnlichen Fehler führen wie dem, den ich gerade erhalte?
RPBTZ
1
Wenn Sie keinen dieser Codes verwenden, können Sie Post-Metas mit Meta-Abfrageparametern abfragen und sortieren .
Milo

Antworten:

8

Das Problem

Ich denke da ist ein Tippfehler drin:

Der Name des Filters ist posts_fieldsnicht post_fields.

Dies könnte erklären, warum das title2Feld unbekannt ist, da seine Definition nicht zur generierten SQL-Zeichenfolge hinzugefügt wird.

Alternative - Einzelfilter

Wir können es umschreiben, um nur einen einzigen Filter zu verwenden:

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

Hier können Sie nun die benutzerdefinierte Bestellung mit dem _customorderby-Parameter aktivieren :

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

Alternative - Rekursiv TRIM()

Lassen Sie uns die rekursive Idee umzusetzen Pascal Birchler , kommentiert hier :

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

wo wir zum Beispiel die rekursive Funktion konstruieren können als:

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

Das bedeutet, dass

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

erzeugt:

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

Alternative - MariaDB

Im Allgemeinen verwende ich gerne MariaDB anstelle von MySQL . Dann ist es viel einfacher, weil MariaDB 10.0.5 unterstützt 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
quelle
Ich denke, dies sollte das Problem besser lösen als meine Lösung
Pieter Goosen
Sie waren absolut korrekt - das Ändern von post_fields in posts_fields hat das Problem behoben und sortiert nun genau so, wie ich es möchte. Vielen Dank! Ich fühle mich jetzt ein wenig dumm, da das das Problem war. Das ist es, was ich für das Codieren um 4 Uhr morgens bekomme. Ich werde mich auch mit der Einzelfilterlösung befassen. Scheint eine wirklich gute Idee zu sein. Danke noch einmal.
RPBTZ
Ich werde dies als die richtige Antwort markieren, da dies die Antwort ist, die am engsten mit meinen ersten Fragen zusammenhängt, obwohl die anderen Antworten, soweit ich das beurteilen kann, auch gültige Lösungen sind.
RPBTZ
Die Einzelfilteralternative funktionierte ebenfalls wie ein Zauber. Ich kann jetzt den Filtercode behalten functions.phpund ihn über aufrufen, orderbywenn ich ihn brauche. Tolle Lösung - danke :-)
rpbtz
1
Ich bin froh zu hören, dass es bei Ihnen funktioniert hat - ich habe die rekursive Methode hinzugefügt. @rpbtz
birgire
12

Eine einfachere Möglichkeit besteht darin, den Permalink-Slug für die Posts zu ändern, die diesen benötigen (unter dem Titel auf dem Post-Schreibbildschirm), und diesen dann nur für die Bestellung anstelle des Titels zu verwenden.

dh verwenden post_namenicht post_titlefür die Sortierung ...

Dies würde auch bedeuten, dass Ihr Permalink möglicherweise anders ist, wenn Sie% postname% in Ihrer Permalink-Struktur verwenden, was ein zusätzlicher Bonus sein könnte.

z.B. gibt es http://example.com/rolling-stones/ nichthttp://example.com/the-rolling-stones/

EDIT : Code zum Aktualisieren der vorhandenen Slugs, Entfernen der unerwünschten Präfixe aus der post_nameSpalte ...

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);
            }
        }
    }
}
Majick
quelle
Tolle Lösung - sehr einfach und effizient zu sortieren.
BillK
Die Tippfehlerlösung von @birgire funktionierte wie ein Zauber, aber dies scheint eine anständige Alternative zu sein. Ich werde fürs Erste mit dem anderen fortfahren, da es einige abgefragte Posts mit einem ersten Artikel gibt und das Ändern aller Permalink-Slugs einige Zeit in Anspruch nehmen kann. Mir gefällt jedoch die Einfachheit dieser Lösung. Danke :-)
rpbtz
1
da Sie mochten, fügten Sie etwas Code hinzu, der alle Schnecken ändern sollte, wenn gewünscht / benötigt. :-)
Majick
6

BEARBEITEN

Ich habe den Code ein wenig verbessert. Alle Codeblöcke werden entsprechend aktualisiert. Nur ein Hinweis, bevor ich zu den Updates in der ORIGINAL-ANTWORT übergehe , habe ich den Code so eingerichtet, dass er mit den folgenden Elementen funktioniert

  • Benutzerdefinierter Beitragstyp -> release

  • Benutzerdefinierte Taxonomie -> game

Stellen Sie sicher, dass dies Ihren Bedürfnissen entspricht

URSPRÜNGLICHE ANTWORT

Zusätzlich zu den anderen Antworten und dem Tippfehler, auf den @birgire hingewiesen hat, gibt es hier einen anderen Ansatz.

Zuerst legen wir den Titel als verborgenes benutzerdefiniertes Feld fest, entfernen jedoch zuerst die Wörter the, die wir ausschließen möchten. Bevor wir das tun, müssen wir zuerst eine Hilfsfunktion erstellen, um die verbotenen Wörter aus den Termnamen und Post-Titeln zu entfernen

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

Nachdem wir das behandelt haben, schauen wir uns den Code an, um unser benutzerdefiniertes Feld festzulegen. Sie müssen diesen Code vollständig entfernen, sobald Sie eine Seite einmal geladen haben. Wenn Sie eine große Website mit einer Tonne Beiträge haben, können Sie festlegen , posts_per_pageum etwas zu 100und die Skripte ein paar Mal laufen , bis alle Beiträge das benutzerdefinierte Feld haben , um alle Beiträge eingestellt worden

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

Nachdem die benutzerdefinierten Felder für alle Beiträge festgelegt wurden und der obige Code entfernt wurde, müssen wir sicherstellen, dass für dieses benutzerdefinierte Feld alle neuen Beiträge festgelegt werden oder wann immer wir den Beitragstitel aktualisieren. Dafür verwenden wir den transition_post_statusHaken. Der folgende Code kann in ein (von mir empfohlenes ) Plugin oder in Ihr Plugin eingefügt werdenfunctions.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 );

ABFRAGEN IHRER POSTS

Sie können Ihre Abfragen wie gewohnt ohne benutzerdefinierte Filter ausführen. Sie können Ihre Beiträge wie folgt abfragen und sortieren

$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
quelle
Ich mag diesen Ansatz (vielleicht ist es genug, um das verbotene Wort vom Anfang des Titels zu entfernen)
Birgire
@birgire Ich bin nur mitgegangen, weil meine SQL-Kenntnisse so schlecht sind wie bei einer Kirchenmaus, hahahaha. Vielen Dank für den Tippfehler
Pieter Goosen
1
Die witzige Maus kann viel agiler sein als der fest codierte SQL-Elefant ;-)
Birgire
0

Die Antworten von Birgire funktionieren gut, wenn Sie nur nach diesem Feld bestellen. Ich habe einige Änderungen vorgenommen, damit es bei der Bestellung nach mehreren Feldern funktioniert (ich bin nicht sicher, ob es richtig funktioniert, wenn die Titelbestellung die primäre ist):

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
quelle