0

I am trying to fetch and display data from a feed when the user goes to a specific url like /items/32 where the 32 is the actual id of the feed item that should be fetched and displayed. Notice that I am not using any custom post type for this. I am just fetching the data from an external resource based on the supplied id:

class FeedPlugin
{
  public function __construct()
  {
    add_action('init', array($this, 'addRewriteRules'));
    add_filter('query_vars', array($this, 'addQueryVars'));
    add_action('template_redirect', array($this, 'addTemplate'));
  }

  /**
   * Add query argument item_id
  **/
  public function addQueryVars(array $query_vars)
  {
    $query_vars[] = 'item_id';
    return $query_vars;
  }

  /**
   * Add a custom rewrite in the form ^/items/123/$
  **/
  public function addRewriteRules()
  {
    add_rewrite_rule(
      'items/([0-9]+)/?$',
      'index.php?pagename=items&item_id=$matches[1]',
      'top'
    );
    // This is just for testing purposes at this point.
    flush_rewrite_rules(true);
  }

  /**
   * Here we actually are fetching the data based on the id
  **/
  public function addTemplate()
  {
    global $item;
    if (get_query_var('pagename') == 'items' && 
        get_query_var('item_id')) {
        $feedUrl = 'https://example.com/api/v1/feed?id=' . get_query_var('item_id');
        $response = wp_remote_request($feedUrl);

        // For now we just assume that this works.
        $body = wp_remote_retrieve_body($response);
        $item = json_decode($body);

        // Include our custom template that renders the content from the $body variable
        // I also manage to do this by using the `template_include` filter
        include plugin_dir_path(__FILE__) . '/custom-item.php';
      }
    }
}

The above sort of works, but since I am not using a real post when visiting this route the title of the page is set to BlogName | Not found and the http status code returned when visiting this route is set to 404. I manage to solve this by using add_filter('pre_get_document_title', array($this, 'setTitle'));:

public function setTitle($title)
{
    global $item;
    if (get_query_var('pagename') == 'items') {
        return get_bloginfo('name') . ' | ' . $item->Title;
    }
    return $title;
}

and to get rid of the 404 status code I used:

global $wp_query;
$wp_query->is_404 = false;
status_header(200);

in the addTemplate() method.

Even though this seems to work it feels very messy and I wonder if there is a smoother way of doing this? Perhaps by creating some kind of fake post or similar?

2

Seems like a good candidate for add_rewrite_endpoint().

add_rewrite_endpoint( 'items', EP_ROOT, 'item_id' );

That will register the endpoint /items/<item ID> and also registers the query var item_id, and eliminates the 404 status header/title as well. But the page title would be the site name and you'd probably still want to hook to pre_get_document_title to customize the title.

And your class would be simpler:

class FeedPlugin {
    public function __construct() {
        add_action( 'init', [ $this, 'add_rewrite_endpoint' ] );
        add_action( 'template_redirect', [ $this, 'addTemplate' ] );
    }

    public function add_rewrite_endpoint() {
        add_rewrite_endpoint( 'items', EP_ROOT, 'item_id' );
    }

    public function addTemplate() {
        if ( $item_id = get_query_var( 'item_id' ) ) {
            /* your code here:
            echo 'Yay, it works! Item ID: ' . $item_id;
            exit;
            */
        }
    }
}

Don't forget to flush the rewrite rules — just visit the permalink settings page.

UPDATE

how can I specify what is valid for release_id e.g [0-9A-Z]+

So I believe you meant item_id when you said release_id?

And (at the moment) there's no "standard"/easy way to specify the preferred RegEx pattern for an endpoint added using add_rewrite_endpoint(), so if you need a custom RegEx pattern, it's actually better to use add_rewrite_rule(). :)

And here's how you can prevent the 404 error with your original code.

I believe you know it happens because you're querying a post/Page that doesn't exist (pagename=items) and I also believe you added that in the rewrite rule as an identifier for the custom feed request.

And I'm not sure if you intentionally didn't do this, but it's a very easy way to prevent the 404 error: create a Page and set its slug to items.

That may sound silly, but it actually works. (Although you'd still need to tweak the page title.) And you could just create a custom Page template (which loads/generates/renders the feed) and assign the template to that items Page, without having to hook to template_redirect.

Alternate solution without creating a Page

  1. In the addRewriteRules(), just omit the pagename=items&:

    public function addRewriteRules()
    {
      add_rewrite_rule(
        'items/([0-9]+)/?$',
        'index.php?item_id=$matches[1]',
        'top'
      );
    }
    
  2. In the addTemplate(), check if the matched rewrite rule is the one you added via add_rewrite_rule() above:

    public function addTemplate()
    {
      global $item, $wp;
      if ( 'items/([0-9]+)/?$' === $wp->matched_rule &&
        ( $item_id = get_query_var('item_id') ) ) {
        // your code here
      }
    }
    

But once again, you'd still need to tweak the page title which defaults to the site name.

  • 1
    +1, but using the above how can I specify what is valid for release_id e.g [0-9A-Z]+? Not really sure I understand. If I go to items/A32 then the release_id will automatically be populated with A32? I will have to play around a little bit with this =) – Cyclonecode Aug 4 at 1:59
  • So, what's your final solution? Is it one of my suggestions? :) – Sally CJ Aug 10 at 5:46

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.