Change record status: 
Project: 
Introduced in branch: 
11.4.x
Introduced in version: 
Description: 

Image fields carry settings that restrict uploads: allowed file extensions, a minimum and a maximum resolution. Until now those settings were enforced only by \Drupal\image\Plugin\Field\FieldWidget\ImageWidget, which built the validators itself. Any other code path that uploads a file into an image field called \Drupal\file\Plugin\Field\FieldType\FileItem::getUploadValidators(), which knows nothing about images, so the resolution limits and the toolkit check on extensions were skipped. This affected the Media Library upload form and the REST file upload resource.

\Drupal\image\Plugin\Field\FieldType\ImageItem now overrides getUploadValidators() and is the single source of the validators for an image field. It adds to the validators inherited from FileItem:

  • FileIsImage: the uploaded file must be an image.
  • FileImageDimensions: only when min_resolution or max_resolution is set on the field; an image larger than the maximum is scaled down as before.
  • FileExtension: the field's allowed extensions restricted to those the active image toolkit supports.

ImageWidget, \Drupal\media_library\Form\FileUploadForm and \Drupal\file\Plugin\rest\resource\FileUploadResource all use it, so uploads to an image field are validated identically through the field widget, the Media Library and REST.

Before

A widget or form that offers a managed_file element for an image field had to assemble the image validators itself:

public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
  $element = parent::formElement($items, $delta, $element, $form, $form_state);
  $field_settings = $this->getFieldSettings();

  $element['#upload_validators']['FileIsImage'] = [];
  if ($field_settings['max_resolution'] || $field_settings['min_resolution']) {
    $element['#upload_validators']['FileImageDimensions'] = [
      'maxDimensions' => $field_settings['max_resolution'],
      'minDimensions' => $field_settings['min_resolution'],
    ];
  }
  $extensions = $field_settings['file_extensions'];
  $supported_extensions = $this->imageFactory->getSupportedExtensions();
  $extensions = !empty($extensions) ? array_intersect(explode(' ', $extensions), $supported_extensions) : $supported_extensions;
  $element['#upload_validators']['FileExtension']['extensions'] = implode(' ', $extensions);

  return $element;
}

After

public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
  $element = parent::formElement($items, $delta, $element, $form, $form_state);
  $element['#upload_validators'] = $items[$delta]->getUploadValidators();

  return $element;
}

Module developers

  • Custom widgets and forms that add FileIsImage, FileImageDimensions or a toolkit-filtered FileExtension to #upload_validators for an image field can drop that code and take the validators from the field item, as shown above.
  • Subclasses of ImageWidget that alter #upload_validators after calling parent::formElement() keep working: the element carries the same validator keys, now sourced from the field item.
  • Code that instantiates a field item only to obtain its upload validators must instantiate the class declared by the item definition rather than FileItem, otherwise the image validators are lost. \Drupal\media_library\Form\FileUploadForm::createFileItem() shows the pattern.
Impacts: 
Module developers