Issue #2405469 by yched, googletorp, amateescu, larowlan, Berdir: FileFormatterBase should extend EntityReferenceFormatterBase
parent
9c0bfb2d3b
commit
ef0cb6e091
|
@ -7,7 +7,8 @@
|
||||||
|
|
||||||
namespace Drupal\Core\Field\Plugin\Field\FieldFormatter;
|
namespace Drupal\Core\Field\Plugin\Field\FieldFormatter;
|
||||||
|
|
||||||
use Drupal\Core\Field\FieldItemListInterface;
|
use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
|
||||||
|
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
|
||||||
use Drupal\Core\Field\FormatterBase;
|
use Drupal\Core\Field\FormatterBase;
|
||||||
use Drupal\Core\TypedData\TranslatableInterface;
|
use Drupal\Core\TypedData\TranslatableInterface;
|
||||||
|
|
||||||
|
@ -17,15 +18,26 @@ use Drupal\Core\TypedData\TranslatableInterface;
|
||||||
abstract class EntityReferenceFormatterBase extends FormatterBase {
|
abstract class EntityReferenceFormatterBase extends FormatterBase {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the accessible and translated entities for view.
|
* Returns the referenced entities for display.
|
||||||
*
|
*
|
||||||
* @param \Drupal\Core\Field\FieldItemListInterface $items
|
* The method takes care of:
|
||||||
|
* - checking entity access,
|
||||||
|
* - placing the entities in the language expected for display.
|
||||||
|
* It is thus strongly recommended that formatters use it in their
|
||||||
|
* implementation of viewElements($items) rather than dealing with $items
|
||||||
|
* directly.
|
||||||
|
*
|
||||||
|
* For each entity, the EntityReferenceItem by which the entity is referenced
|
||||||
|
* is available in $entity->_referringItem. This is useful for field types
|
||||||
|
* that store additional values next to the reference itself.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Field\EntityReferenceFieldItemListInterface $items
|
||||||
* The item list.
|
* The item list.
|
||||||
*
|
*
|
||||||
* @return \Drupal\Core\Entity\EntityInterface[]
|
* @return \Drupal\Core\Entity\EntityInterface[]
|
||||||
* The entities to view.
|
* The array of referenced entities to display, keyed by delta.
|
||||||
*/
|
*/
|
||||||
protected function getEntitiesToView(FieldItemListInterface $items) {
|
protected function getEntitiesToView(EntityReferenceFieldItemListInterface $items) {
|
||||||
$entities = array();
|
$entities = array();
|
||||||
|
|
||||||
$parent_entity_langcode = $items->getEntity()->language()->getId();
|
$parent_entity_langcode = $items->getEntity()->language()->getId();
|
||||||
|
@ -39,8 +51,10 @@ abstract class EntityReferenceFormatterBase extends FormatterBase {
|
||||||
$entity = $entity->getTranslation($parent_entity_langcode);
|
$entity = $entity->getTranslation($parent_entity_langcode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check entity access.
|
// Check entity access if needed.
|
||||||
if ($entity->access('view')) {
|
if (!$this->needsAccessCheck($item) || $entity->access('view')) {
|
||||||
|
// Add the referring item, in case the formatter needs it.
|
||||||
|
$entity->_referringItem = $items[$delta];
|
||||||
$entities[$delta] = $entity;
|
$entities[$delta] = $entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,10 +70,9 @@ abstract class EntityReferenceFormatterBase extends FormatterBase {
|
||||||
* viewed.
|
* viewed.
|
||||||
*/
|
*/
|
||||||
public function prepareView(array $entities_items) {
|
public function prepareView(array $entities_items) {
|
||||||
// Load the existing (non-autocreate) entities. For performance, we want to
|
// Collect entity IDs to load. For performance, we want to use a single
|
||||||
// use a single "multiple entity load" to load all the entities for the
|
// "multiple entity load" to load all the entities for the multiple
|
||||||
// multiple "entity reference item lists" that are being displayed. We thus
|
// "entity reference item lists" being displayed. We thus cannot use
|
||||||
// cannot use
|
|
||||||
// \Drupal\Core\Field\EntityReferenceFieldItemList::referencedEntities().
|
// \Drupal\Core\Field\EntityReferenceFieldItemList::referencedEntities().
|
||||||
$ids = array();
|
$ids = array();
|
||||||
foreach ($entities_items as $items) {
|
foreach ($entities_items as $items) {
|
||||||
|
@ -69,7 +82,7 @@ abstract class EntityReferenceFormatterBase extends FormatterBase {
|
||||||
// contains a valid entity ready for display. All items are initialized
|
// contains a valid entity ready for display. All items are initialized
|
||||||
// at FALSE.
|
// at FALSE.
|
||||||
$item->_loaded = FALSE;
|
$item->_loaded = FALSE;
|
||||||
if ($item->target_id !== NULL) {
|
if ($this->needsEntityLoad($item)) {
|
||||||
$ids[] = $item->target_id;
|
$ids[] = $item->target_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,4 +107,30 @@ abstract class EntityReferenceFormatterBase extends FormatterBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the entity referenced by an item needs to be loaded.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem $item
|
||||||
|
* The item to check.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* TRUE if the entity needs to be loaded.
|
||||||
|
*/
|
||||||
|
protected function needsEntityLoad(EntityReferenceItem $item) {
|
||||||
|
return !$item->hasNewEntity();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether entity access should be checked.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem $item
|
||||||
|
* The item to check.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
* TRUE if entity access should be checked.
|
||||||
|
*/
|
||||||
|
protected function needsAccessCheck(EntityReferenceItem $item) {
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\file\FileAccessFormatterControlHandlerInterface.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\file;
|
||||||
|
|
||||||
|
use Drupal\Core\Entity\EntityAccessControlHandlerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines an interface for file access handlers that need to run on file formatters.
|
||||||
|
*
|
||||||
|
* \Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceFormatterBase,
|
||||||
|
* which file and image formatters extend, checks 'view' access on the
|
||||||
|
* referenced files before displaying them. That check would be useless and
|
||||||
|
* costly with Core's default access control implementation for files
|
||||||
|
* (\Drupal\file\FileAccessControlHandler grants access based on whether
|
||||||
|
* there are existing entities with granted access that reference the file). But
|
||||||
|
* it might be needed if a different access control handler with different logic
|
||||||
|
* is swapped in.
|
||||||
|
*
|
||||||
|
* \Drupal\file\Plugin\Field\FieldFormatter\FileFormatterBase thus adjusts that
|
||||||
|
* behavior, and only checks access if the access control handler in use for
|
||||||
|
* files opts in by implementing this interface.
|
||||||
|
*
|
||||||
|
* @see \Drupal\file\Plugin\Field\FieldFormatter\FileFormatterBase::needsAccessCheck()
|
||||||
|
*/
|
||||||
|
interface FileAccessFormatterControlHandlerInterface extends EntityAccessControlHandlerInterface { }
|
|
@ -7,40 +7,29 @@
|
||||||
|
|
||||||
namespace Drupal\file\Plugin\Field\FieldFormatter;
|
namespace Drupal\file\Plugin\Field\FieldFormatter;
|
||||||
|
|
||||||
use Drupal\Core\Field\FormatterBase;
|
use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceFormatterBase;
|
||||||
|
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for file formatters.
|
* Base class for file formatters.
|
||||||
*/
|
*/
|
||||||
abstract class FileFormatterBase extends FormatterBase {
|
abstract class FileFormatterBase extends EntityReferenceFormatterBase {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function prepareView(array $entities_items) {
|
protected function needsEntityLoad(EntityReferenceItem $item) {
|
||||||
// Remove files specified to not be displayed.
|
return parent::needsEntityLoad($item) && $item->isDisplayed();
|
||||||
$fids = array();
|
|
||||||
foreach ($entities_items as $items) {
|
|
||||||
foreach ($items as $item) {
|
|
||||||
if ($item->isDisplayed() && !empty($item->target_id)) {
|
|
||||||
// Load the files from the files table.
|
|
||||||
$fids[] = $item->target_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($fids) {
|
|
||||||
$files = file_load_multiple($fids);
|
|
||||||
|
|
||||||
foreach ($entities_items as $items) {
|
|
||||||
/** @var \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem $item */
|
|
||||||
foreach ($items as $item) {
|
|
||||||
// If the file does not exist, mark the entire item as empty.
|
|
||||||
if (!empty($item->target_id) && !$item->hasNewEntity()) {
|
|
||||||
$item->entity = isset($files[$item->target_id]) ? $files[$item->target_id] : NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function needsAccessCheck(EntityReferenceItem $item) {
|
||||||
|
// Only check access if the current file access control handler explicitly
|
||||||
|
// opts in by implementing FileAccessFormatterControlHandlerInterface.
|
||||||
|
$access_handler_class = $item->entity->getEntityType()->getHandlerClass('access');
|
||||||
|
return is_subclass_of($access_handler_class, '\Drupal\file\FileAccessFormatterControlHandlerInterface');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,21 +28,20 @@ class GenericFileFormatter extends FileFormatterBase {
|
||||||
public function viewElements(FieldItemListInterface $items) {
|
public function viewElements(FieldItemListInterface $items) {
|
||||||
$elements = array();
|
$elements = array();
|
||||||
|
|
||||||
foreach ($items as $delta => $item) {
|
foreach ($this->getEntitiesToView($items) as $delta => $file) {
|
||||||
if ($item->isDisplayed() && $item->entity) {
|
$item = $file->_referringItem;
|
||||||
$elements[$delta] = array(
|
$elements[$delta] = array(
|
||||||
'#theme' => 'file_link',
|
'#theme' => 'file_link',
|
||||||
'#file' => $item->entity,
|
'#file' => $file,
|
||||||
'#description' => $item->description,
|
'#description' => $item->description,
|
||||||
);
|
);
|
||||||
// Pass field item attributes to the theme function.
|
// Pass field item attributes to the theme function.
|
||||||
if (isset($item->_attributes)) {
|
if (isset($item->_attributes)) {
|
||||||
$elements[$delta] += array('#attributes' => array());
|
$elements[$delta] += array('#attributes' => array());
|
||||||
$elements[$delta]['#attributes'] += $item->_attributes;
|
$elements[$delta]['#attributes'] += $item->_attributes;
|
||||||
// Unset field item attributes since they have been included in the
|
// Unset field item attributes since they have been included in the
|
||||||
// formatter output and should not be rendered in the field template.
|
// formatter output and should not be rendered in the field template.
|
||||||
unset($item->_attributes);
|
unset($item->_attributes);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!empty($elements)) {
|
if (!empty($elements)) {
|
||||||
|
|
|
@ -29,21 +29,16 @@ class RSSEnclosureFormatter extends FileFormatterBase {
|
||||||
$entity = $items->getEntity();
|
$entity = $items->getEntity();
|
||||||
// Add the first file as an enclosure to the RSS item. RSS allows only one
|
// Add the first file as an enclosure to the RSS item. RSS allows only one
|
||||||
// enclosure per item. See: http://en.wikipedia.org/wiki/RSS_enclosure
|
// enclosure per item. See: http://en.wikipedia.org/wiki/RSS_enclosure
|
||||||
foreach ($items as $item) {
|
foreach ($this->getEntitiesToView($items) as $delta => $file) {
|
||||||
if ($item->isDisplayed() && $item->entity) {
|
$entity->rss_elements[] = array(
|
||||||
$file = $item->entity;
|
'key' => 'enclosure',
|
||||||
$entity->rss_elements[] = array(
|
'attributes' => array(
|
||||||
'key' => 'enclosure',
|
'url' => file_create_url($file->getFileUri()),
|
||||||
'attributes' => array(
|
'length' => $file->getSize(),
|
||||||
'url' => file_create_url($file->getFileUri()),
|
'type' => $file->getMimeType(),
|
||||||
'length' => $file->getSize(),
|
),
|
||||||
'type' => $file->getMimeType(),
|
);
|
||||||
),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,22 +28,19 @@ class TableFormatter extends FileFormatterBase {
|
||||||
public function viewElements(FieldItemListInterface $items) {
|
public function viewElements(FieldItemListInterface $items) {
|
||||||
$elements = array();
|
$elements = array();
|
||||||
|
|
||||||
if (!$items->isEmpty()) {
|
if ($files = $this->getEntitiesToView($items)) {
|
||||||
|
|
||||||
$header = array(t('Attachment'), t('Size'));
|
$header = array(t('Attachment'), t('Size'));
|
||||||
$rows = array();
|
$rows = array();
|
||||||
foreach ($items as $delta => $item) {
|
foreach ($files as $delta => $file) {
|
||||||
if ($item->isDisplayed() && $item->entity) {
|
$rows[] = array(
|
||||||
$rows[] = array(
|
array(
|
||||||
array(
|
'data' => array(
|
||||||
'data' => array(
|
'#theme' => 'file_link',
|
||||||
'#theme' => 'file_link',
|
'#file' => $file,
|
||||||
'#file' => $item->entity,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
array('data' => format_size($item->entity->getSize())),
|
),
|
||||||
);
|
array('data' => format_size($file->getSize())),
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$elements[0] = array();
|
$elements[0] = array();
|
||||||
|
|
|
@ -28,10 +28,8 @@ class UrlPlainFormatter extends FileFormatterBase {
|
||||||
public function viewElements(FieldItemListInterface $items) {
|
public function viewElements(FieldItemListInterface $items) {
|
||||||
$elements = array();
|
$elements = array();
|
||||||
|
|
||||||
foreach ($items as $delta => $item) {
|
foreach ($this->getEntitiesToView($items) as $delta => $file) {
|
||||||
if ($item->isDisplayed() && $item->entity) {
|
$elements[$delta] = array('#markup' => file_create_url($file->getFileUri()));
|
||||||
$elements[$delta] = array('#markup' => empty($item->entity) ? '' : file_create_url($item->entity->getFileUri()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $elements;
|
return $elements;
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\file\Tests\FileFieldFormatterAccessTest.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\file\Tests;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests file formatter access.
|
||||||
|
* @group file
|
||||||
|
*/
|
||||||
|
class FileFieldFormatterAccessTest extends FileFieldTestBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules to enable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public static $modules = ['node', 'file', 'field_ui', 'file_test'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the custom access handler is invoked.
|
||||||
|
*/
|
||||||
|
public function testFileAccessHandler() {
|
||||||
|
$type_name = 'article';
|
||||||
|
$field_name = strtolower($this->randomMachineName());
|
||||||
|
$this->createFileField($field_name, 'node', $type_name);
|
||||||
|
\Drupal::state()->set('file_test_alternate_access_handler', TRUE);
|
||||||
|
\Drupal::entityManager()->clearCachedDefinitions();
|
||||||
|
$test_file = $this->getTestFile('text');
|
||||||
|
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
|
||||||
|
$this->drupalGet('node/' . $nid);
|
||||||
|
$this->assertTrue(\Drupal::state()->get('file_access_formatter_check', FALSE));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -333,3 +333,14 @@ function file_test_file_scan_callback($filepath = NULL) {
|
||||||
function file_test_file_scan_callback_reset() {
|
function file_test_file_scan_callback_reset() {
|
||||||
drupal_static_reset('file_test_file_scan_callback');
|
drupal_static_reset('file_test_file_scan_callback');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements hook_entity_info_alter().
|
||||||
|
*/
|
||||||
|
function file_test_entity_type_alter(&$entity_types) {
|
||||||
|
if (\Drupal::state()->get('file_test_alternate_access_handler', FALSE)) {
|
||||||
|
/** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
|
||||||
|
$entity_types['file']
|
||||||
|
->setAccessClass('Drupal\file_test\FileTestAccessControlHandler');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Contains \Drupal\file_test\FileTestAccessControlHandler.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Drupal\file_test;
|
||||||
|
|
||||||
|
use Drupal\Core\Entity\EntityInterface;
|
||||||
|
use Drupal\Core\Session\AccountInterface;
|
||||||
|
use Drupal\file\FileAccessFormatterControlHandlerInterface;
|
||||||
|
use Drupal\file\FileAccessControlHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a class for an alternate file access control handler.
|
||||||
|
*/
|
||||||
|
class FileTestAccessControlHandler extends FileAccessControlHandler implements FileAccessFormatterControlHandlerInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) {
|
||||||
|
\Drupal::state()->set('file_access_formatter_check', TRUE);
|
||||||
|
return parent::checkAccess($entity, $operation, $langcode, $account);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -167,8 +167,14 @@ class ImageFormatter extends ImageFormatterBase implements ContainerFactoryPlugi
|
||||||
*/
|
*/
|
||||||
public function viewElements(FieldItemListInterface $items) {
|
public function viewElements(FieldItemListInterface $items) {
|
||||||
$elements = array();
|
$elements = array();
|
||||||
$url = NULL;
|
$files = $this->getEntitiesToView($items);
|
||||||
|
|
||||||
|
// Early opt-out if the field is empty.
|
||||||
|
if (empty($files)) {
|
||||||
|
return $elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = NULL;
|
||||||
$image_link_setting = $this->getSetting('image_link');
|
$image_link_setting = $this->getSetting('image_link');
|
||||||
// Check if the formatter involves a link.
|
// Check if the formatter involves a link.
|
||||||
if ($image_link_setting == 'content') {
|
if ($image_link_setting == 'content') {
|
||||||
|
@ -190,29 +196,28 @@ class ImageFormatter extends ImageFormatterBase implements ContainerFactoryPlugi
|
||||||
$cache_tags = $image_style->getCacheTags();
|
$cache_tags = $image_style->getCacheTags();
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($items as $delta => $item) {
|
foreach ($files as $delta => $file) {
|
||||||
if ($item->entity) {
|
if (isset($link_file)) {
|
||||||
if (isset($link_file)) {
|
$image_uri = $file->getFileUri();
|
||||||
$image_uri = $item->entity->getFileUri();
|
$url = Url::fromUri(file_create_url($image_uri));
|
||||||
$url = Url::fromUri(file_create_url($image_uri));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract field item attributes for the theme function, and unset them
|
|
||||||
// from the $item so that the field template does not re-render them.
|
|
||||||
$item_attributes = $item->_attributes;
|
|
||||||
unset($item->_attributes);
|
|
||||||
|
|
||||||
$elements[$delta] = array(
|
|
||||||
'#theme' => 'image_formatter',
|
|
||||||
'#item' => $item,
|
|
||||||
'#item_attributes' => $item_attributes,
|
|
||||||
'#image_style' => $image_style_setting,
|
|
||||||
'#url' => $url,
|
|
||||||
'#cache' => array(
|
|
||||||
'tags' => $cache_tags,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract field item attributes for the theme function, and unset them
|
||||||
|
// from the $item so that the field template does not re-render them.
|
||||||
|
$item = $file->_referringItem;
|
||||||
|
$item_attributes = $item->_attributes;
|
||||||
|
unset($item->_attributes);
|
||||||
|
|
||||||
|
$elements[$delta] = array(
|
||||||
|
'#theme' => 'image_formatter',
|
||||||
|
'#item' => $item,
|
||||||
|
'#item_attributes' => $item_attributes,
|
||||||
|
'#image_style' => $image_style_setting,
|
||||||
|
'#url' => $url,
|
||||||
|
'#cache' => array(
|
||||||
|
'tags' => $cache_tags,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $elements;
|
return $elements;
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
namespace Drupal\image\Plugin\Field\FieldFormatter;
|
namespace Drupal\image\Plugin\Field\FieldFormatter;
|
||||||
|
|
||||||
|
use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
|
||||||
use Drupal\field\FieldConfigInterface;
|
use Drupal\field\FieldConfigInterface;
|
||||||
use Drupal\file\Plugin\Field\FieldFormatter\FileFormatterBase;
|
use Drupal\file\Plugin\Field\FieldFormatter\FileFormatterBase;
|
||||||
|
|
||||||
|
@ -18,33 +19,36 @@ abstract class ImageFormatterBase extends FileFormatterBase {
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function prepareView(array $entities_items) {
|
protected function getEntitiesToView(EntityReferenceFieldItemListInterface $items) {
|
||||||
parent::prepareView($entities_items);
|
// Add the default image if needed.
|
||||||
|
if ($items->isEmpty()) {
|
||||||
|
$default_image = $this->getFieldSetting('default_image');
|
||||||
|
// If we are dealing with a configurable field, look in both
|
||||||
|
// instance-level and field-level settings.
|
||||||
|
if (empty($default_image['uuid']) && $this->fieldDefinition instanceof FieldConfigInterface) {
|
||||||
|
$default_image = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('default_image');
|
||||||
|
}
|
||||||
|
|
||||||
// If there are no files specified at all, use the default.
|
if (!empty($default_image['uuid']) && $file = \Drupal::entityManager()->loadEntityByUuid('file', $default_image['uuid'])) {
|
||||||
foreach ($entities_items as $items) {
|
// Clone the FieldItemList into a runtime-only object for the formatter,
|
||||||
if ($items->isEmpty()) {
|
// so that the fallback image can be rendered without affecting the
|
||||||
// Add the default image if one is found.
|
// field values in the entity being rendered.
|
||||||
$default_image = $this->getFieldSetting('default_image');
|
$items = clone $items;
|
||||||
// If we are dealing with a configurable field, look in both
|
$items->setValue(array(
|
||||||
// instance-level and field-level settings.
|
'target_id' => $file->id(),
|
||||||
if (empty($default_image['uuid']) && $this->fieldDefinition instanceof FieldConfigInterface) {
|
'alt' => $default_image['alt'],
|
||||||
$default_image = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('default_image');
|
'title' => $default_image['title'],
|
||||||
}
|
'width' => $default_image['width'],
|
||||||
|
'height' => $default_image['height'],
|
||||||
if (!empty($default_image['uuid']) && ($file = \Drupal::entityManager()->loadEntityByUuid('file', $default_image['uuid']))) {
|
'entity' => $file,
|
||||||
$items->setValue(array(array(
|
'_loaded' => TRUE,
|
||||||
'is_default' => TRUE,
|
'_is_default' => TRUE,
|
||||||
'alt' => $default_image['alt'],
|
));
|
||||||
'title' => $default_image['title'],
|
$file->_referringItem = $items[0];
|
||||||
'width' => $default_image['width'],
|
|
||||||
'height' => $default_image['height'],
|
|
||||||
'entity' => $file,
|
|
||||||
'target_id' => $file->id(),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return parent::getEntitiesToView($items);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,7 +154,7 @@ class ImageFieldDefaultImagesTest extends ImageFieldTestBase {
|
||||||
$article = $this->drupalCreateNode(array('type' => 'article'));
|
$article = $this->drupalCreateNode(array('type' => 'article'));
|
||||||
$article_built = $this->drupalBuildEntityView($article);
|
$article_built = $this->drupalBuildEntityView($article);
|
||||||
$this->assertEqual(
|
$this->assertEqual(
|
||||||
$article_built[$field_name]['#items'][0]->target_id,
|
$article_built[$field_name][0]['#item']->target_id,
|
||||||
$default_images['field']->id(),
|
$default_images['field']->id(),
|
||||||
format_string(
|
format_string(
|
||||||
'A new article node without an image has the expected default image file ID of @fid.',
|
'A new article node without an image has the expected default image file ID of @fid.',
|
||||||
|
@ -166,7 +166,7 @@ class ImageFieldDefaultImagesTest extends ImageFieldTestBase {
|
||||||
$page = $this->drupalCreateNode(array('type' => 'page'));
|
$page = $this->drupalCreateNode(array('type' => 'page'));
|
||||||
$page_built = $this->drupalBuildEntityView($page);
|
$page_built = $this->drupalBuildEntityView($page);
|
||||||
$this->assertEqual(
|
$this->assertEqual(
|
||||||
$page_built[$field_name]['#items'][0]->target_id,
|
$page_built[$field_name][0]['#item']->target_id,
|
||||||
$default_images['field2']->id(),
|
$default_images['field2']->id(),
|
||||||
format_string(
|
format_string(
|
||||||
'A new page node without an image has the expected default image file ID of @fid.',
|
'A new page node without an image has the expected default image file ID of @fid.',
|
||||||
|
@ -196,7 +196,7 @@ class ImageFieldDefaultImagesTest extends ImageFieldTestBase {
|
||||||
$article_built = $this->drupalBuildEntityView($article = $node_storage->load($article->id()));
|
$article_built = $this->drupalBuildEntityView($article = $node_storage->load($article->id()));
|
||||||
$page_built = $this->drupalBuildEntityView($page = $node_storage->load($page->id()));
|
$page_built = $this->drupalBuildEntityView($page = $node_storage->load($page->id()));
|
||||||
$this->assertEqual(
|
$this->assertEqual(
|
||||||
$article_built[$field_name]['#items'][0]->target_id,
|
$article_built[$field_name][0]['#item']->target_id,
|
||||||
$default_images['field']->id(),
|
$default_images['field']->id(),
|
||||||
format_string(
|
format_string(
|
||||||
'An existing article node without an image has the expected default image file ID of @fid.',
|
'An existing article node without an image has the expected default image file ID of @fid.',
|
||||||
|
@ -204,7 +204,7 @@ class ImageFieldDefaultImagesTest extends ImageFieldTestBase {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$this->assertEqual(
|
$this->assertEqual(
|
||||||
$page_built[$field_name]['#items'][0]->target_id,
|
$page_built[$field_name][0]['#item']->target_id,
|
||||||
$default_images['field2']->id(),
|
$default_images['field2']->id(),
|
||||||
format_string(
|
format_string(
|
||||||
'An existing page node without an image has the expected default image file ID of @fid.',
|
'An existing page node without an image has the expected default image file ID of @fid.',
|
||||||
|
@ -235,7 +235,7 @@ class ImageFieldDefaultImagesTest extends ImageFieldTestBase {
|
||||||
|
|
||||||
// Confirm the article uses the new default.
|
// Confirm the article uses the new default.
|
||||||
$this->assertEqual(
|
$this->assertEqual(
|
||||||
$article_built[$field_name]['#items'][0]->target_id,
|
$article_built[$field_name][0]['#item']->target_id,
|
||||||
$default_images['field_new']->id(),
|
$default_images['field_new']->id(),
|
||||||
format_string(
|
format_string(
|
||||||
'An existing article node without an image has the expected default image file ID of @fid.',
|
'An existing article node without an image has the expected default image file ID of @fid.',
|
||||||
|
@ -244,7 +244,7 @@ class ImageFieldDefaultImagesTest extends ImageFieldTestBase {
|
||||||
);
|
);
|
||||||
// Confirm the page remains unchanged.
|
// Confirm the page remains unchanged.
|
||||||
$this->assertEqual(
|
$this->assertEqual(
|
||||||
$page_built[$field_name]['#items'][0]->target_id,
|
$page_built[$field_name][0]['#item']->target_id,
|
||||||
$default_images['field2']->id(),
|
$default_images['field2']->id(),
|
||||||
format_string(
|
format_string(
|
||||||
'An existing page node without an image has the expected default image file ID of @fid.',
|
'An existing page node without an image has the expected default image file ID of @fid.',
|
||||||
|
@ -275,7 +275,7 @@ class ImageFieldDefaultImagesTest extends ImageFieldTestBase {
|
||||||
$page_built = $this->drupalBuildEntityView($page = $node_storage->load($page->id()));
|
$page_built = $this->drupalBuildEntityView($page = $node_storage->load($page->id()));
|
||||||
// Confirm the article uses the new field (not field) default.
|
// Confirm the article uses the new field (not field) default.
|
||||||
$this->assertEqual(
|
$this->assertEqual(
|
||||||
$article_built[$field_name]['#items'][0]->target_id,
|
$article_built[$field_name][0]['#item']->target_id,
|
||||||
$default_images['field_new']->id(),
|
$default_images['field_new']->id(),
|
||||||
format_string(
|
format_string(
|
||||||
'An existing article node without an image has the expected default image file ID of @fid.',
|
'An existing article node without an image has the expected default image file ID of @fid.',
|
||||||
|
@ -284,7 +284,7 @@ class ImageFieldDefaultImagesTest extends ImageFieldTestBase {
|
||||||
);
|
);
|
||||||
// Confirm the page remains unchanged.
|
// Confirm the page remains unchanged.
|
||||||
$this->assertEqual(
|
$this->assertEqual(
|
||||||
$page_built[$field_name]['#items'][0]->target_id,
|
$page_built[$field_name][0]['#item']->target_id,
|
||||||
$default_images['field2']->id(),
|
$default_images['field2']->id(),
|
||||||
format_string(
|
format_string(
|
||||||
'An existing page node without an image has the expected default image file ID of @fid.',
|
'An existing page node without an image has the expected default image file ID of @fid.',
|
||||||
|
|
|
@ -175,6 +175,13 @@ class ResponsiveImageFormatter extends ImageFormatterBase implements ContainerFa
|
||||||
*/
|
*/
|
||||||
public function viewElements(FieldItemListInterface $items) {
|
public function viewElements(FieldItemListInterface $items) {
|
||||||
$elements = array();
|
$elements = array();
|
||||||
|
$files = $this->getEntitiesToView($items);
|
||||||
|
|
||||||
|
// Early opt-out if the field is empty.
|
||||||
|
if (empty($files)) {
|
||||||
|
return $elements;
|
||||||
|
}
|
||||||
|
|
||||||
$url = NULL;
|
$url = NULL;
|
||||||
// Check if the formatter involves a link.
|
// Check if the formatter involves a link.
|
||||||
if ($this->getSetting('image_link') == 'content') {
|
if ($this->getSetting('image_link') == 'content') {
|
||||||
|
@ -220,10 +227,10 @@ class ResponsiveImageFormatter extends ImageFormatterBase implements ContainerFa
|
||||||
$cache_tags = Cache::mergeTags($cache_tags, $image_style->getCacheTags());
|
$cache_tags = Cache::mergeTags($cache_tags, $image_style->getCacheTags());
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($items as $delta => $item) {
|
foreach ($files as $delta => $file) {
|
||||||
// Link the <picture> element to the original file.
|
// Link the <picture> element to the original file.
|
||||||
if (isset($link_file)) {
|
if (isset($link_file)) {
|
||||||
$url = Url::fromUri(file_create_url($item->entity->getFileUri()));
|
$url = Url::fromUri(file_create_url($file->getFileUri()));
|
||||||
}
|
}
|
||||||
$elements[$delta] = array(
|
$elements[$delta] = array(
|
||||||
'#theme' => 'responsive_image_formatter',
|
'#theme' => 'responsive_image_formatter',
|
||||||
|
@ -232,7 +239,7 @@ class ResponsiveImageFormatter extends ImageFormatterBase implements ContainerFa
|
||||||
'core/picturefill',
|
'core/picturefill',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
'#item' => $item,
|
'#item' => $file->_referringItem,
|
||||||
'#image_style' => $fallback_image_style,
|
'#image_style' => $fallback_image_style,
|
||||||
'#responsive_image_style_id' => $responsive_image_style ? $responsive_image_style->id() : '',
|
'#responsive_image_style_id' => $responsive_image_style ? $responsive_image_style->id() : '',
|
||||||
'#url' => $url,
|
'#url' => $url,
|
||||||
|
|
Loading…
Reference in New Issue