diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php index bbc4e2e..f501043 100644 --- a/core/lib/Drupal/Core/CoreBundle.php +++ b/core/lib/Drupal/Core/CoreBundle.php @@ -55,6 +55,8 @@ public function build(ContainerBuilder $container) { ->setFactoryMethod('getConnection') ->addArgument('slave'); $container->register('typed_data', 'Drupal\Core\TypedData\TypedDataManager'); + $container->register('constraint', 'Drupal\Core\Validation\Constraint\ConstraintManager'); + // Add the user's storage for temporary, non-cache data. $container->register('lock', 'Drupal\Core\Lock\DatabaseLockBackend'); $container->register('user.tempstore', 'Drupal\user\TempStoreFactory') diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php index c6e041e..5500f65 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -370,4 +370,9 @@ public function isDefaultRevision($new_value = NULL) { } return $return; } + + // @todo: remove after merge with EntityNG. + public function getConstraintsObjects() { + return array(); + } } diff --git a/core/lib/Drupal/Core/Entity/EntityFormController.php b/core/lib/Drupal/Core/Entity/EntityFormController.php index 2e9daac..5bd04d0 100644 --- a/core/lib/Drupal/Core/Entity/EntityFormController.php +++ b/core/lib/Drupal/Core/Entity/EntityFormController.php @@ -48,12 +48,34 @@ public function build(array $form, array &$form_state, EntityInterface $entity) $entity = $this->getEntity($form_state); $form = $this->form($form, $form_state, $entity); + // Retrieve the constraints as objects. + $constraint_set = $entity->getConstraintsObjects(); + // Loop all, if there's a matching form element, set #required. + foreach ($constraint_set as $field_name => $constraints) { + if (isset($form[$field_name]) && !empty($constraints['field'])) { + foreach ($constraints['field'] as $constraint) { + if (!isset($form[$field_name][0])) { + $form[$field_name] += $constraint->convertToFormAPI(); + } + } + } + if (isset($form[$field_name]) && !empty($constraints[0])) { + foreach ($constraints[0] as $constraint) { + if (!isset($form[$field_name][0])) { + $form[$field_name] = $constraint->convertToFormAPI() + $form[$field_name]; + } + } + } + } + + // Add a #bound_to so you can specify the field name. + // Same logic as above, but reversed. + // Retrieve and add the form actions array. $actions = $this->actionsElement($form, $form_state); if (!empty($actions)) { $form['actions'] = $actions; } - return $form; } diff --git a/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php b/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php index 812a192..19ae5af 100644 --- a/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php +++ b/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php @@ -34,11 +34,17 @@ public function form(array $form, array &$form_state, EntityInterface $entity) { * Overrides EntityFormController::validate(). */ public function validate(array $form, array &$form_state) { - // @todo Exploit the Field API to validate the values submitted for the - // entity properties. $entity = $this->buildEntity($form, $form_state); - $info = $entity->entityInfo(); + if ($violation_set = $entity->validate()) { + $error_counter = 0; + foreach ($violation_set as $name => $violations) { + foreach ($violations as $violation) { + form_set_error('errors_' . $error_counter++, t($violation->getMessage(), $violation->getMessageArguments() + array('%label' => $violation->getTargetLabel()))); + } + } + } + $info = $entity->entityInfo(); if (!empty($info['fieldable'])) { $entity->setCompatibilityMode(TRUE); field_attach_form_validate($entity->entityType(), $entity, $form, $form_state); diff --git a/core/lib/Drupal/Core/Entity/EntityNG.php b/core/lib/Drupal/Core/Entity/EntityNG.php index 35ef89d..d356405 100644 --- a/core/lib/Drupal/Core/Entity/EntityNG.php +++ b/core/lib/Drupal/Core/Entity/EntityNG.php @@ -12,6 +12,7 @@ use Drupal\Component\Uuid\Uuid; use ArrayIterator; use InvalidArgumentException; +use Drupal\Core\Validation\Violation; /** * Implements Entity Field API specific enhancements to the Entity class. @@ -413,4 +414,85 @@ public function __clone() { } } } + + /** + * Validates the entity. + * + * @return array + * Array containing all violations + * + * @see Drupal\Core\Validation\Violation\Violation. + */ + public function validate() { + $violations = array(); + // Loop over all properties in all languages. + foreach ($this->getTranslationLanguages(TRUE) as $langcode => $language) { + foreach ($this->getTranslation($langcode) as $name => $property) { + $violations[$name] = $property->validate(); + } + } + + // Validate entity global constraints. + $definition = $this->entityInfo(); + if (isset($definition['constraints'])) { + foreach ($definition['constraints'] as $constraint_id => $constraint_info) { + if ($constraint = drupal_container()->get('constraint')->create($constraint_id, (array)$constraint_info, $this)) { + if (!$constraint->validate($this)) { + $violations['entity'][] = new Violation\EntityViolation($constraint, $this); + } + } + } + } + return $violations; + } + + /** + * Get all constraints objects for the entity. + * + * @return array + * Array containing all constraints. + * + * @see Drupal\Core\Validation\Constraint\ConstraintInterface. + */ + public function getConstraintsObjects() { + $constraints = array(); + + // Loop over all properties, the constraints are the same for all languages. + foreach ($this->getProperties() as $name => $property) { + $constraints[$name] = $property->getConstraintsObjects(); + } + + // Add the constraints of the entity level. + $definition = $this->entityInfo(); + if (isset($definition['constraints'])) { + foreach ($definition['constraints'] as $constraint_id => $constraint_info) { + if ($constraint = drupal_container()->get('constraint')->create($constraint_id, (array)$constraint_info, $this)) { + $constraints['entity'][] = $constraint; + } + } + } + + return $constraints; + } + + /** + * Get all constraints for the entity. + * + * @return array + * Array containing all constraints. + * + * @see Drupal\Core\Validation\Constraint\ConstraintInterface. + */ + public function getConstraints() { + $constraints = array(); + + // Loop over all properties, the constraints are the same for all languages. + foreach ($this->getProperties() as $name => $property) { + $constraints[$name] = $property->getConstraints(); + } + + // @todo: Add the constraints of the entity level. + + return $constraints; + } } diff --git a/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php b/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php index 5c71aa3..00bad20 100644 --- a/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php +++ b/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php @@ -15,6 +15,7 @@ use ArrayIterator; use IteratorAggregate; use InvalidArgumentException; +use Drupal\Core\Validation\Violation; /** * An entity field item. @@ -121,10 +122,39 @@ public function getString() { } /** + * Implements TypedDataInterface::getConstraints(). + */ + public function getConstraints() { + $property_definition = $this->getPropertyDefinitions(); + if (isset($property_definition['value']['constraints'])) { + return $property_definition['value']['constraints']; + } + return array(); + } + + /** * Implements TypedDataInterface::validate(). */ public function validate() { - // @todo implement + $violations = array(); + foreach($this->getProperties() as $property) { + $violations += $property->validate(); + } + return $violations; + + $property_definition = $this->getPropertyDefinitions(); + if (isset($property_definition['value']['constraints'])) { + $constraints = $property_definition['value']['constraints']; + foreach ($constraints as $constraint => $constraint_info) { + if ($constraint = drupal_container()->get('constraint')->create($constraint, (array)$constraint_info, $this)) { + // @todo: is there are better way for $this->value['value']? + if (!$constraint->validate($this->getValue(), 'typedata validate value')) { + $violations[] = new Violation\Violation(format_string('%value is not valid.', array('%value' => $this->value['value'])) , $constraint, $this); + } + } + } + } + return $violations; } /** diff --git a/core/lib/Drupal/Core/Entity/Field/Type/EntityReferenceItem.php b/core/lib/Drupal/Core/Entity/Field/Type/EntityReferenceItem.php index a60e65e..3683ff8 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/EntityReferenceItem.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/EntityReferenceItem.php @@ -39,6 +39,10 @@ public function getPropertyDefinitions() { // @todo: Lookup the entity type's ID data type and use it here. 'type' => 'integer', 'label' => t('Entity ID'), + 'constraints' => array( + 'notnull' => TRUE, + 'min' => 0, + ), ); self::$propertyDefinitions[$entity_type]['entity'] = array( 'type' => 'entity', diff --git a/core/lib/Drupal/Core/Entity/Field/Type/EntityTranslation.php b/core/lib/Drupal/Core/Entity/Field/Type/EntityTranslation.php index 0db4657..52d910e 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/EntityTranslation.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/EntityTranslation.php @@ -242,5 +242,6 @@ public function access(\Drupal\user\User $account = NULL) { */ public function validate($value = NULL) { // @todo implement + return array(); } } diff --git a/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php b/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php index 97449e7..e277da0 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php @@ -129,6 +129,7 @@ public function getString() { */ public function validate($value = NULL) { // TODO: Implement validate() method. + return array(); } /** diff --git a/core/lib/Drupal/Core/Entity/Field/Type/Field.php b/core/lib/Drupal/Core/Entity/Field/Type/Field.php index e7a5960..809f052 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/Field.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/Field.php @@ -14,6 +14,7 @@ use ArrayIterator; use IteratorAggregate; use InvalidArgumentException; +use Drupal\Core\Validation\Violation; /** * An entity field, i.e. a list of field items. @@ -114,10 +115,91 @@ public function getString() { } /** + * Return constraints objects for each delta. + */ + public function getConstraintsObjects() { + $constraints = array(); + $field_info_constraints = array(); + + // Use field_info_instance('entity', 'field_name', 'bundle')); + // to get the constraints fron the field definition. They will apply to + // all delta's. + $field_info = field_info_instance($this->parent->entityType, $this->name, $this->parent->bundle ? $this->parent->bundle : $this->parent->entityType); + if (isset($field_info->definition['constraints'])) { + foreach($field_info->definition['constraints'] as $constraint_id => $constraint_info) { + if ($constraint = drupal_container()->get('constraint')->create($constraint_id, (array)$constraint_info, $this)) { + $constraints['field'][] = $constraint; + } + } + } + + // Iterate and use TypedData::validate(). + foreach ($this->getIterator() as $delta => $item) { + if ($item_constraints = $item->getConstraintsObjects()) { + $constraints[$delta] = $item_constraints; + } + } + + return $constraints; + } + + /** + * Return constraints for each delta. + */ + public function getConstraints() { + $constraints = array(); + $field_info_constraints = array(); + + // Use field_info_instance('entity', 'field_name', 'bundle')); + // to get the constraints fron the field definition. They will apply to + // all delta's. + $field_info = field_info_instance($this->parent->entityType, $this->name, $this->parent->bundle ? $this->parent->bundle : $this->parent->entityType); + if (isset($field_info->definition['constraints'])) { + $field_info_constraints = $field_info->definition['constraints']; + } + + // Iterate and use TypedData::getConstraint(). + $iterator = $this->getIterator(); + if ($iterator->count()) { + foreach ($this->getIterator() as $delta => $item) { + $constraints[$delta] = array(); + if ($item_constraint = $item->getConstraints()) { + $constraints[$delta] = $item_constraint + $field_info_constraints; + } + $constraints[$delta] += $field_info_constraints; + } + } + else { + // @todo: not every field is using the iterator. + $constraints['field'] = $field_info_constraints; + } + return $constraints; + } + + /** * Implements TypedDataInterface::validate(). */ public function validate() { - // @todo implement + $violations = array(); + + // Iterate and use TypedData::validate(). + foreach ($this->getIterator() as $delta => $item) { + $violations += $item->validate(); + } + + // Do validation on field level. + $constraints = $this->getConstraints(); + if (isset($constraints['field'])) { + foreach($constraints['field'] as $constraint_id => $constraint_info) { + if ($constraint = drupal_container()->get('constraint')->create($constraint_id, (array)$constraint_info, $this)) { + if (!$constraint->validate($this)) { + $violations[] = new Violation\FieldViolation($constraint, $this); + } + } + } + } + + return $violations; } /** diff --git a/core/lib/Drupal/Core/Entity/Field/Type/IntegerItem.php b/core/lib/Drupal/Core/Entity/Field/Type/IntegerItem.php index 1f4b4e6..3830711 100644 --- a/core/lib/Drupal/Core/Entity/Field/Type/IntegerItem.php +++ b/core/lib/Drupal/Core/Entity/Field/Type/IntegerItem.php @@ -32,6 +32,10 @@ public function getPropertyDefinitions() { self::$propertyDefinitions['value'] = array( 'type' => 'integer', 'label' => t('Integer value'), + 'constraints' => array( + 'nonnull' => TRUE, + 'min' => 0, + ), ); } return self::$propertyDefinitions; diff --git a/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php b/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php index bd89a92..144eda0 100644 --- a/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php +++ b/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php @@ -21,6 +21,39 @@ class AnnotatedClassDiscovery implements DiscoveryInterface { /** + * @var array $custom_location + * + * key: namespace of you class. + * value is an array containing: + * dir: the directory to scan. + * class_base: namespace of you class. + */ + protected $custom_location = array(); + + /** + * Sets the custom location(s) to scan. + * + * @param array $custom_locations + * + * key: namespace of you class. + * value is an array containing: + * dir: the directory to scan. + * class_base: namespace of you class. + */ + public function setCustomLocation(array $custom_locations) { + $this->custom_location = $custom_locations; + } + + /** + * Gets the custom locations. + * + * @return array + */ + public function getCustomLocation() { + return $this->custom_location; + } + + /** * Constructs an AnnotatedClassDiscovery object. */ function __construct($owner, $type) { @@ -49,33 +82,42 @@ public function getDefinitions() { AnnotationRegistry::registerAutoloadNamespace('Drupal\Core\Annotation', array(DRUPAL_ROOT . '/core/lib')); // Get all PSR-0 namespaces. $namespaces = drupal_classloader()->getNamespaces(); + // Rewrite all directories. foreach ($namespaces as $ns => $namespace_dirs) { - // OS-Safe directory separators. $ns = str_replace('\\', DIRECTORY_SEPARATOR, $ns); + // Check for the pre-determined directory structure to find plugins. + $prefix = implode(DIRECTORY_SEPARATOR, array( + $ns, + 'Plugin', + $this->owner, + $this->type + )); + foreach ($namespace_dirs as $key => $dir) { + $namespaces[$ns][$key] = array( + 'dir' => $dir .= DIRECTORY_SEPARATOR . $prefix, + 'class_base' => str_replace( + DIRECTORY_SEPARATOR, + '\\', + $prefix + ), + ); + } + } + // Merge in the custom locations. + if (!empty($this->custom_location)) { + $namespaces += $this->custom_location; + } - foreach ($namespace_dirs as $dir) { - // Check for the pre-determined directory structure to find plugins. - $prefix = implode(DIRECTORY_SEPARATOR, array( - $ns, - 'Plugin', - $this->owner, - $this->type - )); - $dir .= DIRECTORY_SEPARATOR . $prefix; - - // If the directory structure exists, look for classes. + foreach ($namespaces as $ns => $namespace_dirs) { + foreach ($namespace_dirs as $dir_info) { + $dir = $dir_info['dir']; if (file_exists($dir)) { $directories = new DirectoryIterator($dir); foreach ($directories as $fileinfo) { // @todo Once core requires 5.3.6, use $fileinfo->getExtension(). if (pathinfo($fileinfo->getFilename(), PATHINFO_EXTENSION) == 'php') { - $class = str_replace( - DIRECTORY_SEPARATOR, - '\\', - $prefix . DIRECTORY_SEPARATOR . $fileinfo->getBasename('.php') - ); - + $class = $dir_info['class_base'] . '\\' . $fileinfo->getBasename('.php'); // The filename is already known, so there is no need to find the // file. However, StaticReflectionParser needs a finder, so use a // mock version. diff --git a/core/lib/Drupal/Core/TypedData/Type/Binary.php b/core/lib/Drupal/Core/TypedData/Type/Binary.php index 7ec5e6b..6b65fab 100644 --- a/core/lib/Drupal/Core/TypedData/Type/Binary.php +++ b/core/lib/Drupal/Core/TypedData/Type/Binary.php @@ -79,6 +79,6 @@ public function getString() { * Implements TypedDataInterface::validate(). */ public function validate() { - // TODO: Implement validate() method. + return parent::validate(); } } diff --git a/core/lib/Drupal/Core/TypedData/Type/Boolean.php b/core/lib/Drupal/Core/TypedData/Type/Boolean.php index 5714599..29f20e5 100644 --- a/core/lib/Drupal/Core/TypedData/Type/Boolean.php +++ b/core/lib/Drupal/Core/TypedData/Type/Boolean.php @@ -35,6 +35,6 @@ public function setValue($value) { * Implements TypedDataInterface::validate(). */ public function validate() { - // TODO: Implement validate() method. + return parent::validate(); } } diff --git a/core/lib/Drupal/Core/TypedData/Type/Date.php b/core/lib/Drupal/Core/TypedData/Type/Date.php index 3c0f3a9..58392fd 100644 --- a/core/lib/Drupal/Core/TypedData/Type/Date.php +++ b/core/lib/Drupal/Core/TypedData/Type/Date.php @@ -57,6 +57,6 @@ public function getString() { * Implements TypedDataInterface::validate(). */ public function validate() { - // TODO: Implement validate() method. + return parent::validate(); } } diff --git a/core/lib/Drupal/Core/TypedData/Type/Duration.php b/core/lib/Drupal/Core/TypedData/Type/Duration.php index 9fd8ceb..8450b77 100644 --- a/core/lib/Drupal/Core/TypedData/Type/Duration.php +++ b/core/lib/Drupal/Core/TypedData/Type/Duration.php @@ -63,6 +63,6 @@ public function getString() { * Implements TypedDataInterface::validate(). */ public function validate() { - // TODO: Implement validate() method. + return parent::validate(); } } diff --git a/core/lib/Drupal/Core/TypedData/Type/Float.php b/core/lib/Drupal/Core/TypedData/Type/Float.php index 798499c..01b8adc 100644 --- a/core/lib/Drupal/Core/TypedData/Type/Float.php +++ b/core/lib/Drupal/Core/TypedData/Type/Float.php @@ -35,6 +35,6 @@ public function setValue($value) { * Implements TypedDataInterface::validate(). */ public function validate() { - // TODO: Implement validate() method. + return parent::validate(); } } diff --git a/core/lib/Drupal/Core/TypedData/Type/Integer.php b/core/lib/Drupal/Core/TypedData/Type/Integer.php index 4e9b59a..086624c 100644 --- a/core/lib/Drupal/Core/TypedData/Type/Integer.php +++ b/core/lib/Drupal/Core/TypedData/Type/Integer.php @@ -35,6 +35,6 @@ public function setValue($value) { * Implements TypedDataInterface::validate(). */ public function validate() { - // TODO: Implement validate() method. + return parent::validate(); } } diff --git a/core/lib/Drupal/Core/TypedData/Type/Language.php b/core/lib/Drupal/Core/TypedData/Type/Language.php index 67f4df8..b9db021 100644 --- a/core/lib/Drupal/Core/TypedData/Type/Language.php +++ b/core/lib/Drupal/Core/TypedData/Type/Language.php @@ -129,6 +129,6 @@ public function getString() { * Implements TypedDataInterface::validate(). */ public function validate() { - // TODO: Implement validate() method. + return parent::validate(); } } diff --git a/core/lib/Drupal/Core/TypedData/Type/String.php b/core/lib/Drupal/Core/TypedData/Type/String.php index fef3248..2556fb0 100644 --- a/core/lib/Drupal/Core/TypedData/Type/String.php +++ b/core/lib/Drupal/Core/TypedData/Type/String.php @@ -35,6 +35,6 @@ public function setValue($value) { * Implements TypedDataInterface::validate(). */ public function validate() { - // TODO: Implement validate() method. + return parent::validate(); } } diff --git a/core/lib/Drupal/Core/TypedData/Type/TypedData.php b/core/lib/Drupal/Core/TypedData/Type/TypedData.php index 1e70c53..9914ba1 100644 --- a/core/lib/Drupal/Core/TypedData/Type/TypedData.php +++ b/core/lib/Drupal/Core/TypedData/Type/TypedData.php @@ -8,6 +8,7 @@ namespace Drupal\Core\TypedData\Type; use Drupal\Core\TypedData\TypedDataInterface; +use Drupal\Core\Validation\Violation; /** * The abstract base class for typed data. @@ -44,6 +45,16 @@ public function getType() { } /** + * Implements TypedDataInterface::getConstraints(). + */ + public function getConstraints() { + if (isset($this->definition['constraints'])) { + return $this->definition['constraints']; + } + return array(); + } + + /** * Implements TypedDataInterface::getDefinition(). */ public function getDefinition() { @@ -70,4 +81,37 @@ public function setValue($value) { public function getString() { return (string) $this->getValue(); } + + /** + * Implements TypedDataInterface::getConstraints(). + */ + public function getConstraintsObjects() { + $constraints = array(); + + foreach ($this->getConstraints() as $constraint_id => $constraint_info) { + if ($constraint = drupal_container()->get('constraint')->create($constraint_id, (array)$constraint_info, $this)) { + $constraints[] = $constraint; + } + } + + return $constraints; + } + + /** + * Implements TypedDataInterface::validate(). + */ + public function validate() { + $violations = array(); + + foreach ($this->getConstraints() as $constraint_id => $constraint_info) { + if ($constraint = drupal_container()->get('constraint')->create($constraint_id, (array)$constraint_info, $this)) { + if (!$constraint->validate($this->getValue())) { + $violations[] = new Violation\TypedDataViolation($constraint, $this); + } + } + } + + return $violations; + } + } diff --git a/core/lib/Drupal/Core/TypedData/Type/Uri.php b/core/lib/Drupal/Core/TypedData/Type/Uri.php index 010fa03..dc4f18b 100644 --- a/core/lib/Drupal/Core/TypedData/Type/Uri.php +++ b/core/lib/Drupal/Core/TypedData/Type/Uri.php @@ -34,6 +34,6 @@ public function setValue($value) { * Implements TypedDataInterface::validate(). */ public function validate() { - // TODO: Implement validate() method. + return parent::validate(); } } diff --git a/core/lib/Drupal/Core/TypedData/TypedDataInterface.php b/core/lib/Drupal/Core/TypedData/TypedDataInterface.php index 1a8ffe7..61bc273 100644 --- a/core/lib/Drupal/Core/TypedData/TypedDataInterface.php +++ b/core/lib/Drupal/Core/TypedData/TypedDataInterface.php @@ -23,6 +23,14 @@ public function getType(); /** + * Gets the constraints. + * + * @return array + * Array of constraints. + */ + public function getConstraints(); + + /** * Gets the data definition. * * @return array diff --git a/core/lib/Drupal/Core/Validation/Constraint/Constraint.php b/core/lib/Drupal/Core/Validation/Constraint/Constraint.php new file mode 100644 index 0000000..0750f2d --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Constraint/Constraint.php @@ -0,0 +1,113 @@ +settings = $settings; + if (isset($definition['message'])) { + $this->message = $definition['message']; + } + if (isset($definition['type'])) { + $this->message = $definition['type']; + } + } + + public function setSettings($settings) { + $this->settings = $settings; + } + + /** + * Gets the error message. + * + * @return string + * Error message. + */ + public function getMessage() { + return $this->message; + } + + /** + * Gets the error message arguments. + * + * @return array + * Keyed array with arguments for the message. + */ + public function getMessageArguments() { + return $this->message_arguments; + } + + /** + * Add an error message argument. + * + * @param type $placeholder + * Placeholder that needs to be replaced. + * + * @param type $value + * Value used by the replace. + */ + protected function addMessageArguments($placeholder, $value) { + $this->message_arguments += array($placeholder => $value); + } + + /** + * Convert the constraint to form API structure. + * + * @return array + * Array usable by the form API. + */ + public function convertToFormAPI() { + return array(); + } + +} diff --git a/core/lib/Drupal/Core/Validation/Constraint/ConstraintBundle.php b/core/lib/Drupal/Core/Validation/Constraint/ConstraintBundle.php new file mode 100644 index 0000000..091bd26 --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Constraint/ConstraintBundle.php @@ -0,0 +1,27 @@ +register('plugin.manager.constraint', 'Drupal\Core\Validation\Constraint\ConstraintManager'); + } +} diff --git a/core/lib/Drupal/Core/Validation/Constraint/ConstraintFactory.php b/core/lib/Drupal/Core/Validation/Constraint/ConstraintFactory.php new file mode 100644 index 0000000..df40bd5 --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Constraint/ConstraintFactory.php @@ -0,0 +1,70 @@ +discovery->getDefinition($plugin_id . '.' . $target->getType()); + } + // Checks to see if the target_object is a field. + if (get_class($target) == 'Drupal\Core\Entity\Field\Type\Field' || is_subclass_of($target, 'Drupal\Core\Entity\Field\Type\Field')) { + $definition = $this->discovery->getDefinition($plugin_id . '.field'); + } + // Checks to see if the target_object is an entity. + if (is_subclass_of($target) == 'Drupal\Core\Entity' || is_subclass_of($target, 'Drupal\Core\Entity\EntityInterface')) { + $definition = $this->discovery->getDefinition($plugin_id . '.entity'); + } + } + if (!$definition) { + $definition = $this->discovery->getDefinition($plugin_id); + } + if ($definition) { + $plugin_class = $definition['class']; + return new $plugin_class($definition, $configuration); + } + } + + /** + * Implements Drupal\Component\Plugin\Factory\FactoryInterface::createInstance(). + * + * @param string $plugin_id + * The id of a plugin, i.e. the data type. + * @param array $configuration + * The plugin configuration, i.e. the data definition. + * + * @return Drupal\Core\Validator\Constraint\Constraint + */ + public function createInstance($plugin_id, array $configuration) { + return $this->create($plugin_id, $configuration, NULL); + } +} diff --git a/core/lib/Drupal/Core/Validation/Constraint/ConstraintInterface.php b/core/lib/Drupal/Core/Validation/Constraint/ConstraintInterface.php new file mode 100644 index 0000000..18079c7 --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Constraint/ConstraintInterface.php @@ -0,0 +1,50 @@ +discovery = new AnnotatedClassDiscovery('Validation', 'Constraint'); + $this->discovery->setCustomLocation(array( + 'Drupal\Core\Validation\Constraint' => array(array( + 'dir' => drupal_realpath('core/lib/Drupal/Core/Validation/Constraint'), + 'class_base' => 'Drupal\Core\Validation\Constraint', + )), + )); + $this->factory = new ConstraintFactory($this->discovery); + } + + /** + * Implements Drupal\Component\Plugin\PluginManagerInterface::createInstance(). + * + * @param string $plugin_id + * The id of a plugin, i.e. the data type. + * + * @return Drupal\Core\TypedData\TypedDataInterface + */ + public function createInstance($plugin_id, array $configuration = array()) { + return $this->factory->createInstance($plugin_id, $configuration); + } + + /** + * Implements Drupal\Component\Plugin\PluginManagerInterface::createInstance(). + * + * @param string $plugin_id + * The id of a plugin, i.e. the data type. + * + * @return Drupal\Core\TypedData\TypedDataInterface + */ + public function create($plugin_id, array $configuration = array(), $typed_data = NULL) { + return $this->factory->create($plugin_id, $configuration, $typed_data); + } + + /** + * Returns a list of Constraints that apply to a certain type. + * + * @param string $type + * The type to filter on. + * @param boolean $include_general + * Whether or not to include the ones with an empty type. + */ + public function getList($type = NULL, $include_general = TRUE) { + $constraints = array(); + $definitions = $this->getDefinitions(); + foreach ($definitions as $plugin_id => $definition) { + if ($type === NULL || $definition['type'] == $type) { + $constraints[$plugin_id] = $definition; + } + elseif ($type === '' && $include_general) { + $constraints[$plugin_id] = $definition; + } + } + return $constraints; + } +} diff --git a/core/lib/Drupal/Core/Validation/Constraint/EntityTypeConstraint.php b/core/lib/Drupal/Core/Validation/Constraint/EntityTypeConstraint.php new file mode 100644 index 0000000..ac91d31 --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Constraint/EntityTypeConstraint.php @@ -0,0 +1,34 @@ +addMessageArguments('%field1', $value->get($this->settings[0])->getName()); + $this->addMessageArguments('%field2', $value->get($this->settings[1])->getName()); + return $value->get($this->settings[0])->getValue() == $value->get($this->settings[1])->getValue(); + } +} diff --git a/core/lib/Drupal/Core/Validation/Constraint/MinValueConstraint.php b/core/lib/Drupal/Core/Validation/Constraint/MinValueConstraint.php new file mode 100644 index 0000000..b61c3b3 --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Constraint/MinValueConstraint.php @@ -0,0 +1,48 @@ +addMessageArguments('%min', current($this->settings)); + return $value >= current($this->settings); + } + + /** + * Convert the constraint to form API structure. + * + * @return array + * Array usable by the form API. + */ + public function convertToFormAPI() { + return array( + '#type' => 'number', + '#min' => current($this->settings), + ); + } +} diff --git a/core/lib/Drupal/Core/Validation/Constraint/MinValueIntegerConstraint.php b/core/lib/Drupal/Core/Validation/Constraint/MinValueIntegerConstraint.php new file mode 100644 index 0000000..b389c4e --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Constraint/MinValueIntegerConstraint.php @@ -0,0 +1,35 @@ +addMessageArguments('%min', current($this->settings)); + return $value >= current($this->settings); + } +} diff --git a/core/lib/Drupal/Core/Validation/Constraint/NotNullConstraint.php b/core/lib/Drupal/Core/Validation/Constraint/NotNullConstraint.php new file mode 100644 index 0000000..58216b5 --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Constraint/NotNullConstraint.php @@ -0,0 +1,34 @@ + TRUE); + } + +} diff --git a/core/lib/Drupal/Core/Validation/Constraint/RequiredFieldConstraint.php b/core/lib/Drupal/Core/Validation/Constraint/RequiredFieldConstraint.php new file mode 100644 index 0000000..4c968f0 --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Constraint/RequiredFieldConstraint.php @@ -0,0 +1,34 @@ +isEmpty(); + } +} diff --git a/core/lib/Drupal/Core/Validation/Validator/ValidatorInterface.php b/core/lib/Drupal/Core/Validation/Validator/ValidatorInterface.php new file mode 100644 index 0000000..739e9ca --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Validator/ValidatorInterface.php @@ -0,0 +1,23 @@ +target; + } + + /** + * Get the label of the entity that caused the violation. + * + * @return string + * Label of the entity. + */ + public function getTargetLabel() { + return $this->target->label(); + } + +} diff --git a/core/lib/Drupal/Core/Validation/Violation/FieldViolation.php b/core/lib/Drupal/Core/Validation/Violation/FieldViolation.php new file mode 100644 index 0000000..f4ed144 --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Violation/FieldViolation.php @@ -0,0 +1,35 @@ +target; + } + + /** + * Get the label of the field that caused the violation. + * + * @return string + * Label of the field. + */ + public function getTargetLabel() { + return $this->target->getName(); + } + +} diff --git a/core/lib/Drupal/Core/Validation/Violation/TypedDataViolation.php b/core/lib/Drupal/Core/Validation/Violation/TypedDataViolation.php new file mode 100644 index 0000000..a3cd7b6 --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Violation/TypedDataViolation.php @@ -0,0 +1,35 @@ +target; + } + + /** + * Get the label of the typed data that caused the violation. + * + * @return string + * Label of the typed data. + */ + public function getTargetLabel() { + return $this->target->getString(); + } + +} diff --git a/core/lib/Drupal/Core/Validation/Violation/Violation.php b/core/lib/Drupal/Core/Validation/Violation/Violation.php new file mode 100644 index 0000000..e645ded --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Violation/Violation.php @@ -0,0 +1,79 @@ +constraint = $constraint; + $this->target = $target; + } + + /** + * Get the error message. + * + * @return string + * Error message. + */ + public function getMessage() { + return $this->constraint->getMessage(); + } + + /** + * Get the error message arguments. + * + * @return array + * Keyed array with arguments for the message. + */ + public function getMessageArguments() { + return $this->constraint->getMessageArguments(); + } + + /** + * Get the constraint. + * + * @return Drupal\Core\Constraint\ConstraintInterface + * The constraint. + */ + public function getConstraint() { + return $this->constraint; + } + + /** + * Get the typed data that caused the violation. + * + * @return mixed + * The typed data. + */ + public function getTarget() { + return $this->target; + } + + /** + * Get the typed data that caused the violation. + * + * @return mixed + * The typed data. + */ + public function getTargetLabel() { + return get_class($this->target); + } +} diff --git a/core/lib/Drupal/Core/Validation/Violation/ViolationInterface.php b/core/lib/Drupal/Core/Validation/Violation/ViolationInterface.php new file mode 100644 index 0000000..8d0c344 --- /dev/null +++ b/core/lib/Drupal/Core/Validation/Violation/ViolationInterface.php @@ -0,0 +1,40 @@ + $instance['field_name'], 'description' => '', 'deleted' => 0, + 'constraints' => array(), ); // Set default instance settings. diff --git a/core/modules/field_ui/field_ui.admin.inc b/core/modules/field_ui/field_ui.admin.inc index 7f58240..43d6299 100644 --- a/core/modules/field_ui/field_ui.admin.inc +++ b/core/modules/field_ui/field_ui.admin.inc @@ -862,6 +862,27 @@ function field_ui_field_edit_form($form, &$form_state, $instance) { '#weight' => -10, ); + // Allow user to select which constraints are enabled. + // @todo Construct a helper to easily build this. + // @todo Some constraints require parameters. + // @todo Some constraints only apply to certain fields. + $constraints = drupal_container()->get('constraint')->getList('field', FALSE); + if ($constraints) { + $form['instance']['constraints'] = array( + '#tree' => TRUE, + '#type' => 'fieldset', + '#title' => t('Enable the constraints you want to apply'), + ); + foreach ($constraints as $constraint_id => $constraint_info) { + $form['instance']['constraints'][$constraint_id] = array( + '#type' => 'checkbox', + '#title' => t($constraints[$constraint_id]['label']), + '#default_value' => !empty($instance['constraints'][$constraint_id]), + '#weight' => -4, + ); + } + } + $form['instance']['required'] = array( '#type' => 'checkbox', '#title' => t('Required field'), diff --git a/core/modules/system/lib/Drupal/system/Tests/Validation/ValidationConstraintTest.php b/core/modules/system/lib/Drupal/system/Tests/Validation/ValidationConstraintTest.php new file mode 100644 index 0000000..c2e1aa2 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Tests/Validation/ValidationConstraintTest.php @@ -0,0 +1,51 @@ + 'ValidationConstraintTest', + 'description' => "Test constraint.", + 'group' => 'Validation', + ); + } + + public function setUp() { + parent::setUp(); + } + + /** + * Tests NotNullConstraint. + */ + public function testNotNullConstraint() { + $constraint = new Constraint\NotNullConstraint(array(), array()); + $this->assertTrue($constraint->validate(1)); + $this->assertTrue($constraint->validate(0)); + $this->assertFalse($constraint->validate(NULL)); + } + + /** + * Tests RequiredConstraint. + */ + public function testRequiredConstraint() { + $constraint = new Constraint\RequiredConstraint(array(), array()); + $this->assertTrue($constraint->validate('a value')); + $this->assertTrue($constraint->validate(array('a'))); + $this->assertFalse($constraint->validate(NULL)); + $this->assertFalse($constraint->validate(FALSE)); + $this->assertFalse($constraint->validate('')); + } +} diff --git a/core/modules/system/tests/modules/entity_test/entity_test.info b/core/modules/system/tests/modules/entity_test/entity_test.info index ce49e8a..e460ae0 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.info +++ b/core/modules/system/tests/modules/entity_test/entity_test.info @@ -4,4 +4,4 @@ package = Testing version = VERSION core = 8.x dependencies[] = field -hidden = TRUE +;hidden = TRUE diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php index a65f06f..6410b9e 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php @@ -27,6 +27,7 @@ public function form(array $form, array &$form_state, EntityInterface $entity) { '#type' => 'textfield', '#title' => t('Name'), '#default_value' => $translation->name->value, + '#constraints' => $translation->name->getConstraints(), '#size' => 60, '#maxlength' => 128, '#required' => TRUE, @@ -37,6 +38,7 @@ public function form(array $form, array &$form_state, EntityInterface $entity) { '#type' => 'textfield', '#title' => 'UID', '#default_value' => $translation->user_id->value, + '#constraints' => $translation->user_id->getConstraints(), '#size' => 60, '#maxlength' => 128, '#required' => TRUE, diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTest.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTest.php index 3c948bd..e9f6d95 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTest.php +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Plugin/Core/Entity/EntityTest.php @@ -36,6 +36,16 @@ class EntityTest extends EntityNG { /** + * How to add entity constraints + * constraints = { + * "equal.field" = { + * "user_id", + * "name" + * } + * } + */ + + /** * The entity ID. * * @var \Drupal\Core\Entity\Field\FieldInterface