Issue #2165725 by Xano: Introduce hook_entity_operation().

8.0.x
webchick 2014-04-07 11:12:13 -07:00
parent 7ba1d72cfc
commit b504423ed0
27 changed files with 296 additions and 90 deletions

View File

@ -30,8 +30,9 @@ class ConfigEntityListBuilder extends EntityListBuilder {
/**
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
$operations = parent::getOperations($entity);
public function getDefaultOperations(EntityInterface $entity) {
/** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */
$operations = parent::getDefaultOperations($entity);
if ($this->entityType->hasKey('status')) {
if (!$entity->status() && $entity->hasLinkTemplate('enable')) {

View File

@ -93,6 +93,25 @@ class EntityListBuilder extends EntityControllerBase implements EntityListBuilde
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
$operations = $this->getDefaultOperations($entity);
$operations += $this->moduleHandler()->invokeAll('entity_operation', array($entity));
$this->moduleHandler->alter('entity_operation', $operations, $entity);
uasort($operations, '\Drupal\Component\Utility\SortArray::sortByWeightElement');
return $operations;
}
/**
* Gets this list's default operations.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity the operations are for.
*
* @return array
* The array structure is identical to the return value of
* self::getOperations().
*/
protected function getDefaultOperations(EntityInterface $entity) {
$operations = array();
if ($entity->access('update') && $entity->hasLinkTemplate('edit-form')) {
$operations['edit'] = array(
@ -151,14 +170,11 @@ class EntityListBuilder extends EntityControllerBase implements EntityListBuilde
* @see \Drupal\Core\Entity\EntityListBuilder::buildRow()
*/
public function buildOperations(EntityInterface $entity) {
// Retrieve and sort operations.
$operations = $this->getOperations($entity);
$this->moduleHandler()->alter('entity_operation', $operations, $entity);
uasort($operations, array('Drupal\Component\Utility\SortArray', 'sortByWeightElement'));
$build = array(
'#type' => 'operations',
'#links' => $operations,
'#links' => $this->getOperations($entity),
);
return $build;
}

View File

@ -101,8 +101,8 @@ class ActionListBuilder extends ConfigEntityListBuilder {
/**
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
$operations = $entity->isConfigurable() ? parent::getOperations($entity) : array();
public function getDefaultOperations(EntityInterface $entity) {
$operations = $entity->isConfigurable() ? parent::getDefaultOperations($entity) : array();
if (isset($operations['edit'])) {
$operations['edit']['title'] = t('Configure');
}

View File

@ -36,8 +36,8 @@ class CustomBlockListBuilder extends EntityListBuilder {
/**
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
$operations = parent::getOperations($entity);
public function getDefaultOperations(EntityInterface $entity) {
$operations = parent::getDefaultOperations($entity);
if (isset($operations['edit'])) {
$operations['edit']['query']['destination'] = 'admin/structure/block/custom-blocks';
}

View File

@ -21,8 +21,8 @@ class CustomBlockTypeListBuilder extends ConfigEntityListBuilder {
/**
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
$operations = parent::getOperations($entity);
public function getDefaultOperations(EntityInterface $entity) {
$operations = parent::getDefaultOperations($entity);
// Place the edit operation after the operations added by field_ui.module
// which have the weights 15, 20, 25.
if (isset($operations['edit'])) {

View File

@ -378,8 +378,8 @@ class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface
/**
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
$operations = parent::getOperations($entity);
public function getDefaultOperations(EntityInterface $entity) {
$operations = parent::getDefaultOperations($entity);
if (isset($operations['edit'])) {
$operations['edit']['title'] = t('Configure');

View File

@ -144,15 +144,18 @@ function config_translation_config_translation_info(&$info) {
}
/**
* Implements hook_entity_operation_alter().
* Implements hook_entity_operation().
*/
function config_translation_entity_operation_alter(array &$operations, EntityInterface $entity) {
function config_translation_entity_operation(EntityInterface $entity) {
$operations = array();
if (\Drupal::currentUser()->hasPermission('translate configuration')) {
$operations['translate'] = array(
'title' => t('Translate'),
'weight' => 50,
) + $entity->urlInfo('drupal:config-translation-overview')->toArray();
}
return $operations;
}
/**

View File

@ -83,13 +83,13 @@ class ConfigTranslationEntityListBuilder extends EntityListBuilder implements Co
/**
* {@inheritdoc}
*/
public function buildOperations(EntityInterface $entity) {
$operations = parent::buildOperations($entity);
foreach (array_keys($operations['#links']) as $operation) {
public function getDefaultOperations(EntityInterface $entity) {
$operations = parent::getDefaultOperations($entity);
foreach (array_keys($operations) as $operation) {
// This is a translation UI for translators. Show the translation
// operation only.
if (!($operation == 'translate')) {
unset($operations['#links'][$operation]);
unset($operations[$operation]);
}
}
return $operations;

View File

@ -23,6 +23,7 @@ use Drupal\field\FieldInstanceConfigInterface;
* id = "field_instance_config",
* label = @Translation("Field instance"),
* controllers = {
* "list_builder" = "\Drupal\field_ui\FieldInstanceConfigListBuilder",
* "storage" = "Drupal\field\FieldInstanceConfigStorage"
* },
* config_prefix = "instance",

View File

@ -159,9 +159,10 @@ function field_ui_form_node_type_form_alter(&$form, $form_state) {
}
/**
* Implements hook_entity_operation_alter().
* Implements hook_entity_operation().
*/
function field_ui_entity_operation_alter(array &$operations, EntityInterface $entity) {
function field_ui_entity_operation(EntityInterface $entity) {
$operations = array();
$info = $entity->getEntityType();
// Add manage fields and display links if this entity type is the bundle
// of another.
@ -185,6 +186,8 @@ function field_ui_entity_operation_alter(array &$operations, EntityInterface $en
) + $entity->urlInfo('field_ui-display')->toArray();
}
}
return $operations;
}
/**

View File

@ -0,0 +1,92 @@
<?php
/**
* @file
* Contains \Drupal\field_ui\FieldInstanceConfigListBuilder.
*/
namespace Drupal\field_ui;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides lists of field instance config entities.
*/
class FieldInstanceConfigListBuilder extends ConfigEntityListBuilder {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a new class instance.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager) {
parent::__construct($entity_type, $entity_manager->getStorage($entity_type->id()));
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static($entity_type, $container->get('entity.manager'));
}
/**
* {@inheritdoc}
*/
public function render() {
// The actual field instance config overview is rendered by
// \Drupal\field_ui\FieldOverview, so we should not use this class to build
// lists.
throw new \Exception('This class is only used for operations and not for building lists.');
}
/**
* {@inheritdoc}
*/
public function getDefaultOperations(EntityInterface $entity) {
/** @var \Drupal\field\FieldInstanceConfigInterface $entity */
$operations = parent::getDefaultOperations($entity);
$target_entity_type_bundle_entity_type_id = $this->entityManager->getDefinition($entity->getTargetEntityTypeId())->getBundleEntityType();
$route_parameters = array(
$target_entity_type_bundle_entity_type_id => $entity->targetBundle(),
'field_instance_config' => $entity->id(),
);
$operations['edit'] = array(
'title' => $this->t('Edit'),
'route_name' => 'field_ui.instance_edit_' . $entity->getTargetEntityTypeId(),
'route_parameters' => $route_parameters,
'attributes' => array('title' => $this->t('Edit instance settings.')),
);
$operations['field-settings'] = array(
'title' => $this->t('Field settings'),
'route_name' => 'field_ui.field_edit_' . $entity->getTargetEntityTypeId(),
'route_parameters' => $route_parameters,
'attributes' => array('title' => $this->t('Edit field settings.')),
);
$operations['delete'] = array(
'title' => $this->t('Delete'),
'route_name' => 'field_ui.delete_' . $entity->getTargetEntityTypeId(),
'route_parameters' => $route_parameters,
'attributes' => array('title' => $this->t('Delete instance.')),
);
return $operations;
}
}

View File

@ -7,6 +7,7 @@
namespace Drupal\field_ui;
use Drupal\Core\Entity\EntityListBuilderInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
@ -27,13 +28,6 @@ class FieldOverview extends OverviewBase {
*/
protected $fieldTypeManager;
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Constructs a new FieldOverview.
*
@ -41,13 +35,10 @@ class FieldOverview extends OverviewBase {
* The entity manager.
* @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
* The field type manager
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke hooks on.
*/
public function __construct(EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager, ModuleHandlerInterface $module_handler) {
public function __construct(EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager) {
parent::__construct($entity_manager);
$this->fieldTypeManager = $field_type_manager;
$this->moduleHandler = $module_handler;
}
/**
@ -56,8 +47,7 @@ class FieldOverview extends OverviewBase {
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager'),
$container->get('plugin.manager.field.field_type'),
$container->get('module_handler')
$container->get('plugin.manager.field.field_type')
);
}
@ -146,30 +136,9 @@ class FieldOverview extends OverviewBase {
),
);
$links = array();
$links['edit'] = array(
'title' => $this->t('Edit'),
'route_name' => 'field_ui.instance_edit_' . $this->entity_type,
'route_parameters' => $route_parameters,
'attributes' => array('title' => $this->t('Edit instance settings.')),
);
$links['field-settings'] = array(
'title' => $this->t('Field settings'),
'route_name' => 'field_ui.field_edit_' . $this->entity_type,
'route_parameters' => $route_parameters,
'attributes' => array('title' => $this->t('Edit field settings.')),
);
$links['delete'] = array(
'title' => $this->t('Delete'),
'route_name' => 'field_ui.delete_' . $this->entity_type,
'route_parameters' => $route_parameters,
'attributes' => array('title' => $this->t('Delete instance.')),
);
// Allow altering the operations on this entity listing.
$this->moduleHandler->alter('entity_operation', $links, $instance);
$table[$name]['operations']['data'] = array(
'#type' => 'operations',
'#links' => $links,
'#links' => $this->entityManager->getListBuilder('field_instance_config')->getOperations($instance),
);
if (!empty($field->locked)) {

View File

@ -106,6 +106,11 @@ class ManageFieldsTest extends FieldUiTestBase {
foreach (array('Add new field', 'Re-use existing field') as $element) {
$this->assertText($element, format_string('"@element" was found.', array('@element' => $element)));
}
// Assert entity operations for all field instances.
$this->assertLinkByHref("admin/structure/types/manage/$type/fields/node.$type.body");
$this->assertLinkByHref("admin/structure/types/manage/$type/fields/node.$type.body/delete");
$this->assertLinkByHref("admin/structure/types/manage/$type/fields/node.$type.body/field");
}
/**

View File

@ -118,8 +118,8 @@ class FilterFormatListBuilder extends DraggableListBuilder {
/**
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
$operations = parent::getOperations($entity);
public function getDefaultOperations(EntityInterface $entity) {
$operations = parent::getDefaultOperations($entity);
if (isset($operations['edit'])) {
$operations['edit']['title'] = t('Configure');

View File

@ -74,13 +74,15 @@ class ImageStyleListBuilder extends ConfigEntityListBuilder {
/**
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
public function getDefaultOperations(EntityInterface $entity) {
$flush = array(
'title' => t('Flush'),
'weight' => 200,
) + $entity->urlInfo('flush-form')->toArray();
return parent::getOperations($entity) + array('flush' => $flush);
return parent::getDefaultOperations($entity) + array(
'flush' => $flush,
);
}
/**

View File

@ -44,8 +44,8 @@ class LanguageListBuilder extends DraggableListBuilder {
/**
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
$operations = parent::getOperations($entity);
public function getDefaultOperations(EntityInterface $entity) {
$operations = parent::getDefaultOperations($entity);
$default = language_default();
// Deleting the site default language is not allowed.

View File

@ -46,8 +46,8 @@ class MenuListBuilder extends ConfigEntityListBuilder {
/**
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
$operations = parent::getOperations($entity);
public function getDefaultOperations(EntityInterface $entity) {
$operations = parent::getDefaultOperations($entity);
if (isset($operations['edit'])) {
$operations['edit']['title'] = t('Edit menu');

View File

@ -124,8 +124,8 @@ class NodeListBuilder extends EntityListBuilder {
/**
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
$operations = parent::getOperations($entity);
protected function getDefaultOperations(EntityInterface $entity) {
$operations = parent::getDefaultOperations($entity);
$destination = drupal_get_destination();
foreach ($operations as $key => $operation) {

View File

@ -83,8 +83,8 @@ class NodeTypeListBuilder extends ConfigEntityListBuilder {
/**
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
$operations = parent::getOperations($entity);
public function getDefaultOperations(EntityInterface $entity) {
$operations = parent::getDefaultOperations($entity);
// Place the edit operation after the operations added by field_ui.module
// which have the weights 15, 20, 25.
if (isset($operations['edit'])) {

View File

@ -36,8 +36,8 @@ class ResponsiveImageMappingListBuilder extends ConfigEntityListBuilder {
/**
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
$operations = parent::getOperations($entity);
public function getDefaultOperations(EntityInterface $entity) {
$operations = parent::getDefaultOperations($entity);
$operations['duplicate'] = array(
'title' => t('Duplicate'),
'weight' => 15,

View File

@ -260,9 +260,9 @@ class SearchPageListBuilder extends DraggableListBuilder implements FormInterfac
/**
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
public function getDefaultOperations(EntityInterface $entity) {
/** @var $entity \Drupal\search\SearchPageInterface */
$operations = parent::getOperations($entity);
$operations = parent::getDefaultOperations($entity);
// Prevent the default search from being disabled or deleted.
if ($entity->isDefaultSearch()) {

View File

@ -28,8 +28,8 @@ class ShortcutSetListBuilder extends ConfigEntityListBuilder {
/**
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
$operations = parent::getOperations($entity);
public function getDefaultOperations(EntityInterface $entity) {
$operations = parent::getDefaultOperations($entity);
if (isset($operations['edit'])) {
$operations['edit']['title'] = t('Edit shortcut set');

View File

@ -756,6 +756,27 @@ function hook_entity_bundle_field_info_alter(&$fields, \Drupal\Core\Entity\Entit
}
}
/**
* Declares entity operations.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity on which the linked operations will be performed.
*
* @return array
* An operations array as returned by
* \Drupal\Core\Entity\EntityListBuilderInterface::getOperations().
*/
function hook_entity_operation(\Drupal\Core\Entity\EntityInterface $entity) {
$operations = array();
$operations['translate'] = array(
'title' => t('Translate'),
'route_name' => 'foo_module.entity.translate',
'weight' => 50,
);
return $operations;
}
/**
* Alter entity operations.
*
@ -766,10 +787,11 @@ function hook_entity_bundle_field_info_alter(&$fields, \Drupal\Core\Entity\Entit
* The entity on which the linked operations will be performed.
*/
function hook_entity_operation_alter(array &$operations, \Drupal\Core\Entity\EntityInterface $entity) {
$operations['translate'] = array(
'title' => t('Translate'),
'weight' => 50,
) + $entity->urlInfo('my-custom-link-template')->toArray();
// Alter the title and weight.
$operations['translate']['title'] = t('Translate @entity_type', array(
'@entity_type' => $entity->getEntityTypeId(),
));
$operations['translate']['weight'] = 99;
}
/**

View File

@ -32,8 +32,8 @@ class VocabularyListBuilder extends DraggableListBuilder {
/**
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
$operations = parent::getOperations($entity);
public function getDefaultOperations(EntityInterface $entity) {
$operations = parent::getDefaultOperations($entity);
if (isset($operations['edit'])) {
$operations['edit']['title'] = t('Edit vocabulary');

View File

@ -43,8 +43,8 @@ class RoleListBuilder extends DraggableListBuilder {
/**
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
$operations = parent::getOperations($entity);
public function getDefaultOperations(EntityInterface $entity) {
$operations = parent::getDefaultOperations($entity);
if ($entity->hasLinkTemplate('edit-permissions-form')) {
$operations['permissions'] = array(

View File

@ -135,8 +135,8 @@ class ViewListBuilder extends ConfigEntityListBuilder {
/**
* {@inheritdoc}
*/
public function getOperations(EntityInterface $entity) {
$operations = parent::getOperations($entity);
public function getDefaultOperations(EntityInterface $entity) {
$operations = parent::getDefaultOperations($entity);
if ($entity->hasLinkTemplate('clone')) {
$operations['clone'] = array(

View File

@ -7,7 +7,9 @@
namespace Drupal\Tests\Core\Entity;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityListBuilder;
use Drupal\entity_test\EntityTestListBuilder;
use Drupal\Tests\UnitTestCase;
@ -20,6 +22,41 @@ use Drupal\Tests\UnitTestCase;
*/
class EntityListBuilderTest extends UnitTestCase {
/**
* The entity type used for testing.
*
* @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $entityType;
/**
* The module handler used for testing.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $moduleHandler;
/**
* The translation manager used for testing.
*
* @var \Drupal\Core\StringTranslation\TranslationInterface
*/
protected $translationManager;
/**
* The role storage used for testing.
*
* @var \Drupal\user\RoleStorageInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $roleStorage;
/**
* The service container used for testing.
*
* @var \Drupal\Core\DependencyInjection\ContainerBuilder
*/
protected $container;
/**
* The entity used to construct the EntityListBuilder.
*
@ -49,10 +86,65 @@ class EntityListBuilderTest extends UnitTestCase {
parent::setUp();
$this->role = $this->getMock('Drupal\user\RoleInterface');
$role_storage = $this->getMock('Drupal\user\RoleStorageInterface');
$module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
$entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
$this->entityListBuilder = new TestEntityListBuilder($entity_type, $role_storage, $module_handler);
$this->roleStorage = $this->getMock('\Drupal\user\RoleStorageInterface');
$this->moduleHandler = $this->getMock('\Drupal\Core\Extension\ModuleHandlerInterface');
$this->entityType = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface');
$this->translationManager = $this->getMock('\Drupal\Core\StringTranslation\TranslationInterface');
$this->entityListBuilder = new TestEntityListBuilder($this->entityType, $this->roleStorage, $this->moduleHandler);
$this->container = new ContainerBuilder();
\Drupal::setContainer($this->container);
}
/**
* @covers \Drupal\Core\Entity\EntityListBuilder::getOperations
*/
public function testGetOperations() {
$operation_name = $this->randomName();
$operations = array(
$operation_name => array(
'title' => $this->randomName(),
),
);
$this->moduleHandler->expects($this->once())
->method('invokeAll')
->with('entity_operation', array($this->role))
->will($this->returnValue($operations));
$this->moduleHandler->expects($this->once())
->method('alter')
->with('entity_operation');
$this->container->set('module_handler', $this->moduleHandler);
$this->role->expects($this->any())
->method('access')
->will($this->returnValue(TRUE));
$this->role->expects($this->any())
->method('hasLinkTemplate')
->will($this->returnValue(TRUE));
$url = $this->getMockBuilder('\Drupal\Core\Url')
->disableOriginalConstructor()
->getMock();
$url->expects($this->any())
->method('toArray')
->will($this->returnValue(array()));
$this->role->expects($this->any())
->method('urlInfo')
->will($this->returnValue($url));
$list = new EntityListBuilder($this->entityType, $this->roleStorage, $this->moduleHandler);
$list->setTranslationManager($this->translationManager);
$operations = $list->getOperations($this->role);
$this->assertInternalType('array', $operations);
$this->assertArrayHasKey('edit', $operations);
$this->assertInternalType('array', $operations['edit']);
$this->assertArrayHasKey('title', $operations['edit']);
$this->assertArrayHasKey('delete', $operations);
$this->assertInternalType('array', $operations['delete']);
$this->assertArrayHasKey('title', $operations['delete']);
$this->assertArrayHasKey($operation_name, $operations);
$this->assertInternalType('array', $operations[$operation_name]);
$this->assertArrayHasKey('title', $operations[$operation_name]);
}
/**