I’m a bit of a wordpress noob and I’m diving head first into a course creation plugin. The structure will go program > course > level > lesson. Right now I have lessons set up as a custom post type and levels, courses and programs are taxonomies. Basically I could use help creating a navigation path where you first pick a program, then a course, then a level, then a lesson. I’m racking my brains and I’m kinda stuck. Like I said I’m pretty new to WordPress and php for that matter…

Is this a viable way to go about this by using a custom post types and taxonomies and does anyone have any suggestions or resources? I’m not sure if I should go about creating a custom theme template file for this either, or just use a shortcode in a page for the navigation.

                 Program
                    |
                   / \
                  /   \
             Course1 Course2
                /\     /\
               /  \   /  \
             Lv1 Lv2 Lv1 Lv2
            /\   /\   /\   /\
          Lessons Lessons Lessons

Below is what I have so far (Sorry, it’s quite long). It works well, I just haven’t quite figured out how to organize it yet. Below I’ve just set up the custom post type and taxonomies. That’s about it

<?php
/**
 * Plugin Name: Course Manager
 * Description: Creates Programs, Courses, Levels and Lessons
 * Version: The Plugin's Version Number, e.g.: 0.1
 */
?>
<?php
function cm_lesson_cp(){
  $labels = array(
    'name'               => _x( 'Lesson', 'post type general name' ),
    'singular_name'      => _x( 'Lesson', 'post type singular name' ),
    'add_new'            => _x( 'Add New', 'Lesson' ),
    'add_new_item'       => __( 'Add New Lesson' ),
    'edit_item'          => __( 'Edit Lesson' ),
    'new_item'           => __( 'New Lesson' ),
    'all_items'          => __( 'All Lessons' ),
    'view_item'          => __( 'View Lesson' ),
    'search_items'       => __( 'Search Lessons' ),
    'not_found'          => __( 'No Lessons found' ),
    'not_found_in_trash' => __( 'No lessons found in the Trash' ), 
    'parent_item_colon'  => '',
    'menu_name'          => 'Lessons'
  );
  $args = array(
    'labels'        => $labels,
    'description'   => 'Enter a lesson description here.',
    'public'        => true,
    'menu_position' => 4,
    'supports'      => array( 'title', 'editor', 'excerpt'),
    'has_archive'   => true,
  );
  register_post_type( 'lesson', $args ); 
  flush_rewrite_rules( false );
}
add_action( 'init', 'cm_lesson_cp' );

//Custom messages for custom post type`

function lesson_messages_cp( $messages ) {
  global $post, $post_ID;
  $messages['lesson'] = array(
    0 => '', 
    1 => sprintf( __('Lesson updated. <a href="https://wordpress.stackexchange.com/questions/156620/%s">View Lesson</a>'), esc_url( get_permalink($post_ID) ) ),
    2 => __('Custom field updated.'),
    3 => __('Custom field deleted.'),
    4 => __('Lesson updated.'),
    5 => isset($_GET['revision']) ? sprintf( __('Lesson restored to revision from %s'), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,
    6 => sprintf( __('Lesson published. <a href="https://wordpress.stackexchange.com/questions/156620/%s">View Lesson</a>'), esc_url( get_permalink($post_ID) ) ),
    7 => __('Lesson saved.'),
    8 => sprintf( __('Lesson submitted. <a target="_blank" href="https://wordpress.stackexchange.com/questions/156620/%s">Preview Lesson</a>'), esc_url( add_query_arg( 'preview', 'true', get_permalink($post_ID) ) ) ),
    9 => sprintf( __('Lesson scheduled for: <strong>%1$s</strong>. <a target="_blank" href="%2$s">Preview Lesson</a>'), date_i18n( __( 'M j, Y @ G:i' ), strtotime( $post->post_date ) ), esc_url( get_permalink($post_ID) ) ),
    10 => sprintf( __('Lesson draft updated. <a target="_blank" href="https://wordpress.stackexchange.com/questions/156620/%s">Preview Lesson</a>'), esc_url( add_query_arg( 'preview', 'true', get_permalink($post_ID) ) ) ),
  );
  return $messages;
}
add_filter( 'post_updated_messages', 'lesson_messages_cp' );


// Register Program Taxonomy


function program_taxonomy_cp() {
  $args = array();
  register_taxonomy( 'program', 'lesson', $args );
}

add_action( 'init', 'program_taxonomy_cp', 0 );

// Customize taxonomy

function program_taxonomy_setting_cp() {
  $labels = array(
    'name'              => _x( 'Program', 'taxonomy general name' ),
    'singular_name'     => _x( 'Program', 'taxonomy name' ),
    'search_items'      => __( 'Search Programs' ),
    'all_items'         => __( 'All Programs' ),
    'parent_item'       => __( 'Parent Program Category' ),
    'parent_item_colon' => __( 'Parent Program Category:' ),
    'edit_item'         => __( 'Edit Program Category' ), 
    'update_item'       => __( 'Update Program' ),
    'add_new_item'      => __( 'Add New Program' ),
    'new_item_name'     => __( 'New Program' ),
    'menu_name'         => __( 'Programs' ),
  );
  $args = array(
    'labels' => $labels,
    'hierarchical' => true,
  );
  register_taxonomy( 'program', 'lesson', $args );
}
add_action( 'init', 'program_taxonomy_setting_cp', 0 );


// Register Course Taxonomy


function course_taxonomy_cp() {
  $args = array();
  register_taxonomy( 'course', 'lesson', $args );
}

add_action( 'init', 'course_taxonomy_cp', 0 );

// Customize taxonomy

function course_taxonomy_setting_cp() {
  $labels = array(
    'name'              => _x( 'Course', 'taxonomy general name' ),
    'singular_name'     => _x( 'Course', 'taxonomy name' ),
    'search_items'      => __( 'Search Courses' ),
    'all_items'         => __( 'All Courses' ),
    'parent_item'       => __( 'Parent Course Category' ),
    'parent_item_colon' => __( 'Parent Course Category:' ),
    'edit_item'         => __( 'Edit Course Category' ), 
    'update_item'       => __( 'Update Course' ),
    'add_new_item'      => __( 'Add New Course' ),
    'new_item_name'     => __( 'New Course' ),
    'menu_name'         => __( 'Courses' ),
  );
  $args = array(
    'labels' => $labels,
    'hierarchical' => true,
  );
  register_taxonomy( 'course', 'lesson', $args );
}
add_action( 'init', 'course_taxonomy_setting_cp', 0 );


// Register Custom Taxonomy "Level"


function level_taxonomy_cp() {
  $args = array();
  register_taxonomy( 'glossary', 'term', $args );
}

add_action( 'init', 'level_taxonomy_cp', 0 );

// Customize taxonomy

function level_taxonomy_setting_cp() {
  $labels = array(
    'name'              => _x( 'Levels', 'taxonomy general name' ),
    'singular_name'     => _x( 'Level', 'taxonomy name' ),
    'search_items'      => __( 'Search Levels' ),
    'all_items'         => __( 'All Levels' ),
    'parent_item'       => __( 'Parent Level Category' ),
    'parent_item_colon' => __( 'Parent Level Category:' ),
    'edit_item'         => __( 'Edit Level Category' ), 
    'update_item'       => __( 'Update Level' ),
    'add_new_item'      => __( 'Add New Level' ),
    'new_item_name'     => __( 'New Level' ),
    'menu_name'         => __( 'Levels' ),
  );
  $args = array(
    'labels' => $labels,
    'hierarchical' => true,
  );
  register_taxonomy( 'level', 'lesson', $args );
}
add_action( 'init', 'level_taxonomy_setting_cp', 0 );

2 s
2

Your data model is interesting. You can answer this question better than me, but I don’t know if we really can say, as @toscho does, that a lesson will never be shared between courses, or that no two levels will have the same lesson in.

I believe that a lesson and a course are both post-like things, but a level is something that makes less sense on its own and needs context (ie its lessons) to be meaningful.

If I was building this, I would relegate ‘level’ to a taxonomy of lessons, and I would use using Posts 2 Posts to relate lessons to courses. Then to get a lesson I would query it directly. For a level in a course I would query lessons in that level’s term that were also related to the course.

For the following reasons:

  • Templating You get the benefits of single-lesson.php etc in your template hierarchy. If you do it @toscho’s way, if you wanted different templates for lessons, courses etc you would need to do something slightly complex, like introduce a conditional before the template was rendered, and handle it yourself.

  • Readability A query for post_type=lesson&level=level_one is more readable than post_parent=123243 (or whatever).

  • Flexibility Want a lesson shared between two courses? Want to reuse lessons across levels? You can do it. With straight post hierarchy, you’d need to duplicate material in the database, which is a recipe for trouble.

  • Simplicity of Levels I admit that this is the point where the system is less flexible, but I think that is ok – it might not be, for your use-case! If you want to have the same name for levels (eg Level 1, Level 2, Level 3) across all your courses, why not have them in the same place? If you need to have per-course, per-level descriptions, add a postmeta field to your course with the description in it. Less flexible, but perhaps enough.

Leave a Reply

Your email address will not be published. Required fields are marked *