ContextDefinition.php 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. <?php
  2. namespace Drupal\Core\Plugin\Context;
  3. use Drupal\Core\DependencyInjection\DependencySerializationTrait;
  4. use Drupal\Core\Entity\ContentEntityStorageInterface;
  5. use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;
  6. use Drupal\Core\Entity\Plugin\Validation\Constraint\BundleConstraint;
  7. use Drupal\Core\Entity\Plugin\Validation\Constraint\EntityTypeConstraint;
  8. use Drupal\Core\Entity\TypedData\EntityDataDefinition;
  9. use Drupal\Core\TypedData\TypedDataTrait;
  10. /**
  11. * Defines a class for context definitions.
  12. */
  13. class ContextDefinition implements ContextDefinitionInterface {
  14. use DependencySerializationTrait;
  15. use TypedDataTrait;
  16. /**
  17. * The data type of the data.
  18. *
  19. * @var string
  20. * The data type.
  21. */
  22. protected $dataType;
  23. /**
  24. * The human-readable label.
  25. *
  26. * @var string
  27. * The label.
  28. */
  29. protected $label;
  30. /**
  31. * The human-readable description.
  32. *
  33. * @var string|null
  34. * The description, or NULL if no description is available.
  35. */
  36. protected $description;
  37. /**
  38. * Whether the data is multi-valued, i.e. a list of data items.
  39. *
  40. * @var bool
  41. */
  42. protected $isMultiple = FALSE;
  43. /**
  44. * Determines whether a data value is required.
  45. *
  46. * @var bool
  47. * Whether a data value is required.
  48. */
  49. protected $isRequired = TRUE;
  50. /**
  51. * The default value.
  52. *
  53. * @var mixed
  54. */
  55. protected $defaultValue;
  56. /**
  57. * An array of constraints.
  58. *
  59. * @var array[]
  60. */
  61. protected $constraints = [];
  62. /**
  63. * Creates a new context definition.
  64. *
  65. * @param string $data_type
  66. * The data type for which to create the context definition. Defaults to
  67. * 'any'.
  68. *
  69. * @return static
  70. * The created context definition object.
  71. */
  72. public static function create($data_type = 'any') {
  73. return new static(
  74. $data_type
  75. );
  76. }
  77. /**
  78. * Constructs a new context definition object.
  79. *
  80. * @param string $data_type
  81. * The required data type.
  82. * @param string|null $label
  83. * The label of this context definition for the UI.
  84. * @param bool $required
  85. * Whether the context definition is required.
  86. * @param bool $multiple
  87. * Whether the context definition is multivalue.
  88. * @param string|null $description
  89. * The description of this context definition for the UI.
  90. * @param mixed $default_value
  91. * The default value of this definition.
  92. */
  93. public function __construct($data_type = 'any', $label = NULL, $required = TRUE, $multiple = FALSE, $description = NULL, $default_value = NULL) {
  94. $this->dataType = $data_type;
  95. $this->label = $label;
  96. $this->isRequired = $required;
  97. $this->isMultiple = $multiple;
  98. $this->description = $description;
  99. $this->defaultValue = $default_value;
  100. }
  101. /**
  102. * {@inheritdoc}
  103. */
  104. public function getDataType() {
  105. return $this->dataType;
  106. }
  107. /**
  108. * {@inheritdoc}
  109. */
  110. public function setDataType($data_type) {
  111. $this->dataType = $data_type;
  112. return $this;
  113. }
  114. /**
  115. * {@inheritdoc}
  116. */
  117. public function getLabel() {
  118. return $this->label;
  119. }
  120. /**
  121. * {@inheritdoc}
  122. */
  123. public function setLabel($label) {
  124. $this->label = $label;
  125. return $this;
  126. }
  127. /**
  128. * {@inheritdoc}
  129. */
  130. public function getDescription() {
  131. return $this->description;
  132. }
  133. /**
  134. * {@inheritdoc}
  135. */
  136. public function setDescription($description) {
  137. $this->description = $description;
  138. return $this;
  139. }
  140. /**
  141. * {@inheritdoc}
  142. */
  143. public function isMultiple() {
  144. return $this->isMultiple;
  145. }
  146. /**
  147. * {@inheritdoc}
  148. */
  149. public function setMultiple($multiple = TRUE) {
  150. $this->isMultiple = $multiple;
  151. return $this;
  152. }
  153. /**
  154. * {@inheritdoc}
  155. */
  156. public function isRequired() {
  157. return $this->isRequired;
  158. }
  159. /**
  160. * {@inheritdoc}
  161. */
  162. public function setRequired($required = TRUE) {
  163. $this->isRequired = $required;
  164. return $this;
  165. }
  166. /**
  167. * {@inheritdoc}
  168. */
  169. public function getDefaultValue() {
  170. return $this->defaultValue;
  171. }
  172. /**
  173. * {@inheritdoc}
  174. */
  175. public function setDefaultValue($default_value) {
  176. $this->defaultValue = $default_value;
  177. return $this;
  178. }
  179. /**
  180. * {@inheritdoc}
  181. */
  182. public function getConstraints() {
  183. // @todo Apply defaults.
  184. return $this->constraints;
  185. }
  186. /**
  187. * {@inheritdoc}
  188. */
  189. public function getConstraint($constraint_name) {
  190. $constraints = $this->getConstraints();
  191. return isset($constraints[$constraint_name]) ? $constraints[$constraint_name] : NULL;
  192. }
  193. /**
  194. * {@inheritdoc}
  195. */
  196. public function setConstraints(array $constraints) {
  197. $this->constraints = $constraints;
  198. return $this;
  199. }
  200. /**
  201. * {@inheritdoc}
  202. */
  203. public function addConstraint($constraint_name, $options = NULL) {
  204. $this->constraints[$constraint_name] = $options;
  205. return $this;
  206. }
  207. /**
  208. * {@inheritdoc}
  209. */
  210. public function getDataDefinition() {
  211. if ($this->isMultiple()) {
  212. $definition = $this->getTypedDataManager()->createListDataDefinition($this->getDataType());
  213. }
  214. else {
  215. $definition = $this->getTypedDataManager()->createDataDefinition($this->getDataType());
  216. }
  217. if (!$definition) {
  218. throw new \Exception("The data type '{$this->getDataType()}' is invalid");
  219. }
  220. $definition->setLabel($this->getLabel())
  221. ->setDescription($this->getDescription())
  222. ->setRequired($this->isRequired());
  223. $constraints = $definition->getConstraints() + $this->getConstraints();
  224. $definition->setConstraints($constraints);
  225. return $definition;
  226. }
  227. /**
  228. * {@inheritdoc}
  229. */
  230. public function isSatisfiedBy(ContextInterface $context) {
  231. $definition = $context->getContextDefinition();
  232. // If the data types do not match, this context is invalid unless the
  233. // expected data type is any, which means all data types are supported.
  234. if ($this->getDataType() != 'any' && $definition->getDataType() != $this->getDataType()) {
  235. return FALSE;
  236. }
  237. // Get the value for this context, either directly if possible or by
  238. // introspecting the definition.
  239. if ($context->hasContextValue()) {
  240. $values = [$context->getContextData()];
  241. }
  242. elseif ($definition instanceof static) {
  243. $values = $definition->getSampleValues();
  244. }
  245. else {
  246. $values = [];
  247. }
  248. $validator = $this->getTypedDataManager()->getValidator();
  249. foreach ($values as $value) {
  250. $violations = $validator->validate($value, array_values($this->getConstraintObjects()));
  251. // If a value has no violations then the requirement is satisfied.
  252. if (!$violations->count()) {
  253. return TRUE;
  254. }
  255. }
  256. return FALSE;
  257. }
  258. /**
  259. * Returns typed data objects representing this context definition.
  260. *
  261. * This should return as many objects as needed to reflect the variations of
  262. * the constraints it supports.
  263. *
  264. * @yield \Drupal\Core\TypedData\TypedDataInterface
  265. * The set of typed data object.
  266. */
  267. protected function getSampleValues() {
  268. // @todo Move the entity specific logic out of this class in
  269. // https://www.drupal.org/node/2932462.
  270. // Get the constraints from the context's definition.
  271. $constraints = $this->getConstraintObjects();
  272. // If constraints include EntityType, we generate an entity or adapter.
  273. if (!empty($constraints['EntityType']) && $constraints['EntityType'] instanceof EntityTypeConstraint) {
  274. $entity_type_manager = \Drupal::entityTypeManager();
  275. $entity_type_id = $constraints['EntityType']->type;
  276. $storage = $entity_type_manager->getStorage($entity_type_id);
  277. // If the storage can generate a sample entity we might delegate to that.
  278. if ($storage instanceof ContentEntityStorageInterface) {
  279. if (!empty($constraints['Bundle']) && $constraints['Bundle'] instanceof BundleConstraint) {
  280. foreach ($constraints['Bundle']->bundle as $bundle) {
  281. // We have a bundle, we are bundleable and we can generate a sample.
  282. yield EntityAdapter::createFromEntity($storage->createWithSampleValues($bundle));
  283. }
  284. return;
  285. }
  286. }
  287. // Either no bundle, or not bundleable, so generate an entity adapter.
  288. $definition = EntityDataDefinition::create($entity_type_id);
  289. yield new EntityAdapter($definition);
  290. return;
  291. }
  292. // No entity related constraints, so generate a basic typed data object.
  293. yield $this->getTypedDataManager()->create($this->getDataDefinition());
  294. }
  295. /**
  296. * Extracts an array of constraints for a context definition object.
  297. *
  298. * @return \Symfony\Component\Validator\Constraint[]
  299. * A list of applied constraints for the context definition.
  300. */
  301. protected function getConstraintObjects() {
  302. $constraint_definitions = $this->getConstraints();
  303. // @todo Move the entity specific logic out of this class in
  304. // https://www.drupal.org/node/2932462.
  305. // If the data type is an entity, manually add one to the constraints array.
  306. if (strpos($this->getDataType(), 'entity:') === 0) {
  307. $entity_type_id = substr($this->getDataType(), 7);
  308. $constraint_definitions['EntityType'] = ['type' => $entity_type_id];
  309. }
  310. $validation_constraint_manager = $this->getTypedDataManager()->getValidationConstraintManager();
  311. $constraints = [];
  312. foreach ($constraint_definitions as $constraint_name => $constraint_definition) {
  313. $constraints[$constraint_name] = $validation_constraint_manager->create($constraint_name, $constraint_definition);
  314. }
  315. return $constraints;
  316. }
  317. }