3

This is both a question on 'how to do this', as well as 'should I do this'.

I have the ACF-heavy site, where I would like to make the permalinks based on one of the ACF-fields (let's call it foo). If the foo-field has the value 123, then I would like the permalink to that page to be http://example.org/s123 (the s is to avoid the collision with post-id's in the permalink). If that URL is taken, then it should call it http://example.org/s123-1 and so forth.

The ACF-field is set on a custom post type. And the site contains some important information, so I'd rather leave this functionality out, than to use a plugin. So it needs to be something that goes in the functions.php-file.

Is it possible to make this? And is it unwise to mess with WordPress' permalink structure (other that what is allowed in the permalink-settings-page)?

Addition

I can see that if you make CPT's have 'root'-permalinks, that that breaks the permalinks for posts and pages. :-/

... I didn't know that it was this hard to let a CPT have a nice/simple permalink.

  • Can we leave the "Addition" aside? The addition part is already mentioned in some question here, so I'm not sure if it's worth to solve it once more. And the main part - so just to be clear - you want to generate the permalinks for CPT based on foo field instead of post_title? The rest of the permalinks logic should be the same (so creating permalinks only during publishing, not changing them later, and so on)? – Krzysiek Dróżdż May 30 '18 at 12:21
  • Is the permalink linking to a page in particular, is it meant to go to another post or is it meant to append the ACF slug to the CPT url? – DevLime May 31 '18 at 6:46
2
+100

URLs made from CF

Well, you can do this in many ways... One of them would be to use parse_request filter and modify it's behavior, but it can easily mess something up.

Another way would be to use save_post action and modify the posts post_name, so WordPress will work like normal, but the post_name will be generated based on your custom field and not based on title. This is the solution I would go for. Why? Because then you can use WP functions to assure that the links are unique...

So here's the code:

function change_post_name_on_save($post_ID, $post, $update ) {
    global $wpdb;

    $post = get_post( $post_ID );
    $cf_post_name = wp_unique_post_slug( sanitize_title( 's' . get_post_field('foo', $post_ID), $post_ID ), $post_ID, $post->post_status, $post->post_type, $post->post_parent );

    if ( ! in_array( $post->post_status, array( 'publish', 'trash' ) ) ) {
        // no changes for post that is already published or trashed
        $wpdb->update( $wpdb->posts, array( 'post_name' => $cf_post_name ), array( 'ID' => $post_ID ) );
        clean_post_cache( $post_ID );
    } elseif ( 'publish' == $post->post_status ) {
        if ( $post->ID == $post->post_name ) {
            // it was published just now
            $wpdb->update( $wpdb->posts, array( 'post_name' => $cf_post_name ), array( 'ID' => $post_ID ) );
            clean_post_cache( $post_ID );
        }
    }
}
add_action( 'save_post', 'change_post_name_on_save', 20, 3 );

It's not the prettiest one, because ACF fields are saved after the post, so you'll have to overwrite the post_name after the post is already saved. But it should work just fine.

CPTs in root

And there's the second part of your question: How to make CPTs URLs without CPT slug...

WordPress uses Rewrite Rules to parse the request. It means that if the request matches one of the rule, it will be parsed using that rule.

The rule for pages is one of the last rule made to catch most of the requests that didn't match any earlier rules. So if you add your CPT without any slug, then pages rule won't be fired up...

One way to fix this is to register CPT with a slug and then change their links and WPs behavior. Here's the code:

function register_mycpt() {
    $arguments = array(
        'label' => 'MyCPT',
        'public' => true,
        'hierarchical' => false,
        ...
        'has_archive' => false,
        'rewrite' => true
    );
    register_post_type('mycpt', $arguments);
}
add_action( 'init', 'register_mycpt' );

Now the URLs for these posts will look like these:

http://example.com/mycpt/{post_name}/

But we can easily change this using post_type_link filter:

function change_mycpt_post_type_link( $url, $post, $leavename ) {
    if ( 'mycpt' == $post->post_type ) {
        $url = site_url('/') . $post->post_name . '/';
    }

    return $url;
}
add_filter( 'post_type_link', 'change_mycpt_post_type_link', 10, 3 );

Now the URLs will be correct, but... They won't work. They will be parsed using rewrite rule for pages and will cause 404 error (since there is no page with such URL). But we can fix that too:

function try_mycpt_before_page_in_parse_request( $wp ) {
    global $wpdb;

    if ( is_admin() ) return;

    if ( array_key_exists( 'name', $wp->query_vars ) ) {
        $post_name = $wp->query_vars['name'];

        $id = $wpdb->get_var( $wpdb->prepare(
            "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_name = %s ",
            'mycpt', $post_name
        ) );

        if ( $id ) {
            $wp->query_vars['mycpt'] = $post_name;
            $wp->query_vars['post_type'] = 'mycpt';
        }

    }
}
add_action( 'parse_request', 'try_mycpt_before_page_in_parse_request' );
  • First of all... Thanks for your very good and detailed answer. The CPT's in root-section works like a charm and is very clever! But I can't get the URLs made from CF to work. For some reason, then I can't get the value of the CPT-field (it just names the permalink to be s or s-1). I tried accessing the var with get_post_field another function (hooking onto admin_footer) - and that worked as it should. Is it possible that the save_post-hook fires 'too soon' or something? ... Ideally it should write/rewrite the permalink upon every time a user presses 'Publish' or 'Update'. – Zeth Jun 4 '18 at 10:49
  • This part was tested. But you'll have to be careful - post_save is not to soon, BUT, you have to hook with priority set - if you'll use default one (10), then it will be to soon. Setting it for 20 should work fine. That's why there is 20 in here add_action( 'save_post', 'change_post_name_on_save', 20, 3 ); – Krzysiek Dróżdż Jun 4 '18 at 10:52
  • @Zeth have you managed to get it working with changing the priority? – Krzysiek Dróżdż Jun 5 '18 at 9:10
  • 1
    I haven't had time to dive further down into it, - so it's still not working. When I do have time, then I'll write it here in the comment, so you can potential add my bug to your answer (in case other people encounter it as well). Thanks for your answer and for your assistance. – Zeth Jun 5 '18 at 17:41
  • Are there a way that I can see what happens inside the 'save_post'-hook, upon pressing 'Publish' or 'Update'? I'm trying to debug it, to see what happens, but it appears that I can only see what that hooks fires upon making a new post, since it creates the auto-draft. Whenever I publish/update, then the result of 'save_post' is not rendered on the page. So I'm working in the dark, unable to see what happens. I can conclude that this line: ` $cf_post_name = wp_unique_post_slug( sanitize_title( 's' ...` only returns s as a permalink. Any idea on how to debug that? – Zeth Jun 13 '18 at 9:07
0

This may be oversimplifying what I'm reading but rather than using ACF fields to control the permalink, to modify the permalink in the URL the easiest thing to do would be to edit the permalink in the edit page of that particular post/page, rather than doing it programmatically.

If this is more complicated, you may want to look at the WP_Rewrite API and create custom logic with that.

Also, you can rewrite the permalink structure in the CPT declaration

  • I don't think that saying to author "you have to do this manually" is a solution in here. Especially, that you can use ACF forms on front-end ;) – Krzysiek Dróżdż May 31 '18 at 10:27
  • OP: "This is both a question on 'how to do this', as well as 'should I do this'." and I don't think going against the grain is a good idea. WP offers solutions for the problem built-in. So as good WP developers, we should strive to use them to make our solutions as robust as possible, within the context of the framework. – DevLime May 31 '18 at 11:03
  • i totally agree. Just don't think that your answer is a solution - cause you suggest no solution other than "do it manually" ;) And this is answer for any question ("Can I automatically assign new posts to given category?" "Yes, do it manually", and so on). But I totally agree that you should use robust solutions using WP built-in api/functions and so on... – Krzysiek Dróżdż May 31 '18 at 11:05
  • 1
    Fair enough. I'll leave this here anyway just in case it nudges the OP or another user on the right lines. – DevLime May 31 '18 at 12:55

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.