3 namespace Drupal\ctools;
6 use Drupal\Core\Field\BaseFieldDefinition;
7 use Drupal\Core\Plugin\Context\Context;
8 use Drupal\Core\Plugin\Context\ContextDefinition;
9 use Drupal\Core\Plugin\Context\ContextDefinitionInterface;
10 use Drupal\Core\Plugin\Context\ContextInterface;
11 use Drupal\Core\StringTranslation\TranslationInterface;
12 use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
13 use Drupal\Core\TypedData\DataReferenceDefinitionInterface;
14 use Drupal\Core\TypedData\DataReferenceInterface;
15 use Drupal\Core\TypedData\ListDataDefinitionInterface;
16 use Drupal\Core\TypedData\ListInterface;
17 use Drupal\Core\TypedData\TypedDataManagerInterface;
19 class TypedDataResolver {
22 * The typed data manager.
24 * @var \Drupal\Core\TypedData\TypedDataManagerInterface
29 * The string translation service.
31 * @var \Drupal\Core\StringTranslation\TranslationInterface
33 protected $translation;
36 * @param \Drupal\Core\TypedData\TypedDataManagerInterface $manager
37 * The typed data manager.
38 * @param \Drupal\Core\StringTranslation\TranslationInterface $translation
39 * The string translation service.
41 public function __construct(TypedDataManagerInterface $manager, TranslationInterface $translation) {
42 $this->manager = $manager;
43 $this->translation = $translation;
47 * Convert a property to a context.
49 * This method will respect the value of contexts as well, so if a context
50 * object is pass that contains a value, the appropriate value will be
51 * extracted and injected into the resulting context object if available.
53 * @param string $property_path
54 * The name of the property.
55 * @param \Drupal\Core\Plugin\Context\ContextInterface $context
56 * The context from which we will extract values if available.
58 * @return \Drupal\Core\Plugin\Context\Context
59 * A context object that represents the definition & value of the property.
62 public function getContextFromProperty($property_path, ContextInterface $context) {
64 $data_definition = NULL;
65 if ($context->hasContextValue()) {
66 /** @var \Drupal\Core\TypedData\ComplexDataInterface $data */
67 $data = $context->getContextData();
68 foreach (explode(':', $property_path) as $name) {
70 if ($data instanceof ListInterface) {
71 if (!is_numeric($name)) {
72 // Implicitly default to delta 0 for lists when not specified.
73 $data = $data->first();
76 // If we have a delta, fetch it and continue with the next part.
77 $data = $data->get($name);
82 // Forward to the target value if this is a data reference.
83 if ($data instanceof DataReferenceInterface) {
84 $data = $data->getTarget();
87 if (!$data->getDataDefinition()->getPropertyDefinition($name)) {
88 throw new \Exception("Unknown property $name in property path $property_path");
90 $data = $data->get($name);
93 $value = $data->getValue();
94 $data_definition = $data instanceof DataReferenceInterface ? $data->getDataDefinition()->getTargetDefinition() : $data->getDataDefinition();
97 /** @var \Drupal\Core\TypedData\ComplexDataDefinitionInterface $data_definition */
98 $data_definition = $context->getContextDefinition()->getDataDefinition();
99 foreach (explode(':', $property_path) as $name) {
101 if ($data_definition instanceof ListDataDefinitionInterface) {
102 $data_definition = $data_definition->getItemDefinition();
104 // If the delta was specified explicitly, continue with the next part.
105 if (is_numeric($name)) {
110 // Forward to the target definition if this is a data reference
112 if ($data_definition instanceof DataReferenceDefinitionInterface) {
113 $data_definition = $data_definition->getTargetDefinition();
116 if (!$data_definition->getPropertyDefinition($name)) {
117 throw new \Exception("Unknown property $name in property path $property_path");
119 $data_definition = $data_definition->getPropertyDefinition($name);
122 // Forward to the target definition if this is a data reference
124 if ($data_definition instanceof DataReferenceDefinitionInterface) {
125 $data_definition = $data_definition->getTargetDefinition();
128 $context_definition = new ContextDefinition($data_definition->getDataType(), $data_definition->getLabel(), $data_definition->isRequired(), FALSE, $data_definition->getDescription());
129 return new Context($context_definition, $value);
133 * Extracts a context from an array of contexts by a tokenized pattern.
135 * This is more than simple isset/empty checks on the contexts array. The
136 * pattern could be node:uid:name which will iterate over all provided
137 * contexts in the array for one named 'node', it will then load the data
138 * definition of 'node' and check for a property named 'uid'. This will then
139 * set a new (temporary) context on the array and recursively call itself to
140 * navigate through related properties all the way down until the request
141 * property is located. At that point the property is passed to a
142 * TypedDataResolver which will convert it to an appropriate ContextInterface
146 * A ":" delimited set of tokens representing
147 * @param \Drupal\Core\Plugin\Context\ContextInterface[] $contexts
148 * The array of available contexts.
150 * @return \Drupal\Core\Plugin\Context\ContextInterface
151 * The requested token as a full Context object.
153 * @throws \Drupal\ctools\ContextNotFoundException
155 public function convertTokenToContext($token, $contexts) {
156 // If the requested token is already a context, just return it.
157 if (isset($contexts[$token])) {
158 return $contexts[$token];
161 list($base, $property_path) = explode(':', $token, 2);
162 // A base must always be set. This method recursively calls itself
163 // setting bases for this reason.
164 if (!empty($contexts[$base])) {
165 return $this->getContextFromProperty($property_path, $contexts[$base]);
167 // @todo improve this exception message.
168 throw new ContextNotFoundException("The requested context was not found in the supplied array of contexts.");
173 * Provides an administrative label for a tokenized relationship.
175 * @param string $token
176 * The token related to a context in the contexts array.
177 * @param \Drupal\Core\Plugin\Context\ContextInterface[] $contexts
178 * An array of contexts from which to extract our token's label.
180 * @return \Drupal\Core\StringTranslation\TranslatableMarkup
181 * The administrative label of $token.
183 public function getLabelByToken($token, $contexts) {
184 // @todo Optimize this by allowing to limit the desired token?
185 $tokens = $this->getTokensForContexts($contexts);
186 if (isset($tokens[$token])) {
187 return $tokens[$token];
192 * Extracts an array of tokens and labels.
194 * @param \Drupal\Core\Plugin\Context\ContextInterface[] $contexts
195 * The array of contexts with which we are currently dealing.
198 * An array of token keys and corresponding labels.
200 public function getTokensForContexts($contexts) {
202 foreach ($contexts as $context_id => $context) {
203 $data_definition = $context->getContextDefinition()->getDataDefinition();
204 if ($data_definition instanceof ComplexDataDefinitionInterface) {
205 foreach ($this->getTokensFromComplexData($data_definition) as $token => $label) {
206 $tokens["$context_id:$token"] = $data_definition->getLabel() . ': ' . $label;
214 * Returns tokens for a complex data definition.
216 * @param \Drupal\Core\TypedData\ComplexDataDefinitionInterface $complex_data_definition
219 * An array of token keys and corresponding labels.
221 protected function getTokensFromComplexData(ComplexDataDefinitionInterface $complex_data_definition) {
223 // Loop over all properties.
224 foreach ($complex_data_definition->getPropertyDefinitions() as $property_name => $property_definition) {
226 // Item definitions do not always have a label. Use the list definition
227 // label if the item does not have one.
228 $property_label = $property_definition->getLabel();
229 if ($property_definition instanceof ListDataDefinitionInterface) {
230 $property_definition = $property_definition->getItemDefinition();
231 $property_label = $property_definition->getLabel() ?: $property_label;
234 // If the property is complex too, recurse to find child properties.
235 if ($property_definition instanceof ComplexDataDefinitionInterface) {
236 $property_tokens = $this->getTokensFromComplexData($property_definition);
237 foreach ($property_tokens as $token => $label) {
238 $tokens[$property_name . ':' . $token] = count($property_tokens) > 1 ? ($property_label . ': ' . $label) : $property_label;
242 // Only expose references as tokens.
243 // @todo Consider to expose primitive and non-reference typed data
244 // definitions too, like strings, integers and dates. The current UI
245 // will not scale to that.
246 if ($property_definition instanceof DataReferenceDefinitionInterface) {
247 $tokens[$property_name] = $property_definition->getLabel();