I have a weird problem created by a bad requirement. (I say this because a lot of comments telling me this is a bad thing to want to do are not helpful. I know. I don’t want to do it.)

Is it possible to change the permalink separator ( / ) to a dash ( - ), specifically for hierarchical pages?

So that URLs for pages would be like:
example.com/page-child-page-grandchild-page

OR EVEN

example.com/page–child-page–grandchild-page

WHY?

I need the child-parent relationship in order to display breadcrumbs and lists of child and sibling pages.

But the client/boss (for reasons that confuse me) does not want (and i quote) “so many folders.”

I think that the site might be compiled out to a static web server when it is done, so that almost makes sense. Please don’t tell me this is dumb. I’m aware it is dumb.

ALTERNATIVE IDEAS

Don’t use WP’s page hierarchy. Use ACF or Posts-to-Posts (or similar) to create an alternative parent-child relationship, and then roll my own Breadcrumb and Child-Page-Listing features.

Comments on whether that is a fundamentally better idea are welcome.

Thanks.

1 Answer
1

You can replace the dash with something else, preferably not a hyphen, because that would be hard to parse back. In the following example, I use a dot.

You have to change two parts: the outgoing permalinks (hook: "{$post_type}_link") and the request (hook: request) parsing for “incoming permalinks”.

The latter is harder, because it is very difficult to prevent collisions with other post types and taxonomies.

Here is a simple class with handlers for both cases. I have tested it with the post type page only and with permalink settings that put normal posts (post type post) at the root level.

class Undash_Permalinks
{
    /**
     * What to use instead of /
     *
     * @var string
     */
    private $replacement;

    /**
     * Undash_Permalinks constructor.
     *
     * @param string $replacement
     */
    public function __construct( $replacement )
    {
        $this->replacement = $replacement;
    }

    /**
     * Change the output URL
     *
     * @wp-hook page_link
     * @param   string $url
     *
     * @return string
     */
    public function output( $url )
    {
        $home     = home_url( "https://wordpress.stackexchange.com/" );
        $start    = strlen( $home );
        $sub      = substr( $url, $start );
        $replaced = str_replace( "https://wordpress.stackexchange.com/", $this->replacement, $sub );

        return $home . $replaced;
    }

    /**
     * Help WordPress to understand the requests as page requests
     *
     * @wp-hook request
     * @param   array $request
     *
     * @return array
     */
    public function input( array $request )
    {
        if ( empty ( $request[ 'name' ] ) )
            return $request;

        if ( FALSE === strpos( $request[ 'name' ], $this->replacement ) )
            return $request;

        $path = str_replace( $this->replacement, "https://wordpress.stackexchange.com/", $request[ 'name' ] );
        $page = get_page_by_path( $path );

        if ( ! $page )
            return $request;

        // Convince WP that we really have a page.
        $request[ 'pagename' ] = $path;
        unset( $request[ 'name' ] );

        return $request;
    }
}

Using this class is pretty simple:

add_action( 'after_setup_theme', function() {
    $undash = new Undash_Permalinks( '.' );
    add_filter( 'page_link', [ $undash, 'output' ] );
    add_filter( 'request', [ $undash, 'input' ] );
});

This might need some changes for other post types. For attachments you can either use the root directory as upload directory, or just live with the “folder”. Requests for them do not pass WordPress, so a filter would help at all.

Tags:

Leave a Reply

Your email address will not be published. Required fields are marked *