diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LanguageItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LanguageItem.php index d0791e9..aa9b0d5 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LanguageItem.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LanguageItem.php @@ -2,7 +2,7 @@ /** * @file - * Contains \Drupal\Core\Entity\Plugin\Field\FieldType\LanguageItem. + * Contains \Drupal\Core\Field\Plugin\Field\FieldType\LanguageItem. */ namespace Drupal\Core\Field\Plugin\Field\FieldType; @@ -25,10 +25,16 @@ * no_ui = TRUE, * constraints = { * "ComplexData" = { - * "value" = {"Length" = {"max" = 12}} + * "value" = { + * "Length" = {"max" = 12}, + * "AllowedValues" = {"callback" = "\Drupal\Core\Field\Plugin\Field\FieldType\LanguageItem::getAllowedLanguageCodes" } + * } * } * } * ) + * + * @todo Define the AllowedValues constraint via an options provider once + * https://www.drupal.org/node/2329937 is completed. */ class LanguageItem extends FieldItemBase { @@ -51,6 +57,15 @@ public static function propertyDefinitions(FieldStorageDefinitionInterface $fiel } /** + * Defines allowed language codes for the field's AllowedValues constraint. + * + * @return string[] + */ + public static function getAllowedLanguageCodes() { + return array_keys(\Drupal::languageManager()->getLanguages(LanguageInterface::STATE_ALL)); + } + + /** * {@inheritdoc} */ public static function schema(FieldStorageDefinitionInterface $field_definition) { diff --git a/core/modules/user/src/AccountForm.php b/core/modules/user/src/AccountForm.php index f3e1740..7729938 100644 --- a/core/modules/user/src/AccountForm.php +++ b/core/modules/user/src/AccountForm.php @@ -380,19 +380,21 @@ public function validate(array $form, FormStateInterface $form_state) { $account = $this->buildEntity($form, $form_state); // Customly trigger validation of manually added fields and add in // violations. - $violations = $account->name->validate(); - foreach ($violations as $violation) { - $form_state->setErrorByName('name', $violation->getMessage()); - } - - $violations = $account->mail->validate(); - foreach ($violations as $violation) { - $form_state->setErrorByName('mail', $violation->getMessage()); - } - - $violations = $account->signature->validate(); - foreach ($violations as $violation) { - $form_state->setErrorByName('signature', $violation->getMessage()); + $field_names = array( + 'name', + 'mail', + 'signature', + 'signature_format', + 'timezone', + 'langcode', + 'preferred_langcode', + 'preferred_admin_langcode' + ); + foreach ($field_names as $field_name) { + $violations = $account->$field_name->validate(); + foreach ($violations as $violation) { + $form_state->setErrorByName($field_name, $violation->getMessage()); + } } } diff --git a/core/modules/user/src/Entity/User.php b/core/modules/user/src/Entity/User.php index 8cc2a09..3a19ede 100644 --- a/core/modules/user/src/Entity/User.php +++ b/core/modules/user/src/Entity/User.php @@ -12,6 +12,7 @@ use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\Core\Language\LanguageInterface; use Drupal\user\UserInterface; /** @@ -478,12 +479,21 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { $fields['preferred_langcode'] = BaseFieldDefinition::create('language') ->setLabel(t('Preferred language code')) - ->setDescription(t("The user's preferred language code for receiving emails and viewing the site.")); + ->setDescription(t("The user's preferred language code for receiving emails and viewing the site.")) + // @todo: Define this via an options provider once + // https://www.drupal.org/node/2329937 is completed. + ->addPropertyConstraints('value', array( + 'AllowedValues' => array('callback' => __CLASS__ . '::getAllowedConfigurableLanguageCodes'), + )); $fields['preferred_admin_langcode'] = BaseFieldDefinition::create('language') ->setLabel(t('Preferred admin language code')) ->setDescription(t("The user's preferred language code for viewing administration pages.")) - ->setDefaultValue(''); + // @todo: Define this via an options provider once + // https://www.drupal.org/node/2329937 is completed. + ->addPropertyConstraints('value', array( + 'AllowedValues' => array('callback' => __CLASS__ . '::getAllowedConfigurableLanguageCodes'), + )); // The name should not vary per language. The username is the visual // identifier for a user and needs to be consistent in all languages. @@ -515,12 +525,22 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setTranslatable(TRUE); $fields['signature_format'] = BaseFieldDefinition::create('string') ->setLabel(t('Signature format')) - ->setDescription(t('The signature format of this user.')); + ->setDescription(t('The signature format of this user.')) + // @todo: Define this via an options provider once + // https://www.drupal.org/node/2329937 is completed. + ->addPropertyConstraints('value', array( + 'AllowedValues' => array('callback' => __CLASS__ . '::getAllowedSignatureFormats'), + )); $fields['timezone'] = BaseFieldDefinition::create('string') ->setLabel(t('Timezone')) ->setDescription(t('The timezone of this user.')) - ->setSetting('max_length', 32); + ->setSetting('max_length', 32) + // @todo: Define this via an options provider once + // https://www.drupal.org/node/2329937 is completed. + ->addPropertyConstraints('value', array( + 'AllowedValues' => array('callback' => __CLASS__ . '::getAllowedTimezones'), + )); $fields['status'] = BaseFieldDefinition::create('boolean') ->setLabel(t('User status')) @@ -569,4 +589,35 @@ protected function getRoleStorage() { return \Drupal::entityManager()->getStorage('user_role'); } + /** + * Defines allowed signature formats for the field's AllowedValues constraint. + * + * @return string[] + */ + public static function getAllowedSignatureFormats() { + if (\Drupal::moduleHandler()->moduleExists('filter')) { + return array_keys(filter_formats()); + } + // If filter.module is disabled, no value may be assigned. + return array(); + } + + /** + * Defines allowed timezones for the field's AllowedValues constraint. + * + * @return string[] + */ + public static function getAllowedTimezones() { + return array_keys(system_time_zones()); + } + + /** + * Defines allowed configurable language codes for AllowedValues constraints. + * + * @return string[] + */ + public static function getAllowedConfigurableLanguageCodes() { + return array_keys(\Drupal::languageManager()->getLanguages(LanguageInterface::STATE_CONFIGURABLE)); + } + } diff --git a/core/modules/user/src/Tests/UserValidationTest.php b/core/modules/user/src/Tests/UserValidationTest.php index 15a165e..25ef677 100644 --- a/core/modules/user/src/Tests/UserValidationTest.php +++ b/core/modules/user/src/Tests/UserValidationTest.php @@ -9,6 +9,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Field\Plugin\Field\FieldType\EmailItem; +use Drupal\Core\Language\Language; use Drupal\Core\Render\Element\Email; use Drupal\simpletest\KernelTestBase; use Drupal\user\Entity\Role; @@ -133,21 +134,33 @@ function testValidation() { $this->assertLengthViolation($user, 'signature', 255); $user->set('signature', NULL); - // Test setting an invalid signature format. $user->set('signature_format', $this->randomString(32)); - $violations = $user->validate(); - $this->assertEqual(count($violations), 1, 'Violation found when an invalid signature format is set.'); + $this->assertAllowedValuesViolation($user, 'signature_format'); $user->set('signature_format', NULL); $user->set('timezone', $this->randomString(33)); - $this->assertLengthViolation($user, 'timezone', 32); + $this->assertLengthViolation($user, 'timezone', 32, 2, 1); + $user->set('timezone', 'invalid zone'); + $this->assertAllowedValuesViolation($user, 'timezone'); $user->set('timezone', NULL); $user->set('init', 'invalid'); $violations = $user->validate(); $this->assertEqual(count($violations), 1, 'Violation found when init email is invalid'); - $this->assertEqual($violations[0]->getPropertyPath(), 'init.0.value'); - $this->assertEqual($violations[0]->getMessage(), t('This value is not a valid email address.')); + $user->set('init', NULL); + + $user->set('langcode', 'invalid'); + $this->assertAllowedValuesViolation($user, 'langcode'); + $user->set('langcode', NULL); + + // Only configurable langcodes are allowed for preferred languages. + $user->set('preferred_langcode', Language::LANGCODE_NOT_SPECIFIED); + $this->assertAllowedValuesViolation($user, 'preferred_langcode'); + $user->set('preferred_langcode', NULL); + + $user->set('preferred_admin_langcode', Language::LANGCODE_NOT_SPECIFIED); + $this->assertAllowedValuesViolation($user, 'preferred_admin_langcode'); + $user->set('preferred_admin_langcode', NULL); Role::create(array('id' => 'role1'))->save(); Role::create(array('id' => 'role2'))->save(); @@ -176,13 +189,32 @@ function testValidation() { * The field that violates the maximum length. * @param int $length * Number of characters that was exceeded. + * @param int $count + * (optional) The number of expected violations. Defaults to 1. + * @param int $expected_index + * (optional) The index at which to expect the violation. Defaults to 0. */ - protected function assertLengthViolation(EntityInterface $entity, $field_name, $length) { + protected function assertLengthViolation(EntityInterface $entity, $field_name, $length, $count = 1, $expected_index = 0) { $violations = $entity->validate(); - $this->assertEqual(count($violations), 1, "Violation found when $field_name is too long."); - $this->assertEqual($violations[0]->getPropertyPath(), "$field_name.0.value"); + $this->assertEqual(count($violations), $count, "Violation found when $field_name is too long."); + $this->assertEqual($violations[$expected_index]->getPropertyPath(), "$field_name.0.value"); $field_label = $entity->get($field_name)->getFieldDefinition()->getLabel(); - $this->assertEqual($violations[0]->getMessage(), t('%name: may not be longer than @max characters.', array('%name' => $field_label, '@max' => $length))); + $this->assertEqual($violations[$expected_index]->getMessage(), t('%name: may not be longer than @max characters.', array('%name' => $field_label, '@max' => $length))); + } + + /** + * Verifies that a AllowedValues violation exists for the given field. + * + * @param \Drupal\core\Entity\EntityInterface $entity + * The entity object to validate. + * @param string $field_name + * The name of the field to verify. + */ + protected function assertAllowedValuesViolation(EntityInterface $entity, $field_name) { + $violations = $entity->validate(); + $this->assertEqual(count($violations), 1, "Allowed values violation for $field_name found."); + $this->assertEqual($violations[0]->getPropertyPath(), "$field_name.0.value"); + $this->assertEqual($violations[0]->getMessage(), t('The value you selected is not a valid choice.')); } }