3

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?

2 Answers 2

11

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.

9
  • 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, 2012 at 17:30
  • If my solution worked for you would you accept it? Feb 9, 2012 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, 2012 at 22:54
  • 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, 2012 at 18:54
  • hope it helps you. it was an interesting question for me and i learned something cool... so bonus! Feb 12, 2012 at 19:21
0

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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