Best way to organize book-page structure

I’m wondering what is the best way to organize book-page content without involving taxonomies. So for example, I have a math book with a bunch of problems that I wish to list. I have a custom post type qa which can include a taxonomy <book-name>, and I want the URL to be: <domain>/qa/<book-name>/<page-number>/<problem-number>. So creating such a structure isn’t a big deal except the <page-number> – there will be lots of pages, and I think that it is not practical to create so many taxonomies. So what will be the optimal implementation? I’d appreciate any suggestions.

Related posts

1 comment

  1. I don’t know if this is the optimal, it’s one suggestion.

    For me I’d created the qa post type as hierachical.

    Then for every page in the book I’d created a child qa post using menu_order field to handle the page number.

    This child has a 3rd level child for problems.

    Hooking intro pre_get_posts for qa archive, you can set the post_parent to 0, so in the archive view you get only books, and not the big list of pages and problems.

    Last thing to is create a rewrite rule that convert the url into some query vars.

    E.g.

    <domain>/qa/<book-name>/<page-number>/<problem-number> should be rewritten in

     <domain>/index.php?name=<book-name>&post_type=qa&number=<page-number>&problem=<problem-number>
    

    That url will end in a very simple query where post id = <problem-number>.

    Example code:

    function my_book_rules() {
      add_rewrite_rule( 'qa/([^/]+)/([0-9]+)/([0-9]+)$', 'index.php?&post_type=qa&name=$matches[1]&number=$matches[2]&problem=$matches[3]', 'top' );
      add_rewrite_rule( 'qa/([^/]+)/([0-9]+)$', 'index.php?&post_type=qa&name=$matches[1]&number=$matches[2]', 'top');
    }
    add_action('init', 'my_book_rules');
    
    function my_book_query_vars($vars) {
      $vars[] = 'problem';
      $vars[] = 'number';   
      return $vars;
    }
    
    add_filter('query_vars', 'my_book_query_vars'); 
    
    function my_book_query( $query ) {
      if ( is_admin() || ! is_main_query() ) return;
      if ( is_archive() && $query->get('post_type') == 'qa') {
         $query->set('post_parent', 0);
      } elseif ( is_single() && get_post_type() == 'qa') {
        $post = get_queried_object();
        if ( $post->post_parent == 0 && $query->get('number') && ! $query->get('problem') ) {
          // a page number is requested
          $query->set('name', '');
          global $wpdb;
          $n = intval($query->get('number'));
          $p = (int) $wpdb->get_var("SELECT ID FROM $wpdb->posts WHERE post_status = 'publish' AND post_type = 'qa' AND post_parent = $post->ID AND menu_order = $n");
          $query->set('p', $n);
        } elseif( $query->get('problem') ) {
          // a problem is required
          $query->set('name', '');
          $n = intval($query->get('problem'));
          $query->set('p', $n);
        }
      }
    }
    add_action('pre_get_posts', 'my_book_query');
    

    In the single view template single-qa.php you can load sub tempalte for books, pages or problems, something like:

    if ( $post->post_parent == 0 ) {
       get_template_part('book', 'content');
    } else {
      $parent = get_post( $post->post_parent );
      if ( $parent->post_parent == 0 ) {
        get_template_part('page-number', 'content');
      } else {
        get_template_part('problem', 'content');
      }
    }
    

    Another snippet is for display the book title and the page number inside the problem template (problem-content.php according to previous snippet),

    $thepage = get_post( $post->post_parent);
    $pagenumber = $thepage->menu_order
    $the_book = get_post( $thepage->post_parent )->post_title
    
    echo "The problem " . get_the_title() . 
      " is in the book " . $the_book->post_title .
      " at the page number " . $pagenumber;
    

    Note that the code is a proof of concept and untested.

Comments are closed.