I tested out, this module can only replace a file. Will you, in the future, add replace an image function too?

CommentFileSizeAuthor
#9 duplicate-managed-files.png81.45 KBflyke

Comments

zyzero1987 created an issue. See original summary.

zyzero1987’s picture

I tried to replace an image, the origin file entity detail has been changed to the replaced image info, but the link is still remaining with the origin file link.

zyzero1987’s picture

I found out what's going on, NVM, Great Job for this module.

temkin’s picture

Status: Active » Closed (works as designed)
flyke’s picture

I had the same problem.
When you replace an image file, the replacement is not immediatly visible because the generated image style image is still from the original image.

You can flush a certain image style, like 'medium' like this:
drush image:flush medium

Would be nice if this module has an option to do that automatically, so that replacing an image will be immediatly visible on the website.

flyke’s picture

So I solved this by adding a submit handler.
The submit handler creates an image style uri of the image for each existing image style.
This does not mean that if that image style image did not exist that it will be created.

At first, for each generated image style uri, I checked if the file existed and if it did, I did:
\Drupal::service('file_system')->unlink($style_path);
However, I got notices that the file did not exist in many cases, so the file_exists was not working properly.

I had better success using the image.factory service to get the image from the generated image style.

$image_uri = $style->buildUri($uri);
$image = $image_factory->get($image_uri);

I then checked if it was valid and then just saved it, that successfully recreated the image in all existing image styles for me:

if ($image->isValid()) {
   $image->save();
}

But after more testing I found out that for some reason, I did not even had to check and re-save.
As long is I called $style->buildUri the images would be refreshed. So my working code now is this:

/**
 * Implements hook_form_form_id_alter().
 *
 * When replacing an image, add submit handler to flush all the image's image styles.
 */
function mymodule_form_file_replace_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Get the File object.
  $file = \Drupal::routeMatch()->getParameter('file');

  // Validate the File object.
  if (!$file) {
    return;
  }

  // Validate if the File is an image.
  if (count(file_validate_is_image($file))) {
    return;
  }

  // Add submit handler to flush image stlyes for the image.
  $form['actions']['submit']['#submit'][] = '_mymodule_flush_image_styles';
}

/**
 * Flush an images' image styles.
 *
 * Custom function to flush all generated image styles of a specific image.
 */
function _mymodule_flush_image_styles(&$form, FormStateInterface $form_state) {
  // Get the File object.
  $file = \Drupal::routeMatch()->getParameter('file');

  // Validate the File object.
  if (!$file) {
    return;
  }

  // Validate if the File is an image.
  $uri = $file->getFileUri();
  $image_factory = \Drupal::service('image.factory');
  $image = $image_factory->get($uri);
  if (!$image->isValid()) {
    return;
  }

  // Call the buildUri on all image styles. Nothing else, it is enough to regenerate derived images.
  $styles = \Drupal::entityTypeManager()->getStorage('image_style')->loadMultiple();
  foreach ($styles as $style) {
    $image_uri = $style->buildUri($uri);
  }

}
bbrala’s picture

Status: Closed (works as designed) » Active

Perhaps you can create a patch that adds this check and such to the module? Sounds like something that might be helpfull.

flyke’s picture

Hi,

I have still been working on this and I changed my strategy and code.
I use the media_pdf_thumbnail module and I also wanted that when you replace a PDF file, the thumbnail images get refreshed too.

the logic is now: always trigger a custom submit handler.
In it, load all Media items that reference our replaced file.
Simply save those Media items. that will automatically regenerate a thumbnail if it was a PDF file.
But not the derived images, so delete existing derived images so the image style images will be regenerated.

/**
 * Implements hook_form_form_id_alter().
 *
 * Update referencing Media items when replacing a File.
 */
function mymodule_form_file_replace_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Get the File object.
  $file = \Drupal::routeMatch()->getParameter('file');

  // Add message to form indicating you should view file url in incognito window.
  $form['original']['notice'] = [
    '#type' => 'markup',
    '#markup' => t('<p>P.S.: your browser could cache files. If you think you see an old file, please open the file link in an <b>INCOGNITO window</b>.</p>'),
  ];

  // Validate the File object.
  if (!$file) {
    return;
  }

  // Add custom submit handler to resave Media items when they reference the replaced file.
  $form['actions']['submit']['#submit'][] = '_mymodule_update_referencing_media_files';
}


/**
 * Get Media items referencing a File and re-save them.
 */
function _mymodule_update_referencing_media_files(&$form, FormStateInterface $form_state) {

  // Get the File object.
  $file = \Drupal::routeMatch()->getParameter('file');

  // Validate the File object.
  if (!$file) {
    return;
  }

  // Get all Media items that use the File.
  $usage_list = \Drupal::service('file.usage')->listUsage($file);
  $media_file_list = isset($usage_list['file']['media']) ? $usage_list['file']['media'] : [];
  // Re-save each Media item, so that pdf thumbnails get recreated etc.
  foreach ($media_file_list as $mid => $usage) {
    $media = Media::load($mid);
    $media->save();
    // Flush image styles for the thumbnail.
    $thumbnail_fid = $media->thumbnail->target_id;
    _mymodule_flush_image_styles($thumbnail_fid);
  }
}

/**
 * Flush an images' image styles.
 *
 * Custom function to flush all generated image styles of a specific image file.
 */
function _mymodule_flush_image_styles($fid) {
  // Validate file id.
  if (!$fid) {
    return;
  }

  // Load the file.
  $file = File::load($fid);

  // Check if that is a valid file.
  if (!$file) {
    return;
  }

  // Validate if the File is an image.
  $uri = $file->getFileUri();
  $image_factory = \Drupal::service('image.factory');
  $image = $image_factory->get($uri);
  if (!$image->isValid()) {
    return;
  }
  
  // Loop image styles to see if image is using that image style.
  $styles = \Drupal::entityTypeManager()->getStorage('image_style')->loadMultiple();
  foreach ($styles as $style) {
    // Create an image style url (no worries, it does not create the images if they dont exist).
    $image_uri = $style->buildUri($uri);
    // The delete function will automatically check if the file exists first.
    // When the derived image is deleted, it will automatically be regenerated when it is displayed, so our image will be updated.
    \Drupal::service('file_system')->delete($image_uri);
  }
}
flyke’s picture

StatusFileSize
new81.45 KB

By the way,
I am still trying to finetune this because each time I replace a PDF (probably when Media save is triggered) the system seems to create a new File (see screenshot). This is for sure not in the scope of this issue, and might be an issue for the media_pdf_preview module, but I'm trying to find and integrate a fix in the code above.