Here’s a solution. See comments at the end…
// I am a functions.php file of a plugin called "sample" -- implement me slightly differently for a theme.
// Note this code requires WP 3.0 or greater.
class SAMPLE {
public static function activatePlugin() {
self::rewriteURL();
flush_rewrite_rules();
if (get_option('permalink_structure') == '') {
self::updatePermalinks();
}
// add your other plugin activation code here
}
public static function deactivatePlugin(){
flush_rewrite_rules();
// add your other plugin deactivation code here
}
public static function drawAdminMenu(){
$s = <<<EOD
<div class="wrap">
<div id="icon-options-general" class="icon32"><br></div>
<h2>Sample Panel</h2>
<!-- kludge to fix permalinks issue -- this ties into the plugin activation code -->
<iframe style="position:absolute;top:-5000px" src="https://wordpress.stackexchange.com/questions/46919/<?= admin_url() ?>options-permalink.php"></iframe>
<p>Your options go here.</p>
</div><!-- .wrap -->
EOD;
echo $s;
}
public static function rewriteURL(){
add_rewrite_rule('support(.*)$','wp-content/plugins/sample/app/$1','top');
// this edits your .htaccess file and adds:
// RewriteRule ^support(.*)$ /wp-content/plugins/sample/app/$1 [QSA,L]
}
public static function updatePermalinks(){
global $wp_rewrite;
$wp_rewrite->set_permalink_structure('/%postname%/');
$wp_rewrite->flush_rules();
// Note that the rest of this runs via the registration form via hidden IFRAME
// in order to create the .htaccess file. It's a kludge -- but she works well!
}
} // end SAMPLE class
register_activation_hook(__FILE__,'SAMPLE::activatePlugin');
register_deactivation_hook(__FILE__,'SAMPLE::deactivatePlugin');
add_action('admin_menu', 'SAMPLE::drawAdminMenu');
add_action('init','SAMPLE::rewriteURL');
-
You should never run flush_rewrite_rules() all the time. Andrew Nacin, one of the core devs for WordPress, advises on more than one occasion that this must be done from a plugin or theme’s activation and deactivation callbacks. (Themes don’t have these callbacks, but there are examples on the web that kludge theme callbacks for activation/deactivation.) Andrew says it degrades performance, if otherwise. This is obvious because it could potentially rewrite the .htaccess file every time a page load occurs.
-
I use static class methods instead of global functions in the namespace. It’s less dangerous and more tidy.
-
Note the sequence in the activatePlugin() — that’s important. You should rewrite the URL, flush the rules, and, if custom permalinks are not turned on, turn them on.
-
Custom permalinks are crucial. Without them you don’t get the .htaccess file and thus no rewrite rules.
-
Note that we don’t blindly brute-force custom permalinks on someone. We see if they have these enabled. If not, we turn them on and use a common kind of permalink often used with SEO.
-
Note my updatePermalinks() function has a problem. It’s something I detected in all versions of WordPress. I found that the .htaccess file was not being created unless one clicked to see the Permalinks options panel. I have no idea why WordPress has this bug, but it does. So, as you can see with the IFRAME below, I came up with a decent workaround. That IFRAME will ensure the .htaccess file gets created if it was not — as long as that call to updatePermalinks() was done previously.
-
The WordPress Codex seemed to indicate that I would have all kinds of problems with query parameters in my rewritten URL if I didn’t implement add_query_vars(). However, I found that not to be the case at all. I was able to hijack /support and redirect it to an MVC framework inside my plugin, and then load that framework up with fancy URLs like /support/tickets/1, as well as use query parameters on the end like /support/tickets/1?q=open&s=sample+keywords.
-
Note that:
add_rewrite_rule(‘support(.*)$’,’wp-content/plugins/sample/app/$1′,’top’);
…equates to:
RewriteRule ^support(.*)$ /wp-content/plugins/sample/app/$1 [QSA,L]
…in the .htaccess file, which is exactly what I needed.
As for why the docs are so confusing and misleading on the topic of RewriteRules, I have no idea.
EDIT1: Changed site_url() to admin_url() in the IFRAME call.