Ignorando artigos iniciais (como 'a', 'an' ou 'the') ao classificar consultas?

13

Atualmente, estou tentando produzir uma lista de títulos de músicas e gostaria que a classificação ignorasse (mas ainda seja exibida) o artigo inicial do título.

Por exemplo, se eu tivesse uma lista de bandas, ela será exibida em ordem alfabética no WordPress assim:

  • Sábado Negro
  • LED Zeppelin
  • Pink Floyd
  • Os Beatles
  • The Kinks
  • As pedras rolantes
  • Thin Lizzy

Em vez disso, gostaria de exibi-lo em ordem alfabética, ignorando o artigo inicial 'The', assim:

  • Os Beatles
  • Sábado Negro
  • The Kinks
  • LED Zeppelin
  • Pink Floyd
  • As pedras rolantes
  • Thin Lizzy

Me deparei com uma solução em uma entrada de blog do ano passado , que sugere o seguinte código em 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;
}

e, em seguida, agrupando a consulta com add_filterantes e remove_filterdepois.

Eu tentei isso, mas continuo recebendo o seguinte erro no meu site:

Erro no banco de dados do WordPress: [coluna desconhecida 'title2' na 'cláusula de ordem']

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

Não vou mentir, sou muito novo na parte php do WordPress, por isso não sei por que estou recebendo esse erro. Percebo que tem algo a ver com a coluna 'title2', mas entendi que a primeira função deveria cuidar disso. Além disso, se houver uma maneira mais inteligente de fazer isso, sou todo ouvidos. Tenho pesquisado e pesquisado neste site, mas ainda não encontrei muitas soluções.

Meu código usando os filtros fica assim se houver alguma ajuda:

<?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
fonte
1
uma solução alternativa pode ser armazenar o título que você deseja classificar como meta dados e ordem nesse campo em vez de título.
Milo
Estou um pouco inseguro sobre como continuar com isso. Armazenar isso em uma nova coluna resultaria em um erro semelhante ao que estou recebendo atualmente?
Rpbtz 07/02
1
como você não usaria esse código, é possível consultar e classificar na pós-meta com parâmetros de meta-consulta .
Milo

Respostas:

8

O problema

Eu acho que há um erro de digitação lá:

O nome do filtro posts_fieldsnão é post_fields.

Isso poderia explicar por que o title2campo é desconhecido, porque sua definição não é adicionada à sequência SQL gerada.

Alternativa - filtro único

Podemos reescrevê-lo para usar apenas um único filtro:

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

onde agora você pode ativar a ordem personalizada com o _customparâmetro 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();

Alternativa - Recursiva TRIM()

Vamos implementar a idéia recursiva de Pascal Birchler , comentada aqui :

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

onde podemos, por exemplo, construir a função recursiva como:

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

Isso significa que

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

irá gerar:

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

Alternativa - MariaDB

Em geral, eu gosto de usar o MariaDB em vez do MySQL . Então é muito mais fácil, porque o MariaDB 10.0.5 suporta 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
fonte
Eu acho que isso deve resolver o problema melhor do que a minha solução
Pieter Goosen
Você estava absolutamente correto - alterar post_fields para posts_fields corrigiu o problema e agora está ordenando exatamente do jeito que eu quero. Obrigado! Eu me sinto um pouco estúpido agora, visto que esse era o problema. É o que eu ganho por codificar às 4 da manhã, acho. Também analisarei a solução de filtro único. Parece uma boa ideia. Obrigado novamente.
Rpbtz
Marcarei isso como a resposta correta, pois é a que está mais intimamente relacionada às minhas perguntas iniciais, embora, até onde eu saiba, as outras respostas também sejam soluções válidas.
rpbtz
A alternativa de filtro único também funcionou como um encanto. Agora posso manter o código do filtro functions.phpe chamá-lo orderbyquando precisar. Grande solução - obrigado :-)
rpbtz
1
Fico feliz em ouvir que funcionou para você - eu adicionei o método recursivo. @rpbtz
birgire
12

Uma maneira mais fácil pode ser percorrer e alterar a lesma do permalink nas postagens que precisam dela (abaixo do título na tela de redação da postagem) e depois usá-la para fazer pedidos em vez do título.

ie use post_namenão post_titlepara classificar ...

Isso também significa que seu link permanente pode ser diferente se você usar% postname% em sua estrutura de link permanente, o que pode ser um bônus adicional.

por exemplo. http://example.com/rolling-stones/ não dáhttp://example.com/the-rolling-stones/

EDIT : código para atualizar as lesmas existentes, removendo os prefixos indesejados da post_namecoluna ...

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
fonte
Ótima solução - muito simples e eficiente para classificação.
BillK
A solução de erro de digitação da @birgire funcionou como um encanto, mas parece uma alternativa decente. Por enquanto, irei com a outra, já que há algumas postagens consultadas com um artigo inicial e a alteração de todas as lesmas permalink pode levar algum tempo. Eu gosto da simplicidade desta solução, no entanto. Obrigado :-)
rpbtz
1
desde que você gostou, adicionou algum código que deve alterar todas as lesmas, se desejado / necessário. :-)
majick 07/02
6

EDITAR

Eu melhorei um pouco o código. Todo o bloco de código é atualizado de acordo. Apenas uma observação: antes de pular para as atualizações da RESPOSTA ORIGINAL , configurei o código para trabalhar com os seguintes

  • Tipo de postagem personalizada -> release

  • Taxonomia personalizada -> game

Certifique-se de definir isso de acordo com suas necessidades

RESPOSTA ORIGINAL

Além das outras respostas e do erro de digitação apontado por @birgire, aqui está outra abordagem.

Primeiro, definiremos o título como um campo personalizado oculto, mas primeiro removeremos as palavras theque gostaríamos de excluir. Antes de fazer isso, precisamos primeiro criar uma função auxiliar para remover as palavras proibidas dos nomes de termo e postar títulos

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

Agora que abordamos isso, vamos analisar o trecho de código para definir nosso campo personalizado. Você deve remover esse código completamente assim que carregar qualquer página uma vez. Se você tiver um site enorme com várias postagens, poderá definir posts_per_pagealgo para 100executar os scripts algumas vezes até que todas as postagens tenham o campo personalizado definido para todas as postagens

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

Agora que os campos personalizados estão definidos para todas as postagens e o código acima foi removido, precisamos garantir que definimos esse campo personalizado para todas as novas postagens ou sempre que atualizarmos o título da postagem. Para isso, usaremos o transition_post_statusgancho. O código a seguir pode entrar em um plug-in ( que eu recomendo ) ou no seufunctions.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 );

Consultando seus posts

Você pode executar suas consultas normalmente sem nenhum filtro personalizado. Você pode consultar e classificar suas postagens da seguinte maneira

$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
fonte
Eu gosto desta abordagem (talvez seja o suficiente para remover a palavra proibida a partir do início do título)
birgire
@ Birgire Eu só fui com isso porque meu conhecimento de SQL é ruim como um rato de igreja, hahahaha. Obrigado pelo erro de digitação
Pieter Goosen
1
O mouse espirituoso pode ser muito mais ágil do que o elefante SQL codificado ;-)
birgire
0

As respostas da birgire funcionam bem ao fazer pedidos somente por esse campo. Fiz algumas modificações para fazê-lo funcionar ao fazer o pedido por vários campos (não tenho certeza de que funcione corretamente quando o pedido do título for o principal):

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
fonte