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

Read More

Can anyone suggest a way to do this?

Related posts

Leave a Reply

2 comments

  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">%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">%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.