0

Working on a client site I've encounter my worst enemy... WP URL rewrite :(. I actually can't figure it out and after many days trying and searching, I can't make it work.

What I'm trying to get is a permalink like this company.com/product/%product-name%/ regardless of the /parent/child/ nesting depth. I do need the functionalities of the hierarchical pages for other reasons, but it should not appear in the URL.

Here is my set up:

  • Wordpress 4.4.1
  • Permalink settings set to Post name

  • I created a hierarchical custom post type product with rewrite parameter to false.

    $args = array(
        'label'                 => __( 'Product', 'domain' ),
        'description'           => __( 'Company products', 'domain' ),
        'labels'                => $labels,
        'supports'              => array( 'title', 'editor', 'excerpt', 'thumbnail', 'revisions', 'page-attributes' ),
        'taxonomies'            => array( 'category', 'post_tag' ),
        'hierarchical'          => true,
        'public'                => true,
        'show_ui'               => true,
        'show_in_menu'          => true,
        'menu_position'         => 5,
        'show_in_admin_bar'     => true,
        'show_in_nav_menus'     => false,
        'can_export'            => true,
        'has_archive'           => true,
        'exclude_from_search'   => false,
        'publicly_queryable'    => true,
        'rewrite'               => false,
        'capability_type'       => 'page',
    );
    register_post_type( 'product', $args );
    

At this point the products don't have a pretty permalink, they look like this and they both work:

  1. First level: company.com/?product=my-first-product -> ok
  2. Second level: company.com/?product=my-first-product/child-product -> ok

After that, I registered the rewrite rule and permastruct then flushed the rewrite rules by saving changes in the permalink settings page

    function bvt_product_rewrite_rule() {
        add_rewrite_rule( 
            '^product/([^/]+)/?$',
            'index.php?post_type=product&pagename=$matches[1]',
            'top'
        );
        add_permastruct( 'product', '/product/%product%/' );
    }
    add_action( 'init', 'bvt_product_rewrite_rule', 10 );

Now the permalinks where correctly displayed for first level page but not correct for child page. Also both levels gave me a 404 error.

  1. First Level: company.com/product/my-first-product/ -> 404
  2. Second Level: company.com/product/my-first-product/child-product/ -> 404

I also tried a solution given here https://wordpress.stackexchange.com/a/101077/86838 where it allowed to remove the parent slug from child permalink, but I was still unsuccessful

function bvt_product_flatten_hierarchies( $post_link, $post ) {
    if ( 'product' != $post->post_type ) {
        return $post_link;
    }

    $uri = '';
    foreach ( $post->ancestors as $parent ) {
        $uri = get_post( $parent )->post_name . "/" . $uri;
    }

    return str_replace( $uri, '', $post_link );
}
add_filter( 'post_type_link', 'bvt_product_flatten_hierarchies', 10, 2 );

I installed two plugins debug-bar and monkeyman-rewrite-analyzer to analyse the rewrite and query, but I keep can't make it work.

My last try was with wp-permastructure plugin with allow to set custom permalink to custom post type. Which enables the custom setting permastruct in the rewrite array option of the register_post_type.

[...]
'publicly_queryable'    => true,
'rewrite'               => array(
    'permastruct'   => '/%postname%/',
),
'capability_type'       => 'page',
[...]

With this setting and both previous custom rewrite/permatruct/flatten_hierarchies desabled, it worked as stated by the plugin description

  1. First Level: company.com/my-first-product/ -> ok
  2. Second Level: company.com/child-product/ -> ok

But now the /product/ level is missing, and if I try to put it in the 'permastruct' => '/product/%postname%/' config it brings me back to both a 404 error.

Which leads to my main question.

Is it possible to do what I need to or only as close as I could go with the latest plugin ?

Thanks already for anybody who will take the time to answer me, and maybe save me from my poor situation. Thanks

PS I can't insert more than two links in the body so here is the mentioned plugin links:

https://wordpress.org/plugins/debug-bar/
https://wordpress.org/plugins/monkeyman-rewrite-analyzer/
https://wordpress.org/plugins/wp-permastructure/
  • One thing that will mess with rewrites are templates. Make sure nothing is standing in your way there too. Personally I would stay away from plugins at first, and try to get your rewrite to work with the CPT argument rewrite. – Nathan Powell Jan 15 '16 at 2:49
  • Hey Nathan, I do agree with you about plugins. I always try to do it as simple as possible but this time I was quite desperate. – Kuuak Jan 15 '16 at 8:54
1

You're pretty close. Your rewrite rule is using the wrong query var, pagename should be just name.

Here's a version that works for me on a fresh 4.4.1 install and twentysixteen theme-

function bvt_product_init() {
    $args = array(
        'label'                 => __( 'Product', 'domain' ),
        'description'           => __( 'Company products', 'domain' ),
        'supports'              => array( 'title', 'editor', 'excerpt', 'thumbnail', 'revisions', 'page-attributes' ),
        'taxonomies'            => array( 'category', 'post_tag' ),
        'hierarchical'          => true,
        'public'                => true,
        'show_ui'               => true,
        'show_in_menu'          => true,
        'menu_position'         => 5,
        'show_in_admin_bar'     => true,
        'show_in_nav_menus'     => false,
        'can_export'            => true,
        'has_archive'           => true,
        'rewrite'               => array( 'slug' => 'product' ),
    );
    register_post_type( 'product', $args );

    add_rewrite_rule( 
        '^product/([^/]+)/?$',
        'index.php?post_type=product&name=$matches[1]',
        'top'
    );
}
add_action( 'init', 'bvt_product_init' );

function bvt_product_flatten_hierarchies( $post_link, $post ) {
    if ( 'product' != $post->post_type ) {
        return $post_link;
    }
    $uri = '';
    foreach ( $post->ancestors as $parent ) {
        $uri = get_post( $parent )->post_name . "/" . $uri;
    }
    return str_replace( $uri, '', $post_link );
}
add_filter( 'post_type_link', 'bvt_product_flatten_hierarchies', 10, 2 );

One potential issue to look out for- hierarchical post types let you create posts with the same slug that have different parents. This normally works because they are queried by their parent/child paths. Without having that parent/child relationship in the URL structure, you can create posts that can never be queried on the front end if the slug matches an existing post. Just something to keep in mind.

  • Hello Milo, thank you very much, it was indeed really close. By changing only pagename by name it worked. About the same slug issue, I took care of that by forcing a unique sku number in the slug. Thanks again – Kuuak Jan 15 '16 at 8:56

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.