Commit 69ce3554 authored by alexpott's avatar alexpott

Issue #2105797 by dawehner, fago, larowlan, effulgentsia, jibran, amateescu,...

Issue #2105797 by dawehner, fago, larowlan, effulgentsia, jibran, amateescu, yched: Add CompositeConstraintBase so that constraints involving multiple fields, such as CommentNameConstraint, can be discovered
parent 437fa378
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Plugin\Validation\Constraint\CompositeConstraintBase.
*/
namespace Drupal\Core\Entity\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* Provides a base class for constraints validating multiple fields.
*
* The constraint must be defined on entity-level; i.e., added to the entity
* type.
*
* @see \Drupal\Core\Entity\EntityType::addConstraint
*/
abstract class CompositeConstraintBase extends Constraint {
/**
* An array of entity fields which should be passed to the validator.
*
* @return string[]
* An array of field names.
*/
abstract public function coversFields();
}
......@@ -323,9 +323,12 @@ public function validate(array $form, FormStateInterface $form_state) {
foreach ($violations as $violation) {
$form_state->setErrorByName('date', $violation->getMessage());
}
$violations = $comment->name->validate();
$violations = $comment->validate();
// Filter out violations for the name path.
foreach ($violations as $violation) {
$form_state->setErrorByName('name', $violation->getMessage());
if ($violation->getPropertyPath() === 'name') {
$form_state->setErrorByName('name', $violation->getMessage());
}
}
return $comment;
......
......@@ -54,6 +54,9 @@
* },
* bundle_entity_type = "comment_type",
* field_ui_base_route = "entity.comment_type.edit_form",
* constraints = {
* "CommentName" = {}
* }
* )
*/
class Comment extends ContentEntityBase implements CommentInterface {
......@@ -257,8 +260,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
->setDescription(t("The comment author's name."))
->setTranslatable(TRUE)
->setSetting('max_length', 60)
->setDefaultValue('')
->addConstraint('CommentName', array());
->setDefaultValue('');
$fields['mail'] = BaseFieldDefinition::create('email')
->setLabel(t('Email'))
......
......@@ -7,17 +7,18 @@
namespace Drupal\comment\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
use Drupal\Core\Entity\Plugin\Validation\Constraint\CompositeConstraintBase;
/**
* Supports validating comment author names.
*
* @Plugin(
* id = "CommentName",
* label = @Translation("Comment author name", context = "Validation")
* label = @Translation("Comment author name", context = "Validation"),
* type = "entity:comment"
* )
*/
class CommentNameConstraint extends Constraint {
class CommentNameConstraint extends CompositeConstraintBase {
/**
* Message shown when an anonymous user comments using a registered name.
......@@ -40,4 +41,11 @@ class CommentNameConstraint extends Constraint {
*/
public $messageMatch = 'The specified author name does not match the comment author.';
/**
* {@inheritdoc}
*/
public function coversFields() {
return ['name', 'uid'];
}
}
......@@ -19,6 +19,13 @@
*/
class CommentNameConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
/**
* Validator 2.5 and upwards compatible execution context.
*
* @var \Symfony\Component\Validator\Context\ExecutionContextInterface
*/
protected $context;
/**
* User storage handler.
*
......@@ -46,41 +53,36 @@ public static function create(ContainerInterface $container) {
/**
* {@inheritdoc}
*/
public function validate($items, Constraint $constraint) {
/** @var CommentNameConstraint $constraint */
if (!isset($items)) {
return;
}
/** @var CommentInterface $comment */
$comment = $items->getEntity();
if (!isset($comment)) {
// Looks like we are validating a field not being part of a comment,
// nothing we can do then.
return;
}
$author_name = $items->first()->value;
public function validate($entity, Constraint $constraint) {
$author_name = $entity->name->value;
$owner_id = (int) $entity->uid->target_id;
// Do not allow unauthenticated comment authors to use a name that is
// taken by a registered user.
if (isset($author_name) && $author_name !== '' && $comment->getOwnerId() === 0) {
if (isset($author_name) && $author_name !== '' && $owner_id === 0) {
$users = $this->userStorage->loadByProperties(array('name' => $author_name));
if (!empty($users)) {
$this->context->addViolation($constraint->messageNameTaken, array('%name' => $author_name));
$this->context->buildViolation($constraint->messageNameTaken, array('%name' => $author_name))
->atPath('name')
->addViolation();
}
}
// If an author name and owner are given, make sure they match.
elseif (isset($author_name) && $author_name !== '' && $comment->getOwnerId()) {
$owner = $comment->getOwner();
elseif (isset($author_name) && $author_name !== '' && $owner_id) {
$owner = $this->userStorage->load($owner_id);
if ($owner->getUsername() != $author_name) {
$this->context->addViolation($constraint->messageMatch);
$this->context->buildViolation($constraint->messageMatch)
->atPath('name')
->addViolation();
}
}
// Anonymous account might be required - depending on field settings.
if ($comment->getOwnerId() === 0 && empty($author_name) &&
$this->getAnonymousContactDetailsSetting($comment) === COMMENT_ANONYMOUS_MUST_CONTACT) {
$this->context->addViolation($constraint->messageRequired);
if ($owner_id === 0 && empty($author_name) &&
$this->getAnonymousContactDetailsSetting($entity) === COMMENT_ANONYMOUS_MUST_CONTACT) {
$this->context->buildViolation($constraint->messageRequired)
->atPath('name')
->addViolation();
}
}
......
......@@ -7,6 +7,8 @@
namespace Drupal\system\Tests\Entity;
use Drupal\Core\Entity\Plugin\Validation\Constraint\CompositeConstraintBase;
/**
* Tests the Entity Validation API.
*
......@@ -61,7 +63,6 @@ protected function setUp() {
protected function createTestEntity($entity_type) {
$this->entityName = $this->randomMachineName();
$this->entityUser = $this->createUser();
$this->entityFieldText = $this->randomMachineName();
// Pass in the value of the name field when creating. With the user
// field we test setting a field after creation.
......@@ -70,7 +71,10 @@ protected function createTestEntity($entity_type) {
$entity->name->value = $this->entityName;
// Set a value for the test field.
$entity->field_test_text->value = $this->entityFieldText;
if ($entity->hasField('field_test_text')) {
$this->entityFieldText = $this->randomMachineName();
$entity->field_test_text->value = $this->entityFieldText;
}
return $entity;
}
......@@ -168,4 +172,27 @@ protected function checkValidation($entity_type) {
$this->assertEqual($violation->getInvalidValue(), $test_entity->field_test_text->format, 'Violation contains invalid value.');
}
/**
* Tests composite constraints.
*/
public function testCompositeConstraintValidation() {
$entity = $this->createTestEntity('entity_test_composite_constraint');
$violations = $entity->validate();
$this->assertEqual($violations->count(), 0);
// Trigger violation condition.
$entity->name->value = 'test';
$entity->type->value = 'test2';
$violations = $entity->validate();
$this->assertEqual($violations->count(), 1);
// Make sure we can determine this is composite constraint.
$constraint = $violations[0]->getConstraint();
$this->assertTrue($constraint instanceof CompositeConstraintBase, 'Constraint is composite constraint.');
$this->assertEqual('type', $violations[0]->getPropertyPath());
/** @var CompositeConstraintBase $constraint */
$this->assertEqual($constraint->coversFields(), ['name', 'type'], 'Information about covered fields can be retrieved.');
}
}
<?php
/**
* @file
* Contains \Drupal\entity_test\Entity\EntityTestCompositeConstraint.
*/
namespace Drupal\entity_test\Entity;
/**
* Defines a test class for testing composite constraints.
*
* @ContentEntityType(
* id = "entity_test_composite_constraint",
* label = @Translation("Test entity constraints with composite constraint"),
* entity_keys = {
* "id" = "id",
* "uuid" = "uuid",
* "bundle" = "type",
* "label" = "name"
* },
* base_table = "entity_test_composite_constraint",
* persistent_cache = FALSE,
* constraints = {
* "EntityTestComposite" = {},
* }
* )
*/
class EntityTestCompositeConstraint extends EntityTest {
}
<?php
/**
* @file
* Contains \Drupal\entity_test\Plugin\Validation\Constraint\EntityTestCompositeConstraint.
*/
namespace Drupal\entity_test\Plugin\Validation\Constraint;
use Drupal\Core\Entity\Plugin\Validation\Constraint\CompositeConstraintBase;
/**
* Constraint with multiple fields.
*
* @Plugin(
* id = "EntityTestComposite",
* label = @Translation("Constraint with multiple fields."),
* type = "entity"
* )
*/
class EntityTestCompositeConstraint extends CompositeConstraintBase {
public $message = 'Multiple fields are validated';
/**
* {@inheritdoc}
*/
public function coversFields() {
return ['name', 'type'];
}
}
<?php
/**
* @file
* Contains \Drupal\entity_test\Plugin\Validation\Constraint\EntityTestCompositeConstraintValidator.
*/
namespace Drupal\entity_test\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Constraint validator for the EntityTestComposite constraint.
*/
class EntityTestCompositeConstraintValidator extends ConstraintValidator {
/**
* Validator 2.5 and upwards compatible execution context.
*
* @var \Symfony\Component\Validator\Context\ExecutionContextInterface
*/
protected $context;
/**
* {@inheritdoc}
*/
public function validate($entity, Constraint $constraint) {
if ($entity->name->value === 'test' && $entity->type->value === 'test2') {
$this->context->buildViolation($constraint->message)
->atPath('type')
->addViolation();
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment