1<?php 2 3namespace Drupal\image\Plugin\Field\FieldFormatter; 4 5use Drupal\Core\Entity\EntityStorageInterface; 6use Drupal\Core\Field\FieldItemListInterface; 7use Drupal\Core\Field\FieldDefinitionInterface; 8use Drupal\Core\Link; 9use Drupal\Core\Session\AccountInterface; 10use Drupal\Core\Url; 11use Drupal\image\Entity\ImageStyle; 12use Symfony\Component\DependencyInjection\ContainerInterface; 13use Drupal\Core\Form\FormStateInterface; 14use Drupal\Core\Cache\Cache; 15 16/** 17 * Plugin implementation of the 'image' formatter. 18 * 19 * @FieldFormatter( 20 * id = "image", 21 * label = @Translation("Image"), 22 * field_types = { 23 * "image" 24 * }, 25 * quickedit = { 26 * "editor" = "image" 27 * } 28 * ) 29 */ 30class ImageFormatter extends ImageFormatterBase { 31 32 /** 33 * The current user. 34 * 35 * @var \Drupal\Core\Session\AccountInterface 36 */ 37 protected $currentUser; 38 39 /** 40 * The image style entity storage. 41 * 42 * @var \Drupal\image\ImageStyleStorageInterface 43 */ 44 protected $imageStyleStorage; 45 46 /** 47 * Constructs an ImageFormatter object. 48 * 49 * @param string $plugin_id 50 * The plugin_id for the formatter. 51 * @param mixed $plugin_definition 52 * The plugin implementation definition. 53 * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition 54 * The definition of the field to which the formatter is associated. 55 * @param array $settings 56 * The formatter settings. 57 * @param string $label 58 * The formatter label display setting. 59 * @param string $view_mode 60 * The view mode. 61 * @param array $third_party_settings 62 * Any third party settings settings. 63 * @param \Drupal\Core\Session\AccountInterface $current_user 64 * The current user. 65 * @param \Drupal\Core\Entity\EntityStorageInterface $image_style_storage 66 * The image style storage. 67 */ 68 public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, AccountInterface $current_user, EntityStorageInterface $image_style_storage) { 69 parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings); 70 $this->currentUser = $current_user; 71 $this->imageStyleStorage = $image_style_storage; 72 } 73 74 /** 75 * {@inheritdoc} 76 */ 77 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { 78 return new static( 79 $plugin_id, 80 $plugin_definition, 81 $configuration['field_definition'], 82 $configuration['settings'], 83 $configuration['label'], 84 $configuration['view_mode'], 85 $configuration['third_party_settings'], 86 $container->get('current_user'), 87 $container->get('entity_type.manager')->getStorage('image_style') 88 ); 89 } 90 91 /** 92 * {@inheritdoc} 93 */ 94 public static function defaultSettings() { 95 return [ 96 'image_style' => '', 97 'image_link' => '', 98 ] + parent::defaultSettings(); 99 } 100 101 /** 102 * {@inheritdoc} 103 */ 104 public function settingsForm(array $form, FormStateInterface $form_state) { 105 $image_styles = image_style_options(FALSE); 106 $description_link = Link::fromTextAndUrl( 107 $this->t('Configure Image Styles'), 108 Url::fromRoute('entity.image_style.collection') 109 ); 110 $element['image_style'] = [ 111 '#title' => t('Image style'), 112 '#type' => 'select', 113 '#default_value' => $this->getSetting('image_style'), 114 '#empty_option' => t('None (original image)'), 115 '#options' => $image_styles, 116 '#description' => $description_link->toRenderable() + [ 117 '#access' => $this->currentUser->hasPermission('administer image styles'), 118 ], 119 ]; 120 $link_types = [ 121 'content' => t('Content'), 122 'file' => t('File'), 123 ]; 124 $element['image_link'] = [ 125 '#title' => t('Link image to'), 126 '#type' => 'select', 127 '#default_value' => $this->getSetting('image_link'), 128 '#empty_option' => t('Nothing'), 129 '#options' => $link_types, 130 ]; 131 132 return $element; 133 } 134 135 /** 136 * {@inheritdoc} 137 */ 138 public function settingsSummary() { 139 $summary = []; 140 141 $image_styles = image_style_options(FALSE); 142 // Unset possible 'No defined styles' option. 143 unset($image_styles['']); 144 // Styles could be lost because of enabled/disabled modules that defines 145 // their styles in code. 146 $image_style_setting = $this->getSetting('image_style'); 147 if (isset($image_styles[$image_style_setting])) { 148 $summary[] = t('Image style: @style', ['@style' => $image_styles[$image_style_setting]]); 149 } 150 else { 151 $summary[] = t('Original image'); 152 } 153 154 $link_types = [ 155 'content' => t('Linked to content'), 156 'file' => t('Linked to file'), 157 ]; 158 // Display this setting only if image is linked. 159 $image_link_setting = $this->getSetting('image_link'); 160 if (isset($link_types[$image_link_setting])) { 161 $summary[] = $link_types[$image_link_setting]; 162 } 163 164 return $summary; 165 } 166 167 /** 168 * {@inheritdoc} 169 */ 170 public function viewElements(FieldItemListInterface $items, $langcode) { 171 $elements = []; 172 $files = $this->getEntitiesToView($items, $langcode); 173 174 // Early opt-out if the field is empty. 175 if (empty($files)) { 176 return $elements; 177 } 178 179 $url = NULL; 180 $image_link_setting = $this->getSetting('image_link'); 181 // Check if the formatter involves a link. 182 if ($image_link_setting == 'content') { 183 $entity = $items->getEntity(); 184 if (!$entity->isNew()) { 185 $url = $entity->toUrl(); 186 } 187 } 188 elseif ($image_link_setting == 'file') { 189 $link_file = TRUE; 190 } 191 192 $image_style_setting = $this->getSetting('image_style'); 193 194 // Collect cache tags to be added for each item in the field. 195 $base_cache_tags = []; 196 if (!empty($image_style_setting)) { 197 $image_style = $this->imageStyleStorage->load($image_style_setting); 198 $base_cache_tags = $image_style->getCacheTags(); 199 } 200 201 foreach ($files as $delta => $file) { 202 $cache_contexts = []; 203 if (isset($link_file)) { 204 $image_uri = $file->getFileUri(); 205 // @todo Wrap in file_url_transform_relative(). This is currently 206 // impossible. As a work-around, we currently add the 'url.site' cache 207 // context to ensure different file URLs are generated for different 208 // sites in a multisite setup, including HTTP and HTTPS versions of the 209 // same site. Fix in https://www.drupal.org/node/2646744. 210 $url = Url::fromUri(file_create_url($image_uri)); 211 $cache_contexts[] = 'url.site'; 212 } 213 $cache_tags = Cache::mergeTags($base_cache_tags, $file->getCacheTags()); 214 215 // Extract field item attributes for the theme function, and unset them 216 // from the $item so that the field template does not re-render them. 217 $item = $file->_referringItem; 218 $item_attributes = $item->_attributes; 219 unset($item->_attributes); 220 221 $elements[$delta] = [ 222 '#theme' => 'image_formatter', 223 '#item' => $item, 224 '#item_attributes' => $item_attributes, 225 '#image_style' => $image_style_setting, 226 '#url' => $url, 227 '#cache' => [ 228 'tags' => $cache_tags, 229 'contexts' => $cache_contexts, 230 ], 231 ]; 232 } 233 234 return $elements; 235 } 236 237 /** 238 * {@inheritdoc} 239 */ 240 public function calculateDependencies() { 241 $dependencies = parent::calculateDependencies(); 242 $style_id = $this->getSetting('image_style'); 243 /** @var \Drupal\image\ImageStyleInterface $style */ 244 if ($style_id && $style = ImageStyle::load($style_id)) { 245 // If this formatter uses a valid image style to display the image, add 246 // the image style configuration entity as dependency of this formatter. 247 $dependencies[$style->getConfigDependencyKey()][] = $style->getConfigDependencyName(); 248 } 249 return $dependencies; 250 } 251 252 /** 253 * {@inheritdoc} 254 */ 255 public function onDependencyRemoval(array $dependencies) { 256 $changed = parent::onDependencyRemoval($dependencies); 257 $style_id = $this->getSetting('image_style'); 258 /** @var \Drupal\image\ImageStyleInterface $style */ 259 if ($style_id && $style = ImageStyle::load($style_id)) { 260 if (!empty($dependencies[$style->getConfigDependencyKey()][$style->getConfigDependencyName()])) { 261 $replacement_id = $this->imageStyleStorage->getReplacementId($style_id); 262 // If a valid replacement has been provided in the storage, replace the 263 // image style with the replacement and signal that the formatter plugin 264 // settings were updated. 265 if ($replacement_id && ImageStyle::load($replacement_id)) { 266 $this->setSetting('image_style', $replacement_id); 267 $changed = TRUE; 268 } 269 } 270 } 271 return $changed; 272 } 273 274} 275