3

May well be missing the obvious here. So in Drupal 8 I have a content type. That has a date range field and then using paragraphs, a paragraph with several fields including a date field.

So I need to validate it so that the date range field (field_A) checks that the start and end are correct. That is the start is on or before the end. No problem, that is done and dusted - FirstConstraintValidator.php.

Next (SecondConstraintValidator.php) for the paragraphs on that content type I need to check the date field, call it field_B, is on or between the start and end date of Field_B. This is where I am having issues. I may be trying to do it a way that it cannot be done.

So I have (I know it is messy and incomplete):

my_module.module

function my_module_entity_bundle_field_info_alter(&$fields, \Drupal\Core\Entity\EntityTypeInterface $entity_type, $bundle) {
  if ($entity_type->id() === 'paragraph' && $bundle === 'bundle_B') {
    $fields['field_B']->addConstraint('SecondConstraint', []);
  } else if ($entity_type->id() === 'node' && $bundle === 'bundle_A') {
    $fields['field_A']->addConstraint('FirstConstraint', []);
  }
}

FirstConstraintValidator.php

class FirstConstraintValidator extends ConstraintValidator {
  public function validate($items, Constraint $constraint) {

    $entity = $items->getEntity();

    $start_date = date("Y-m-d",strtotime($entity->get('field_A')->getValue()[0]['value']));    
    $end_date = date("Y-m-d",strtotime($entity->get('field_A')->getValue()[0]['end_value']));

    if ($start_date > $end_date) {
      // Start date must be on or before end date...
      $this->context->addViolation( xxxxxx );
    }
  }
}

SecondConstraintValidator.php

class SecondConstraintValidator extends ConstraintValidator {
  public function validate($items, Constraint $constraint) {

    $entity = $items->getEntity();
    $parentEntity = $entity->getParentEntity();

    // THIS IS WHERE I NEED TO GET SUBMITTED VALUES.
    // LINES BELOW RETURNED ORIGINAL STORED VALUE, NOT UPDATED VALUE
    $start_date = date("Y-m-d",strtotime($parentEntity->get('field_A')->getValue()[0]['value']));   
    $end_date = date("Y-m-d",strtotime($parentEntity->get('field_A')->getValue()[0]['end_value']);

    // field_a is valid (i.e. start date is before or on end date)
    if ($start_date <= $end_date) {

      if ($items->value > $end_date || $items->value < $start_date) {
        // The date is after the end date of field_a 
        // OR
        // the date is before the start date of field_a
        $this->context->addViolation( xxxxx );
      }
    }
  }
}

I notice when I save the node the paragraphs are validated before the rest of the node. Anyway, the alternative is to use form_alter and $form['#validate'][] but my understanding was in D8 we should be using ConstraintValidator and similar. So question is, am I doing this the right way? Am I missing something?

Thanks

------- UPDATE - PARTLY SOLVED -------

Thanks to @4k4 have a partly working solution. This may help someone else.

my_module.module changed from original to (i.e.adding constraint to the paragraph field.

function my_module_entity_bundle_field_info_alter(&$fields, \Drupal\Core\Entity\EntityTypeInterface $entity_type, $bundle) {
 if ($entity_type->id() === 'node' && $bundle === 'bundle_A') {
    $fields['field_A']->addConstraint('FirstConstraint', []);
    $fields['field_B']->addConstraint('SecondConstraint', []);
  }
}

field_A is a date range field_B refers to a paragraph which has a date field and others in it.

SecondConstraintValidator.php changed to:

class SecondConstraintValidator extends ConstraintValidator {
  public function validate($items, Constraint $constraint) {

    $entity = $items->getEntity();

    $start_date = date("Y-m-d",strtotime($parentEntity->get('field_A')->getValue()[0]['value']));   
    $end_date = date("Y-m-d",strtotime($parentEntity->get('field_A')->getValue()[0]['end_value']);

    // field_a is valid (i.e. start date is before or on end date)
    if ($start_date <= $end_date) {

      if ($items->value > $end_date || $items->value < $start_date) {

        // field_A is valid, but field_B is not. show an error for field_B
        $this->context->addViolation( xxxxx );
      }
    }
  }
}

The above is fine and works if you only have one paragraphs. If you allow multiple, note this is not working correctly, you need something like this:

SecondConstraintValidator.php

class SecondConstraintValidator extends ConstraintValidator {
  public function validate($items, Constraint $constraint) {

    $entity = $items->getEntity();

    $start_date = date("Y-m-d",strtotime($entity->get('field_a')->getValue()[0]['value'])); 
    $end_date = date("Y-m-d",strtotime($entity->get('field_a')->getValue()[0]['end_value']));

    // field_a is valid (i.e. start date is before or on end date)
    if ($start_date <= $end_date) {
        foreach ($items as $activity) {

          $date = $activity->getValue()['subform']['field_b'][0]['value']->format('Y-m-d');  
          if ($date > $end_date || $date < $start_date) {
            $this->context->addViolation( xxxxx );
          }
        }
    }
  }
}

The above, again may not be the best solution. It adds a violation to every field_B, so if you have added 2 copies of the paragraph and only one doesn't validate this will show an error on all. $this->context->addViolation( xxxxx ); needs changing to add violation only to the field in that paragraph.

  • 2
    @nickbits, I think you found the problem already: "I notice when I save the node the paragraphs are validated before the rest of the node." You could try to attach the second constraint to the paragraph field in the node and then check the referenced paragraph entities and their fields values, which should contain the submitted values at this point. – 4k4 Jan 15 at 8:13
  • @4k4 didn't even see that, not sure why, obvious now you have pointed it out. Will give it a go and report back. Thanks. – nickbits Jan 15 at 11:20
  • Also you could have your second constraint applied at node's entity level instead of field so you will have the whole node and its fields (ergo the paragraphs). Check this on how to add entity level constraints drupal.org/node/2438011 – d70rr3s Jan 15 at 13:32
  • @4k4spot on, that has worked perfectly. Couldn't see it before, just too long staring at it. – nickbits Jan 16 at 9:44
  • @d70rr3s thanks, that is an alternative I may use at a future point. Thanks. – nickbits Jan 16 at 9:45
2

Thanks to all the comments, have a working solution which may help others.

my_module.module changed from original to (i.e.adding constraint to the paragraph field.

function my_module_entity_bundle_field_info_alter(&$fields, \Drupal\Core\Entity\EntityTypeInterface $entity_type, $bundle) {
 if ($entity_type->id() === 'node' && $bundle === 'bundle_A') {
    $fields['field_A']->addConstraint('FirstConstraint', []);
    $fields['field_B']->addConstraint('SecondConstraint', []);
  }
}

field_A is a date range field_B refers to a paragraph which has a date field and others in it. field_C is the date field inside the paragraph

SecondConstraintValidator.php changed to:

class SecondConstraintValidator extends ConstraintValidator {
  public function validate($items, Constraint $constraint) {

    $entity = $items->getEntity();

    $start_date = date("Y-m-d",strtotime($parentEntity->get('field_A')->getValue()[0]['value']));   
    $end_date = date("Y-m-d",strtotime($parentEntity->get('field_A')->getValue()[0]['end_value']);

    // field_a is valid (i.e. start date is before or on end date)
    if ($start_date <= $end_date) {

      if ($items->value > $end_date || $items->value < $start_date) {

        // field_A is valid, but field_B is not. show an error for field_B
        $this->context->addViolation( xxxxx );
      }
    }
  }
}

The above is fine and works if you only have one paragraphs. If you allow multiple you need something like this:

SecondConstraintValidator.php

class SecondConstraintValidator extends ConstraintValidator {
  public function validate($items, Constraint $constraint) {

    $entity = $items->getEntity();

    $start_date = date("Y-m-d",strtotime($entity->get('field_a')->getValue()[0]['value'])); 
    $end_date = date("Y-m-d",strtotime($entity->get('field_a')->getValue()[0]['end_value']));

    // field_a is valid (i.e. start date is before or on end date)
    if ($start_date <= $end_date) {
        foreach ($items as $key as $activity) {

          $date = $activity->getValue()['subform']['field_c'][0]['value']->format('Y-m-d');  
          if ($date > $end_date || $date < $start_date) {
            $this->context->buildViolation("message")->atPath($key.'.field_c')->addViolation();
          }
        }
    }
  }
}

The above, again may not be the best solution. Open to comments, but it works!

|improve this answer|||||
  • Since you answered your question mark it as "correct answer". – d70rr3s Jan 16 at 11:59
  • @d70rr3s I will do when it lets me! – nickbits Jan 16 at 16:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.