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
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("https://wordpress.stackexchange.com/") );
}
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="https://wordpress.stackexchange.com/questions/41660/%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="https://wordpress.stackexchange.com/questions/41660/%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.