58

SOLUTION


I've gone with Update 3 and have got it working. What I had misunderstood, was that I believed that a rewrite rule would change designers/?designer=some-designer&bid=9 to /designers/some-designer. But it is of course the other way around.


I've been doing some research, but I don't quite get how to use add_rewrite_rule().

This post is pretty close to what I need, but do I really need that much code?

My current link is this:
http://www.norwegianfashion.no/designers/?designer=Batlak-og-Selvig&bid=9
What I want is this
http://www.norwegianfashion.no/designers/Batlak-og-Selvig/

  1. How do I use add_rewrite_rule()?
  2. I need to pass the designer ID (bid). How can I do that when using re.write rules with out it showing in the URL?

Update 1

I found this which is a bit more explainatory. But it's not working:

function add_rewrite_rules( $wp_rewrite ) 
{
    $new_rules = array(
        '('.$template_page_name.')/designers/(.*?)/?([0-9]{1,})/?$' => 'designers.php?designer='.
        $wp_rewrite->preg_index(1).'&bid='.
        $wp_rewrite->preg_index(2)
    );
       // Always add your rules to the top, to make sure your rules have priority
    $wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
}
add_action('generate_rewrite_rules', 'add_rewrite_rules');

function query_vars($public_query_vars) {

    $public_query_vars[] = "designer";
    $public_query_vars[] = "bid";
    return $public_query_vars;
}
add_filter('query_vars', 'query_vars')

Update 2

Here is another solution I've tried, that Jan Fabry suggesten in another thread. I put this in my functions.php, but it has no effect.

add_action('generate_rewrite_rules', 'add_rewrite_rules');
add_filter('query_vars', 'query_vars');

/* Custom re-write rules for desigenrs
------------------------------------------------------------------------------*/
function add_rewrite_rules( $wp_rewrite ) 
{
    add_rewrite_rule('^designers/([^/]*)/([^/]*)$ /designers/?designer=$1&bid=$2 [L]', 'top');
    flush_rewrite_rules(false);
}


function query_vars($public_query_vars) {

    $public_query_vars[] = "designer";
    $public_query_vars[] = "bid";
    return $public_query_vars;
}

Update 3
I found a few examples here as well. But this is not working either :(

add_filter('rewrite_rules_array','wp_insertMyRewriteRules');
add_filter('query_vars','wp_insertMyRewriteQueryVars');
add_filter('init','flushRules');  

// Remember to flush_rules() when adding rules
function flushRules(){
    global $wp_rewrite;
    $wp_rewrite->flush_rules();
}

// Adding a new rule
function wp_insertMyRewriteRules($rules)
{
    $newrules = array();
    $newrules['(designers)/(\d*)$'] = 'index.php?pagename=designers&designer=$matches[1]';
    return $newrules + $rules;
}

// Adding the bid var so that WP recognizes it
function wp_insertMyRewriteQueryVars($vars)
{
    array_push($vars, 'designer');
    return $vars;
}

Update 4 I also tried this solution, but still no sucess.

add_filter('search_rewrite_rules', 'designers_createRewriteRules');

function designers_createRewriteRules($rewrite) {
global $wp_rewrite;

// add rewrite tokens
$designer_token = '%designers%';
$wp_rewrite->add_rewrite_tag($designer_token, '(.+)', 'designers=');

$bid_token = '%bid%';
$wp_rewrite->add_rewrite_tag($bid_token, '(.+)', 'bid=');

$keywords_structure = $wp_rewrite->root . "designers/$designer_token/$bid_token";
$keywords_rewrite = $wp_rewrite->generate_rewrite_rules($keywords_structure);

return ( $rewrite + $keywords_rewrite );

}

Update 5
It seems that my rewrite rules are not added. Executing the follwoing afte radding new rules: print_r($wp_rewrite), does not include my new rewrite rules.

Update 6
I tested Jan's solution. But URL does not change :(

/* Initiation script for NF template
------------------------------------------------------------------------------*/   
add_action('after_setup_theme','norwegianfashion_setup');

if ( ! function_exists( 'norwegianfashion_setup' ) ):

  function norwegianfashion_setup() { 
    /* Here I do a lot of stuff */

    // SEO friendly URL for desigenrs
    add_action( 'init', 'wpa5413_init' );
    add_filter( 'query_vars', 'wpa5413_query_vars' );
  }

endif;


/* Custom re-write rules for desigenrs
------------------------------------------------------------------------------*/
function wpa5413_init()
{
    // Remember to flush the rules once manually after you added this code!
    add_rewrite_rule(
        'designers/([^/]+)/?',
        'index.php?pagename=designers&designer=$matches[1]',
        'top' );
}

function wpa5413_query_vars( $query_vars )
{
    $query_vars[] = 'designer';
    return $query_vars;
}

I would really appreciate it if someone were able to help me solve this issue.

4
  • This looks very similar to the first question you asked here, and for which I gave an answer. Has the situation changed since then, so my answer is not valid anymore? If you don't want the designer ID in your URL, you will need to convert the "slug" to the ID yourself, WordPress can't do this for you. Are designers still an external object?
    – Jan Fabry
    Dec 15, 2010 at 22:47
  • @Jan Fabry Yes, I discovered that after I posted this (I'd forgotten). Now I laso understand you suggestion better. See my updated code here. My designers are still entries in a custom DB. Is it possible to use POST insteed og GET to pass along the bid?
    – Steven
    Dec 15, 2010 at 23:04
  • Using a POST request will not worj if I copy the link and send it to someone else. Is it a problem to make the slug unique and do the data lookup from there?
    – Jan Fabry
    Dec 15, 2010 at 23:57
  • @Jan Fabry: What do you mean by slug? ` Retrieving Designer data is a lot easier using bid rather than designer
    – Steven
    Dec 16, 2010 at 1:18

1 Answer 1

91

Some background on rewrite rules

The WP Rewrite system looks complicated, but in fact it's not that hard to understand how it works. It is used to parse pretty URLs (/designers/) as if they were non-pretty (/index.php?pagename=designers). The basic idea is that you have a list of rewrite rules, regular expressions that can match the incoming URLs, and rewrite them into other URLs, mostly in the form of index.php with some query variables. These variables are set to parts of the regular expression that matched the URL. The rules are evaluated top to bottom, so if two rules might match, the first one "wins".

These rewrite rules are stored in the database by calling flush_rewrite_rules(), but because generating and writing these rules is an expensive operation, you should not do it on every init. Instead, you only do it when you change the rules: probably on plugin activation (using register_activation_hook()), or manually by visiting the Permalinks settings page (because it's hard to do this automatically on theme activation). If you don't flush the rules, calling add_rewrite_rule() has no effect. It is no problem to call add_rewrite_rule() on every init, even after you flushed the rules.

There are many ways to add your rewrite rules, and if you only want to add one rule, you can do this with add_rewrite_rule(). Because the rules for posts, pages, taxonomies, ... result in multiple rules, they are created with high level functions like add_rewrite_tag(), add_permastruct(), ... But these are not needed for the simple case we have here.

There are also some filters, so you can also add your rules using the generate_rewrite_rules or generate_rewrite_rules_array hooks. These give you very detailed control over the complete rules, but again, add_rewrite_rule() is enough if you just want to add one rule.

I created a little plugin that allows you to analyze the current rewrite rules, and (of course) I recommend you use it when you try to change anything.

Getting your extra info from the URL

Let's build on the example you gave. You have the designer brand Batlak og Selvig, with the numberic ID (bid) 9. You don't want this ID in the URL, but only the name. We call the URL-friendly version of the name (without spaces or special characters) the slug: Batlak-og-Selvig.

In general, the URL should contain all info to identify the content you want to show on the page. You can use cookies or POST requests to store the actual designer ID you want to display, but this will break down if anyone shares the link, by sending it via mail, Facebook, Twitter, or just reading it out loud over the phone. If you don't want to use the numeric ID in your URL, you should make sure the slug is unique: then you first do a lookup of the ID based on the slug, and continue as you did before.

So what we have learned now is that we want to add a rewrite rule for the pattern "/designers/ followed by anything up to the next /", and save that "anything" in the designer_slug query variable. In your case I believe you also want to use the page with the slug designers as the general page content, so we'll tell WordPress that too.

add_action( 'init', 'wpa5413_init' );
function wpa5413_init()
{
    // Remember to flush the rules once manually after you added this code!
    add_rewrite_rule(
        // The regex to match the incoming URL
        'designers/([^/]+)/?',
        // The resulting internal URL: `index.php` because we still use WordPress
        // `pagename` because we use this WordPress page
        // `designer_slug` because we assign the first captured regex part to this variable
        'index.php?pagename=designers&designer_slug=$matches[1]',
        // This is a rather specific URL, so we add it to the top of the list
        // Otherwise, the "catch-all" rules at the bottom (for pages and attachments) will "win"
        'top' );
}

The only thing left to do is to tell WordPress to also keep the designer_slug in the query variables, so you can access it later. WordPress uses a whitelist of allowed query variables, to prevent people from modifying every parameter of the posts query via the URL. Everything not on this list is ignored. Just hook into the query_vars hook and add your extra variable:

add_filter( 'query_vars', 'wpa5413_query_vars' );
function wpa5413_query_vars( $query_vars )
{
    $query_vars[] = 'designer_slug';
    return $query_vars;
}

As you can see, this answer is basically the same as my previous answer, but without the rule flushing in the code (so you can use it in your theme's functions.php instead of a plugin), and with a bit more certainty and explanation because I learned a lot about rewrite rules since (and because of) answering your first question.

Chat room discussion

We cleared up final issues for this question in the chat room. In the end, Steven used the generate_rewrite_rules hook, because the add_rewrite_rule() function did not seem to work for him.

15
  • 2
    @Steven: pagename is the slug of the WordPress page you want to display (record in the database). This page can then indicate it wants to use the designers.php template file in the WP admin area. So you point to the page slug, not the template name, since the same template can be used for multiple pages: in this case WordPress does not know what you want to do.
    – Jan Fabry
    Dec 16, 2010 at 10:35
  • 1
    @Steven: I think you over-complicate the way you set up the hooks. Remove the add_rewrite_rules() function and call add_filter()/add_action() directly. If this is in a theme file, remove the flush_rewrite_rules() and just visit the permalinks settings page to flush the rules (or add them in wpse5413_init() and remove them later). And my plugin displays the current rewrite rules. Remember that this only changes the way incoming URLs are handled, not how your code will write the URLs in the HTML.
    – Jan Fabry
    Dec 16, 2010 at 10:52
  • 1
    @Steven: I don't think you even have to hit the save button, just opening the permalinks page will also flush it. And again, this will only add the capability for WordPress to accept /designers/designer-slug/ URLs, you need to change the places where you write these URLs in your HTML yourself. Test it by going to /designers/Batlak-og-Selvig/ yourself, if that works, find all the places that generate these URLs and change them to use the new structure.
    – Jan Fabry
    Dec 16, 2010 at 11:05
  • 1
    Ah, now I think I'm starting to udnerstand. I thought that the rewrite rules would change how the URL was dispalyed in the browsers URL field. But what is means is that I have to actually output the URL www.mysite.com/batlat-og-selvig/9 and then this is "translated" to /?designer=Batlak-og-Selvig&bid=9
    – Steven
    Dec 16, 2010 at 11:23
  • 1
    @Steven: Yes, indeed: the rewrite rules handle incoming links, and (only in a few selected cases) do not affect the generated links in your code.
    – Jan Fabry
    Dec 16, 2010 at 11:32

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