0

I created a Profile photo image field on the user entity. This is for a decoupled site where users upload files with JSON:API.

The file field is configured like this:

  • File directory: profile-pics/[current-user:uid]

My app always uploads the file as profile_photo.jpg. But, each time the user uploads a file, it gets renamed: profile_photo_0.jpg, profile_photo_1.jpg, and so on.

If the user uploads a new photo, I always want to overwrite the old photo; I do not want to store extra data.

What I tried

I only want to force overwrites for a specific file name (profile_photo.jpg at the path profile-pics/[current-user:uid] for a specific field (field_profile_pic on the user entity).

Is there a hook I can use or a method I can override to force overwriting of files in this case?

0

1 Answer 1

3

A possible solution I could imagine would be overriding getDestinationFilename() of the file system service.

Thus, you could create a custom module with a service provider class for altering the definition of the Core file_system service to use your adapted version.

In your adapted method, you could check the destination path of the target file and add your logic (e.g., renaming existing files, rather than the newly added file) before returning the destination file name.

A quick wrap-up:

  • Create a custom module 'profile_pic_path'
  • Create a ProfilePicPathServiceProvider.php class file in its src folder. If you use a different module name, this class name should be constructed using the camel case module name and added ServiceProvider suffix:
<?php

namespace Drupal\profile_pic_path;

use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
use Drupal\Core\DependencyInjection\ServiceProviderInterface;

/**
 * {@inheritdoc}
 *
 * Used to alter the container `file_system`
 * service definition.
 */
class ProfilePicPathServiceProvider extends ServiceProviderBase implements ServiceProviderInterface {

  /**
   * {@inheritdoc}
   */
  public function alter(ContainerBuilder $container) {
    $service_def = $container->getDefinition('file_system');
    // Use our custom class `CustomFileSystem`.
    $service_def->setClass(CustomFileSystem::class);
  }

}
  • Create your file system class override CustomFileSystem.php in the src folder:
<?php

namespace Drupal\profile_pic_path;

use Drupal\Core\File\FileSystem;
use Drupal\Core\File\FileSystemInterface;

/**
 * {@inheritdoc}
 *
 * Example override of the core `file_system`
 * method `getDestinationFilename()`.
 */
class CustomFileSystem extends FileSystem implements FileSystemInterface {

  /**
   * {@inheritdoc}
   *
   * Renames existing files to a new file name
   * rather than renaming new files while
   * keeping the old files.
   */
  public function getDestinationFilename($destination, $replace) {
    $target_destination = parent::getDestinationFilename($destination, $replace);

    // Add your logic to check, whether it's a profile picture
    // and possibly to remove existing files/file entities/alter
    // the destination path/or rename existing files to the
    // suggested new file name while returning the original file
    // destination here. An example:
    if (
      // Whether the target file exists.
      file_exists($destination)
      // Whether file renaming has been requested.
      && $replace === FileSystemInterface::EXISTS_RENAME
    ) {
      // Rename the existing file to the new
      // name returned by the core method.
      $this->move($destination, $target_destination);

      // Update any existing file entities using
      // the existing file with the new URI.
      /** @var \Drupal\file\FileInterface[] $files */
      $files = \Drupal::entityTypeManager()
        ->getStorage('file')
        ->loadByProperties(['uri' => $destination]);

      if ($files) {
        foreach ($files as $file) {
          $file->setFileUri($target_destination);
          $file->save();
        }
      }

      // Return the original destination.
      return $destination;
    }

    // Return the destination determined by the
    // core method.
    return $target_destination;
  }

}

2
  • Excellent! The only thing I have to add is that because the same file name is used even though the file has changed, you have to be sure to bust the cache for that file in the frontend. Commented Nov 7, 2022 at 16:08
  • 1
    Glad it helped. Some words of caution: If you have a files intensive site, you may want to add a bit logic to determine, whether temporary files are used and skip the database calls for those. Also, be cautious when overriding services this way. If another of your modules does it for the file_system service (not very likely, but possible), there might be unintended side effects. Commented Nov 7, 2022 at 23:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.