The ENVS Site Documentation & Program Syndication Aside

We’re on our way to building an interesting knitting of sites for our Environmental Studies program. Imagine a tiered connection of syndication that moves from student portfolio sites at the base through courses in the middle and up to the program at the top. It’s a pyramid of aggregation where the metadata can be added at each level. I feel like I’ve sketched this out before but, if so, I can’t find it. It doesn’t hurt to do it again anyway.

So this is the basic idea. Students can build the kind of portfolios they want rather than highly constrained artifices that focus more on serving the program. Now the visual design and structure can be what the student wants. The only thing that needs to be consistent is the categories and tags used to indicate how that piece of content fits into the program/course. As long as a post is associated with an assignment1 or competency then it can be pulled elsewhere for consistent display, association with a particular course, etc.

At a basic level, this structure provides a pretty frictionless workflow to have assignments bubble up to the program-level that are great examples of the kind of work your students can/will do. This is PR for potential students. Used properly helps build consistency in expectations between faculty. It makes accreditation evidence very easy to gather and each level allows you to add/remove metadata to better serve the needs of that level but without sacrificing the student experience. This is a way to look into a program in a way that’s hard to achieve in any other way. If you find no work that you want to show potential students or use for accreditation then you also know some important things about your program.2

The Program Level Theme

So far, we’ve been focused on building out the program level theme for WordPress. It’s built on Understrap which we customize to do the various tricks needed. I’m not going to get into the visual stuff much but will focus on some things that seem to make sense for repeatable patterns in our work and maybe your work as well.

Example WordPress editor view for the custom post type publications. Indicates various specific entry fields that help keep authoring easy and structured.

Custom Post Types/Advanced Custom Fields

One thing I like more and more is the blending of custom post types and Advanced Custom Fields when you need consistent authoring from authors of various levels. If you want consistently built content, you need to provide solid structured authoring patterns. That’s what we’re doing above for creating publications.

You can see the WordPress editor view for the custom post type publications. It’s a piece of content with very specific pieces that they wanted to be able to create repeatedly. Making a custom post type is easy (if a bit long) and made easier because I created a Sublime snippet to speed it up.3

With ACF, I can set up all kinds of form entry fields (checkbox, select, text entry, images etc.) and even do some pretty decent layout structure for the editor in just a few seconds. I can also export that structure as code that goes into the functions.php file and now it can’t be messed up unintentionally by future admins of the site. ACF still needs to be activated but none of the structure you built is visible on a new site. It will still show on the site you used to build it (unless you trash it). This can be confusing as the functions.php takes precedent over the stuff in the WYSIWYG editor. That can mean you’ll make lots of changes there and not be able to figure out why nothing is changing.

Once you get all the data entry straight, you’re then adding it to the theme for each post type. For this theme that means adding single-publication.php at the root and then changing the content loop in that new page to point to the loop-template folder and a custom content-publication.php loop.4 For another example, the naming pattern for a post type named faculty would be single-faculty.php and content loop content-faculty.php. That pattern is pretty straight forward as long as you realize you’re creating two files and changing one to point at the other.

Now displaying this will vary widely depending on the custom fields used and, of course, what you want things to look like.

Getting single item ACF fields is something like this … although I think you can get away with omitting the $post->ID portion.

function the_pub_pages(){
   global $post;
   $pub_pages = get_field( "pages", $post->ID ); //is there that custom field through acf?
   if ($pub_pages){ //if so
    return $pub_pages; //give me back the value which I can then echo out with echo the_pub_pages()

ACF fields that hold arrays require a bit more. This is for a repeater field that might hold any number of values with multiple subfields (topic_image, topic_title, topic_details).

function get_the_topic_details(){
    global $post;
    $html = "";
    if( have_rows('topic_details') ):

    while ( have_rows('topic_details') ) : the_row();

      $html .= '<div class="col-md-3 topic">';
        // Your loop code
      $html .= '<img class="topic-details-images" src="' . get_sub_field('topic_image') . '">';
      $html .= '<h3 class="topic-title">' . get_sub_field('topic_title') . '</h3>';
      $html .= '<p>' . get_sub_field('topic_details') . '</p>';
      $html .= '</div>';
    else :

        // no rows found

    return $html;

There are also certain fields, like images, where you can choose between getting an object back or the image url. Taxonomy structures are another that offer some choice in what you get back.

This gives you tons of options and the ability to build them much faster than I could doing all that by hand each time. I’m pretty happy with it even though I’ve resisted ACF for a good while.


To meet some other needs, we built out three different shortcodes to combine particular data/display combos that would be controlled by the authors of the site. In the example below you can see we have three argument options that fit into the WordPress query. The arguments are ‘cat’ for the category desired, ‘num’ for the number of posts to return (default value is 3), and ‘ex’ to include excerpt (default is false). That query is executed and it runs whatever you set through the loop to build a chunk of HTML to return. I’m of mixed opinion about building the HTML like this vs running a template loop of some sort through it. I went with the former just to make things more direct.

//shortcode for content by category
function altlab_content_shortcode( $atts, $content = null ) {
    extract(shortcode_atts( array(
         'cat' => '',  
         'num' => '',
         'ex' => ''
    ), $atts));     

    if (!$num){
      $num = 3;

    if (!$cat){
      $cat = false;
      $cat_link = get_post_type_archive_link();
    } else {
      $cat_id = get_cat_id($cat);
      $cat_name = get_cat_name($cat_id);
      $cat_link = get_category_link($cat_id);

    if (!$ex){
      $ex = false;

    $html ='';
    $num = intval($num);    
               $args = array(
                      'posts_per_page' => $num,
                      'post_type'   => 'post', 
                      'post_status' => 'publish', 
                      'orderby' => 'date',  
                      'category_name' => $cat,
                      'nopaging' => false,                                        
                    // query
                    $the_query = new WP_Query( $args );
                    if( $the_query->have_posts() ): 
                      while ( $the_query->have_posts() ) : $the_query->the_post(); 
                      $html .= '<div class="row sc-posts">';
                      $html .= '<div class="sc-post-img col-md-12">';
                        if ( has_post_thumbnail() ) {
                        $html .=  get_the_post_thumbnail(get_the_ID(),'medium', array('class' => 'sc-post-image responsive aligncenter', 'alt' => 'Featured image.'));
                       $html .= '</div><a href="'.get_post_permalink().'"><h3 class="sc-post-title">';
                       $html .=  get_the_title();
                       $html .= '</h3></a>';  
                       if ($ex){                  
                        $html .= '<div class="sc-post-content">' .get_the_excerpt() . '</div>';
                       $html .= '</div>';          
                     $html .= '<a class="content-button" href="'.$cat_link.'">See More '. $cat_name .'</a>';
            wp_reset_query();  // Restore global post data stomped by the_post().
   return $html;

add_shortcode( 'get-posts', 'altlab_content_shortcode' );

You can also do stuff like this with shortcodes to pull just a particular post type (faculty in this case). This one pulls lots and lots of ACF fields via functions that aren’t included here. It only has one argument ‘type’ and will give you all of the results (the -1 in posts_per_page in the $args).

//shortcode for faculty content by type
function altlab_faculty_shortcode( $atts, $content = null ) {
    extract(shortcode_atts( array(
         'type' => '',  
    ), $atts));     

    $html ='';
    $type = htmlspecialchars_decode($type);
               $args = array(
                      'posts_per_page' => -1,
                      'post_type'   => 'faculty', 
                      'post_status' => 'publish', 
                      'orderby' => 'name', 
                      'order' => 'ASC',                
                      'meta_query' => array(
                      'relation'    => 'OR',
                        'key'   => 'staff_group',
                        'value'   => $type,
                        'compare' => 'LIKE'
                      //do the published option and consider sorting
                    // query
                    $the_query = new WP_Query( $args );
                    if( $the_query->have_posts() ): 
                      while ( $the_query->have_posts() ) : $the_query->the_post(); 
                      $html .= '<div class="row the-faculty">';
                      $html .= '<div class="faculty-img col-md-4">';
                        if ( has_post_thumbnail() ) {
                        $html .=  get_the_post_thumbnail(get_the_ID(),'large', array('class' => 'faculty-bio-image responsive', 'alt' => 'Faculty portrait.'));
                       $html .= '</div><div class="col-md-8"><h2 class="faculty-title">';
                       $html .=  get_the_title(); the_faculty_degree();
                       $html .= '</h2><div class="row"><div class="col-md-6 faculty-bio-content"><div class="faculty-titles">';
                       $html .= the_faculty_title();
                       $html .= '</div>';
                       $html .= the_faculty_expertise();
                       $html .= '</div><div class="col-md-6 faculty-contact-info">';
                       $html .= the_faculty_phone();
                       $html .= the_faculty_office();    
                       $html .= the_faculty_email(); 
                       $html .= the_faculty_website();
                       $html .= the_content();
                       //$html .= the_faculty_group();
                       $html .= '</div></div></div></div>';          
            wp_reset_query();  // Restore global post data stomped by the_post().
   return $html;

add_shortcode( 'get-faculty', 'altlab_faculty_shortcode' );


The other tweak here is to deal with some semi-standard widget content. We want the content to be the same but we want to choose 3 of 4 total options depending on what site it is. So I made four widgets and three places to put those widgets.

This is a widget holder. It’ll show up under Appearance>Widgets and be a destination called ‘Custom Zone – Left’.

if ( function_exists('register_sidebar') )
    'name' => 'Custom Zone - Left',
    'id'            => 'custom-left',    // ID should be LOWERCASE  ! ! !    
    'before_widget' => '<div class = "widgetizedArea">',
    'after_widget' => '</div>',
    'before_title' => '<h3>',
    'after_title' => '</h3>',

The widget display in the theme will be like this. Note how the ‘Custom Zone – Left’ portion matches. That’s important.

            <?php if ( !function_exists('dynamic_sidebar') || !dynamic_sidebar("Custom Zone - Left") ) : ?><?php endif;?>

The custom content to drag to these destinations doesn’t give you any options so it’s simpler than other versions. Other than standardizing naming, the HTML is the only part I added. That’s the string of HTML assigned to the $env_studies variable.

//CENTER FOR ENV STUDIES____________________________________________

// Register and load the widget
function env_studies_load_widget() {
    register_widget( 'env_studies_widget' );
add_action( 'widgets_init', 'env_studies_load_widget' );

// Creating the widget 
class env_studies_widget extends WP_Widget {

function __construct() {
  // Base ID of your widget
  // Widget name will appear in UI
  __('VCU Center for ENVS', 'env_studies_widget_domain'), 
  // Widget description
  array( 'description' => __( 'Add env studies box ', 'wpb_widget_domain' ), ) 
// Creating widget front-end
public function widget( $args, $instance ) {
  //$title = apply_filters( 'widget_title', $instance['title'] );

$env_studies = '<div class="col-lg-4"><div class="card"><img class="card-img-top" src="'. THEME_IMG_PATH . 'envs_center.jpg" alt=""><a href=""><div class="card-body hvr-underline-from-center"><h3 class="card-title">Center for Environmental Studies</h3></div></a></div></div>';

// before and after widget arguments are defined by themes
      // This is where you run the code and display the output
      echo __( $env_studies, 'env_studies_widget_domain' );
} // Class ${1:this}_widget ends here

1 If assignments are aligned to competencies then you can simplify even that but it risks making some of that more opaque to the students.

2 The big picture change would be to allow students to choose what content demonstrates what competency/competencies. The specificity/granularity of that could be pretty intense if that was desired.

3 Seriously. I just tab over and enter the name of the post type and everything gets filled in. This is so handy and saves so much time. More on Sublime snippets here . . . and I know it’s not as cool as Code. I can’t quite make the switch yet.

4 Really handy you can link to lines in github although only Alan is the only person who might click on that.

Comments on this post

  1. Tom Woodward said on July 7, 2018 at 10:20 am

    In looking through some old photos I saw my drawing of the same concept pictured above back in 2014. This stuff often takes a long time to get there.