Group Posts by First Letter of Title

I am working on a website that is using posts as a way to manage glossary articles similar to the example website below. I would like to find a way to display articles alphabetically and grouped by a selected letter (such as the letter ‘F’ in the example). I would like the process to be automatic.

Example of desired output: http://www.retirementdictionary.com/glossary/f

Can anyone suggest a way to do this?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

once up a time i did a client project where i had to have archives by first letter. thinking back i’m wondering if shouldn’t have just created a hidden taxonomy and then saved the first letter as a term in that taxonomy.

anyway, here’s what i actually did:

/*
 * Function Create Array of Letters that have post titles (for archive)
 */

/* When the post is saved, saves our custom data */
function kia_save_first_letter( $post_id ) {
    // verify if this is an auto save routine. 
    // If it is our form has not been submitted, so we dont want to do anything
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) 
        return;

    //check location (only run for posts)
    $limitPostTypes = array('post');
    if (!in_array($_POST['post_type'], $limitPostTypes)) return;

    // Check permissions
    if ( !current_user_can( 'edit_post', $post_id ) )
        return;


    // OK, we're authenticated: we need to find and save the data
    $alphabet = get_option( 'kia_alphabet_archive' );

    $alphabet = is_array($alphabet) ? $alphabet : array($alphabet);

    // pop off first letter of post title
    $letter = substr($_POST['post_title'], 0, 1);

    // if it doesn't exist, add it to array
    if(!in_array($letter, $alphabet))
        $alphabet[] = $letter;
        sort($alphabet);

    $alphabet = is_array($alphabet) ? array_unique($alphabet) : array($alphabet);

    update_option( 'kia_alphabet_archive', $alphabet );
}
add_action( 'save_post', 'kia_save_first_letter' );

if you have posts already before adding this you’ll need to run the following once to grab the first letters for the existing posts:

//create array from existing posts
function kia_run_once(){
    $alphabet = array();
    $posts = get_posts(array(   
            'numberposts'     => -1
            ) );

    foreach($posts as $p) :  
        $alphabet[] = strtoupper(substr($p->post_title, 0, 1)); //first letter of post title, capitalized 
    endforeach;

    sort($alphabet);

    update_option( 'kia_alphabet_archive', array_unique($alphabet) );

}
add_action('init','kia_run_once');

now we need some stuff to decipher when were are on a custom archive page and what to do differently

/*
 * Custom Archives by KIA
 */

function archive_queryvars( $qvars ) {
    $qvars[] = 'showarchive';
    return $qvars;
}
add_filter('query_vars', 'archive_queryvars' );


function is_custom_archive() {
    global $wp_query;
    return isset( $wp_query->query_vars['showarchive'] );
}

function archive_search_where( $where ){
    global $wpdb;

    if( is_custom_archive() ) {
        $char = get_query_var('showarchive');
        if ( ! empty($char) ) {
            $where .= "AND {$wpdb->posts}.post_title LIKE '{$char}%'";
        }
    } 
  return $where;
}
add_filter('posts_where', 'archive_search_where' );

little helper function to make links

/* 
 * add archive query arg to link
 */
function get_custom_archive_link($char = '') {
    $params = array(
            'showarchive' => $char,
            );
    return add_query_arg( $params, home_url('/') );
}

now create our custom archive menu

$alphabet = get_option ('kia_alphabet_archive');

if(count($alphabet)>0){ ?>
    <div id="archive-menu" class="menu">
    <?php for ($i=65; $i < 91; $i++) : 
            $current = (chr($i) == get_query_var('showarchive')) ? "current-menu-item" : "menu-item";

            if (is_array($alphabet) && in_array(chr($i), $alphabet)){ ?>
                <li class="az-char <?php echo $current;?>">
                    <?php printf('<a href="%s" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener">%s</a>', get_custom_archive_link(chr($i)), chr($i) ) ?>
                </li>
            <?php } else { ?>
                <li class="az-char <?php echo $current;?>">
                    <?php echo chr($i); ?>
                </li>
            <?php } ?>  

            <?php endfor; ?>
    </div>
<?php }

clicking on the links should now bring you to a page that only shows posts that state with that letter.


looking back i think the taxonomy idea would be a lot less code and have cleaner rewrite support built-in from the beginning (ie… no query vars, even though those could be re-written… i don’t know how). the taxonomy approach would also add tax data to the DB, whereas this only adds the one option. so trade-off?

*EDIT**

ok, i took a stab at the taxonomy route and it is slightly more elegant like i expected

first register the taxonomy. i have this only running on posts, but you could easily mod it to suite whatever post type you’d like.

// Add new taxonomy, NOT hierarchical (like tags)
function kia_create_glossary_taxonomy(){
    if(!taxonomy_exists('glossary')){
        register_taxonomy('glossary',array('post'),array(
        'show_ui' => false
      ));
     }
}
add_action('init','kia_create_glossary_taxonomy');

similar save function to store our tax data when a post is saved

/* When the post is saved, saves our custom data */
function kia_save_first_letter( $post_id ) {
    // verify if this is an auto save routine. 
    // If it is our form has not been submitted, so we dont want to do anything
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) 
        return;

    //check location (only run for posts)
    $limitPostTypes = array('post');
    if (!in_array($_POST['post_type'], $limitPostTypes)) return;

    // Check permissions
    if ( !current_user_can( 'edit_post', $post_id ) )
        return;


    // OK, we're authenticated: we need to find and save the data
    $taxonomy = 'glossary';

    //set term as first letter of post title, lower case
    wp_set_post_terms( $post_id, strtolower(substr($_POST['post_title'], 0, 1)), $taxonomy );

    //delete the transient that is storing the alphabet letters
    delete_transient( 'kia_archive_alphabet');
}
add_action( 'save_post', 'kia_save_first_letter' );

again need to run something once to grab old posts, remove when done

//create array from existing posts
function kia_run_once(){
    $taxonomy = 'glossary';

    $alphabet = array();
    $posts = get_posts(array('numberposts' => -1) );

    foreach($posts as $p) :  
        //set term as first letter of post title, lower case
        wp_set_post_terms( $p->ID, strtolower(substr($p->post_title, 0, 1)), $taxonomy );
    endforeach;     
}
add_action('init','kia_run_once');

add the menu this was the only part that wasn’t totally elegant, as it wasn’t straightforward to test whether a term had posts w/o the extra foreach loop. but i have mitigated that by storing it in a transient that only resets on post save.

$taxonomy = 'glossary';  

// save the terms that have posts in an array as a transient
if ( false === ( $alphabet = get_transient( 'kia_archive_alphabet' ) ) ) {
    // It wasn't there, so regenerate the data and save the transient
    $terms = get_terms($taxonomy);

    $alphabet = array();
    if($terms){
        foreach ($terms as $term){
            $alphabet[] = $term->slug;
        }
    }
     set_transient( 'kia_archive_alphabet', $alphabet );
}

?>

<div id="archive-menu" class="menu">
    <?php foreach(range('a', 'z') as $i) : 
            $current = ($i == get_query_var($taxonomy)) ? "current-menu-item" : "menu-item";

            if (in_array( $i, $alphabet )){ ?>
                <li class="az-char <?php echo $current;?>">
                    <?php printf('<a href="%s" rel="nofollow noreferrer noopener" rel="nofollow noreferrer noopener">%s</a>', get_term_link( $i, $taxonomy ), strtoupper($i) ) ?>
                </li>
            <?php } else { ?>
                <li class="az-char <?php echo $current;?>">
                    <?php echo strtoupper($i); ?>
                </li>
            <?php } ?>  

        <?php endforeach; ?>
</div>

so there are 2 solutions. the latter will get your URLs to say site.com/glossary/l without any htaccess rules to re-write query vars on your part. hope that helps.

Method 2

I would suggest you to use WP_Query for this purpose. An example of this query is here in this plugin which fetches only relevant posts instead of using further PHP functions.

You can choose a faster way to go on, Alphabetic Pagination Plugin


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x