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

13

J'essaie actuellement de produire une liste de titres de musique et j'aimerais que le tri ignore (mais affiche toujours) l'article initial du titre.

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

  • Sabbat noir
  • Led Zeppelin
  • Pink Floyd
  • Les Beatles
  • The Kinks
  • Les pierres qui roulent
  • Thin Lizzy

Au lieu de cela, je voudrais l'afficher par ordre alphabétique tout en ignorant l'article initial «The», comme ceci:

  • Les Beatles
  • Sabbat noir
  • The Kinks
  • Led Zeppelin
  • Pink Floyd
  • Les pierres qui roulent
  • Thin Lizzy

Je suis tombé sur une solution dans un article 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;
}

puis encapsuler la requête avec add_filteravant et remove_filteraprès.

J'ai essayé cela, mais je reçois toujours l'erreur suivante sur mon site:

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

SELECT wp_posts. * FROM wp_posts WHERE 1 = 1 AND wp_posts.post_type = 'release' AND (wp_posts.post_status = 'publish' 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, donc 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 que la première fonction devrait s'en occuper. De plus, s'il existe un moyen plus intelligent de le faire, je suis tout à fait à l'écoute. J'ai fait des recherches sur ce site et fait des recherches sur ce site, mais je n'ai pas vraiment trouvé beaucoup de solutions.

Mon code utilisant les filtres ressemble à ceci si cela peut aider:

<?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
la source
1
une autre solution pourrait être de stocker le titre que vous souhaitez trier en tant que métadonnées de publication et de les commander sur ce champ au lieu du titre.
Milo
Je ne sais pas trop comment procéder. Est-ce que le stockage de cela dans une nouvelle colonne n'entraînerait pas une erreur similaire à celle que j'obtiens actuellement?
rpbtz
1
vous n'utiliseriez aucun de ce code, vous pouvez interroger et trier sur la méta-publication avec des paramètres de méta-requête .
Milo

Réponses:

8

Le problème

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

Le nom du filtre est posts_fields pas post_fields.

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

Alternative - Filtre simple

On peut 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 _customparamètre 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 de Pascal Birchler , commentée 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ù l'on peut 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) " );

générera:

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 . Ensuite, c'est 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 );
Birgire
la source
Je pense que cela devrait résoudre le problème mieux que ma solution
Pieter Goosen
Vous aviez tout à fait raison - changer post_fields en posts_fields a résolu le problème et le tri est maintenant exactement comme je le veux. Je vous remercie! Je me sens un peu stupide maintenant vu que c'était le problème. C'est ce que j'obtiens pour le codage à 4 heures du matin, je suppose. J'examinerai également la solution à filtre unique. Semble être une très bonne idée. Merci encore.
rpbtz
Je vais marquer cela comme la bonne réponse car c'est celle qui est la plus étroitement liée à mes questions initiales, bien que pour autant que je sache, les autres réponses sont également des solutions valables.
rpbtz
L'alternative à filtre unique a également fonctionné comme un charme. Je peux maintenant conserver le code du filtre functions.phpet l'appeler via orderbyquand j'en ai besoin. Excellente solution - merci :-)
rpbtz
1
Heureux d'entendre que cela a fonctionné pour vous - j'ai ajouté la méthode récursive. @rpbtz
birgire
12

Un moyen plus simple peut être de parcourir et de modifier le slug de permalien sur les publications qui en ont besoin (sous le titre sur l'écran de rédaction de la publication), puis de simplement l'utiliser pour commander au lieu du titre.

c'est à dire. post_namene pas utiliserpost_title pour le tri ...

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

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

EDIT : code pour mettre à jour les slugs existants, en supprimant les préfixes indésirables de la post_namecolonne ...

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
la source
Excellente solution - très simple et efficace pour le tri.
BillK
La solution de faute de frappe de @birgire a fonctionné comme un charme, mais cela semble être une alternative décente. Je vais aller avec l'autre pour l'instant car il y a pas mal de messages interrogés avec un article initial et changer tous les slugs de permalien peut prendre un certain temps. J'aime la simplicité de cette solution. Merci :-)
rpbtz
1
depuis que vous avez aimé, ajouté du code qui devrait changer tous les slugs si voulu / nécessaire. :-)
majick
6

ÉDITER

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 dans la RÉPONSE ORIGINALE , j'ai configuré le code pour fonctionner avec les éléments suivants

  • Type de publication personnalisé -> release

  • Taxonomie personnalisée -> game

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

RÉPONSE ORIGINALE

En plus des autres réponses et de la faute de frappe signalées par @birgire, voici une autre approche.

Tout d'abord, nous définirons le titre comme un champ personnalisé masqué, mais nous supprimerons d'abord les mots comme thecelui que nous souhaiterions exclure. Avant de faire cela, nous devons d'abord créer une fonction d'aide afin de supprimer les mots interdits des noms de termes et des 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 complètement ce code 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_pagequelque chose pour 100et 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 tous les articles et que le code ci-dessus est supprimé, nous devons nous assurer que nous définissons ce champ personnalisé sur tous les nouveaux articles ou chaque fois que nous mettons à jour le titre de l'article. Pour cela, nous utiliserons le transition_post_statuscrochet. Le code suivant peut aller dans un plugin ( que je recommande ) ou dans votrefunctions.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 );

DEMANDER VOS ARTICLES

Vous pouvez exécuter vos requêtes normalement sans aucun filtre personnalisé. 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 );
Pieter Goosen
la source
J'aime cette approche (peut-être qu'il suffit de supprimer le mot interdit du début du titre)
birgire
@birgire Je ne suis allé avec cela que parce que mes connaissances SQL sont aussi médiocres qu'une souris d'église, hahahaha. Merci pour la faute de frappe
Pieter Goosen
1
La souris pleine d'esprit peut être beaucoup plus agile que l'éléphant SQL codé en dur ;-)
birgire
0

Les réponses de birgire fonctionnent bien lors de la commande uniquement par ce champ. J'ai apporté quelques modifications pour le faire fonctionner lors de la commande par plusieurs champs (je ne suis pas sûr que cela fonctionne correctement lorsque la commande du titre est la principale):

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
la source