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

12

J'essaie actuellement de générer une liste de titres musicaux et j'aimerais que le tri ignore le premier article du titre (mais l'affiche toujours).

Par exemple, si j'ai une liste de groupes, elle sera affichée par ordre alphabétique dans WordPress comme suit:

  • Sabbat noir
  • Led Zeppelin
  • Pink Floyd
  • Les Beatles
  • Les Kinks
  • Les Rolling Stones
  • Thin Lizzy

J'aimerais plutôt qu'il soit affiché par ordre alphabétique tout en ignorant l'article initial "Le", comme ceci:

  • Les Beatles
  • Sabbat noir
  • Les Kinks
  • Led Zeppelin
  • Pink Floyd
  • Les Rolling Stones
  • Thin Lizzy

J'ai trouvé une solution dans une entrée de blog de l'année dernière , qui suggère la 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;
}

puis encapsulez la requête avec add_filter before et remove_filter after.

J'ai essayé cela, mais l'erreur continue à apparaître 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' OU   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. De plus, s'il y a une façon plus intelligente de faire cela, je suis tout ouïe. J'ai consulté Google et cherché sur ce site, mais je n'ai pas vraiment trouvé beaucoup de solutions.

Mon code utilisant les filtres ressemble à ceci s'il s'agit d'une aide quelconque:

<?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();
?>
    
posée rpbtz 07.02.2016 - 02:12

4 réponses

8

Le problème

Je pense qu'il y a une faute de frappe ici:

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 unique

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

Mettons en œuvre l'idée récursive de Pascal Birchler , a 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 utiliser MariaDB au lieu de MySQL . C’est alors beaucoup plus facile car MariaDB 10.0.5 prend en charge 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 );
    
réponse donnée birgire 07.02.2016 - 09:34
12

Un moyen plus simple consiste peut-être à modifier le slogan du lien permanent sur les publications qui en ont besoin (sous le titre sur l’écran de rédaction) et à ne l'utiliser que pour commander à la place du titre.

ie. utilisez post_name et non 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 permettant de mettre à jour les slugs existants, en supprimant les préfixes indésirables de post_name column ...

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);
            }
        }
    }
}
    
réponse donnée majick 07.02.2016 - 08:30
6

EDIT

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 passer aux mises à jour de la RÉPONSE 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 définir ceci en fonction de vos besoins

RÉPONSE ORIGINALE

Outre les autres réponses et la faute de frappe 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’aide afin de supprimer les mots interdits des noms de terme et de publier des titres

/**
 * 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 énorme avec une tonne de publications, vous pouvez définir posts_per_page sur quelque chose comme 100 et exécuter les scripts plusieurs fois jusqu'à ce que toutes les publications aient le champ personnalisé défini 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 ce champ personnalisé sur toutes les nouvelles publications ou chaque fois que nous mettons à jour le titre de la publication. Pour cela, nous allons utiliser le crochet 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 );

RECHERCHE DE 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 );
    
réponse donnée Pieter Goosen 07.02.2016 - 10:01
0
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 );
    
réponse donnée Yedidel Elhayany 28.05.2017 - 18:17

Lire d'autres questions sur les étiquettes