web-dev-qa-db-fra.com

Ignorer les articles initiaux (comme 'a', 'an' ou 'the') lors du tri des requêtes?

J'essaie actuellement de sortir une liste de titres musicaux et j'aimerais que le tri ignore (mais continue d'afficher) l'article initial du titre.

Par exemple, si j'avais une liste de groupes, elle serait affichée alphabétiquement dans WordPress comme ceci:

  • Black Sabbath
  • Led Zeppelin
  • Pink Floyd
  • Les Beatles
  • Les kinks
  • Les pierres qui roulent
  • Thin Lizzy

Au lieu de cela, j'aimerais qu'il soit affiché par ordre alphabétique tout en ignorant l'article initial 'The', comme ceci:

  • Les Beatles
  • Black Sabbath
  • Les kinks
  • Led Zeppelin
  • Pink Floyd
  • Les pierres qui roulent
  • Thin Lizzy

Je suis tombé sur une solution dans une entrée de blog de l'année dernière , qui suggère le code suivant dans 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;
}

et ensuite encapsuler la requête avec add_filter avant et remove_filter après.

J'ai essayé cela, mais je continue à avoir l'erreur suivante sur mon site:

Erreur de base de données WordPress: [Colonne inconnue 'title2' dans 'clause de commande']

SELECT wp_posts. * FROM wp_posts WHERE 1 = 1 AND wp_posts.post_type = 'release' AND (wp_posts.post_status = 'publier' OR wp_posts.post_status = 'private') ORDER BY UPPER (title2) ASC

Je ne vais pas mentir, je suis assez nouveau dans la partie php de WordPress, alors je ne sais pas pourquoi je reçois cette erreur. Je peux voir que cela a quelque chose à voir avec la colonne 'title2', mais je croyais comprendre que la première fonction devrait en tenir compte. Aussi, s'il y a une façon plus intelligente de faire cela, je suis tout ouïe. J'ai cherché sur ce site sur Google, mais je n'ai pas vraiment trouvé beaucoup de solutions.

Mon code utilisant les filtres ressemble à ceci si c'est une aide:

<?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

Le problème

Je pense qu'il y a une faute de frappe là-dedans:

Le nom du filtre est posts_fields et non post_fields.

Cela pourrait expliquer pourquoi le champ title2 est inconnu, car sa définition n'est pas ajoutée à la chaîne SQL générée.

Alternative - Filtre simple

Nous pouvons le réécrire pour n’utiliser qu’un seul filtre:

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

où vous pouvez maintenant activer la commande personnalisée avec le paramètre _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();

Alternative - récursive TRIM()

Implémentons l'idée récursive par Pascal Birchler , commenté ici :

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

où nous pouvons par exemple construire la fonction récursive comme:

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

Cela signifie que

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

va générer:

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

Alternative - MariaDB

En général, j'aime bien utiliser MariaDB au lieu de MySQL. Alors c'est beaucoup plus facile car MariaDB 10.0.5supporteREGEXP_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 );
8
birgire

Un moyen plus simple consiste à modifier le slug de permalien des publications qui en ont besoin (sous le titre sur l'écran de rédaction de publication), puis à l'utiliser simplement pour passer commande à la place du titre.

c'est à dire. utilisez post_name pas post_title pour le tri ...

Cela signifierait également que votre lien permanent peut être différent si vous utilisez% postname% dans votre structure de lien permanent, ce qui pourrait constituer un bonus supplémentaire.

par exemple. donne http://example.com/rolling-stones/ pas http://example.com/the-rolling-stones/

EDIT: code pour mettre à jour les slugs existants, en supprimant les préfixes indésirables de la colonne 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

MODIFIER

J'ai un peu amélioré le code. Tous les blocs de code sont mis à jour en conséquence. Juste une note cependant avant de sauter dans les mises à jour de la REPONSE ORIGINALE , j'ai configuré le code pour qu'il fonctionne avec les éléments suivants

  • Type de message personnalisé ->release

  • Taxonomie personnalisée ->game

Assurez-vous de régler ceci en fonction de vos besoins

RÉPONSE ORIGINALE

Outre les autres réponses et la typo signalée par @birgire, voici une autre approche.

Tout d'abord, nous allons définir le titre en tant que champ personnalisé masqué, mais nous allons d'abord supprimer les mots tels que the que nous voudrions exclure. Avant de faire cela, nous devons d'abord créer une fonction d'assistance afin de supprimer les mots interdits des noms de terme et les titres de publication.

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

Maintenant que nous avons couvert cela, regardons le morceau de code pour définir notre champ personnalisé. Vous devez supprimer ce code complètement dès que vous avez chargé une page une fois. Si vous avez un site immense avec une tonne de publications, vous pouvez définir posts_per_page sur quelque chose à 100 et exécuter les scripts plusieurs fois jusqu'à ce que toutes les publications du champ personnalisé soient définies sur toutes les publications.

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

Maintenant que les champs personnalisés sont définis sur toutes les publications et que le code ci-dessus est supprimé, nous devons nous assurer de définir le champ personnalisé sur toutes les nouvelles publications ou chaque fois que nous mettons à jour le titre de la publication. Pour cela, nous utiliserons le hook transition_post_status. Le code suivant peut aller dans un plugin ( que je recommande ) ou dans votre 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 );

RECHERCHER VOS POSTES

Vous pouvez exécuter vos requêtes normalement, sans filtres personnalisés. Vous pouvez interroger et trier vos messages comme suit

$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

les réponses de birgire fonctionnent bien lorsque vous commandez uniquement par ce champ. J'ai apporté quelques modifications pour que cela fonctionne lors de la commande par plusieurs champs (je ne suis pas sûr que cela fonctionne correctement lorsque l'ordre des titres est le premier):

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
Yedidel Elhayany