diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php index 534048c..2300f60 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -9,6 +9,8 @@ use Drupal\Core\DependencyInjection\DependencySerialization; use Drupal\Component\Utility\String; +use Drupal\Component\Utility\Unicode; +use Drupal\Core\Config\Entity\Exception\ConfigEntityIdLengthException; use Drupal\Core\Entity\Exception\UndefinedLinkTemplateException; use Drupal\Core\Language\Language; use Drupal\Core\Session\AccountInterface; @@ -336,6 +338,18 @@ public function getEntityType() { * {@inheritdoc} */ public function preSave(EntityStorageInterface $storage) { + // Check if this is an entity bundle. + if ($this->getEntityType()->getBundleOf()) { + // Throw an exception if the bundle ID is longer than 32 characters. + if (Unicode::strlen($this->id()) > EntityTypeInterface::BUNDLE_MAX_LENGTH) { + throw new ConfigEntityIdLengthException(String::format( + 'Attempt to create a bundle with an ID longer than @max characters: @id.', array( + '@max' => EntityTypeInterface::BUNDLE_MAX_LENGTH, + '@id' => $this->id(), + ) + )); + } + } } /** diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php index 93f6062..de4411c 100644 --- a/core/lib/Drupal/Core/Entity/EntityType.php +++ b/core/lib/Drupal/Core/Entity/EntityType.php @@ -7,7 +7,9 @@ namespace Drupal\Core\Entity; +use Drupal\Component\Utility\String; use Drupal\Component\Utility\Unicode; +use Drupal\Core\Entity\Exception\EntityTypeIdLengthException; /** * Provides an implementation of an entity type and its metadata. @@ -182,8 +184,21 @@ class EntityType implements EntityTypeInterface { * * @param array $definition * An array of values from the annotation. + * + * @throws \Drupal\Core\Entity\Exception\EntityTypeIdLengthException + * Thrown when attempting to instantiate an entity type with too long ID. */ public function __construct($definition) { + // Throw an exception if the entity type ID is longer than 32 characters. + if (Unicode::strlen($definition['id']) > static::ID_MAX_LENGTH) { + throw new EntityTypeIdLengthException(String::format( + 'Attempt to create an entity type with an ID longer than @max characters: @id.', array( + '@max' => static::ID_MAX_LENGTH, + '@id' => $definition['id'], + ) + )); + } + foreach ($definition as $property => $value) { $this->{$property} = $value; } diff --git a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php index 17d9555..b7b9d0d 100644 --- a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php @@ -18,6 +18,16 @@ interface EntityTypeInterface { /** + * The maximum length of ID, in characters. + */ + const ID_MAX_LENGTH = 32; + + /** + * The maximum length of bundle name, in characters. + */ + const BUNDLE_MAX_LENGTH = 32; + + /** * Gets any arbitrary property. * * @param string $property diff --git a/core/lib/Drupal/Core/Entity/Exception/EntityTypeIdLengthException.php b/core/lib/Drupal/Core/Entity/Exception/EntityTypeIdLengthException.php new file mode 100644 index 0000000..e1a4feb --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Exception/EntityTypeIdLengthException.php @@ -0,0 +1,13 @@ + array( 'description' => 'The type of this custom block.', 'type' => 'varchar', - 'length' => 32, + 'length' => EntityTypeInterface::BUNDLE_MAX_LENGTH, 'not null' => TRUE, 'default' => '', ), @@ -64,7 +66,7 @@ function custom_block_schema() { ), 'primary key' => array('id'), 'indexes' => array( - 'block_custom_type' => array(array('type', 4)), + 'block_custom_type' => array('type'), ), 'unique keys' => array( 'revision_id' => array('revision_id'), @@ -161,7 +163,7 @@ function custom_block_schema_0() { 'type' => array( 'description' => 'The type of this custom block.', 'type' => 'varchar', - 'length' => 32, + 'length' => EntityTypeInterface::BUNDLE_MAX_LENGTH, 'not null' => TRUE, 'default' => '', ), @@ -181,7 +183,7 @@ function custom_block_schema_0() { ), 'primary key' => array('id'), 'indexes' => array( - 'block_custom_type' => array(array('type', 4)), + 'block_custom_type' => array('type'), ), 'unique keys' => array( 'revision_id' => array('revision_id'), diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeFormController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeFormController.php index 5758788..a2f3008 100644 --- a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeFormController.php +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeFormController.php @@ -8,6 +8,7 @@ namespace Drupal\custom_block; use Drupal\Core\Entity\EntityFormController; +use Drupal\Core\Entity\EntityTypeInterface; /** * Base form controller for category edit forms. @@ -36,6 +37,7 @@ public function form(array $form, array &$form_state) { '#machine_name' => array( 'exists' => 'custom_block_type_load', ), + '#maxlength' => EntityTypeInterface::BUNDLE_MAX_LENGTH, '#disabled' => !$block_type->isNew(), ); diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Entity/CustomBlock.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Entity/CustomBlock.php index 421e3b5..be01107 100644 --- a/core/modules/block/custom_block/lib/Drupal/custom_block/Entity/CustomBlock.php +++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Entity/CustomBlock.php @@ -190,7 +190,8 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { $fields['type'] = FieldDefinition::create('entity_reference') ->setLabel(t('Block type')) ->setDescription(t('The block type.')) - ->setSetting('target_type', 'custom_block_type'); + ->setSetting('target_type', 'custom_block_type') + ->setSetting('max_length', EntityTypeInterface::BUNDLE_MAX_LENGTH); $fields['log'] = FieldDefinition::create('string') ->setLabel(t('Revision log message')) diff --git a/core/modules/comment/tests/Drupal/comment/Tests/Entity/CommentLockTest.php b/core/modules/comment/tests/Drupal/comment/Tests/Entity/CommentLockTest.php index 5ba7f5e..2101d93 100644 --- a/core/modules/comment/tests/Drupal/comment/Tests/Entity/CommentLockTest.php +++ b/core/modules/comment/tests/Drupal/comment/Tests/Entity/CommentLockTest.php @@ -7,6 +7,7 @@ namespace Drupal\comment\Tests\Entity; use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\Core\Entity\EntityType; use Drupal\Tests\UnitTestCase; /** @@ -74,10 +75,16 @@ public function testLocks() { $comment->expects($this->any()) ->method('getThread') ->will($this->returnValue('')); - $comment->expects($this->at(0)) + + $entity_type = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface'); + $comment->expects($this->any()) + ->method('getEntityType') + ->will($this->returnValue($entity_type)); + $comment->expects($this->at(1)) ->method('get') ->with('status') ->will($this->returnValue((object) array('value' => NULL))); + $storage = $this->getMock('Drupal\comment\CommentStorageInterface'); $comment->preSave($storage); $comment->postSave($storage); diff --git a/core/modules/content_translation/content_translation.install b/core/modules/content_translation/content_translation.install index 2d4963e..8ff4c7e 100644 --- a/core/modules/content_translation/content_translation.install +++ b/core/modules/content_translation/content_translation.install @@ -5,6 +5,7 @@ * Installation functions for Content Translation module. */ +use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Language\Language; use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl; @@ -17,7 +18,7 @@ function content_translation_schema() { 'fields' => array( 'entity_type' => array( 'type' => 'varchar', - 'length' => 128, + 'length' => EntityTypeInterface::ID_MAX_LENGTH, 'not null' => TRUE, 'default' => '', 'description' => 'The entity type this translation relates to', diff --git a/core/modules/node/lib/Drupal/node/NodeTypeFormController.php b/core/modules/node/lib/Drupal/node/NodeTypeFormController.php index 7d6bd72..63f36f9 100644 --- a/core/modules/node/lib/Drupal/node/NodeTypeFormController.php +++ b/core/modules/node/lib/Drupal/node/NodeTypeFormController.php @@ -9,6 +9,7 @@ use Drupal\Core\Entity\EntityFormController; use Drupal\Component\Utility\String; +use Drupal\Core\Entity\EntityTypeInterface; /** * Form controller for node type forms. @@ -45,7 +46,7 @@ public function form(array $form, array &$form_state) { $form['type'] = array( '#type' => 'machine_name', '#default_value' => $type->id(), - '#maxlength' => 32, + '#maxlength' => EntityTypeInterface::BUNDLE_MAX_LENGTH, '#disabled' => $type->isLocked(), '#machine_name' => array( 'exists' => 'node_type_load', diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php index bbbed9c..0107003 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php @@ -122,7 +122,8 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { $fields['vid'] = FieldDefinition::create('entity_reference') ->setLabel(t('Vocabulary')) ->setDescription(t('The vocabulary to which the term is assigned.')) - ->setSetting('target_type', 'taxonomy_vocabulary'); + ->setSetting('target_type', 'taxonomy_vocabulary') + ->setSetting('max_length', EntityTypeInterface::BUNDLE_MAX_LENGTH); $fields['langcode'] = FieldDefinition::create('language') ->setLabel(t('Language code')) diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php index 4394ab0..6843596 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermTest.php @@ -258,14 +258,19 @@ function testTermAutocompletion() { $third_term->save(); // Try to autocomplete a term name that matches both terms. - // We should get both term in a json encoded string. + // We should get both terms in a json encoded string. $input = '10/'; $path = 'taxonomy/autocomplete/node/taxonomy_' . $this->vocabulary->id(); // The result order is not guaranteed, so check each term separately. $result = $this->drupalGet($path, array('query' => array('q' => $input))); + // Pull the label properties from the array of arrays. $data = Json::decode($result); - $this->assertEqual($data[0]['label'], String::checkPlain($first_term->getName()), 'Autocomplete returned the first matching term'); - $this->assertEqual($data[1]['label'], String::checkPlain($second_term->getName()), 'Autocomplete returned the second matching term'); + $data = array_map(function ($item) { + return $item['label']; + }, $data); + + $this->assertTrue(in_array(String::checkPlain($first_term->getName()), $data), 'Autocomplete returned the first matching term'); + $this->assertTrue(in_array(String::checkPlain($second_term->getName()), $data), 'Autocomplete returned the second matching term'); // Try to autocomplete a term name that matches first term. // We should only get the first term in a json encoded string. diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyFormController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyFormController.php index 3474bfb..c446c28 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyFormController.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyFormController.php @@ -8,6 +8,7 @@ namespace Drupal\taxonomy; use Drupal\Core\Entity\EntityFormController; +use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Language\Language; /** @@ -37,7 +38,7 @@ public function form(array $form, array &$form_state) { $form['vid'] = array( '#type' => 'machine_name', '#default_value' => $vocabulary->id(), - '#maxlength' => 255, + '#maxlength' => EntityTypeInterface::BUNDLE_MAX_LENGTH, '#machine_name' => array( 'exists' => 'taxonomy_vocabulary_load', 'source' => array('name'), diff --git a/core/modules/taxonomy/taxonomy.install b/core/modules/taxonomy/taxonomy.install index f76d350..a3f7e12 100644 --- a/core/modules/taxonomy/taxonomy.install +++ b/core/modules/taxonomy/taxonomy.install @@ -5,6 +5,8 @@ * Install, update and uninstall functions for the taxonomy module. */ +use Drupal\Core\Entity\EntityTypeInterface; + /** * Implements hook_schema(). */ @@ -26,7 +28,7 @@ function taxonomy_schema() { ), 'vid' => array( 'type' => 'varchar', - 'length' => 255, + 'length' => EntityTypeInterface::BUNDLE_MAX_LENGTH, 'not null' => TRUE, 'default' => '', 'description' => 'The ID of the vocabulary to which the term is assigned.', @@ -75,8 +77,8 @@ function taxonomy_schema() { 'uuid' => array('uuid'), ), 'indexes' => array( - 'taxonomy_tree' => array(array('vid', 64), 'weight', 'name'), - 'vid_name' => array(array('vid', 64), 'name'), + 'taxonomy_tree' => array('vid', 'weight', 'name'), + 'vid_name' => array('vid', 'name'), 'name' => array('name'), ), ); diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php index 918e596..d6f5c9c 100644 --- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php @@ -11,6 +11,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Language\Language; use Drupal\Tests\UnitTestCase; +use Symfony\Component\DependencyInjection\ContainerBuilder; /** * @coversDefaultClass \Drupal\Core\Config\Entity\ConfigEntityStorage @@ -76,6 +77,13 @@ class ConfigEntityStorageTest extends UnitTestCase { protected $entityQuery; /** + * The entity manager used for testing. + * + * @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $entityManager; + + /** * {@inheritdoc} */ public static function getInfo() { @@ -129,6 +137,17 @@ protected function setUp() { ->method('getQuery') ->will($this->returnValue($this->entityQuery)); $this->entityStorage->setModuleHandler($this->moduleHandler); + + $this->entityManager = $this->getMock('\Drupal\Core\Entity\EntityManagerInterface'); + $this->entityManager->expects($this->any()) + ->method('getDefinition') + ->with('test_entity_type') + ->will($this->returnValue($this->entityType)); + + $container = new ContainerBuilder(); + $container->set('entity.manager', $this->entityManager); + \Drupal::setContainer($container); + } /** diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityTypeTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityTypeTest.php index 6befb00..3db843a 100644 --- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityTypeTest.php +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityTypeTest.php @@ -30,104 +30,62 @@ public static function getInfo() { ); } + /** + * Sets up a ConfigEntityType object for a given set of values. + * + * @param array $definition + * An array of values to use for the ConfigEntityType. + * + * @return \Drupal\Core\Entity\EntityTypeInterface + */ + protected function setUpConfigEntityType($definition) { + if (!isset($definition['id'])) { + $definition += array( + 'id' => 'example_config_entity_type', + ); + } + return new ConfigEntityType($definition); + } /** * Tests that we get an exception when the length of the config prefix that is * returned by getConfigPrefix() exceeds the maximum defined prefix length. * - * @dataProvider providerPrefixLengthExceeds * @covers ::getConfigPrefix() */ - public function testConfigPrefixLengthExceeds($entity_data, $exception, $message) { - $config_entity = new ConfigEntityType($entity_data); - $this->setExpectedException($exception, $message); - $this->assertEmpty($config_entity->getConfigPrefix()); - } - - /** - * Provides arguments to instantiate a ConfigEntityType with a configuration - * entity prefix that exceeds the maximum character length. - * - * @return array - */ - public function providerPrefixLengthExceeds() { - $test_parameters = array(); + public function testConfigPrefixLengthExceeds() { $message_text = 'The configuration file name prefix @config_prefix exceeds the maximum character limit of @max_char.'; - // A provider length of 24 and id length of 59 (+1 for the .) results - // in a config length of 84, which is too long. - $entity_data = array( - 'provider' => $this->randomName(24), - 'id' => $this->randomName(59), - ); - $test_parameters[] = array( - $entity_data, - '\Drupal\Core\Config\ConfigPrefixLengthException', - String::format($message_text, array( - '@config_prefix' => $entity_data['provider'] . '.' . $entity_data['id'], - '@max_char' => ConfigEntityType::PREFIX_LENGTH, - )), - ); - // A provider length of 24 and config_prefix length of 59 (+1 for the .) // results in a config length of 84, which is too long. - $entity_data = array( + $definition = array( 'provider' => $this->randomName(24), 'config_prefix' => $this->randomName(59), ); - $test_parameters[] = array( - $entity_data, - '\Drupal\Core\Config\ConfigPrefixLengthException', - String::format($message_text, array( - '@config_prefix' => $entity_data['provider'] . '.' . $entity_data['config_prefix'], - '@max_char' => ConfigEntityType::PREFIX_LENGTH, - )), - ); - - return $test_parameters; + $config_entity = $this->setUpConfigEntityType($definition); + $this->setExpectedException('\Drupal\Core\Config\ConfigPrefixLengthException', String::format($message_text, array( + '@config_prefix' => $definition['provider'] . '.' . $definition['config_prefix'], + '@max_char' => ConfigEntityType::PREFIX_LENGTH, + ))); + $this->assertEmpty($config_entity->getConfigPrefix()); } /** * Tests that a valid config prefix returned by getConfigPrefix() * does not throw an exception and is formatted as expected. * - * @dataProvider providerPrefixLengthValid * @covers ::getConfigPrefix() */ - public function testConfigPrefixLengthValid($entity_data) { - $config_entity = new ConfigEntityType($entity_data); - if (isset($entity_data['config_prefix'])) { - $expected_prefix = $entity_data['provider'] . '.' . $entity_data['config_prefix']; - } else { - $expected_prefix = $entity_data['provider'] . '.' . $entity_data['id']; - } - $this->assertEquals($expected_prefix, $config_entity->getConfigPrefix()); - } - - /** - * Provides arguments to instantiate a ConfigEntityType with a configuration - * entity prefix that does not exceed the maximum character length. - * - * @return array - */ - public function providerPrefixLengthValid() { - $test_parameters = array(); - + public function testConfigPrefixLengthValid() { // A provider length of 24 and config_prefix length of 58 (+1 for the .) // results in a config length of 83, which is right at the limit. - $test_parameters[] = array(array( + $definition = array( 'provider' => $this->randomName(24), 'config_prefix' => $this->randomName(58), - )); - - // A provider length of 24 and id length of 58 (+1 for the .) results in a - // config length of 83, which is right at the limit. - $test_parameters[] = array(array( - 'provider' => $this->randomName(24), - 'id' => $this->randomName(58), - )); - - return $test_parameters; + ); + $config_entity = $this->setUpConfigEntityType($definition); + $expected_prefix = $definition['provider'] . '.' . $definition['config_prefix']; + $this->assertEquals($expected_prefix, $config_entity->getConfigPrefix()); } } diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php index d26ccd1..1cb561c 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php @@ -11,7 +11,7 @@ use Drupal\Tests\UnitTestCase; /** - * Tests the \Drupal\Core\Entity\EntityType class. + * @coversDefaultClass \Drupal\Core\Entity\EntityType * * @group Drupal * @group Entity @@ -38,6 +38,9 @@ public static function getInfo() { * @return \Drupal\Core\Entity\EntityTypeInterface */ protected function setUpEntityType($definition) { + $definition += array( + 'id' => 'example_entity_type', + ); return new EntityType($definition); } @@ -191,6 +194,25 @@ public function testGetViewBuilderClass() { } /** + * @covers ::__construct + */ + public function testIdExceedsMaxLength() { + $id = $this->randomName(33); + $message = 'Attempt to create an entity type with an ID longer than 32 characters: ' . $id; + $this->setExpectedException('Drupal\Core\Entity\Exception\EntityTypeIdLengthException', $message); + $this->setUpEntityType(array('id' => $id)); + } + + /** + * @covers ::id + */ + public function testId() { + $id = $this->randomName(32); + $entity_type = $this->setUpEntityType(array('id' => $id)); + $this->assertEquals($id, $entity_type->id()); + } + + /** * Gets a mock controller class name. * * @return string