For one of my projects, I needed to rename uploaded files so that their name contains the hash of their contents. Doing this makes the names CDN-friendly and avoids unnecessary purges. For example, consider the scenario when you upload a wrong file, delete it, and re-upload the correct file with the same name. If you click “Preview” after uploading the wrong file, the CDN may cache it. Then, if you delete the file and re-upload a different one with the same name, chances are the CDN will still serve the old file. You will then need to go to the CDN and request a purge of the wrong file. And for some CDNs (hello, Photon!), this is not even possible.
In such scenarios, you need to ensure the file name is always unique. One way is to add a hash of the file’s contents to its filename. Similar to how Webpack does this.
Luckily, there is an easy way to do this.
WordPress has a filter, wp_handle_upload_prefilter, to handle uploads (and the wp_handle_sideload_prefilter to handle sideloads). The only argument passed to the filter is the entry from the $_FILES array.
That array has three valuable items:
error: the error code associated with this file upload, or zero if everything is OK;tmp_name: the temporary filename of the file in which the uploaded file was stored on the server;name: the original name of the file on the client machine.
We can check whether error is 0 to ensure the file was uploaded successfully. The tmp_name refers to the uploaded file on the server, and we can use it to get the hash of the file’s contents. Finally, WordPress uses name to construct the final name of the uploaded file. If we modify the its value, this will affect the final name of the uploaded file.
Here is the sample code:
add_filter( 'wp_handle_upload_prefilter', function ( array $file ): array {
if ( empty( $file['error'] ) && file_exists( $file['tmp_name'] ) ) {
$hash = md5_file( $file['tmp_name'] );
if ( false !== $hash ) {
$info = pathinfo( $file['name'] );
$ext = empty( $info['extension'] ) ? '' : '.' . $info['extension'];
$name = basename( $file['name'], $ext );
$file['name'] = $name . '.' . substr( $hash, 0, 6 ) . $ext;
}
}
return $file;
} );
If necessary, you can use a different hash algorithm by using the hash_file() function, and use a different length of the suffix by adjusting the last argument of substr(). Or even use a completely different naming scheme.