summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwebchick2012-09-30 20:48:46 (GMT)
committerwebchick2012-09-30 20:48:46 (GMT)
commit4ecc6798234d6da34d2ef5a75114c83de78caff0 (patch)
tree748c76e3e8e581be68591740514a636d2429c446
parent294f1790e234c6ac8711d108d5cb0b314f6d603b (diff)
Issue #1781372 by tim.plunkett, xjm, sun, damiankloip, andypost, Jelle_S, Gábor Hojtsy: Change notice: Add an API for listing (configuration) entities.
-rw-r--r--core/includes/entity.api.php4
-rw-r--r--core/includes/entity.inc19
-rw-r--r--core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php26
-rw-r--r--core/lib/Drupal/Core/Entity/EntityListController.php162
-rw-r--r--core/lib/Drupal/Core/Entity/EntityListControllerInterface.php58
-rw-r--r--core/modules/config/lib/Drupal/config/Tests/ConfigEntityListTest.php185
-rw-r--r--core/modules/config/tests/config_test/config_test.module33
7 files changed, 457 insertions, 30 deletions
diff --git a/core/includes/entity.api.php b/core/includes/entity.api.php
index 102fa70..9c9d123 100644
--- a/core/includes/entity.api.php
+++ b/core/includes/entity.api.php
@@ -34,6 +34,10 @@
* different operations, the name of the operation is passed also to the
* constructor of the form controller class. This way, one class can be used
* for multiple entity forms.
+ * - list controller class: The name of the class that is used to provide
+ * listings of the entity. The class must implement
+ * Drupal\Core\Entity\EntityListControllerInterface. Defaults to
+ * Drupal\Core\Entity\EntityListController.
* - base table: (used by Drupal\Core\Entity\DatabaseStorageController) The
* name of the entity type's base table.
* - static cache: (used by Drupal\Core\Entity\DatabaseStorageController)
diff --git a/core/includes/entity.inc b/core/includes/entity.inc
index b651e32..2b504aa 100644
--- a/core/includes/entity.inc
+++ b/core/includes/entity.inc
@@ -47,6 +47,7 @@ function entity_get_info($entity_type = NULL) {
'fieldable' => FALSE,
'entity class' => 'Drupal\Core\Entity\Entity',
'controller class' => 'Drupal\Core\Entity\DatabaseStorageController',
+ 'list controller class' => 'Drupal\Core\Entity\EntityListController',
'form controller class' => array(
'default' => 'Drupal\Core\Entity\EntityFormController',
),
@@ -530,3 +531,21 @@ function entity_form_submit_build_entity($entity_type, $entity, $form, &$form_st
field_attach_submit($entity_type, $entity, $form, $form_state);
}
}
+
+/**
+ * Returns an entity list controller for a given entity type.
+ *
+ * @param string $entity_type
+ * The type of the entity.
+ *
+ * @return Drupal\Core\Entity\EntityListControllerInterface
+ * An entity list controller.
+ *
+ * @see hook_entity_info()
+ */
+function entity_list_controller($entity_type) {
+ $storage = entity_get_controller($entity_type);
+ $entity_info = entity_get_info($entity_type);
+ $class = $entity_info['list controller class'];
+ return new $class($entity_type, $storage);
+}
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php
new file mode 100644
index 0000000..3977310
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Config\Entity\ConfigEntityListController.
+ */
+
+namespace Drupal\Core\Config\Entity;
+
+use Drupal\Core\Entity\EntityListController;
+
+/**
+ * Defines the default list controller for ConfigEntity objects.
+ */
+class ConfigEntityListController extends EntityListController {
+
+ /**
+ * Overrides Drupal\Core\Entity\EntityListController::load().
+ */
+ public function load() {
+ $entities = parent::load();
+ uasort($entities, 'Drupal\Core\Config\Entity\ConfigEntityBase::sort');
+ return $entities;
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityListController.php b/core/lib/Drupal/Core/Entity/EntityListController.php
new file mode 100644
index 0000000..508db1c
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityListController.php
@@ -0,0 +1,162 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Entity\EntityListController.
+ */
+
+namespace Drupal\Core\Entity;
+
+/**
+ * Provides a generic implementation of an entity list controller.
+ */
+class EntityListController implements EntityListControllerInterface {
+
+ /**
+ * The entity storage controller class.
+ *
+ * @var Drupal\Core\Entity\EntityStorageControllerInterface
+ */
+ protected $storage;
+
+ /**
+ * The entity type name.
+ *
+ * @var string
+ */
+ protected $entityType;
+
+ /**
+ * The entity info array.
+ *
+ * @var array
+ *
+ * @see entity_get_info()
+ */
+ protected $entityInfo;
+
+ /**
+ * Constructs a new EntityListController object.
+ *
+ * @param string $entity_type.
+ * The type of entity to be listed.
+ * @param Drupal\Core\Entity\EntityStorageControllerInterface $storage.
+ * The entity storage controller class.
+ */
+ public function __construct($entity_type, EntityStorageControllerInterface $storage) {
+ $this->entityType = $entity_type;
+ $this->storage = $storage;
+ $this->entityInfo = entity_get_info($this->entityType);
+ }
+
+ /**
+ * Implements Drupal\Core\Entity\EntityListControllerInterface::getStorageController().
+ */
+ public function getStorageController() {
+ return $this->storage;
+ }
+
+ /**
+ * Implements Drupal\Core\Entity\EntityListControllerInterface::load().
+ */
+ public function load() {
+ return $this->storage->load();
+ }
+
+ /**
+ * Implements Drupal\Core\Entity\EntityListControllerInterface::getOperations().
+ */
+ public function getOperations(EntityInterface $entity) {
+ $uri = $entity->uri();
+ $operations['edit'] = array(
+ 'title' => t('Edit'),
+ 'href' => $uri['path'] . '/edit',
+ 'options' => $uri['options'],
+ 'weight' => 10,
+ );
+ $operations['delete'] = array(
+ 'title' => t('Delete'),
+ 'href' => $uri['path'] . '/delete',
+ 'options' => $uri['options'],
+ 'weight' => 100,
+ );
+ return $operations;
+ }
+
+ /**
+ * Builds the header row for the entity listing.
+ *
+ * @return array
+ * A render array structure of header strings.
+ *
+ * @see Drupal\Core\Entity\EntityListController::render()
+ */
+ public function buildHeader() {
+ $row['label'] = t('Label');
+ $row['id'] = t('Machine name');
+ $row['operations'] = t('Operations');
+ return $row;
+ }
+
+ /**
+ * Builds a row for an entity in the entity listing.
+ *
+ * @param Drupal\Core\Entity\EntityInterface $entity
+ * The entity for this row of the list.
+ *
+ * @return array
+ * A render array structure of fields for this entity.
+ *
+ * @see Drupal\Core\Entity\EntityListController::render()
+ */
+ public function buildRow(EntityInterface $entity) {
+ $row['label'] = $entity->label();
+ $row['id'] = $entity->id();
+ $operations = $this->buildOperations($entity);
+ $row['operations']['data'] = $operations;
+ return $row;
+ }
+
+ /**
+ * Builds a renderable list of operation links for the entity.
+ *
+ * @param Drupal\Core\Entity\EntityInterface $entity
+ * The entity on which the linked operations will be performed.
+ *
+ * @return array
+ * A renderable array of operation links.
+ *
+ * @see Drupal\Core\Entity\EntityListController::render()
+ */
+ public function buildOperations(EntityInterface $entity) {
+ // Retrieve and sort operations.
+ $operations = $this->getOperations($entity);
+ uasort($operations, 'drupal_sort_weight');
+ $build = array(
+ '#theme' => 'links',
+ '#links' => $operations,
+ );
+ return $build;
+ }
+
+ /**
+ * Implements Drupal\Core\Entity\EntityListControllerInterface::render().
+ *
+ * Builds the entity list as renderable array for theme_table().
+ *
+ * @todo Add a link to add a new item to the #empty text.
+ */
+ public function render() {
+ $build = array(
+ '#theme' => 'table',
+ '#header' => $this->buildHeader(),
+ '#rows' => array(),
+ '#empty' => t('There is no @label yet.', array('@label' => $this->entityInfo['label'])),
+ );
+ foreach ($this->load() as $entity) {
+ $build['#rows'][$entity->id()] = $this->buildRow($entity);
+ }
+ return $build;
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityListControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityListControllerInterface.php
new file mode 100644
index 0000000..85790c3
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityListControllerInterface.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Entity\EntityListControllerInterface.
+ */
+
+namespace Drupal\Core\Entity;
+
+/**
+ * Defines an interface for entity list controllers.
+ */
+interface EntityListControllerInterface {
+
+ /**
+ * Gets the entity storage controller.
+ *
+ * @return Drupal\Core\Entity\EntityStorageControllerInterface
+ * The storage controller used by this list controller.
+ */
+ public function getStorageController();
+
+ /**
+ * Loads entities of this type from storage for listing.
+ *
+ * This allows the controller to manipulate the list, like filtering or
+ * sorting the loaded entities.
+ *
+ * @return array
+ * An array of entities implementing Drupal\Core\Entity\EntityInterface.
+ */
+ public function load();
+
+ /**
+ * Provides an array of information to build a list of operation links.
+ *
+ * @param Drupal\Core\Entity\EntityInterface $entity
+ * The entity the operations are for.
+ *
+ * @return array
+ * An associative array of operation link data for this list, keyed by
+ * operation name, containing the following key-value pairs:
+ * - title: The localized title of the operation.
+ * - href: The path for the operation.
+ * - options: An array of URL options for the path.
+ * - weight: The weight of this operation.
+ */
+ public function getOperations(EntityInterface $entity);
+
+ /**
+ * Renders the list page markup to be output.
+ *
+ * @return string
+ * The output markup for the listing page.
+ */
+ public function render();
+
+}
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigEntityListTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityListTest.php
new file mode 100644
index 0000000..cd48f71
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigEntityListTest.php
@@ -0,0 +1,185 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\config\Tests\ConfigEntityListTest.
+ */
+
+namespace Drupal\config\Tests;
+
+use Drupal\simpletest\WebTestBase;
+use Drupal\config_test\ConfigTest;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+
+/**
+ * Tests the listing of configuration entities.
+ */
+class ConfigEntityListTest extends WebTestBase {
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = array('config_test');
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Configuration entity list',
+ 'description' => 'Tests the listing of configuration entities.',
+ 'group' => 'Configuration',
+ );
+ }
+
+ /**
+ * Tests entity list controller methods.
+ */
+ function testList() {
+ $controller = entity_list_controller('config_test');
+
+ // Test getStorageController() method.
+ $this->assertTrue($controller->getStorageController() instanceof EntityStorageControllerInterface, 'EntityStorageController instance in storage.');
+
+ // Get a list of ConfigTest entities and confirm that it contains the
+ // ConfigTest entity provided by the config_test module.
+ // @see config_test.dynamic.default.yml
+ $list = $controller->load();
+ $this->assertEqual(count($list), 1, '1 ConfigTest entity found.');
+ $entity = $list['default'];
+ $this->assertTrue(!empty($entity), '"Default" ConfigTest entity ID found.');
+ $this->assertTrue($entity instanceof ConfigTest, '"Default" ConfigTest entity is an instance of ConfigTest.');
+
+ // Test getOperations() method.
+ $uri = $entity->uri();
+ $expected_operations = array(
+ 'edit' => array (
+ 'title' => 'Edit',
+ 'href' => 'admin/structure/config_test/manage/default/edit',
+ 'options' => $uri['options'],
+ 'weight' => 10,
+ ),
+ 'delete' => array (
+ 'title' => 'Delete',
+ 'href' => 'admin/structure/config_test/manage/default/delete',
+ 'options' => $uri['options'],
+ 'weight' => 100,
+ ),
+ );
+ $actual_operations = $controller->getOperations($entity);
+ $this->assertIdentical($expected_operations, $actual_operations, 'Return value from getOperations matches expected.');
+
+ // Test buildHeader() method.
+ $expected_items = array(
+ 'label' => 'Label',
+ 'id' => 'Machine name',
+ 'operations' => 'Operations',
+ );
+ $actual_items = $controller->buildHeader();
+ $this->assertIdentical($expected_items, $actual_items, 'Return value from buildHeader matches expected.');
+
+ // Test buildRow() method.
+ $build_operations = $controller->buildOperations($entity);
+ $expected_items = array(
+ 'label' => 'Default',
+ 'id' => 'default',
+ 'operations' => array(
+ 'data' => $build_operations,
+ ),
+ );
+ $actual_items = $controller->buildRow($entity);
+ $this->assertIdentical($expected_items, $actual_items, 'Return value from buildRow matches expected.');
+ }
+
+ /**
+ * Tests the listing UI.
+ */
+ function testListUI() {
+ // Log in as an administrative user to access the full menu trail.
+ $this->drupalLogin($this->drupalCreateUser(array('access administration pages')));
+
+ // Get the list callback page.
+ $this->drupalGet('admin/structure/config_test');
+
+ // Test for the page title.
+ $this->assertTitle('Test configuration | Drupal');
+
+ // Test for the table.
+ $element = $this->xpath('//div[@id="content"]//table');
+ $this->assertTrue($element, 'Configuration entity list table found.');
+
+ // Test the table header.
+ $elements = $this->xpath('//div[@id="content"]//table/thead/tr/th');
+ $this->assertEqual(count($elements), 3, 'Correct number of table header cells found.');
+
+ // Test the contents of each th cell.
+ $expected_items = array('Label', 'Machine name', 'Operations');
+ foreach ($elements as $key => $element) {
+ $this->assertIdentical((string) $element[0], $expected_items[$key]);
+ }
+
+ // Check the number of table row cells.
+ $elements = $this->xpath('//div[@id="content"]//table/tbody/tr[@class="odd"]/td');
+ $this->assertEqual(count($elements), 3, 'Correct number of table row cells found.');
+
+ // Check the contents of each row cell. The first cell contains the label,
+ // the second contains the machine name, and the third contains the
+ // operations list.
+ $this->assertIdentical((string) $elements[0], 'Default');
+ $this->assertIdentical((string) $elements[1], 'default');
+ $this->assertTrue($elements[2]->children()->xpath('//ul'), 'Operations list found.');
+
+ // Add a new entity using the operations link.
+ $this->assertLink('Add test configuration');
+ $this->clickLink('Add test configuration');
+ $this->assertResponse(200);
+ $edit = array('label' => 'Antelope', 'id' => 'antelope');
+ $this->drupalPost(NULL, $edit, t('Save'));
+
+ // Confirm that the user is returned to the listing, and verify that the
+ // text of the label and machine name appears in the list (versus elsewhere
+ // on the page).
+ $this->assertFieldByXpath('//td', 'Antelope', "Label found for added 'Antelope' entity.");
+ $this->assertFieldByXpath('//td', 'antelope', "Machine name found for added 'Antelope' entity.");
+
+ // Edit the entity using the operations link.
+ $this->assertLink('Edit');
+ $this->clickLink('Edit');
+ $this->assertResponse(200);
+ $this->assertTitle('Edit test configuration | Drupal');
+ $edit = array('label' => 'Albatross', 'id' => 'albatross');
+ $this->drupalPost(NULL, $edit, t('Save'));
+
+ // Confirm that the user is returned to the listing, and verify that the
+ // text of the label and machine name appears in the list (versus elsewhere
+ // on the page).
+ $this->assertFieldByXpath('//td', 'Albatross', "Label found for updated 'Albatross' entity.");
+ $this->assertFieldByXpath('//td', 'albatross', "Machine name found for updated 'Albatross' entity.");
+
+ // Delete the added entity using the operations link.
+ $this->assertLink('Delete');
+ $this->clickLink('Delete');
+ $this->assertResponse(200);
+ $this->assertTitle('Are you sure you want to delete Albatross | Drupal');
+ $this->drupalPost(NULL, array(), t('Delete'));
+
+ // Verify that the text of the label and machine name does not appear in
+ // the list (though it may appear elsewhere on the page).
+ $this->assertNoFieldByXpath('//td', 'Albatross', "No label found for deleted 'Albatross' entity.");
+ $this->assertNoFieldByXpath('//td', 'albatross', "No machine name found for deleted 'Albatross' entity.");
+
+ // Delete the original entity using the operations link.
+ $this->clickLink('Delete');
+ $this->assertResponse(200);
+ $this->assertTitle('Are you sure you want to delete Default | Drupal');
+ $this->drupalPost(NULL, array(), t('Delete'));
+
+ // Verify that the text of the label and machine name does not appear in
+ // the list (though it may appear elsewhere on the page).
+ $this->assertNoFieldByXpath('//td', 'Default', "No label found for deleted 'Default' entity.");
+ $this->assertNoFieldByXpath('//td', 'default', "No machine name found for deleted 'Default' entity.");
+
+ // Confirm that the empty text is displayed.
+ $this->assertText('There is no Test configuration yet.');
+ }
+
+}
diff --git a/core/modules/config/tests/config_test/config_test.module b/core/modules/config/tests/config_test/config_test.module
index 61d92df..44df4da 100644
--- a/core/modules/config/tests/config_test/config_test.module
+++ b/core/modules/config/tests/config_test/config_test.module
@@ -82,6 +82,7 @@ function config_test_entity_info() {
'label' => 'Test configuration',
'controller class' => 'Drupal\Core\Config\Entity\ConfigStorageController',
'entity class' => 'Drupal\config_test\ConfigTest',
+ 'list controller class' => 'Drupal\Core\Config\Entity\ConfigEntityListController',
'uri callback' => 'config_test_uri',
'config prefix' => 'config_test.dynamic',
'entity keys' => array(
@@ -176,36 +177,8 @@ function config_test_delete($id) {
* Page callback; Lists available ConfigTest objects.
*/
function config_test_list_page() {
- $entities = entity_load_multiple('config_test');
- uasort($entities, 'Drupal\Core\Config\Entity\ConfigEntityBase::sort');
-
- $rows = array();
- foreach ($entities as $config_test) {
- $uri = $config_test->uri();
- $row = array();
- $row['name']['data'] = array(
- '#type' => 'link',
- '#title' => $config_test->label(),
- '#href' => $uri['path'],
- '#options' => $uri['options'],
- );
- $row['delete']['data'] = array(
- '#type' => 'link',
- '#title' => t('Delete'),
- '#href' => $uri['path'] . '/delete',
- '#options' => $uri['options'],
- );
- $rows[] = $row;
- }
- $build = array(
- '#theme' => 'table',
- '#header' => array('Name', 'Operations'),
- '#rows' => $rows,
- '#empty' => format_string('No test configuration defined. <a href="@add-url">Add some</a>', array(
- '@add-url' => url('admin/structure/config_test/add'),
- )),
- );
- return $build;
+ $controller = entity_list_controller('config_test');
+ return $controller->render();
}
/**