I'm currently working on a file browser plugin which requires frontend editing capabilities for a registered user. For this scenario, I have registered some custom rewrite rules for my custom post type.

Rewrite Analyzer

Rewrite Analyzer Window

As you can see in the screenshot above, the plugin has a basic CRUD interface for managing the downloads. The routing is made through the template_redirect hook, which checks if the query variable action is set and then make a call to the appropriate action method.

add_action('template_redirect', array($this, '_requestHandler'), 9);
// ...
public function _requestHandler() {
    try {
        $requestedPage = get_query_var('pagename');

        if((isset($requestedPage) && $requestedPage == 'webeo-download')) {
            $action = get_query_var('action');
            $this->action = (isset($action)) ? $action : null;

            $downloadId = get_query_var('download');
            $this->downloadId = (isset($downloadId)) ? (int) $downloadId : null;

            if(isset($this->action) && !is_null($this->action) && strlen($this->action) > 0) {
                $method = 'action' . ucfirst($this->action);

                if(method_exists($this, $method)) {
                    call_user_func(array($this, $method));
                } else {
                    throw new Webeo_Exception(sprintf(__('Action method <code>%s</code> does not exists.', WEBEO_DOWNLOAD_TEXTDOMAIN), $method));
            } else {

            echo $this->view->render($this->view->viewTemplate);
    } catch (Webeo_Exception $e) {
        $this->view->assign('error', $e);
        echo $this->view->render('default.phtml');

This works really well, except one thing. I wanted to prevent WordPress from generating any default rewrite rules for my custom post type. Access to my posts from outside should also be restricted. Only my controller above should be responsible to serve any download data to the user.

To do this, I have set the arguments public and publicly_queryable to false inside the register_post_type function. I've also set the argument exclude_from_search to true.

This seems to work. No post is showing under the default rewrite rule (e.g. example.com/downloads/<postname>) nor is a download listed in the default search results. Unfortunately the $wp_query arguments are also not set anymore. Therefore I'm not able to use comments_template() or any other loop-function inside my templates.

It's clear: WordPress doesn't know my page structure and is not able to generate the correct settings. I've tried to manually pre-populate the $wp_query arguments before the redirect inside the template_redirect method. But this doesn't seem to work. I'm probably to late in the chain.

global $wp_query, $post, $withcomments;
$wp_query->is_single = true;
$wp_query->is_page = true;
$post = get_post($this->downloadId);
$withcomments = true;

Any suggestions?

Thanks in advance Roman

  • How about setting it to public again and just push out not is_user_logged_in() users (and wp_redirect() them)? In short: abandon your solution and just do it the other way around - make it public but don't serve it? – kaiser Jun 1 '13 at 1:40
  • @kaiser Thanks for your reply. The problem here is, that I don't have full control over my data. For example the search: If the the arguments are set, the downloads will also be listed in the default search. I've already restricted the access and added custom capabilities to the post type. The access to the post is locked, but the title, excerpt and other non-public data are still served to an non-logged in user in the search or a widget. Of course, this would be my workaround if nothing else will work ;) – Roman Jun 1 '13 at 7:15

I've probably found the solution for the above problem. To pre-populate the global variable $wp_query you have to run the query_posts() function with the correct arguments and reset the $wp_query->post with the function wp_reset_postdata(). This must be done on the template_redirect hook or earlier.

class Webeo_Download {
    protected $downloadId;
    protected $action;

    public function setup() {
        // ...
        add_action('template_redirect', array($this, '_requestHandler'), 9);

    public function _requestHandler() {
        $this->action = get_query_var('action');
        $this->downloadId = get_query_var('download');

        // ... load appropirate action method based on the query variable "action". See code example in the question above. 

    public function actionView() {
        // Check permissions
        if(!is_user_logged_in()) {

        // Populate $wp_query for single download view
        global $wp_query;
        $args = array('post_type' => 'download', 'p' => $this->downloadId);

        // Load the template

    public function actionIndex() {
        // Do the same for all other action methods

    public function actionAdd() {
        // ...

    public function actionEdit() {
        // ...

    public function actionDelete() {
        // ...

    public function actionDownload() {
        // ...

If someone has a better approach, please let me know!

  • Please don't tell me you're writing a download post type. WordPress already has them built in, and it's called attachments. Just extend on them: Use media tags and handle the rest in your attachment template. Hint: You can even have different templates based on the MIME type. – kaiser Jun 1 '13 at 15:06
  • 1
    @kasier Thanks for the hint. I'm aware of the attachment post type. But this post type should be completely independent. Custom user roles, custom capabilities and custom front- & backend. It's a bit complicated, but these are the requirements. – Roman Jun 1 '13 at 15:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.