diff --git a/.travis.yml b/.travis.yml index 7c84d51e471c238382d76e9e17367042d096af28..0a4764e3327c7bed6585eda94acdc4e509b09aef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,10 +40,20 @@ before_script: # Download Drupal 8 core. - travis_retry git clone --branch 8.0.x --depth 1 http://git.drupal.org/project/drupal.git - cd drupal + # Add a patch for BrowserTestBase + - curl -o 2636228-6.patch https://www.drupal.org/files/issues/2636228-6.patch + - patch -p1 < 2636228-6.patch # Reference entity in build site. - ln -s $TESTDIR modules/entity + # Start a web server on port 8888, run in the background; wait for + # initialization. + - nohup php -S localhost:8888 > /dev/null 2>&1 & + + # Export web server URL for browser tests. + - export SIMPLETEST_BASE_URL=http://localhost:8888 + script: # Run the PHPUnit tests which also include the kernel tests. - - ./vendor/phpunit/phpunit/phpunit -c ./core/phpunit.xml.dist ./modules/entity + - ./vendor/phpunit/phpunit/phpunit -c ./core/phpunit.xml.dist --verbose ./modules/entity diff --git a/entity.module b/entity.module index 3ee6637635e529ec7ab7266698f74d6ad909a3ba..f1845403d133d6e80fa2581a5d4153f70063eb4b 100644 --- a/entity.module +++ b/entity.module @@ -5,7 +5,6 @@ * Provides expanded entity APIs. */ -use Drupal\Core\Link; use Drupal\Core\Url; /** @@ -17,7 +16,6 @@ function entity_theme() { 'variables' => [ 'bundles' => [], 'bundle_type' => NULL, - 'form_route_name' => NULL, ], 'template' => 'entity-add-list', ], @@ -32,8 +30,7 @@ function entity_theme() { * @param array $variables * An associative array containing: * - bundle_type: The entity type of the bundles. - * - bundles: An array of bundles. - * - form_route_name: The add form route. + * - bundles: An array of bundles with the label, description, add_link keys. */ function template_preprocess_entity_add_list(&$variables) { $bundle_type = \Drupal::entityTypeManager()->getDefinition($variables['bundle_type']); @@ -42,15 +39,9 @@ function template_preprocess_entity_add_list(&$variables) { 'bundle_type_label' => $bundle_type->getLowercaseLabel(), ]; - foreach ($variables['bundles'] as $bundle) { - $bundle_id = $bundle->id(); - $variables['bundles'][$bundle_id] = [ - 'add_link' => Link::createFromRoute($bundle->label(), $variables['form_route_name'], [$bundle_type->id() => $bundle_id]), + foreach ($variables['bundles'] as $bundle_name => $bundle_info) { + $variables['bundles'][$bundle_name]['description'] = [ + '#markup' => $bundle_info['description'], ]; - if ($bundle instanceof \Drupal\entity\Entity\EntityDescriptionInterface) { - $variables['bundles'][$bundle_id]['description'] = [ - '#markup' => $bundle->getDescription(), - ]; - } } } diff --git a/src/Controller/EntityCreateController.php b/src/Controller/EntityCreateController.php index d9014fd5ce4f8143f68e8503a31c31c6e4be125e..bece86b969131b8eb7b1329fd686868bacf2f5cc 100644 --- a/src/Controller/EntityCreateController.php +++ b/src/Controller/EntityCreateController.php @@ -11,9 +11,10 @@ use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; use Drupal\Core\Render\RendererInterface; use Drupal\Core\Routing\RouteMatchInterface; -use Drupal\Core\Url; +use Drupal\Core\Link; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * A generic controller for creating entities. @@ -74,6 +75,7 @@ class EntityCreateController extends ControllerBase { public function addPage($entity_type_id, Request $request) { $entity_type = $this->entityTypeManager()->getDefinition($entity_type_id); $bundle_type = $entity_type->getBundleEntityType(); + $bundle_key = $entity_type->getKey('bundle'); $form_route_name = 'entity.' . $entity_type_id . '.add_form'; $build = [ '#theme' => 'entity_add_list', @@ -81,25 +83,32 @@ class EntityCreateController extends ControllerBase { 'tags' => $entity_type->getListCacheTags(), ], '#bundle_type' => $bundle_type, - '#form_route_name' => $form_route_name, ]; - $bundles = array_keys($this->entityTypeBundleInfo->getBundleInfo($entity_type_id)); + $bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type_id); // Filter out the bundles the user doesn't have access to. $access_control_handler = $this->entityTypeManager()->getAccessControlHandler($bundle_type); - foreach ($bundles as $index => $bundle_name) { + foreach ($bundles as $bundle_name => $bundle_info) { $access = $access_control_handler->createAccess($bundle_name, NULL, [], TRUE); if (!$access->isAllowed()) { - unset($bundles[$index]); + unset($bundles[$bundle_name]); } $this->renderer->addCacheableDependency($build, $access); } // Redirect if there's only one bundle available. if (count($bundles) == 1) { - $bundle_name = reset($bundles); - return $this->redirect($form_route_name, [$bundle_type => $bundle_name]); + $bundle_names = array_keys($bundles); + $bundle_name = reset($bundle_names); + return $this->redirect($form_route_name, [$bundle_key => $bundle_name]); + } + // Prepare the #bundles array for the template. + $bundles = $this->loadBundleDescriptions($bundles, $bundle_type); + foreach ($bundles as $bundle_name => $bundle_info) { + $build['#bundles'][$bundle_name] = [ + 'label' => $bundle_info['label'], + 'description' => $bundle_info['description'], + 'add_link' => Link::createFromRoute($bundle_info['label'], $form_route_name, [$bundle_key => $bundle_name]), + ]; } - // The theme function needs the full bundle entities. - $build['#bundles'] = $this->entityTypeManager->getStorage($bundle_type)->loadMultiple($bundles); return $build; } @@ -133,9 +142,14 @@ class EntityCreateController extends ControllerBase { $entity_type = $this->entityTypeManager()->getDefinition($entity_type_id); $values = []; // Entities of this type have bundles, one was provided in the url. - if ($bundle_type = $entity_type->getBundleEntityType()) { - $bundle_key = $entity_type->getKey('bundle'); - $values[$bundle_key] = $route_match->getRawParameter($bundle_type); + if ($bundle_key = $entity_type->getKey('bundle')) { + $bundle_name = $route_match->getRawParameter($bundle_key); + $bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type_id); + if (empty($bundle_name) || !isset($bundles[$bundle_name])) { + // The bundle parameter is invalid. + throw new NotFoundHttpException(); + } + $values[$bundle_key] = $bundle_name; } $entity = $this->entityTypeManager()->getStorage($entity_type_id)->create($values); @@ -155,9 +169,10 @@ class EntityCreateController extends ControllerBase { */ public function addFormTitle($entity_type_id, RouteMatchInterface $route_match) { $entity_type = $this->entityTypeManager()->getDefinition($entity_type_id); + $bundle_key = $entity_type->getKey('bundle'); $bundles = $this->entityTypeBundleInfo->getBundleInfo($entity_type_id); - if (count($bundles) > 1) { - $bundle_name = $route_match->getRawParameter($bundle_type); + if ($bundle_key && count($bundles) > 1) { + $bundle_name = $route_match->getRawParameter($bundle_key); $title = $this->t('Add @bundle', ['@bundle' => $bundles[$bundle_name]['label']]); } else { @@ -167,4 +182,39 @@ class EntityCreateController extends ControllerBase { return $title; } + /** + * Expands the bundle information with descriptions, if known. + * + * @param array $bundles + * An array of bundle information. + * @param string $bundle_type + * The id of the bundle entity type. + * + * @return array + * The expanded array of bundle information. + */ + protected function loadBundleDescriptions(array $bundles, $bundle_type) { + // Ensure the presence of the description key. + foreach ($bundles as $bundle_name => &$bundle_info) { + $bundle_info['description'] = ''; + } + // Only bundles provided by entity types have descriptions. + if (empty($bundle_type)) { + return $bundles; + } + $bundle_entity_type = $this->entityTypeManager()->getDefinition($bundle_type); + if (!$bundle_entity_type->isSubclassOf('\Drupal\entity\Entity\EntityDescriptionInterface')) { + return $bundles; + } + $bundle_names = array_keys($bundles); + $bundle_entities = $this->entityTypeManager->getStorage($bundle_type)->loadMultiple($bundle_names); + foreach ($bundles as $bundle_name => &$bundle_info) { + if (isset($bundle_entities[$bundle_name])) { + $bundle_info['description'] = $bundle_entities[$bundle_name]->getDescription(); + } + } + + return $bundles; + } + } diff --git a/src/Routing/CreateHtmlRouteProvider.php b/src/Routing/CreateHtmlRouteProvider.php index e3be78a536b9cdcf71c1a1034191eb0e99ae74c5..221b598e906d7403d4a814d0308f06df87132169 100644 --- a/src/Routing/CreateHtmlRouteProvider.php +++ b/src/Routing/CreateHtmlRouteProvider.php @@ -41,7 +41,7 @@ class CreateHtmlRouteProvider implements EntityRouteProviderInterface { /** * Returns the add page route. * - * Built only for entity types that have bundle entity types. + * Built only for entity types that have bundles. * * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type * The entity type. @@ -50,7 +50,7 @@ class CreateHtmlRouteProvider implements EntityRouteProviderInterface { * The generated route, if available. */ protected function addPageRoute(EntityTypeInterface $entity_type) { - if ($entity_type->hasLinkTemplate('add-page') && $entity_type->getBundleEntityType()) { + if ($entity_type->hasLinkTemplate('add-page') && $entity_type->getKey('bundle')) { $route = new Route($entity_type->getLinkTemplate('add-page')); $route->setDefault('_controller', '\Drupal\entity\Controller\EntityCreateController::addPage'); $route->setDefault('_title_callback', '\Drupal\entity\Controller\EntityCreateController::addPageTitle'); @@ -77,12 +77,6 @@ class CreateHtmlRouteProvider implements EntityRouteProviderInterface { $route->setDefault('_title_callback', '\Drupal\entity\Controller\EntityCreateController::addFormTitle'); $route->setDefault('entity_type_id', $entity_type->id()); $route->setRequirement('_entity_create_access', $entity_type->id()); - // The route needs a bundle parameter. - if ($bundle_type = $entity_type->getBundleEntityType()) { - $route->setOption('parameters', [ - $bundle_type => ['type' => 'entity:' . $bundle_type], - ]); - } return $route; } diff --git a/tests/Functional/CreateUITest.php b/tests/Functional/CreateUITest.php new file mode 100644 index 0000000000000000000000000000000000000000..5c2022e1eac4295a2be1c7d01587c1a0942c9e76 --- /dev/null +++ b/tests/Functional/CreateUITest.php @@ -0,0 +1,90 @@ +<?php + +/** + * @file + * Contains \Drupal\Tests\entity\Functional\CreateUITest. + */ + +namespace Drupal\Tests\entity\Functional; + +use Drupal\entity_module_test\Entity\EnhancedEntity; +use Drupal\entity_module_test\Entity\EnhancedEntityBundle; +use Drupal\simpletest\BrowserTestBase; + +/** + * Tests the entity creation UI provided by EntityCreateController. + * + * @group entity + * @runTestsInSeparateProcesses + * @preserveGlobalState disabled + */ +class CreateUITest extends BrowserTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = ['entity_module_test', 'user', 'entity']; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + EnhancedEntityBundle::create([ + 'id' => 'first', + 'label' => 'First', + 'description' => 'The first bundle', + ])->save(); + $account = $this->drupalCreateUser(['administer entity_test_enhanced']); + $this->drupalLogin($account); + } + + /** + * Tests the add page. + */ + public function testAddPage() { + // This test revealed that if the first bundle gets created in testAddPage + // before drupalGet(), the built cache won't get reset when the second + // bundle is created. This is a BrowserTestBase specific bug. + // @todo Remove comment when the bug is fixed. + + // When only one bundle exists, the add page should redirect to the form. + $this->drupalGet('/entity_test_enhanced/add'); + $this->assertSession()->addressEquals('/entity_test_enhanced/add/first'); + + EnhancedEntityBundle::create([ + 'id' => 'second', + 'label' => 'Second', + 'description' => 'The <b>second</b> bundle', + ])->save(); + $this->drupalGet('/entity_test_enhanced/add'); + $assert = $this->assertSession(); + $assert->addressEquals('/entity_test_enhanced/add'); + $assert->statusCodeEquals(200); + $assert->elementTextContains('css', '.page-title', 'Add entity test with enhancements'); + // @todo Bundle links. + } + + /** + * Tests the add form. + */ + public function testAddForm() { + $this->drupalGet('/entity_test_enhanced/add/first'); + $assert = $this->assertSession(); + $assert->elementTextContains('css', '.page-title', 'Add entity test with enhancements'); + $assert->elementExists('css', 'form.entity-test-enhanced-first-add-form'); + + // In case of multiple bundles, the current one is a part of the page title. + EnhancedEntityBundle::create([ + 'id' => 'second', + 'label' => 'Second', + 'description' => 'The <b>second</b> bundle', + ])->save(); + $this->drupalGet('/entity_test_enhanced/add/first'); + $this->assertSession()->elementTextContains('css', '.page-title', 'Add First'); + } + +} diff --git a/tests/Kernel/EntityRevisionLogTraitTest.php b/tests/Kernel/EntityRevisionLogTraitTest.php index d2c7a87adabbabd5d7098629c6eb605bc12575b1..a0b3b3834c0772b1adc111c36e2954e16e685aee 100644 --- a/tests/Kernel/EntityRevisionLogTraitTest.php +++ b/tests/Kernel/EntityRevisionLogTraitTest.php @@ -8,6 +8,7 @@ namespace Drupal\Tests\entity\Kernel; use Drupal\entity_module_test\Entity\EnhancedEntity; +use Drupal\entity_module_test\Entity\EnhancedEntityBundle; use Drupal\KernelTests\KernelTestBase; use Drupal\user\Entity\User; @@ -29,6 +30,12 @@ class EntityRevisionLogTraitTest extends KernelTestBase { $this->installEntitySchema('user'); $this->installSchema('system', 'sequences'); + + $bundle = EnhancedEntityBundle::create([ + 'id' => 'default', + 'label' => 'Default', + ]); + $bundle->save(); } public function testEntityRevisionLog() { @@ -43,6 +50,7 @@ class EntityRevisionLogTraitTest extends KernelTestBase { /** @var \Drupal\entity\Revision\EntityRevisionLogInterface $entity */ $entity = EnhancedEntity::create([ + 'type' => 'default', 'revision_user' => $user->id(), 'revision_created' => 1447941735, 'revision_log_message' => 'Test message', diff --git a/tests/Kernel/RevisionBasicUITest.php b/tests/Kernel/RevisionBasicUITest.php index e19fdefae5f3f86b8047cc58cb6c0b20ecff8028..b716c66b51182e95fec0e62e9407f423f7dc2ae3 100644 --- a/tests/Kernel/RevisionBasicUITest.php +++ b/tests/Kernel/RevisionBasicUITest.php @@ -8,6 +8,7 @@ namespace Drupal\Tests\entity\Kernel; use Drupal\entity_module_test\Entity\EnhancedEntity; +use Drupal\entity_module_test\Entity\EnhancedEntityBundle; use Drupal\KernelTests\KernelTestBase; use Drupal\user\Entity\Role; use Drupal\user\Entity\User; @@ -33,12 +34,19 @@ class RevisionBasicUITest extends KernelTestBase { $this->installEntitySchema('entity_test_enhanced'); $this->installSchema('system', 'router'); + $bundle = EnhancedEntityBundle::create([ + 'id' => 'default', + 'label' => 'Default', + ]); + $bundle->save(); + \Drupal::service('router.builder')->rebuild(); } public function testRevisionView() { $entity = EnhancedEntity::create([ 'name' => 'rev 1', + 'type' => 'default', ]); $entity->save(); diff --git a/tests/modules/entity_module_test/config/schema/entity_module_test.schema.yml b/tests/modules/entity_module_test/config/schema/entity_module_test.schema.yml new file mode 100644 index 0000000000000000000000000000000000000000..c20ef335f0c3530f2704266f24cb8c123b2e1b8b --- /dev/null +++ b/tests/modules/entity_module_test/config/schema/entity_module_test.schema.yml @@ -0,0 +1,13 @@ +entity_module_test.entity_test_enhanced_bundle.*: + type: config_entity + label: 'Entity test with enhancments - Bundle' + mapping: + id: + type: string + label: 'Type' + label: + type: label + label: 'Label' + description: + type: text + label: 'Description' diff --git a/tests/modules/entity_module_test/entity_module_test.permissions.yml b/tests/modules/entity_module_test/entity_module_test.permissions.yml new file mode 100644 index 0000000000000000000000000000000000000000..b41f84b6331b2323beb46675a974cf1ee8f9790b --- /dev/null +++ b/tests/modules/entity_module_test/entity_module_test.permissions.yml @@ -0,0 +1,3 @@ +'administer entity_test_enhanced': + title: 'Administer entity_test_enhanced' + 'restrict access': TRUE diff --git a/tests/modules/entity_module_test/src/Entity/EnhancedEntity.php b/tests/modules/entity_module_test/src/Entity/EnhancedEntity.php index 9cefdace06b340505575e0b7b63c857b49a607be..765c43ea017daa4f06e2f988fe74ccda00952ab3 100644 --- a/tests/modules/entity_module_test/src/Entity/EnhancedEntity.php +++ b/tests/modules/entity_module_test/src/Entity/EnhancedEntity.php @@ -21,8 +21,14 @@ use Drupal\entity\Revision\EntityRevisionLogTrait; * label = @Translation("Entity test with enhancements"), * handlers = { * "storage" = "\Drupal\Core\Entity\Sql\SqlContentEntityStorage", + * "form" = { + * "add" = "\Drupal\Core\Entity\ContentEntityForm", + * "edit" = "\Drupal\Core\Entity\ContentEntityForm", + * "delete" = "\Drupal\Core\Entity\EntityDeleteForm", + * }, * "route_provider" = { * "revision" = "\Drupal\entity\Routing\RevisionRouteProvider", + * "create" = "\Drupal\entity\Routing\CreateHtmlRouteProvider", * }, * }, * base_table = "entity_test_enhanced", @@ -34,12 +40,16 @@ use Drupal\entity\Revision\EntityRevisionLogTrait; * admin_permission = "administer entity_test_enhanced", * entity_keys = { * "id" = "id", + * "bundle" = "type", * "revision" = "vid", * "langcode" = "langcode", * }, * links = { + * "add-page" = "/entity_test_enhanced/add", + * "add-form" = "/entity_test_enhanced/add/{type}", * "revision" = "/entity_test_enhanced/{entity_test_enhanced}/revisions/{entity_test_enhanced_revision}/view", - * } + * }, + * bundle_entity_type = "entity_test_enhanced_bundle" * ) */ class EnhancedEntity extends ContentEntityBase { diff --git a/tests/modules/entity_module_test/src/Entity/EnhancedEntityBundle.php b/tests/modules/entity_module_test/src/Entity/EnhancedEntityBundle.php new file mode 100644 index 0000000000000000000000000000000000000000..afe49a7d4b231d4072d6751f7ba8f067c4c6e179 --- /dev/null +++ b/tests/modules/entity_module_test/src/Entity/EnhancedEntityBundle.php @@ -0,0 +1,79 @@ +<?php + +/** + * @file + * Contains \Drupal\entity_module_test\Entity\EnhancedEntityBundle. + */ + +namespace Drupal\entity_module_test\Entity; + +use Drupal\Core\Config\Entity\ConfigEntityBundleBase; +use Drupal\entity\Entity\EntityDescriptionInterface; + +/** + * Provides bundles for the test entity. + * + * @ConfigEntityType( + * id = "entity_test_enhanced_bundle", + * label = @Translation("Entity test with enhancments - Bundle"), + * handlers = { + * "route_provider" = { + * "create" = "\Drupal\entity\Routing\CreateHtmlRouteProvider", + * }, + * }, + * admin_permission = "administer entity_test_enhanced", + * config_prefix = "entity_test_enhanced_bundle", + * bundle_of = "entity_test_enhanced", + * entity_keys = { + * "id" = "id", + * "label" = "label" + * }, + * config_export = { + * "id", + * "label", + * "description" + * }, + * links = { + * "add-form" = "/entity_test_enhanced_bundle/add", + * }, + * ) + */ +class EnhancedEntityBundle extends ConfigEntityBundleBase implements EntityDescriptionInterface { + + /** + * The bundle ID. + * + * @var string + */ + protected $id; + + /** + * The bundle label. + * + * @var string + */ + protected $label; + + /** + * The bundle description. + * + * @var string + */ + protected $description; + + /** + * {@inheritdoc} + */ + public function getDescription() { + return $this->description; + } + + /** + * {@inheritdoc} + */ + public function setDescription($description) { + $this->description = $description; + return $this; + } + +}