PageRenderTime 43ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/core/lib/Drupal/Core/Plugin/Context/ContextDefinition.php

http://github.com/drupal/drupal
PHP | 453 lines | 200 code | 52 blank | 201 comment | 20 complexity | bae3e37fcae7367fb0f494c4832f88a1 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. namespace Drupal\Core\Plugin\Context;
  3. use Drupal\Core\DependencyInjection\DependencySerializationTrait;
  4. use Drupal\Core\TypedData\TypedDataTrait;
  5. /**
  6. * Defines a class for context definitions.
  7. */
  8. class ContextDefinition implements ContextDefinitionInterface {
  9. use DependencySerializationTrait {
  10. __sleep as traitSleep;
  11. }
  12. use TypedDataTrait;
  13. /**
  14. * The data type of the data.
  15. *
  16. * @var string
  17. * The data type.
  18. */
  19. protected $dataType;
  20. /**
  21. * The human-readable label.
  22. *
  23. * @var string
  24. * The label.
  25. */
  26. protected $label;
  27. /**
  28. * The human-readable description.
  29. *
  30. * @var string|null
  31. * The description, or NULL if no description is available.
  32. */
  33. protected $description;
  34. /**
  35. * Whether the data is multi-valued, i.e. a list of data items.
  36. *
  37. * @var bool
  38. */
  39. protected $isMultiple = FALSE;
  40. /**
  41. * Determines whether a data value is required.
  42. *
  43. * @var bool
  44. * Whether a data value is required.
  45. */
  46. protected $isRequired = TRUE;
  47. /**
  48. * The default value.
  49. *
  50. * @var mixed
  51. */
  52. protected $defaultValue;
  53. /**
  54. * An array of constraints.
  55. *
  56. * @var array[]
  57. */
  58. protected $constraints = [];
  59. /**
  60. * An EntityContextDefinition instance, for backwards compatibility.
  61. *
  62. * If this context is created with a data type that starts with 'entity:',
  63. * this property will be an instance of EntityContextDefinition, and certain
  64. * methods of this object will delegate to their overridden counterparts in
  65. * $this->entityContextDefinition.
  66. *
  67. * This property should be kept private so that it is only accessible to this
  68. * class for backwards compatibility reasons. It will be removed in Drupal 9.
  69. *
  70. * @deprecated
  71. * Constructing a context definition for an entity type (i.e., the data type
  72. * begins with 'entity:') is deprecated in Drupal 8.6.0. Instead, use
  73. * the static factory methods of EntityContextDefinition to create context
  74. * definitions for entity types, or the static ::create() method of this
  75. * class for any other data type. See https://www.drupal.org/node/2976400
  76. * for more information.
  77. *
  78. * @see ::__construct()
  79. * @see ::__sleep()
  80. * @see ::__wakeup()
  81. * @see ::getConstraintObjects()
  82. * @see ::getSampleValues()
  83. * @see ::initializeEntityContextDefinition()
  84. * @see https://www.drupal.org/node/2932462
  85. * @see https://www.drupal.org/node/2976400
  86. *
  87. * @var \Drupal\Core\Plugin\Context\EntityContextDefinition
  88. */
  89. private $entityContextDefinition;
  90. /**
  91. * Creates a new context definition.
  92. *
  93. * @param string $data_type
  94. * The data type for which to create the context definition. Defaults to
  95. * 'any'.
  96. *
  97. * @return static
  98. * The created context definition object.
  99. */
  100. public static function create($data_type = 'any') {
  101. return new static(
  102. $data_type
  103. );
  104. }
  105. /**
  106. * Constructs a new context definition object.
  107. *
  108. * @param string $data_type
  109. * The required data type.
  110. * @param string|null $label
  111. * The label of this context definition for the UI.
  112. * @param bool $required
  113. * Whether the context definition is required.
  114. * @param bool $multiple
  115. * Whether the context definition is multivalue.
  116. * @param string|null $description
  117. * The description of this context definition for the UI.
  118. * @param mixed $default_value
  119. * The default value of this definition.
  120. */
  121. public function __construct($data_type = 'any', $label = NULL, $required = TRUE, $multiple = FALSE, $description = NULL, $default_value = NULL) {
  122. $this->dataType = $data_type;
  123. $this->label = $label;
  124. $this->isRequired = $required;
  125. $this->isMultiple = $multiple;
  126. $this->description = $description;
  127. $this->defaultValue = $default_value;
  128. if (strpos($data_type, 'entity:') === 0 && !($this instanceof EntityContextDefinition)) {
  129. @trigger_error('Constructing a ContextDefinition object for an entity type is deprecated in Drupal 8.6.0. Use ' . __NAMESPACE__ . '\EntityContextDefinition instead. See https://www.drupal.org/node/2976400 for more information.', E_USER_DEPRECATED);
  130. $this->initializeEntityContextDefinition();
  131. }
  132. }
  133. /**
  134. * {@inheritdoc}
  135. */
  136. public function getDataType() {
  137. return $this->dataType;
  138. }
  139. /**
  140. * {@inheritdoc}
  141. */
  142. public function setDataType($data_type) {
  143. $this->dataType = $data_type;
  144. return $this;
  145. }
  146. /**
  147. * {@inheritdoc}
  148. */
  149. public function getLabel() {
  150. return $this->label;
  151. }
  152. /**
  153. * {@inheritdoc}
  154. */
  155. public function setLabel($label) {
  156. $this->label = $label;
  157. return $this;
  158. }
  159. /**
  160. * {@inheritdoc}
  161. */
  162. public function getDescription() {
  163. return $this->description;
  164. }
  165. /**
  166. * {@inheritdoc}
  167. */
  168. public function setDescription($description) {
  169. $this->description = $description;
  170. return $this;
  171. }
  172. /**
  173. * {@inheritdoc}
  174. */
  175. public function isMultiple() {
  176. return $this->isMultiple;
  177. }
  178. /**
  179. * {@inheritdoc}
  180. */
  181. public function setMultiple($multiple = TRUE) {
  182. $this->isMultiple = $multiple;
  183. return $this;
  184. }
  185. /**
  186. * {@inheritdoc}
  187. */
  188. public function isRequired() {
  189. return $this->isRequired;
  190. }
  191. /**
  192. * {@inheritdoc}
  193. */
  194. public function setRequired($required = TRUE) {
  195. $this->isRequired = $required;
  196. return $this;
  197. }
  198. /**
  199. * {@inheritdoc}
  200. */
  201. public function getDefaultValue() {
  202. return $this->defaultValue;
  203. }
  204. /**
  205. * {@inheritdoc}
  206. */
  207. public function setDefaultValue($default_value) {
  208. $this->defaultValue = $default_value;
  209. return $this;
  210. }
  211. /**
  212. * {@inheritdoc}
  213. */
  214. public function getConstraints() {
  215. // If the backwards compatibility layer is present, delegate to that.
  216. $this->initializeEntityContextDefinition();
  217. if ($this->entityContextDefinition) {
  218. return $this->entityContextDefinition->getConstraints();
  219. }
  220. // @todo Apply defaults.
  221. return $this->constraints;
  222. }
  223. /**
  224. * {@inheritdoc}
  225. */
  226. public function getConstraint($constraint_name) {
  227. // If the backwards compatibility layer is present, delegate to that.
  228. $this->initializeEntityContextDefinition();
  229. if ($this->entityContextDefinition) {
  230. return $this->entityContextDefinition->getConstraint($constraint_name);
  231. }
  232. $constraints = $this->getConstraints();
  233. return isset($constraints[$constraint_name]) ? $constraints[$constraint_name] : NULL;
  234. }
  235. /**
  236. * {@inheritdoc}
  237. */
  238. public function setConstraints(array $constraints) {
  239. // If the backwards compatibility layer is present, delegate to that.
  240. $this->initializeEntityContextDefinition();
  241. if ($this->entityContextDefinition) {
  242. $this->entityContextDefinition->setConstraints($constraints);
  243. }
  244. $this->constraints = $constraints;
  245. return $this;
  246. }
  247. /**
  248. * {@inheritdoc}
  249. */
  250. public function addConstraint($constraint_name, $options = NULL) {
  251. // If the backwards compatibility layer is present, delegate to that.
  252. $this->initializeEntityContextDefinition();
  253. if ($this->entityContextDefinition) {
  254. $this->entityContextDefinition->addConstraint($constraint_name, $options);
  255. }
  256. $this->constraints[$constraint_name] = $options;
  257. return $this;
  258. }
  259. /**
  260. * {@inheritdoc}
  261. */
  262. public function getDataDefinition() {
  263. if ($this->isMultiple()) {
  264. $definition = $this->getTypedDataManager()->createListDataDefinition($this->getDataType());
  265. }
  266. else {
  267. $definition = $this->getTypedDataManager()->createDataDefinition($this->getDataType());
  268. }
  269. if (!$definition) {
  270. throw new \Exception("The data type '{$this->getDataType()}' is invalid");
  271. }
  272. $definition->setLabel($this->getLabel())
  273. ->setDescription($this->getDescription())
  274. ->setRequired($this->isRequired());
  275. $constraints = $definition->getConstraints() + $this->getConstraints();
  276. $definition->setConstraints($constraints);
  277. return $definition;
  278. }
  279. /**
  280. * Checks if this definition's data type matches that of the given context.
  281. *
  282. * @param \Drupal\Core\Plugin\Context\ContextInterface $context
  283. * The context to test against.
  284. *
  285. * @return bool
  286. * TRUE if the data types match, otherwise FALSE.
  287. */
  288. protected function dataTypeMatches(ContextInterface $context) {
  289. $this_type = $this->getDataType();
  290. $that_type = $context->getContextDefinition()->getDataType();
  291. return (
  292. // 'any' means all data types are supported.
  293. $this_type === 'any' ||
  294. $this_type === $that_type ||
  295. // Allow a more generic data type like 'entity' to be fulfilled by a more
  296. // specific data type like 'entity:user'. However, if this type is more
  297. // specific, do not consider a more generic type to be a match.
  298. strpos($that_type, "$this_type:") === 0
  299. );
  300. }
  301. /**
  302. * {@inheritdoc}
  303. */
  304. public function isSatisfiedBy(ContextInterface $context) {
  305. $definition = $context->getContextDefinition();
  306. if (!$this->dataTypeMatches($context)) {
  307. return FALSE;
  308. }
  309. // Get the value for this context, either directly if possible or by
  310. // introspecting the definition.
  311. if ($context->hasContextValue()) {
  312. $values = [$context->getContextData()];
  313. }
  314. elseif ($definition instanceof self) {
  315. $this->initializeEntityContextDefinition();
  316. if ($this->entityContextDefinition) {
  317. $values = $this->entityContextDefinition->getSampleValues();
  318. }
  319. else {
  320. $values = $definition->getSampleValues();
  321. }
  322. }
  323. else {
  324. $values = [];
  325. }
  326. $validator = $this->getTypedDataManager()->getValidator();
  327. foreach ($values as $value) {
  328. $constraints = array_values($this->getConstraintObjects());
  329. $violations = $validator->validate($value, $constraints);
  330. foreach ($violations as $delta => $violation) {
  331. // Remove any violation that does not correspond to the constraints.
  332. if (!in_array($violation->getConstraint(), $constraints)) {
  333. $violations->remove($delta);
  334. }
  335. }
  336. // If a value has no violations then the requirement is satisfied.
  337. if (!$violations->count()) {
  338. return TRUE;
  339. }
  340. }
  341. return FALSE;
  342. }
  343. /**
  344. * Returns typed data objects representing this context definition.
  345. *
  346. * This should return as many objects as needed to reflect the variations of
  347. * the constraints it supports.
  348. *
  349. * @yield \Drupal\Core\TypedData\TypedDataInterface
  350. * The set of typed data object.
  351. */
  352. protected function getSampleValues() {
  353. yield $this->getTypedDataManager()->create($this->getDataDefinition());
  354. }
  355. /**
  356. * Extracts an array of constraints for a context definition object.
  357. *
  358. * @return \Symfony\Component\Validator\Constraint[]
  359. * A list of applied constraints for the context definition.
  360. */
  361. protected function getConstraintObjects() {
  362. // If the backwards compatibility layer is present, delegate to that.
  363. $this->initializeEntityContextDefinition();
  364. if ($this->entityContextDefinition) {
  365. return $this->entityContextDefinition->getConstraintObjects();
  366. }
  367. $constraint_definitions = $this->getConstraints();
  368. $validation_constraint_manager = $this->getTypedDataManager()->getValidationConstraintManager();
  369. $constraints = [];
  370. foreach ($constraint_definitions as $constraint_name => $constraint_definition) {
  371. $constraints[$constraint_name] = $validation_constraint_manager->create($constraint_name, $constraint_definition);
  372. }
  373. return $constraints;
  374. }
  375. /**
  376. * Implements magic __sleep() method.
  377. */
  378. public function __sleep() {
  379. return array_diff($this->traitSleep(), ['entityContextDefinition']);
  380. }
  381. /**
  382. * Initializes $this->entityContextDefinition for backwards compatibility.
  383. *
  384. * This method should be kept private so that it is only accessible to this
  385. * class for backwards compatibility reasons. It will be removed in Drupal 9.
  386. *
  387. * @deprecated
  388. */
  389. private function initializeEntityContextDefinition() {
  390. if (!$this instanceof EntityContextDefinition && strpos($this->getDataType(), 'entity:') === 0 && !$this->entityContextDefinition) {
  391. $this->entityContextDefinition = EntityContextDefinition::create()
  392. ->setDataType($this->getDataType())
  393. ->setLabel($this->getLabel())
  394. ->setRequired($this->isRequired())
  395. ->setMultiple($this->isMultiple())
  396. ->setDescription($this->getDescription())
  397. ->setConstraints($this->constraints)
  398. ->setDefaultValue($this->getDefaultValue());
  399. }
  400. }
  401. }