EntityConstraintViolationList.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. <?php
  2. namespace Drupal\Core\Entity;
  3. use Drupal\Core\Entity\Plugin\Validation\Constraint\CompositeConstraintBase;
  4. use Drupal\Core\Session\AccountInterface;
  5. use Drupal\Core\StringTranslation\StringTranslationTrait;
  6. use Symfony\Component\Validator\ConstraintViolation;
  7. use Symfony\Component\Validator\ConstraintViolationInterface;
  8. use Symfony\Component\Validator\ConstraintViolationList;
  9. /**
  10. * Implements an entity constraint violation list.
  11. */
  12. class EntityConstraintViolationList extends ConstraintViolationList implements EntityConstraintViolationListInterface {
  13. use StringTranslationTrait;
  14. /**
  15. * The entity that has been validated.
  16. *
  17. * @var \Drupal\Core\Entity\FieldableEntityInterface
  18. */
  19. protected $entity;
  20. /**
  21. * Violations offsets of entity level violations.
  22. *
  23. * @var int[]|null
  24. */
  25. protected $entityViolationOffsets;
  26. /**
  27. * Violation offsets grouped by field.
  28. *
  29. * Keys are field names, values are arrays of violation offsets.
  30. *
  31. * @var array[]|null
  32. */
  33. protected $violationOffsetsByField;
  34. /**
  35. * {@inheritdoc}
  36. *
  37. * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
  38. * The entity that has been validated.
  39. * @param array $violations
  40. * The array of violations.
  41. */
  42. public function __construct(FieldableEntityInterface $entity, array $violations = []) {
  43. parent::__construct($violations);
  44. $this->entity = $entity;
  45. }
  46. /**
  47. * Groups violation offsets by field and entity level.
  48. *
  49. * Sets the $violationOffsetsByField and $entityViolationOffsets properties.
  50. */
  51. protected function groupViolationOffsets() {
  52. if (!isset($this->violationOffsetsByField)) {
  53. $this->violationOffsetsByField = [];
  54. $this->entityViolationOffsets = [];
  55. foreach ($this as $offset => $violation) {
  56. if ($path = $violation->getPropertyPath()) {
  57. // An example of $path might be 'title.0.value'.
  58. list($field_name) = explode('.', $path, 2);
  59. if ($this->entity->hasField($field_name)) {
  60. $this->violationOffsetsByField[$field_name][$offset] = $offset;
  61. }
  62. // If the first part of the violation property path is not a valid
  63. // field name, we're dealing with an entity-level validation.
  64. else {
  65. $this->entityViolationOffsets[$offset] = $offset;
  66. }
  67. }
  68. else {
  69. $this->entityViolationOffsets[$offset] = $offset;
  70. }
  71. }
  72. }
  73. }
  74. /**
  75. * {@inheritdoc}
  76. */
  77. public function getEntityViolations() {
  78. $this->groupViolationOffsets();
  79. $violations = [];
  80. foreach ($this->entityViolationOffsets as $offset) {
  81. $violations[] = $this->get($offset);
  82. }
  83. return new static($this->entity, $violations);
  84. }
  85. /**
  86. * {@inheritdoc}
  87. */
  88. public function getByField($field_name) {
  89. return $this->getByFields([$field_name]);
  90. }
  91. /**
  92. * {@inheritdoc}
  93. */
  94. public function getByFields(array $field_names) {
  95. $this->groupViolationOffsets();
  96. $violations = [];
  97. foreach (array_intersect_key($this->violationOffsetsByField, array_flip($field_names)) as $field_name => $offsets) {
  98. foreach ($offsets as $offset) {
  99. $violations[] = $this->get($offset);
  100. }
  101. }
  102. return new static($this->entity, $violations);
  103. }
  104. /**
  105. * {@inheritdoc}
  106. */
  107. public function filterByFields(array $field_names) {
  108. $this->groupViolationOffsets();
  109. $new_violations = [];
  110. foreach (array_intersect_key($this->violationOffsetsByField, array_flip($field_names)) as $field_name => $offsets) {
  111. foreach ($offsets as $offset) {
  112. $violation = $this->get($offset);
  113. // Take care of composite field violations and re-map them to some
  114. // covered field if necessary.
  115. if ($violation->getConstraint() instanceof CompositeConstraintBase) {
  116. $covered_fields = $violation->getConstraint()->coversFields();
  117. // Keep the composite field if it covers some remaining field and put
  118. // a violation on some other covered field instead.
  119. if ($remaining_fields = array_diff($covered_fields, $field_names)) {
  120. $message_params = ['%field_name' => $field_name];
  121. $violation = new ConstraintViolation(
  122. $this->t('The validation failed because the value conflicts with the value in %field_name, which you cannot access.', $message_params),
  123. 'The validation failed because the value conflicts with the value in %field_name, which you cannot access.',
  124. $message_params,
  125. $violation->getRoot(),
  126. reset($remaining_fields),
  127. $violation->getInvalidValue(),
  128. $violation->getPlural(),
  129. $violation->getCode(),
  130. $violation->getConstraint(),
  131. $violation->getCause()
  132. );
  133. $new_violations[] = $violation;
  134. }
  135. }
  136. $this->remove($offset);
  137. }
  138. }
  139. foreach ($new_violations as $violation) {
  140. $this->add($violation);
  141. }
  142. return $this;
  143. }
  144. /**
  145. * {@inheritdoc}
  146. */
  147. public function filterByFieldAccess(AccountInterface $account = NULL) {
  148. $filtered_fields = [];
  149. foreach ($this->getFieldNames() as $field_name) {
  150. if (!$this->entity->get($field_name)->access('edit', $account)) {
  151. $filtered_fields[] = $field_name;
  152. }
  153. }
  154. return $this->filterByFields($filtered_fields);
  155. }
  156. /**
  157. * {@inheritdoc}
  158. */
  159. public function getFieldNames() {
  160. $this->groupViolationOffsets();
  161. return array_keys($this->violationOffsetsByField);
  162. }
  163. /**
  164. * {@inheritdoc}
  165. */
  166. public function getEntity() {
  167. return $this->entity;
  168. }
  169. /**
  170. * {@inheritdoc}
  171. */
  172. public function add(ConstraintViolationInterface $violation) {
  173. parent::add($violation);
  174. $this->violationOffsetsByField = NULL;
  175. $this->entityViolationOffsets = NULL;
  176. }
  177. /**
  178. * {@inheritdoc}
  179. */
  180. public function remove($offset) {
  181. parent::remove($offset);
  182. $this->violationOffsetsByField = NULL;
  183. $this->entityViolationOffsets = NULL;
  184. }
  185. /**
  186. * {@inheritdoc}
  187. */
  188. public function set($offset, ConstraintViolationInterface $violation) {
  189. parent::set($offset, $violation);
  190. $this->violationOffsetsByField = NULL;
  191. $this->entityViolationOffsets = NULL;
  192. }
  193. }