Paragraphs Editor
ParagraphsEditorParagraphAnalyzer.php
Go to the documentation of this file.
1 <?php
2 
4 
14 
15 /**
16  * An analyzer plugin for extracting paragraphs editor data from a DOM tree.
17  *
18  * The paragraph analyzer will first look to see if there is an edit context for
19  * a paragraph, and will try to load the paragraph from that context's edit
20  * buffer if available.
21  *
22  * If no edits exist in the edit buffer for the context, the analyzer will
23  * attempt to load the paragraph from the field items that the paragraph is
24  * nested within.
25  *
26  * If the entity cannot be found in either of those locations, it is loaded from
27  * through the entity storage system.
28  *
29  * @DomProcessorSemanticAnalyzer(
30  * id = "paragraphs_editor_paragraph_analyzer",
31  * label = "Paragraphs Editor Paragraph Analyzer"
32  * )
33  */
34 class ParagraphsEditorParagraphAnalyzer implements SemanticAnalyzerInterface, ContainerFactoryPluginInterface {
36 
37  /**
38  * The paragraph storage handler.
39  *
40  * @var \Drupal\Core\Entity\EntityStorageInterface
41  */
42  protected $storage;
43 
44  /**
45  * The context factory to get contexts for loading unsaved edits.
46  *
47  * @var \Drupal\paragraphs_editor\EditorCommand\CommandContextFactoryInterface
48  */
49  protected $contextFactory;
50 
51  /**
52  * Creates a paragraph analyzer plugin.
53  *
54  * @param \Drupal\paragraphs_editor\EditorFieldValue\FieldValueManagerInterface $field_value_manager
55  * The field value manager service to initialize the element trait.
56  * @param \Drupal\Core\Entity\EntityStorageInterface $storage
57  * The paragraph storage handler for loading otherwise unresolvable
58  * entities.
59  * @param \Drupal\paragraphs_editor\EditorCommand\CommandContextFactoryInterface $context_factory
60  * The context factory to use for looking up unsaved entity edits.
61  */
62  public function __construct(FieldValueManagerInterface $field_value_manager, EntityStorageInterface $storage, CommandContextFactoryInterface $context_factory) {
63  $this->initializeParagraphsEditorElementTrait($field_value_manager);
64  $this->storage = $storage;
65  $this->contextFactory = $context_factory;
66  }
67 
68  /**
69  * {@inheritdoc}
70  */
71  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
72  return new static(
73  $container->get('paragraphs_editor.field_value.manager'),
74  $container->get('entity_type.manager')->getStorage('paragraph'),
75  $container->get('paragraphs_editor.command.context_factory')
76  );
77  }
78 
79  /**
80  * {@inheritdoc}
81  */
82  public function analyze(SemanticDataInterface $data) {
83  if ($data->is($this->getSelector('widget'))) {
84  return $this->analyzeWidget($data);
85  }
86  elseif ($data->is($this->getSelector('field'))) {
87  return $this->analyzeField($data);
88  }
89  else {
90  return $data;
91  }
92  }
93 
94  /**
95  * Analyzes a widget DOM element to produce semantic data.
96  *
97  * @param \Drupal\dom_processor\DomProcessor\SemanticDataInterface $data
98  * The current data state for the analyzer.
99  *
100  * @return \Drupal\dom_processor\DomProcessor\SemanticDataInterface
101  * The updated data state for the processor decorated with information about
102  * the referenced paragraph.
103  */
104  protected function analyzeWidget(SemanticDataInterface $data) {
105  $uuid = $this->getAttribute($data->node(), 'widget', '<uuid>');
106 
107  // Try to load the entity from it's hinted context first, then try the
108  // context for the field it belongs to, then try to locate the item in the
109  // field items of the field it belongs to, and if all else fails, try to
110  // load the entity from the database.
111  $attempts = [
112  [
113  'type' => 'context',
114  'context_id' => $this->getAttribute($data->node(), 'widget', '<context>'),
115  ],
116  [
117  'type' => 'context',
118  'context_id' => $data->get('field.context_id'),
119  ],
120  [
121  'type' => 'items',
122  'items' => $data->get('field.items'),
123  ],
124  [
125  'type' => 'storage',
126  ],
127  ];
128 
129  while (!empty($attempts)) {
130  $attempt = array_shift($attempts);
131  try {
132  if ($attempt['type'] == 'context') {
133  $context = $this->contextFactory->get($attempt['context_id']);
134  if (!empty($context)) {
135  $edit_buffer = $context->getEditBuffer();
136  $item = $edit_buffer->getItem($uuid);
137  if ($item) {
138  return $data->tag('paragraph', [
139  'entity' => $item->getEntity(),
140  'context_id' => $attempt['context_id'],
141  ]);
142  }
143  }
144  }
145  elseif ($attempt['type'] == 'items' && $attempt['items']) {
146  foreach ($this->fieldValueManager->getReferencedEntities($attempt['items']) as $candidate) {
147  if ($candidate->uuid() == $uuid) {
148  return $data->tag('paragraph', [
149  'entity' => $candidate,
150  ]);
151  }
152  }
153  }
154  elseif ($attempt['type'] == 'storage') {
155  $matches = $this->storage->loadByProperties([
156  'uuid' => $uuid,
157  ]);
158  if (!empty($matches)) {
159  return $data->tag('paragraph', [
160  'entity' => reset($matches),
161  ]);
162  }
163  }
164  }
165  catch (\Exception $e) {
166  throw new DomProcessorError("Unkown load type.");
167  }
168  }
169 
170  throw new DomProcessorError("Could not load entity.");
171  }
172 
173  /**
174  * Analyzes a field DOM element to produce semantic data.
175  *
176  * @param \Drupal\dom_processor\DomProcessor\SemanticDataInterface $data
177  * The current data state for the analyzer.
178  *
179  * @return \Drupal\dom_processor\DomProcessor\SemanticDataInterface
180  * The updated data state for the processor decorated with information about
181  * the referenced field.
182  */
183  protected function analyzeField(SemanticDataInterface $data) {
184  $field_name = $this->getAttribute($data->node(), 'field', '<name>');
185  $paragraph = $data->get('paragraph.entity');
186  if (!$field_name || !$paragraph || !isset($paragraph->{$field_name})) {
187  throw new DomProcessorError("Could not access field on entity.");
188  }
189 
190  $items = $paragraph->{$field_name};
191  $field_definition = $items->getFieldDefinition();
192  if (!$this->fieldValueManager->isParagraphsField($field_definition)) {
193  throw new DomProcessorError("Attempted to access non-paragraphs field.");
194  }
195 
196  if ($this->fieldValueManager->isParagraphsEditorField($field_definition)) {
197  $field_value_wrapper = $this->fieldValueManager->wrapItems($items);
198  $field_value_wrapper->setReferencedEntities([]);
199  $context_id = $this->getAttribute($data->node(), 'field', '<context>');
200  }
201  else {
202  $context_id = NULL;
203  $field_value_wrapper = NULL;
204  }
205 
206  return $data->tag('field', [
207  'items' => $items,
208  'context_id' => $context_id,
209  'wrapper' => $field_value_wrapper,
210  ]);
211  }
212 
213 }
__construct(FieldValueManagerInterface $field_value_manager, EntityStorageInterface $storage, CommandContextFactoryInterface $context_factory)
static create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition)
initializeParagraphsEditorElementTrait(FieldValueManagerInterface $field_value_manager)
getAttribute(\DOMNode $node, $element_name, $attribute_name)