Avoid WordPress Image Size Suffix in Filenames

“Is it safe to use WordPress image size suffix pattern in image filenames?”

Have you ever asked yourself this? I guess not. With a few years of WordPress development experience behind my back, the question didn’t come up for me either until recently. When I polished an old code, I’ve run into this relatively rare, but more interesting issue. Let’s dig into it.

When uploading images or manipulating them programmatically, for example by the Regenerate Thumbnails plugin, WordPress creates a couple of different size alternatives from the original image, known as thumbnails.

These variations use the following naming pattern: image-{width}x{height}, where -{width}x{height} is the image size suffix. As a result, when we upload a file called “image.jpg”, in addition to the original image, we will get a bunch of files like image-150×150.jpg or image-300×550.jpg. The number of thumbnails and their sizes depend on your settings, installed plugins, and your theme.

But what happens if the original filename contains the image size pattern, and we already have a generated file with the same name? Should we blindly trust WordPress to handle this correctly? We’ll see. It may seem unnecessary to deal with file naming, but we’ll hardly check the image names one by one when uploading. It’s conceivable to have such suffixes in filenames of stock photos or content accidentally taken from Google image search. Therefore, it is good to know what happens in such cases.

Let’s get to the point

To find out the truth as quickly as possible, let’s do a simple test. We need two different images: “cat.jpg” and “cat-150×150.jpg” and a registered image size with a width and height of 150 pixels. (This is the default thumbnail size in WordPress).

Upload the “cat.jpg” file first, then the “cat-150×150.jpg” file. There is no surprise: we can see both images in the Media Library and their corresponding size-variations in the uploads folder. WordPress did a great job as it renamed the original “cat-150×150.jpg” file to “cat-150×150-1.jpg” to avoid name collisions.

Renamed File after Upload

Now, let’s repeat the process in the reverse order. First, upload the “cat-150×150.jpg”, then the “cat.jpg” file. Once finished, we can see that something went wrong. There are two identical images in the Media Library. However, there is only one “cat-150×150.jpg” file in the uploads folder. The auto-resized version of “cat.jpg” (cat-150×150.jpg) has overwritten our original “cat-150×150.jpg” file. This time, WordPress didn’t rename the uploaded file. One of our uploaded files is missing, and our database became inconsistent.

Overwritten File after Upload

Furthermore, if we delete the “cat.jpg” image from the Media Library the “cat-150×150.jpg” file will also be removed from the uploads folder, leaving a broken image behind in the Media Library.

Broken Media Library

Why does this happen?

WordPress uses the wp_unique_filename() function during uploads. It ensures that the uploaded files have sanitized and unique names for the given directory, to prevent overwriting our current files. However, this function compares only the original filename to the existing ones in the selected folder. It doesn’t deal with the potentially generated thumbnails – it doesn’t see the future. That’s why a file, with WordPress-like image size suffix pattern in its name, can be easily overwritten by an auto-generated file.

How to avoid image size in the original filename?

Investigating filenames by hand before uploading makes no sense. As WordPress developers, we can create a function to modify the name of the uploaded file on the fly to prevent such conflicts. Hook our function to the sanitize_file_name filter, which is called at the beginning of the wp_unique_filename() function. On the one hand, this is a good thing as sanitization is the first step when creating safe and unique filenames. The sanitized name is used to check if the filename is unique, so we don’t have to do it.

On the other hand, we should be cautious, as our function runs after the core sanitization process. That makes WordPress unable to help by keeping things clean. It’s our job to ensure that we don’t use any special characters.

Here is our little code snippet which does the job. You can paste it into your functions.php of the (child) theme:

/**
 * Modify the WordPress image size suffix pattern 
 * in the uploaded jpg, png and gif filenames.
 *
 * @param string $filename Sanitized filename.
 * @param string $filename_raw Filename prior to sanitization.
 * @return string Filtered filename.
 */
function lwp_1866_replace_size_pattern( $filename, $filename_raw ) {
    if ( !preg_match( 
            '/\.(?:jp(?:e)?g|png|gif)$/', 
            $filename 
    ) ) return $filename;
    
    return preg_replace( 
            '/(\d+)x(\d+)/', 
            'w$1xh$2$3', 
            $filename
    );
}

add_filter( 'sanitize_file_name', 'lwp_1866_replace_size_pattern' );

But what’s happening exactly? We catch all the {width}x{height} patterns in the names of JPG, PNG, and GIF files and replace them with w{width}xh{height} pattern. For better naming consistency, we modify matching patterns anywhere in the filename, not just in the suffix. Our new replacement doesn’t use any conflict-prone special characters.

You can check the source code of the sanitize_file_name() function as a cheat sheet for special characters when creating a custom pattern. However, do not call sanitize_file_name() itself in your function, else you’ll run into an infinite loop.

The result what we’ve got after applying the code:

Unchanged Title

Minor Glitch in Attachment Title

Eagle-eyed visitors might spot the issue on the last screenshot. The attachment title remained unchanged. To understand what’s happening we should again take a look under the hood.

When uploading an image to the Media Library by hand, we perform it via a file input field <input type="file" ... > using a POST method. WordPress uses the media_handle_upload() function to handle the POST request itself, and create the attachment post. We will find the reason when digging into its source code:

function media_handle_upload( ... ) {

    ... some code ...
    
    $file = wp_handle_upload( $_FILES[$file_id], $overrides, $time);

    if ( isset($file['error']) )
        return new WP_Error( 'upload_error', $file['error'] );

    $name = $_FILES[$file_id]['name'];
    $ext  = pathinfo( $name, PATHINFO_EXTENSION );
    $name = wp_basename( $name, ".$ext" );

    $url = $file['url'];
    $type = $file['type'];
    $file = $file['file'];
    $title = sanitize_text_field( $name );
    $content = '';
    $excerpt = '';
    
    ... some code ...    
 
}

The uploaded temporary files are stored in the $_FILES superglobal array. It looks like this:

Array
(
    [async-upload] => Array
        (
            [name] => cat-500x500.jpg
            [type] => image/jpeg
            [tmp_name] => ...\tmp\phpA388.tmp
            [error] => 0
            [size] => 1479491
        )
)

The async-upload is the name of the following file input field:

<input type="file" name="async-upload" id="async-upload">

This is part of the upload form, which WordPress generates for file uploads.

The $_FILES['async-upload'] array stores data about the previously uploaded temporary file. This data is passed to wp_handle_upload() function. As the name suggests, it handles the actual upload, including the sanitization. The function returns an array, which is stored in the $file variable. (See the inserted code fragment from the media_handle_upload() function above.)

Its structure is similar to $_FILES array:

Array
(
    [file] => ...\www\wordpress/wp-content/uploads/2018/09/cat-w150xh150.jpg
    [url] => http://.../wordpress/wp-content/uploads/2018/09/cat-w150xh150.jpg
    [type] => image/jpeg
)

Now comes the interesting part. When defining the title of the attachment post ($name variable in the code), the media_handle_upload() function uses the original filename $_FILES[$file_id]['name'] instead of the sanitized name $file['file']. Is this a bug or feature? I don’t know but we should adapt to it.

Final Solution

We should extend our function to sanitize the $_FILES array data as well.

/**
 * Modify the WordPress image size suffix pattern
 * in the uploaded jpg, png and gif filenames.
 *
 * @param string $filename Sanitized filename.
 * @return string Filtered filename.
 */
function lwp_1866_replace_size_pattern( $filename ) {
    if ( !preg_match(
        '/\.(?:jp(?:e)?g|png|gif)$/',
        $filename
    ) ) return $filename;

    return preg_replace(
        '/(\d+)x(\d+)/',
        'w$1xh$2$3',
        $filename
    );
}

/**
 * Wrapper function to the
 * lwp_1866_replace_size_pattern() function
 *
 * @param string $filename Sanitized filename.
 * @return string Filtered filename.
 */
function lwp_1866_sanitize_file_name( $filename ) {
    if ( !empty( $_FILES ) ) {
        foreach( $_FILES as &$file ) {
            if ( isset( $file['name'] ) && $file['name'] == $filename ) {
                $file['name'] = lwp_1866_replace_size_pattern( $filename );
            }
        }
        unset($file);
    }

    return lwp_1866_replace_size_pattern( $filename );
}

add_filter( 'sanitize_file_name', 'lwp_1866_sanitize_file_name' );

We kept the original concept, as the first code handles the filenames correctly. Although, due to the mentioned function behavior (probably a bug), we should filter the $_FILES superglobal as well. Walking through the whole $_FILES array, looking for matching files – this gives our code the flexibility to work with custom-built upload solutions as well.

The final result:

Final Solution

Conclusion

Although the probability is low, it could happen that we don’t get the expected result after uploading images. For this, several conditions must coincide. The higher the number of registered image sizes and the pictures we are working with, the greater the risk. Using the functions above, we can reduce this low risk zero.

Related Posts

Leave a Reply

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

Join Our Newsletter!

Get secret tips and valuable details about making your site more successful.

Follow Us!

We Recommend

This site is powered by Elementor

100% Free  WP Website Builder  All-in-One Solution

Monthly Archives

Scroll Up