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.
1 Answer
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.
-
Thank you sir. I've got the idea. I'm new to WP and I want to ask you what does the
$query->set('p', $foo);
mean? I've chosen to modify the architecture a bit, introducing new table which will hold data about books. Can I create my own query inmy_book_query
such that the fetched items will be available atsingle-qa.php
? Because WP supplies methods such asthe_title()
andthe_content()
insingle.php
, but can I affect the way these methods work without hardcoding the core files? Can it be done in themy_book_query
method?– somebodySep 7, 2013 at 14:19 -
I'm asking this because I have to execute a certain queries for my new tables in the db (so I don't have to create this nested structure you've suggested). I just want to execute different query for each option: if it is
qa/<book-name>
then I'll fetch on my own the pages and I would like to pass the result tosingle-qa.php
. If it isqa/<book-name>/<book-page>
I would like to execute a different query, fetching all the problems and sending it, again, tosingle-qa.php
. So can I update somehow the object that operates the wholesingle-qa.php
thing? Thanks.– somebodySep 7, 2013 at 14:25 -
I guess I could fetch the book name or book page in
single-qa.php
using$_SERVER['REQUEST_URI'];
and then execute queries for different conditions, but I know that WP has already executed its own queries and there's some data in methods likethe_title()
andthe_content()
, so I want to avoid making unnecessary queries. That is why I want to affect the way WP fetches the object that operatessingle-qa.php
. I hope that I explained myself better. Thank you in advance!– somebodySep 7, 2013 at 14:31 -
the_title()
and other template tags useglobal $post
variable to work. If you use your custom table to store data, you need extra code to make it works. It's hard to explain here in a comment, expecially without knowing how you have structured your table. The built-in query runned by wordpress known as 'main query', can be edited before it run using 'pre_get_posts' hook. My function does that, andquery->set
is the way to change the WP main query. If you work with your custom table you have to create your custom query andquery->set
will be un-useful for you, I think. @somebody– gmazzapSep 7, 2013 at 15:52