it-swarm.com.de

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

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 auf eine Lösung in einem Blogeintrag aus dem letzten Jahr gestoßen, die den folgenden Code in functions.php vorschlägt:

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_filter vor und remove_filter nach 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 eine intelligentere Möglichkeit 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();
?>
13
rpbtz

Das Problem

Ich denke da ist ein Tippfehler drin:

Der Name des Filters lautet posts_fields und nicht post_fields.

Dies könnte erklären, warum das Feld title2 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 Parameter _custom orderby 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 - rekursive TRIM()

Implementieren wir die rekursive Idee mit Pascal Birchler , hier kommentiert :

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.5REGEXP_REPLACE unterstützt:

/**
 * 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 );
8
birgire

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 benutze post_name nicht post_title zum sortieren ...

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 http://example.com/rolling-stones/ nicht http://example.com/the-rolling-stones/

EDIT: Code zum Aktualisieren der vorhandenen Slugs, Entfernen der unerwünschten Präfixe aus der Spalte 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);
            }
        }
    }
}
12
majick

BEARBEITEN

Ich habe den Code ein bisschen verbessert. Alle Codeblöcke werden entsprechend aktualisiert. Nur eine Anmerkung, bevor ich in die Aktualisierungen in ORIGINAL ANSWER springe, habe ich den Code so eingerichtet, dass er mit den folgenden arbeitet

  • 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 wie 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 riesige Site mit einer Menge Posts haben, können Sie posts_per_page auf 100 setzen und die Skripte ein paarmal ausführen, bis für alle Posts das benutzerdefinierte Feld für alle Posts festgelegt wurde

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 Hook transition_post_status. Der folgende Code kann in ein Plugin ( was ich empfehle ) oder in Ihren functions.php eingefügt werden

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 );
6
Pieter Goosen

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