1

How can I add a custom rewrite rule so I can catch paths like these:

/products/imports/filters
/products/imports/elements
/products/imports/parts

And point them to a template file, for examples:

sector.php

My code:

function sector_rewrite_rule() {
    add_rewrite_rule(
        '(.?.+?)/([^/]*)/([^/]*)/?$',
        'index.php?pagename=$matches[1]/$matches[2]&sector=$matches[3]',
        'top'
    );
}
add_action('init', 'sector_rewrite_rule', 10, 0);

But I get 404 when I tried with the URLs above.

I have created a standard page called imports created under products already:

products/
   imports/

So I want to catch any word right after this page:

/products/imports/
1

You need to create query_var for sector and you need to create a rewrite rule with RegEX. I created a plugin for this. Create a file and add into the wp-content/plugins folder and activate it.

<?php

/*
Plugin Name: Custom Rewrite
Plugin URI: https://serkanalgur.com.tr
Description: Custom Rewrite
Version: 1
Author: Serkan Algur
Author URI: https://www.serkanalgur.com.tr
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
*/

class FilterRewriteRule
{
    public function init()
    {
        $this->InitRewriteRules();
        add_filter('query_vars', [$this,'add_query_for_sector']);
        add_filter('template_include', array($this, 'filter_template'), 99);
    }

    private function add_query_for_sector($vars)
    {
        $vars[] = 'sector';
        return $vars;
    }

    private function InitRewriteRules()
    {
        add_rewrite_rule(
            'products/import/([^/]*)/?$',
            'index.php?pagename=products/import&sector=$matches[1]',
            'top'
        );

        add_rewrite_rule(
            'products/import/page/([0-9]{1,})/([^/]*)/?$',
            'index.php?paged=$matches[1]&pagename=products/import&sector=$matches[2]',
            'top'
        );
    }

    public function filter_template($template)
    {
        global $wp;
        if (get_query_var('sector') && is_page('import')) {
            $custom_template = locate_template('sector.php');
            if ($custom_template) {
                return $custom_template;
            }
        }
        return $template;
    }
}

register_activation_hook(__FILE__, function () {
    $writing_ideas = new FilterRewriteRule();
    $writing_ideas->init();
    flush_rewrite_rules();
});

add_rewrite_rule add the rewrite rule with pagination support. filter_template locate your sector.php file and redirect the request to this file. query_vars adds sector query to WordPress system.

2
  • thanks. it works with fixed prefixed 'products/imports/([^/]*)/?$', . I need to make this bit products/imports/ dynamic... – laukok Jun 11 at 0:40
  • You are welcome. You can change first products/imports/([^/]*)/?$ to ([^/]*)/([^/]*)/?$ and index.php?pagename=products/import&sector=$matches[1] to index.php?pagename=$matches[1]t&sector=$matches[2] but, that can be effect WordPress base rewrite rules. Please be careful when you change rewrite rules. – Serkan Algur Jun 11 at 1:18
1

Try these steps, which worked well for me:

  1. Use a more specific rewrite rule RegEx pattern like so which ensures the current URL path is in the form of products/imports/<word>:

    function sector_rewrite_rule() {
        add_rewrite_rule(
            // matches products/imports/filters, but not products/imports/filters/foo
            '^products/imports/([^/]+)/?$',
    
            // ensure pagename is in the form of <parent>/<child>
            'index.php?pagename=products/imports&sector=$matches[1]',
    
            'top'
        );
    }
    
  2. Register sector as a public query var via the query_vars hook: (public query vars are those that will be read from GET/URL and POST parameters)

    add_filter( 'query_vars', 'sector_query_vars' );
    function sector_query_vars( $vars ) {
        $vars[] = 'sector';
    
        return $vars;
    }
    
  3. Now that the rewrite rule and query var has been both added, we just need to ensure the sector.php template is used on the products/imports/<word> endpoints. So since we're targeting a Page (post of type page), then I'm using the page_template hook, but you could instead use the template_include hook if you want to:

    add_filter( 'page_template', 'my_sector_page_template' );
    function my_sector_page_template( $template ) {
        if ( is_page( 'imports' ) && get_query_var( 'sector' ) ) {
            // this assumes the template is in the active theme directory
            // and just change the path if the template is somewhere else
            $template = locate_template( 'sector.php' ); // use a full absolute path
        }
    
        return $template;
    }
    

Remember to flush the rewrite rules after you're done with step 1 above! Just visit the Permalink Settings page without having to click on the Save Changes button.

Also, if you need to support pagination (e.g. products/imports/filters/page/2), then you'd need to adjust the above rewrite rule RegEx pattern and query for that.

2
  • thanks. it works with a fixed prefixed 'products/imports/([^/]*)/?$', . I need to make this bit products/imports/ dynamic... because the slug can be changed by someone who manages the contents – laukok Jun 11 at 0:41
  • 1
    Yes, I actually did noticed that. But anyway, I've added another answer, so I hope the solutions there would also help you. Happy coding! – Sally CJ Jun 11 at 3:36
1

An alternate Solution

If you're concerned about the Page slug being changed later on, then you could instead use add_rewrite_endpoint(), but this means the URL would need an extra path, e.g. /sector as in example.com/products/imports/sector/filters.

So the steps are identical, except that you no longer need to manually hook on query_vars because add_rewrite_endpoint() will, by default, do that automatically for you:

  1. Register the sector endpoint for all Pages:

    add_action( 'init', 'my_sector_init' );
    function my_sector_init() {
        add_rewrite_endpoint( 'sector', EP_PAGES );
    }
    

    Remember to remove the add_action('init', 'sector_rewrite_rule', 10, 0); in your code.. and as I already said before, flush the rewrite rules! :)

  2. Load the sector.php template on those custom endpoint pages:

    add_filter( 'page_template', 'my_sector_page_template' );
    function my_sector_page_template( $template ) {
        if ( get_query_var( 'sector' ) ) {
            $template = locate_template( 'sector.php' );
        }
    
        return $template;
    }
    

Other Option when using add_rewrite_rule() with a specific Page slug or path

Store the Page ID in a variable (or database option if you want to) and the previous path in a post metadata, and whenever the Page or its parent is updated, specifically if the slug is changed, then you can programmatically flush the rewrite rules.

So based on my original answer, in step 1, we'll call flush_rewrite_rules() only if applicable:

function sector_rewrite_rule() {
    $page_id = 123;

    // Ensure that the post exists.
    if ( ! get_post( $page_id ) ) {
        return;
    }

    $post_ancestors = get_post_ancestors( $page_id );
    $previous_slug  = get_post_meta( $page_id, '_prev_path', true );

    // Build the hierarchical path.
    $post_ancestors[] = $page_id;
    $current_slug = implode( '/', array_map( function ( $id ) {
            return get_post_field( 'post_name', $id );
    }, $post_ancestors ) );

    add_rewrite_rule(
        '^' . preg_quote( $current_slug ) . '/([^/]+)/?$',
        'index.php?page_id=' . $page_id . '&sector=$matches[1]',
        'top'
    );

    global $pagenow; // this is defined on admin pages
    if ( 'options-permalink.php' !== $pagenow && $current_slug !== $previous_slug ) {
        update_post_meta( $page_id, '_prev_path', $current_slug );
        flush_rewrite_rules();
    }
}

And step 2 remains the same, but step 3 would be (note the $page_id):

add_filter( 'page_template', 'my_sector_page_template' );
function my_sector_page_template( $template ) {
    $page_id = 123;

    if ( is_page( $page_id ) && get_query_var( 'sector' ) ) {
        $template = locate_template( 'sector.php' );
    }

    return $template;
}

So you can try those and just replace the value of $page_id with the correct ID.

1
  • thanks for the great answer! – laukok Jun 12 at 22:09

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.