2

Introduction

When uploading a file (stackexchange.jpg) to the media library in a WordPress site, there are two relevant links.

  1. Direct link to file
    http://example.com/wp-content/uploads/stackexchange.jpg

  2. Attachment page
    http://example.com/stackexchange/

My question deals with the second one: attachment page.

What I Want to Do

I would like to add the file extension in the attachment page permalink. For example, instead of

http://example.com/stackexchange/

I would like

http://example.com/stackexchange-jpg/

Why This Would be Useful

This would be very useful for a couple of reasons. For one, it won't be hogging permalinks in case I want to use it for a post or a page, etc. For another, I can then upload another file called stackexchange.png. Then, I would have:
http://example.com/stackexchange-jpg/
and
http://example.com/stackexchange-png/

And again, I can still use http://example.com/stackexchange/ for an actual page.

I do realize that I can go through and edit every single file's slug manually, which in turn would indeed change the attachment page permalink, but I think it almost goes without saying that I wouldn't need to actually make a thread if I was looking to do that.

Question

Is there a function I can write to make it so that whenever a file is uploaded, it reflects the changes I'm looking for? Is this even possible? If so, how do I do it? If you know how to edit the default permalink, but you don't know how to add the file extension part, I can most likely still work with that information.

I'm thinking this is probably not possible without tampering with core files (which would be a deal breaker), but hopefully it is.

Thanks for reading and for any help.

1

Great question! I've been thinking about this myself and your question prompted me to dig into it.

There is a filter wp_insert_attachment_data than can be used to fix the slug name for uploaded files. It is called for attachments shortly before the attachment is inserted into the database in post.php.

The following sample will append the mime type to the slug name, for example image-jpg will be added to a jpeg image title.

    /**
     * Filter attachment post data before it is added to the database
     *  - Add mime type to post_name to reduce slug collisions
     *
     * @param array $data    Array of santized attachment post data
     * @param array $postarr Array of unsanitized attachment post data
     * @return $data, array of post data
     */
    function filter_attachment_slug($data, $postarr)
    {
        /**
         * Only work on attachment types
         */
        if ( ! array_key_exists( 'post_type', $data ) || 'attachment' != $data['post_type'] )
            return $data;

        /**
         * Add mime type to the post title to build post-name
         */
        $post_title = array_key_exists( 'post_title', $data ) ? $data['post_title'] : $postarr['post_title'];
        $post_mime_type = array_key_exists( 'post_mime_type', $data ) ? $data['post_mime_type'] : $postarr['post_mime_type'];
        $post_mime_type = str_replace( '/', '-', $post_mime_type );
        $post_name = sanitize_title( $post_title . '-' . $post_mime_type );

        /**
         * Generate unique slug for post name
         */
        $post_ID = array_key_exists( 'ID', $data ) ? $data['ID'] : $postarr['ID'];
        $post_status = array_key_exists( 'post_status', $data ) ? $data['post_status'] : $postarr['post_status'];
        $post_type = array_key_exists( 'post_type', $data ) ? $data['post_type'] : $postarr['post_type'];
        $post_parent = array_key_exists( 'post_parent', $data ) ? $data['post_parent'] : $postarr['post_parent'];

        $post_name = wp_unique_post_slug( $post_name, $post_ID, $post_status, $post_type, $post_parent );
        $data['post_name'] = $post_name;

        return $data;
    }

    /**
     * Adjust slug for uploaded files to include mime type
     */
    add_filter( 'wp_insert_attachment_data', 'filter_attachment_slug', 10, 2 );
  • Absolutely brilliant. You, sir, are a hero and have been most helpful. Greatly appreciated. I have an idea for a slight tweak; may I have your permission to expand upon your work and post my results? Regardless, thanks again! – Leon Williams Sep 5 '16 at 19:30
  • P.S. I did upvote your answer, but I guess you won't see it until I gain a little more rep. – Leon Williams Sep 5 '16 at 19:32
  • Please do! I'm curious to see where you take it! I have the this code fragment in my test area at the moment. Waiting to deploy in my live environment and now that you have some ideas to enhance I'm going to hold off to see what you come up with. – KenB Sep 5 '16 at 19:32
0

This is an expansion upon KenB's answer; I've just included minor edits.

The Difference

Let's consider the file stackexchange.png being uploaded to the media library.

KenB's original solution

Result: http://example.com/stackexchange-image-png/
This taps into the database for posts and pulls from the post_mime_type column, which in this case results in image/png.

+ These edits

Result: http://example.com/stackexchange-png/
Instead of pulling from post_mime_type, we can pull from guid (which in this case, results in http://example.com/wp-content/uploads/stackexchange.png), then we can strip off everything but the file extension and work with that.

Code

So now that I've explained the difference between that solution and this one, here's how to do it. I've included and done a little editing to DisgruntledGoat and redanimalwar's answer to a question on StackOverflow.

Everything I disabled in KenB's solution, I left in the code, but just commented it out, so you can see what I actually did.

/**
 * This finds the last '.' in a URI and returns the string after that.
 * For example, in the case of 'example.com/photo.jpg', 'jpg' is returned. 
 *
 * @author DisgruntledGoat
 * @author redanimalwar
 *
 * @link https://stackoverflow.com/a/1361752/5675729
 */
function get_file_extension($uri) {
    $position = strrpos($uri, '.');
    $extension = $position === false ? $uri : substr($uri, $position + 1);
    return $extension;
}

/**
 * Filter attachment post data before it is added to the database
 *  - Add mime type to post_name to reduce slug collisions
 *
 * @author KenB
 *
 * @link https://wordpress.stackexchange.com/a/238205/88601 Original solution
 * @link https://wordpress.stackexchange.com/a/238291/88601 This variation
 *
 * @param array $data    Array of santized attachment post data
 * @param array $postarr Array of unsanitized attachment post data
 *
 * @return $data, array of post data
 */
function filter_attachment_slug($data, $postarr) {
    /**
     * Only work on attachment types
     */
    if ( ! array_key_exists( 'post_type', $data ) || 'attachment' != $data['post_type'] )
        return $data;

    /**
     * Add mime type to the post title to build post-name
     */
    $post_title = array_key_exists( 'post_title', $data ) ? $data['post_title'] : $postarr['post_title'];

    /**
     * This was in KenB's original solution, but was removed by Leon Williams,
     * as this version does not deal with the MIME type.
     */
    //$post_mime_type = array_key_exists( 'post_mime_type', $data ) ? $data['post_mime_type'] : $postarr['post_mime_type'];

    /**
     * This this takes the MIME type, for example, 'image/jpg', into 'image-jpg'.
     * This was in KenB's original solution, but was removed by Leon Williams,
     * as this version does not deal with the MIME type.
     */
    //$post_mime_type = str_replace( '/', '-', $post_mime_type );

    /**
     * Access the 'guid' column from the database (which retrieves the link to the file),
     * then send it to get_file_extension().
     *
     * @author Leon Williams
     */
    $post_file_extention = get_file_extension($postarr['guid']);

    // Instead of tacking on the MIME type, tack on the file extension that we just obtained.
    $post_name = sanitize_title( $post_title . '-' /*. $post_mime_type*/ . $post_file_extention);

    /**
     * Generate unique slug for post name
     */
    $post_ID = array_key_exists( 'ID', $data ) ? $data['ID'] : $postarr['ID'];
    $post_status = array_key_exists( 'post_status', $data ) ? $data['post_status'] : $postarr['post_status'];
    $post_type = array_key_exists( 'post_type', $data ) ? $data['post_type'] : $postarr['post_type'];
    $post_parent = array_key_exists( 'post_parent', $data ) ? $data['post_parent'] : $postarr['post_parent'];

    $post_name = wp_unique_post_slug( $post_name, $post_ID, $post_status, $post_type, $post_parent );
    $data['post_name'] = $post_name;

    return $data;
}

/**
 * Adjust slug for uploaded files to include file extension
 */
add_filter( 'wp_insert_attachment_data', 'filter_attachment_slug', 10, 2 );

Alternative (using post_mime_type instead of guid)

You could also just take the png off of image/png (by having get_file_extension() look for / instead of .), which works just as well. It's just, with this (the way I have it), you can also use the get_file_extension() for other links as well.

Word of Caution

With a little bit of testing, I discovered that in both the original solution and this version, this filter also fires anytime you push the "Update" button while editing an attachment.

For example, let's say you're editing an attachment by the name of example.png and it has the custom slug of example-file-slug (let's suppose you gave it that custom slug a long time ago). Today you're editing the description and when you're finished with the description and hit "Update", this filter will fire and will automatically change the slug from example-file-slug to example-png in addition to updating the description. If you try to go back and change the slug to anything else, again, the filter will fire when you hit "Update" and will override your attempt to change the slug.

This poses no problem for me personally, but it may be something to consider for others.

Workarounds
If you want to change the slug name, you would have to disable this filter (say, by commenting it out) then save your changes to the attachment, then enable the filter again. Alternatively, you could edit the database directly.

I'm sure there's a way to code a solution, but I don't currently have one.


Thanks to these fine people:

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.