Hi i am currently writing a WordPress plugin that i want to release to the public. I am having a few issues with URL rewrites however.

I have got the plugin to redirect to the plugin page add_action('template_redirect', 'myfuncdisplay') to display custom output.

I also have got the redirect working, www.mydomain.com/index.php?form=contact2 for instance will cause the template_redirect code to be called.

So far so good. However, if i go mydomain.com/form/contact it will actually redirect to an existing page mydomain.com/products/contact-centres.

If i go to mydomain.com/form/contact2 then it will redirect to my plugin.

Checking on the cool plugin Rewrite Analyzer, i get the following:

         formid: contact

       pagename: form/contact

I was playing about the with code, and added the parameter to true... now it seems to work and ignore the fact that i have other pages listed with contact, it now just goes direct to my plugin:

add_permastruct( 'formid', 'form/%formid%', true );

Is the above correct? Does setting the 3rd parameter give priority to my permalink? As far as i can tell, Wordpress will try and match the post-name and disregard any /my/other/url/post-name... seems to do this on all sites which is a little confusing.

Also, in order to get my plugin to work, i need to goto the Permalink options in the admin menu and click save... if i dont do that its as if my rules are not included. Is there any way around that, cant it be done by the plugin on activation or do i need to tell my plugin users to click on the Permalinks menu item in order to activate the plugin redirects?

My code is below, appreciate the help and time for your replies.




/*interface JFormerForWPFormClass
  public function GetForm();
  public function ProcessForm();

class JFormerForWP
    static $errors = false; /* for debugging */

    const DB_VERSION = 1; // This number represents the current version of the plugins table structure. Increment this every time you modify the scheme of the database tables that you create for your plugin.

    static $pluginPath;  
    static $pluginUrl;  
    static $registry;

    static $permastructString = 'form';

    /*public static function RegisterForm($form)
        $self::registry[$id] = $form();

    public static function makeFormFactory($id,$class)
        $classPath = self::$pluginPath . "/forms/{$class}.php";
        require_once $classPath; 
        /*$this->registry[$id] = new $class();*/

    /* called each request */
    public static function init()
        self::$pluginPath = dirname(__FILE__);  // Set Plugin Path  
        self::$pluginUrl = WP_PLUGIN_URL . '/jformer-for-wp/'; // Set Plugin URL  

        /* During the admin_menu action we can add our own items into WordPress' Administration menu. */
        // add_action('admin_menu', array($this,'InsertAdminMenuLink'));
        // add_action('init', array($this,'RouteActions'));

        /* The next two functions run when our plugin is activated or deactivated. In these functions we run the code necessary to install, and possibly uninstall our plugin when the user activates or deactivates it from the Plugins page. */
        // Deactivation function

        // This function runs on each page load to check if our plugin has been updated and, if so, what SQL functions we need to run to upgrade our plugin's tables to the most recent version
        // add_action('init', array($this, 'Update'),1);

        /* add_filter( 'query_vars', 'JFormerForWP::addQueryVars'); */
        add_action( 'template_redirect', 'JFormerForWP::formDisplay'); /* used when rewrite tags are used */

        /* Often times, our plugins need to attach new javascript files or css files to our page. WordPress provides a very clean mechanism for doing so that helps prevent us from needlessly including the same file multiple times (which would slow down your page load time). Scroll down to the StylesAndScripts function to see how to properly attach a JavaScript or CSS file. */
        add_action('wp_print_styles', 'JFormerForWP::styles');
        add_action('wp_print_scripts', 'JFormerForWP::scripts');
        add_shortcode('jformer', 'JFormerForWP::shortcodeHandler');

        /* for ajax functionality */
        add_action('wp_ajax_nopriv_jFormerForWp', 'JFormerForWP::ajaxHandler');
        add_action('wp_ajax_jFormerForWp', 'JFormerForWP::ajaxHandler');

        // add_action('wp_jformer-for-wp_ajax_get', array($this, 'handle_ajax'));
        self::$errors = new WP_Error();

    public static function ajaxHandler()
        $formID = $_POST['jFormerId'];
        echo JFormerForWP::getForm($formID);

    public static function registerForms()
    /*  $files = array();
        $path = getcwd();
        $supported_ext = array('gif', 'jpg', 'jpeg');
        $dir = @opendir($path);
        while ($file = @readdir($dir))
          if (!is_dir($path.$file))
                $splitted = explode('.', $file);
                $ext = strtolower($splitted[count($splitted)-1]);
                if (in_array($ext, $supported_ext)) $files[] = $file;

    public static function getForm($id)
        $formFileName = self::$pluginPath . '/forms/' . $id . '.php';

        if(file_exists($formFileName) == true)
            $contactForm = new JFormer($id, array('submitButtonText' => 'Submit', action => admin_url('admin-ajax.php')));
            return $contactForm->processRequest();  
            // echo 'warning from jformer-for-wp - ' . $formFileName . ' not found.';
            return -1;

    public static function shortcodeHandler($atts, $content)
        extract( shortcode_atts( array(
        'id' => '0',
        'class' => '',
        'text' => ''
        ), $atts ) );

        // echo 'dumping shortcode values. content: ' . $content . ', id: ' . $id . ', class: ' . $class . '<br >';

        if( $text == '' ) 
            return self::getForm($id); /* return the actual form */
        else /* return a link to the form */
            if( $class=='' )
                return '<a href="' . home_url() . '/' . $permastructString . '/' . $id . '" title="'. $title .'">' . $text . '</a>';
                return '<a href="' . home_url() . '/' . $permastructString . '/' . $id . '" title="'. $title .'" class="'. $class .'">' . $text . '</a>';

    public static function setRewriteRules()
        // this two lines are to add external page
        add_rewrite_tag( '%formid%', '([^/]+)');
        add_permastruct( 'formid', 'form/%formid%', true );

        // this is to rewrite rule and direct to an internal page
        // add_rewrite_rule( 'forms/?([^/]*)', 'index.php?pagename=forms-page&formid=$matches[1]', 'top' );

    public static function addQueryVars($vars)
        $vars[] = 'formid';
        return $vars;


    public static function formDisplay()
        if( $formid = get_query_var( 'formid' ) )
            $formCode = self::getForm($formid);
            if($formCode != -1)
                include ( self::$pluginPath . '/html/redirect-page-header.php' );
                echo $formCode;
                include ( self::$pluginPath . '/html/redirect-page-footer.php' );

    public static function activate() 
        /* When you use the global keyword to define a variable, what you are saying is that you want to be able to access a variable that exists outside of our class. The $wpdb variable in WordPress allows us to easily interact with the database that WordPress is installed on. To learn more about how to use the $wpdb Object visit here: http://codex.wordpress.org/Function_Reference/wpdb_Class */
        global $wpdb;


    /* This function will run when the user deactivates the plugin from the WordPress Plugin screen. You might want to put some code in here to remove the tables and options that your plugin created, but, if you do that here, and the user reenables your plugin, they will lose all the data and settings they had previously set. Your alternative, is to create an uninstall.php file and place your cleanup code in there. This code is run when the user deletes your plugin. Open uninstall.php to see how this file works.  */   
    public static function deactivate()

    /* load_plugin_textdomain simply tells WordPress the textdomain we set for our translation strings, and tells it where to look for our language files.  */
    public static function setLanguage()
        load_plugin_textdomain( 'JFormerForWP', null,  dirname( plugin_basename( __FILE__ ) ) . '/languages/');

    public static function insertAdminMenuLink()
        /* Using the add_menu_page and add_submenu_page functions we can add our own pages to the Administration menu. More information about this function can be found here: http://codex.wordpress.org/Adding_Administration_Menus  */

        /* $page = add_submenu_page( 
            'options-general.php' , // This is the parent page that we want to add our configuration page to
            __( 'jFormer Plugin' , 'JFormerForWP' ), // This will be the title of our configuration page
            __( 'jFormer Plugin' , 'JFormerForWP' ), // This will be the name of the link in the menu
            'edit_plugins' , // This is the minimum role that the user must have to be able to see and click on this link. You use this parameter to limit access to only certain WordPress users, i.e. authors cannot access the page but Administrators can. A complete list of user roles and capabilities can be found here: http://codex.wordpress.org/Roles_and_Capabilities
            'JFormerForWP' , // This is a unique string used to identify your configuration page. The URI of this admin page will be options-general.php?page=B2Template. If you have multiple configuration pages, remember that they should all have unique identifiers
            array( $this , 'ConfigPageHtml' ) // This is the function that will be run to genereate the page when the user clicks on the link. 

        );      */

        // Add_submenu_page returns a unique identifier for our page. We can use that identifier in the hooks below so that these functions only run when the user visits our Administration page, and not other pages. This is helpful because we don't want to slow down the users loading time by including stylesheets and javascript across the entire WordPress admin, when it's only being used on our page.
        //add_action( 'admin_print_scripts-'.$page , array( $this, 'AdminScripts' ) );
        //add_action( 'admin_print_styles-'.$page , array( $this, 'AdminStyles' ) );

        // WordPress requires us to "register" our plugins settings before we can let the user update them. This is a security feature to prevent unauthorized addition of settings. 
        //add_action( 'admin_init' , array( $this,'RegisterAdminSettings' ) );


    public static function registerAdminSettings()
        /* The first parameter is the name of the Settings group that is going to be saved. As you can see I have two separate groups of settings that can be saved independently. The second parameter is the name of the setting you want to save. Notice that I've added the plugin name to the beginning of the setting to avoid a naming conflict in the database.*/
        register_setting('JFormerForWPSettings', 'JFormerForWPSettings_option1');


    /*  This is the callback function that we defined above to run when the user clicks on the link to get to our plugin's configuration page. You could simple us an echo statement in this function to output all of your html here, but in order to keep things a little cleaner and more organized, I use the following method to retrieve the html from an external file. */       
    public static function configPageHtml()
        $content = '';
        ob_start(); // This function begins output buffering, this means that any echo or output that is generated after this function will be captured by php instead of being sent directly to the user.
        require_once('html/config.php'); // This function includes my configuration page html. Open the file html/config.php to see how to format a configuration page to save options.
        $content = ob_get_contents(); // This function takes all the html retreived from html/config.php and stores it in the variable $content
        ob_end_clean(); // This function ends the output buffering

        echo $content; // Now I simply echo the content out to the user


    public static function adminScripts()

    public static function adminStyles()

    /* This filter lets us add or own links to this list. I'm using it to provide users with a shortcut to the Plugin's settings page, but it could be used for anything (e.g. to insert a donate link, or link to Plugin suppert forums). The function takes an array of links, adds our link to the array and then returns the modified array. */
    public static function insertSettingsLink($links)
        $settings_link = '<a href="options-general.php?page=JFormerForWP">'.__('Settings','JFormerForWP').'</a>'; 
         array_unshift( $links, $settings_link ); 
         return $links; 

    /* This function adds JavaScript and CSS to the viewer facing side of WordPress. Scroll up to AdminStyles and AdminScripts() to see how these functions work */
    public static function styles()
        wp_enqueue_style('jformer', self::$pluginUrl. 'css/style.css');

    public static function scripts()
        wp_enqueue_script('jformer', self::$pluginUrl . 'jFormer/jFormer.js',array('jquery'));

                'AjaxError'=>__('An error occurred. Please try your action again. If the problem persists please contact the plugin developer.','B2Template')


1 Answer 1


Parameter in add_permastruct()

The third parameter of add_permastruct() is for the is_front parameter. This is a basename, which you can add to the default permalink structure of all your posts in the WordPress admin.

For example, if you have set the permalink structure to /blog/%postname% the word blog will be prepended to your rule. In your case you should set this to false.

Ordering of rewrite rules

add_permastruct() adds the rewrite rule at the end of the list. Therefore the first rule WordPress matches is the one with the pagename. If WordPress can not find a page with the given slug, the filter redirect_canonical will search for a similar one.

This behavior can be changed. Just remove the filter with the following snippet:

// Stop WordPress from auto redirecting if page/post was not found 
remove_filter('template_redirect', 'redirect_canonical');

But in my opionion, In your usecase you should use the filter rewrite_rules_array to add your rewrite rules. There you can modify the complete rewrite array on your own.

add_filter('rewrite_rules_array', array(__CLASS__, 'addRewriteRules'));
public static function addRewriteRules($rules) {
    $newRules = array(
        // Add your rules e.g.
        'search/?$' => 'index.php?pagename=search',

    return $newRules + $rules; // add the rules on top of the array

Flushing rewrite rules

You can flush the rewrite rules on plugin acitvation, just use the function flush_rewrite_rules(). If you don't pass a parameter to the function, the .htaccess file will also be recreated. If you set the parameter to false, only the database rewrite transient will be updated.

Actions / Filters in classes

You don't have to write the class name everytime you call a static method or use a filter or action inside a class.

Instead of:

echo JPFormerForWP::methodName();

add_action('wp_print_scripts', 'JFormerForWP::scripts');

You can use:


add_action('wp_print_scripts', array(__CLASS__, 'scripts'));

This has some advantages. If you change the name of your class, you don't have to replace every occurrence of JFormerForWP. This will also help if you're moving the method to another class. You don't have to worry about the naming.

For action and filters, WordPress uses the php function call_user_func behind the scenes which allows different ways of calling class methods.

  • Thanks for the reply Roman, I will mention your help in the plugin source. Very good info. One thing is that when I set the third parameter to false again it doesn't find the template redirect just a page with a matching slug. Also I flushed the rewrite rules as per the code on activation... unless that is not getting called? Regards, Chris
    – user8931
    Sep 25, 2011 at 11:04
  • Next time, please add this as a comment to the answer and re-read the Ordering of rewrite rules below. The first rule matches and this will be the WordPress pagename default rule. It's just a luky break that the rule "works" with the basename in it (cause of the canonical filter discribed below). Use the rewrite_rules_array filter here. Please open a new question about the flush_rewrite_rules() problem with your activation code and used hooks.
    – Roman
    Sep 25, 2011 at 11:18
  • @Chris Please accept the answer, so it would not stay open the hole time. Thanks.
    – Roman
    Sep 26, 2011 at 8:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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