Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

I am working on a site that is using posts as a way to manage glossary articles like the example site 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 reasonably automatic.

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

Can anyone suggest a way to do this?

A plugin would be my first choice, but I'm open to other solutions.

share|improve this question
add comment

2 Answers

up vote 6 down vote accepted

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.

share|improve this answer
    
After I wrote my question, I considered creating categories for each letter and then building links to the specific category. Your explanation is very nicley written. –  Nohl Feb 9 '12 at 17:30
    
If my solution worked for you would you accept it? –  helgatheviking Feb 9 '12 at 21:57
    
Helgatheviking, thanks for your input. I have not had time to mark this as the best answer. I understand how important to step is when using the stackexchange system. I will mark it after I've read it with more care. Thanks for your patience –  Nohl Feb 10 '12 at 22:54
    
no problem. my accept rate is pretty low. if this doesn't answer your problem then don't mark it as the answer! –  helgatheviking Feb 10 '12 at 23:31
    
This clearly is one of the most thoughtful responses out there. I'm not sure if it's the answer, but I learned quite a bit skimming it. I'm going to give it credit as an answer. thanks. –  Nohl Feb 12 '12 at 18:54
show 3 more comments

@Helgatheviking, that was an awesome solution. Really helpful.

The part that did not quite work for me was the last one. Here's what i did:

<?php
//GLOSSARY WITH ACTIVE STATES
$glossary_terms = get_terms('glossary', 'orderby=name&order=ASC&hide_empty=0'); // get all terms in taxonomy
$glossary_current_terms = wp_get_post_terms( $post->ID, 'glossary', array('fields' => 'slugs') ); // get terms current post has
?>
<div class="paginationdiv">
    <ul class="pagination pagination-sm alpha-pagination">
    <?php 
        foreach( $glossary_terms as $glossary_term ) :
        $glossary_term_slug = $glossary_term->slug;
    ?>
        <li<?php if ( in_array($glossary_term_slug, $glossary_current_terms) ) { echo ' class="active"'; } ?>><?php echo '<a href="' . get_term_link( $glossary_term ) . '" title="' . sprintf(__('View all databases filed under %s', 'my_localization_domain'), $glossary_term->name) . '">' . $glossary_term->name . '</a>'; ?></li>
    <?php endforeach; ?>
    </ul>
</div>
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.