• Register a CPT ("homework" post type)
  • Register Taxonomy
  • The Theme Archive and Single Template Files
  • Metadata with CPTs
  • Custom Wrapper Classes for CPTs

Register a CPT with the function register_post_type( $post_type, $args );, and in most cases, you are going to register your CPT in your theme’s functions.php file or in a custom plugin file.

<?php
// custom function to register a "homework" post type
function ssd_register_post_type_homework() {
    register_post_type( 'homework',
        array(
            'labels' => array(
                'name' => __( 'Homework' ),
                'singular_name' => __( 'Homework' )
            ),
        'public' => true,
        'has_archive' => true,
        )
    );
}
// call our custom function with the init hook
add_action( 'init', 'ssd_register_post_type_homework' );
 
// custom function to register a "submissions" post type
function ssd_register_post_type_submission() {
    register_post_type( 'submissions',
        array(
            'labels' => array(
                'name' => __( 'Submissions' ),
                'singular_name' => __( 'Submission' )
            ),
        'public' => true,
        'has_archive' => true,
        )
    );
}
// call our custom function with the init hook
add_action( 'init', 'ssd_register_post_type_submission' );
?>

Taxonomies group posts by terms. Think post categories and post tags; these are just builtin taxonomies attached to the default post type. You can define as many custom taxonomies or categories as you want and span them across multiple post types. Tax or Meta? Since we’ll be able to pre-filter to get all assignments by textbook or by student, we can use a chapter meta field, or possibly a textbook_chapter meta field with data like “PrinciplesOfMath.Ch1” to order the assignments for the report.

<?php
// custom function to register the "subject" taxonomy
function ssd_register_taxonomy_subject() {
   register_taxonomy(
      'subject',
      'homework',
      array(
         'label' => __( 'Subjects' ),
         'rewrite' => array( 'slug' => 'subject' ),
         'hierarchical' => true
      )
   );
}
// call our custom function with the init hook
add_action( 'init', 'ssd_register_taxonomy_subject' );
?>
/*What if you wanted to use a default taxonomy on a custom post type? Say you want to
use the same tags taxonomy attached to the posts post type on our homework post type.*/
<?php
function ssd_register_taxonomy_for_object_type_homework(){
    register_taxonomy_for_object_type( 'post_tag', 'homework' );
}
add_action( 'init', 'ssd_register_taxonomy_for_object_type_homework' );
?>

The Theme Archive and Single Template Files Make a copy of archive.php and name it archive-homework.php. You should now automatically have a listing archive page of all of your homework assignment posts in the same format of your regular posts archive page (at domain.com/homework/). This same method can be applied to the single.php file. Make a copy if it and call it singlehomework. php. You should now have a single page for each of your homework assignments (at domain.com/homework/science-worksheet/). Now you can change the markup of the CPT archive or single file to display your data differently from how your blog posts are displayed. Good Old WP_Query and get_posts() you can set the post_type parameter to query and loop through your CPT posts the same way you would with regular posts.

//submission form
<?php
function ssd_the_content_homework_submission($content){
 
    global $post;
 
    // Don't do this for any other post type than homework
    // and if a user is not logged in
    $current_user = wp_get_current_user();
    if ( ! is_single() || $post->post_type != 'homework' || ! $current_user )
        return $content;
 
    // get current user's submission to this homework assignment
    $submissions = get_posts( array( 
        'post_author'    => $current_user->ID, 
        'posts_per_page' => '1',
        'post_type'      => 'submissions', 
        'meta_key'       => '_submission_homework_id', 
        'meta_value'     => $post->ID  
    ) );
    foreach ( $submissions as $submission ) {
        $submission_id = $submission->ID;
    }
 
    // Process the form submission if the user hasn't already
    if ( !$submission_id && 
            isset( $_POST['submit-homework-submission'] ) && 
            isset( $_POST['homework-submission'] ) ) {
 
        $submission = $_POST['homework-submission'];
        $post_title = $post->post_title; 
        $post_title .= ' - Submission by ' . $current_user->display_name;
        // Insert submission as a post into our submissions CPT.
        $args = array(
            'post_title'   => $post_title,
            'post_content' => $submission,
            'post_type'    => 'submissions',
            'post_status'  => 'publish',
            'post_author'  => $current_user->ID
        );
        $submission_id = wp_insert_post( $args );
        // add post meta to tie this submission post to the homework post
        add_post_meta( $submission_id, 
            '_submission_homework_id', 
            $post->ID 
        );
        // create a custom message
        $message = __( 
            'Homework submitted and is awaiting review.', 
            'ssd' 
        );
        $message = '<div class="message">' . $message . '</div>';
        // drop message before the filtered $content variable
        $content = $message . $content;
    }
 
    // Add a link to the user's submission
    if( $submission_id ) {
 
        $message = sprintf( __( 
            'Click %s here %s to view your submission.',
            'ssd' ), 
            '<a href="' . get_permalink( $submission_id ) . '">',
            '</a>' );
        $message = '<div class="link">' . $message . '</div>';
        $content .= $message;
 
    // Add a form after the $content variable being filtered.
    } else {
 
        ob_start();
        ?>
        <h3>
        <?php _e( 'Submit your Homework below!', 'ssd' );?>
        </h3>
        <form method="post">
        <?php 
        wp_editor( '', 
            'homework-submission', 
            array( 'media_buttons' => false ) 
        );
        ?>
        <input type="submit" name="homework-submission" value="Submit" />
        </form>
        <?php 
        $form = ob_get_contents();
        ob_end_clean();
        $content .= $form;
    }
 
    return $content;
 
}
// add a filter on 'the_content' to run our homework submissions code
add_filter( 'the_content', 
    'ssd_the_content_homework_submission',
    999 
);
?>

Using ob_start(), wp_editor(), ob_get_contents(), ob_end_clean() because the wp_editor() function does not currently have an argument to return the editor as a variable and outputs it to the browser when it’s called. If we didn’t use these functions, we wouldn’t be able to put our editor after the $content variable passed into the the_content filter

<?php
function ssd_submissions_template_redirect(){
    global $post, $user_ID;
 
    // only run this function for the submissions post type
    if ( $post->post_type != 'submissions' )
        return;
 
    // check if post_author is the current user_ID 
    if ( $post->post_author == $user_ID )
        $no_redirect = true;
 
    // check if current user is an administrator
    if ( current_user_can( 'manage_options' ) )
        $no_redirect = true;
 
    // if $no_redirect is false redirect to the home page
    if ( ! $no_redirect ) {
        wp_redirect( home_url() );
        exit();
    }
}
// use the template_redirect hook to call a function that decides if the
// current user can access the current homework submission
add_action( 'template_redirect', 'ssd_submissions_template_redirect' );
?>

Metadata with CPTs

<?php
// function for adding a custom meta box
function ssd_homework_add_meta_boxes(){
 
    add_meta_box(
        'homework_meta',
        'Additonal Homework Info',
        'ssd_homework_meta_box',
        'homework',
        'side'
    );
 
}
// use the add_meta_boxes hook to call a custom function to add a new meta box
add_action( 'add_meta_boxes', 'ssd_homework_add_meta_boxes' );
 
// this is the callback function called from add_meta_box
function ssd_homework_meta_box( $post ){
    // doing this so the url will fit in the book ;)
    $jquery_url = 'http://ajax.googleapis.com/ajax/libs/';
    $jquery_url.= 'jqueryui/1.8.2/themes/smoothness/jquery-ui.css';
 
    // enqueue jquery date picker
    wp_enqueue_script( 'jquery-ui-datepicker' );
    wp_enqueue_style( 'jquery-style', $jquery_url );
 
    // set meta data if already exists
    $is_required = get_post_meta( $post->ID, 
        '_ssd_homework_is_required', 1 );
 
    $due_date = get_post_meta( $post->ID, 
        '_ssd_homework_due_date', 1 );
    // output meta data fields
    ?>
    <p>
    <input type="checkbox" 
    name="is_required" value="1" <?php checked( $is_required, '1' ); ?>>
    This assignment is required.
    </p>
    <p>
    Due Date:
    <input type="text" 
    name="due_date" id="due_date" value="<?php echo $due_date;?>">
    </p>
    <?php // attach jquery date picker to our due_date field?>
    <script>
    jQuery(document).ready(function() {
        jQuery('#due_date').datepicker({
            dateFormat : 'mm/dd/yy'
        });
    });
    </script>
    <?php
}
 
// function for saving custom meta data to the database
function ssd_homework_save_post( $post_id ){
 
  // don't save anything if WP is auto saving
  if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) 
      return $post_id;
 
  // check if correct post type and that the user has correct permissions
  if ( 'homework' == $_POST['post_type'] ) {
 
    if ( ! current_user_can( 'edit_page', $post_id ) )
        return $post_id;
 
  } else {
 
    if ( ! current_user_can( 'edit_post', $post_id ) )
        return $post_id;
  }
 
  // update homework meta data
  update_post_meta( $post_id, 
      '_ssd_homework_is_required', 
      $_POST['is_required'] 
  );
  update_post_meta( $post_id, 
      '_ssd_homework_due_date', 
      $_POST['due_date'] 
  );
 
}
// call a custom function to handle saving our meta data
add_action( 'save_post', 'ssd_homework_save_post' );
?>

Custom Wrapper Classes for CPTs CPTs are just posts. So you can use a call like get_post($post_id) to get an object of the WP_Post class to work with. For complex CPTs, it helps to create a wrapper class so you can interact with your CPT in a more object-oriented way. The basic idea is to create a custom-defined PHP class that includes as a property a post object generated from the ID of the CPT post. In addition to storing that post object, the wrapper class also houses methods for all of the functionality related to that CPT.

//CPT wrapper class
<?php
/*
    Class Wrapper for Homework CPT
    /wp-content/plugins/ssd/classes/class.homework.php
*/
class Homework {
    //constructor can take a $post_id
    function __construct( $post_id = NULL ) {
        if ( !empty( $post_id ) )
            $this->getPost( $post_id );
    }
 
    //get the associated post and prepopulate some properties
    function getPost( $post_id ) {
        //get post
        $this->post = get_post( $post_id );
 
        //set some properties for easy access
        if ( !empty( $this->post ) ) {
        $this->id = $this->post->ID;
        $this->post_id = $this->post->ID;
        $this->title = $this->post->post_title;
        $this->teacher_id = $this->post->post_author;
        $this->content = $this->post->post_content;
        $this->required = $this->post->_ssd_homework_is_required;
        $this->due_date = $this->post->due_date;
        }
 
        //return post id if found or false if not
        if ( !empty( $this->id ) )
            return $this->id;
        else
            return false;
    }
}
?>
 
//Get and print a specific homework assignment
 
<?php
$assignment_id = 1;
$assignment = new Homework($assignment_id);
echo '<pre>'; 
print_r($assignment);
echo '</pre>';
//Outputs:
/*
Homework Object
(
    [post] => WP_Post Object
        (
            [ID] => 1
            [post_author] => 1
            [post_date] => 2013-03-28 14:53:56
            [post_date_gmt] => 2013-03-28 14:53:56
            [post_content] => This is the assignment...
            [post_title] => Assignment #1
            [post_excerpt] => 
            [post_status] => publish
            [comment_status] => open
            [ping_status] => open
            [post_password] => 
            [post_name] => assignment-1
            [to_ping] => 
            [pinged] => 
            [post_modified] => 2013-03-28 14:53:56
            [post_modified_gmt] => 2013-03-28 14:53:56
            [post_content_filtered] => 
            [post_parent] => 0
            [guid] => http://ssd.me/?p=1
            [menu_order] => 0
            [post_type] => homework
            [post_mime_type] => 
            [comment_count] => 3
            [filter] => raw
            [format_content] => 
        )
 
    [id] => 1
    [post_id] => 1
    [title] => Assignment 1
    [teacher_id] => 1
    [content] => This is the assignment...
    [required] => 1
    [due_date] => 2013-11-05
)
*/
?>

Why Use Wrapper Classes? Building a wrapper class for your CPT is a good idea for a few reasons:

  • put all of your code to register the CPT in one place.
  • put all of your code to register related taxonomies in one place.
  • build all of your CPT-related functionality as methods on the wrapper class.
  • code will read better.

Keep Your CPTs and Taxonomies Together Put all of your code to register the CPT and taxonomies in one place. Instead of having one block of code to register a CPT and define the taxonomies and a separate class wrapper to handle working with the CPT, you can simply place your CPT and taxonomy definitions into the class wrapper itself:

<?php
/*
    Class Wrapper for Homework CPT with Init Function
    /wp-content/plugins/ssd/classes/class.homework.php
*/
class Homework {
    //constructor can take a $post_id
    function __construct( $post_id = NULL ) {
        if ( !empty( $post_id ) )
            $this->getPost( $post_id );
    }
 
    //get the associated post and prepopulate some properties
    function getPost( $post_id ) {
        /* snipped */
    }
 
    //register CPT and Taxonomies on init
    function init() {
        //homework CPT
        register_post_type(
            'homework',
            array(
                'labels' => array(
                    'name' => __( 'Homework' ),
                    'singular_name' => __( 'Homework' )
                ),
                'public' => true,
                'has_archive' => true,
            )
        );
 
        //subject taxonomy
        register_taxonomy(
            'subject',
            'homework',
            array(
                'label' => __( 'Subjects' ),
                'rewrite' => array( 'slug' => 'subject' ),
                'hierarchical' => true
            )
        );
    }
 
 
 
//Adding methods to the Homework class
 
    /*
        Get related submissions.
        Set $force to true to force the method to get children again.
    */
    function getSubmissions( $force = false ) {
        //need a post ID to do this
        if ( empty( $this->id ) )
            return array();
 
        //did we get them already?
        if ( !empty( $this->submissions ) && !$force )
            return $this->submissions;
 
        //okay get submissions
        $this->submissions = get_children( array(
                'post_parent' => $this->id,
                'post_type' => 'submissions',
                'post_status' => 'published'
            ) );
 
        //make sure submissions is an array at least
        if ( empty( $this->submissions ) )
            $this->submissions = array();
 
        return $this->submissions;
    }
 
    /*
        Calculate a grade curve
    */
    function doFlatCurve( $maxscore = 100 ) {
        $this->getSubmissions();
 
        //figure out the highest score
        $highscore = 0;
        foreach ( $this->submissions as $submission ) {
            $highscore = max( $submission->score, $highscore );
        }
 
        //figure out the curve
        $curve = $maxscore - $highscore;
 
        //fix lower scores
        foreach ( $this->submissions as $submission ) {
            update_post_meta(
                $submission->ID,
                "score",
                min( $maxscore, $submission->score + $curve )
            );
        }
    }
}
 
//run the Homework init on init
add_action( 'init', array( 'Homework', 'init' ) );
?>

Wrapper Classes Read Better In addition to organizing code to make things easier to find, working with wrapper classes also makes code easier to read and understand. With fully wrapped Homework and Submission CPTs and special user classes, code like the following is possible:

<?php
//static function of Student class to check if the current user is a student
if ( Student::is_student() ) {
    //student defaults to current user
    $student = new Student();
 
    //let's figure out when their next assignment is due
    $assignment = $student->getNextAssignment();
 
    //display info and links
    if ( !empty( $assignment ) ) {
    ?>
    <p>Your next assignment
    <a href="<?php echo get_permalink( $assignment->id );?>">
    <?php echo $assignment->title;?></a>
    for the 
    <a href="<?php echo get_permalink( $assignment->class_id );?>">
    <?php echo $assignment->class->title;?></a>
    class is due on <?php echo $assignment->getDueDate();?>.</p>
    <?php
    }
}
?>

Leave a Comment

Fields with * are required.

Please enter the letters as they are shown in the image above.
Letters are not case-sensitive.