3

I'm trying to write a function to rename the attachment file, both on upload, but also after upload.

I've written a nice function that can do this, but it results in missing thumbnails in the WP dashboard. I suspect it's because the GUID becomes wrong, but WP has made it impossible to change that GUID now (I think), so I'm not sure what else to do. Hoping someone here can help.

This is what I have, based on everything I could find online.

add_action("add_attachment", "rename_attachment", 10, 1);
function rename_attachment($post_id) {
     custom_attachment_rename($post_id, "My name filename here");
}

function custom_attachment_rename( $post_id, $new_filename = 'new filename' ){

    // Get path info of orginal file
    $og_path = get_attached_file($post_id);
    $path_info = pathinfo($og_path);

    // Santize filename
    $safe_filename = wp_unique_filename($path_info['dirname'], $new_filename);

    // Build out path to new file
    $new_path = $path_info['dirname']. "/" . $safe_filename . "." .$path_info['extension'];
    
    // Rename the file and update it's location in WP
    rename($og_path, $new_path);    
    update_attached_file( $post_id, $new_path );

    // URL to the new file
    $new_url = wp_get_attachment_url($post_id);
    
    // Update attachment data
    $id = wp_update_post([
        'ID' => $post_id,
        'post_title' => $new_filename,
        'guid' => $new_url // Doesn't seem to work
    ]);
    
    // Try this to reset the GUID for the attachment. Doesn't seem to actually reset it.
    // global $wpdb;
    // $result = $wpdb->update($wpdb->posts, ['guid' => $new_url], ['ID' => $post_id]);
    
    // Update all links to old "sizes" files, or create it for new upload.
    $metadata = get_post_meta($post_id, '_wp_attachment_metadata', true);
    if( empty($metadata) ) {

        // New upload.
        $data = wp_generate_attachment_metadata($post_id, $new_path);
        //update_post_meta($post_id, '_wp_attachment_metadata', $data); // Tried this. Doesn't work.
        wp_update_attachment_metadata($post_id, $data); // Also doesn't work
        
    } else {
        // Regenerating an existing image
        // TODO loop through $metadata and update the filename and resave? Maybe just delete it and regenerate instead?
    }
    
    // TODO Update use of the old filename in post_content throughout site
}

These have been the helpful posts I've gone over so far.

The weird thing is, if I die at the end of this function, then it works. So I suspect something else in WP is overwriting the _wp_attachment_metadata for this attachment. That is why I suspect the GUID is the issue. Something else is looking up the attachment via GUID and find a URL to a file that no longer exists (as I changed the filename) and running wp_generate_attachment_metadata on a bad file path. That is my hunch.

I don't have any other plugins installed.

Drew Baker
  • 14,154
  • 15
  • 58
  • 97

2 Answers2

3

The reason your code doesn't work is not related to the GUID.

It is because, in WP core, the function wp_update_attachment_metadata() is called with the original file name at the end of the media upload request handling. And the hook add_attachment is called inside wp_insert_attachment() function.

function media_handle_upload( $file_id, $post_id, $post_data = array(), $overrides = array( 'test_form' => false ) ) {

    ... ...

    // Save the data.
    $attachment_id = wp_insert_attachment( $attachment, $file, $post_id, true );

    if ( ! is_wp_error( $attachment_id ) ) {
        // Set a custom header with the attachment_id.
        // Used by the browser/client to resume creating image sub-sizes after a PHP fatal error.
        if ( ! headers_sent() ) {
            header( 'X-WP-Upload-Attachment-ID: ' . $attachment_id );
        }

        // The image sub-sizes are created during wp_generate_attachment_metadata().
        // This is generally slow and may cause timeouts or out of memory errors.
        wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) );
    }

    return $attachment_id;
}

We can use the "wp_update_attachment_metadata" filter to change the metadata with the new file name.

Please check the below code. I tested and it is working well.

class CustomAttachmentRename {
    public $new_filename = 'custom file';

    private $new_path;

    function __construct () {
        add_action("add_attachment", array($this, "rename_attachment"), 10, 1);     
    }

    function rename_attachment($post_id) {
        // Get path info of orginal file
        $og_path = get_attached_file($post_id);
        $path_info = pathinfo($og_path);

        // Santize filename
        $safe_filename = wp_unique_filename($path_info['dirname'], $this->new_filename);

        // Build out path to new file
        $this->new_path = $path_info['dirname']. "/" . $safe_filename . "." .$path_info['extension'];
        
        // Rename the file and update it's location in WP
        rename($og_path, $this->new_path);    
        update_attached_file( $post_id, $this->new_path );

        // Register filter to update metadata.
        add_filter('wp_update_attachment_metadata', array($this, 'custom_update_attachment_metadata'), 10, 2);
    }

    function custom_update_attachment_metadata($data, $post_id) {
        return wp_generate_attachment_metadata($post_id, $this->new_path);
    }
}

$customAttachmentRename = new CustomAttachmentRename();
$customAttachmentRename->new_filename = "test image" . time();
  • This is great to see. Although will this result in the orginal images intermediate sizes still being on the sever right? I probably need to extend this to rename those files and then search-and-replace the use of those files in post_content. – Drew Baker Nov 27 '20 at 23:30
  • The result will be exactly the same as uploading prior-renamed image files. All image sizes including registered by plugins work. And, yes, you need to extend the function as your need. – Intelligent Web Solution Nov 28 '20 at 00:54
  • 1
    Getting "Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate 262144 bytes) in /www/wp-includes/formatting.php on line 4998 Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate 262144 bytes) in Unknown on line 0" with this. – Drew Baker Nov 29 '20 at 21:19
2

I turned @chengmin answer into a single function like this:

/**
* Renames a file. Will also regenerate all the thumbnails that go with the file.
* @SEE https://stackoverflow.com/questions/64990515/wordpress-rename-attachment-file-with-working-thumbnails
*
* @param string $post_id The WordPress post_id for the attachment
* @param string $new_file_name The filename (without extension) that you want to rename to
*/  
function attachment_rename($post_id, $filename) {

    // Get path info of orginal file
    $og_url = wp_get_attachment_url($post_id);
    $og_path = get_attached_file($post_id);
    $path_info = pathinfo($og_path);
    $og_meta = get_post_meta($post_id, '_wp_attachment_metadata', true);

    // Santize filename
    $safe_filename = wp_unique_filename($path_info['dirname'], $filename);

    // Build out path to new file
    $new_path = $path_info['dirname']. "/" . $safe_filename . "." .$path_info['extension'];
    
    // Rename the file in the file system
    rename($og_path, $new_path); 

    // Delete old image sizes if we have them
    if( !empty($og_meta) ) {
        delete_attachment_files($post_id);
    }

    // Now save new path to file in WP
    update_attached_file( $post_id, $new_path );

    // Register filter to update metadata
    $new_data = wp_generate_attachment_metadata($post_id, $new_path);    
    return add_filter('wp_update_attachment_metadata', function($data, $post_id) use ($new_data) {        
        return $new_data;
    }, 10, 2);
}

/**
 * Delete all old image files, if they aren't used post_content anywhere. 
 * The dont-delete check isn't perfect, it will give a lot of false positives (keeping more files than it should), but it's best I could come up with.
 * @SEE https://github.com/WordPress/WordPress/blob/f4cda1b62ffca52115e4b04d9d75047060d69e68/wp-includes/post.php#L5983
 *
 * @param string $post_id The WordPress post_id for the attachment
 */ 
function delete_attachment_files($post_id) {
    
    $meta = wp_get_attachment_metadata( $post_id );
    $backup_sizes = get_post_meta( $post_id, '_wp_attachment_backup_sizes', true );
    $file = get_attached_file( $post_id );   
    $url = wp_get_attachment_url($post_id);
    
    // Remove og image so it doesn't get deleted in wp_delete_attachment_files()
    $meta['original_image'] = "";
    
    // Check if image is used in a post somehwere
    $url_without_extension = substr($url, 0 , (strrpos($url, ".")));
    $args = [
        "s" => $url_without_extension,
        "posts_per_page" => 1,
        "post_type" => "any"
    ];
    $found = get_posts($args);
    if( empty($found) ) {
        return wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file );
    }

    return false;
}
Drew Baker
  • 14,154
  • 15
  • 58
  • 97