Paragraphs Editor
WidgetBinderDataCompiler.php
Go to the documentation of this file.
1 <?php
2 
4 
13 
14 /**
15  * The default implementation of the widget binder data model compiler.
16  */
18 
19  /**
20  * The paragraph entity view builder.
21  *
22  * @var \Drupal\Core\Entity\EntityViewBuilderInterface
23  */
24  protected $viewBuilder;
25 
26  /**
27  * The renderer service.
28  *
29  * @var \Drupal\Core\Render\RendererInterface
30  */
31  protected $renderer;
32 
33  /**
34  * The paragraphs editor field value manager.
35  *
36  * @var \Drupal\paragraphs_editor\EditorFieldValue\FieldValueManagerInterface
37  */
38  protected $fieldValueManager;
39 
40  /**
41  * The generator objects to run on compile.
42  *
43  * @var \Drupal\paragraphs_editor\WidgetBinder[]
44  */
45  protected $generators = [];
46 
47  /**
48  * Creates a WidgetBinderDataCompiler.
49  *
50  * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
51  * The entity type manager service to get the paragraph view builder from.
52  * @param \Drupal\Core\Render\RendererInterface $renderer
53  * The renderer service for rendering the paragraph.
54  * @param \Drupal\paragraphs_editor\EditorFieldValue\FieldValueManagerInterface $field_value_manager
55  * The field value manager service for reading paragraphs editor field
56  * information.
57  */
58  public function __construct(EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer, FieldValueManagerInterface $field_value_manager) {
59  $this->viewBuilder = $entity_type_manager->getViewBuilder('paragraph');
60  $this->renderer = $renderer;
61  $this->fieldValueManager = $field_value_manager;
62  }
63 
64  /**
65  * {@inheritdoc}
66  */
67  public function addGenerator(GeneratorInterface $generator) {
68  $this->generators[$generator->id()] = $generator;
69  }
70 
71  /**
72  * {@inheritdoc}
73  */
74  public function compile(CommandContextInterface $context, EditBufferItemInterface $item, $view_mode = 'full', $langcode = NULL) {
75  $data = new WidgetBinderData();
76  $state = new WidgetBinderDataCompilerState($this->generators, $data, $context, $item);
77  $paragraph = $item->getEntity();
78  $this->applyGenerators('initialize', $data, $state, $paragraph);
79  $this->traverseParagraph($data, $state, $paragraph);
80 
81  // Attach the view builder that will decorate the view with information
82  // needed to make nested paragraphs into nested editables.
83  $render_context = new RenderContext();
84  $view = $this->viewBuilder->view($paragraph, $view_mode, $langcode);
85  $this->processElement($view, $state);
86 
87  // Render the rendered markup for the view and collect any bubbled
88  // attachment information from the context.
90  $markup = $renderer->executeInRenderContext($render_context, function () use ($renderer, $view) {
91  return $renderer->render($view);
92  });
93 
94  $this->applyGenerators('complete', $data, $state, $render_context, $markup);
95  return $data;
96  }
97 
98  /**
99  * Pre-render callback for attaching the editor state to the render array.
100  *
101  * @param array $build
102  * The render array to operate on.
103  *
104  * @return array
105  * The updated render array with the attached state.
106  */
107  public function buildView(array $build) {
108  $state = $build['#paragraphs_editor_state'];
109 
110  foreach (Element::children($build) as $field_name) {
111  if (!empty($build[$field_name]['#items'])) {
112  $items = $build[$field_name]['#items'];
113  $field_definition = $items->getFieldDefinition();
114 
115  if ($this->fieldValueManager->isParagraphsField($field_definition)) {
116  $this->processElement($build[$field_name], $state, FALSE);
117 
118  if (!$this->fieldValueManager->isParagraphsEditorField($field_definition)) {
119  foreach (Element::children($build[$field_name]) as $delta) {
120  $this->processElement($build[$field_name][$delta], $state);
121  }
122  }
123  }
124  }
125  }
126 
127  return $build;
128  }
129 
130  /**
131  * Marks an element for editor state attachment.
132  *
133  * @param array &$element
134  * The element to attach the state to.
135  * @param \Drupal\paragraphs_editor\WidgetBinder\WidgetBinderDataCompilerState $state
136  * The state to attach.
137  * @param bool $process_children
138  * TRUE if children should also be passed through the pre-render attachment
139  * processor, or if the state attachment should end at this render node.
140  * Defaults to TRUE.
141  */
142  protected function processElement(array &$element, WidgetBinderDataCompilerState $state, $process_children = TRUE) {
143  $element['#paragraphs_editor_state'] = $state;
144  if ($process_children) {
145  $element['#pre_render'][] = [$this, 'buildView'];
146  }
147  }
148 
149  /**
150  * Traverses the entire paragraph tree for a paragraph, applying generators.
151  *
152  * Each paragraph reference field is recursively followed so that the entire
153  * tree below the paragraph is covered.
154  *
155  * @param \Drupal\paragraphs_editor\WidgetBinder\WidgetBinderData $data
156  * The data being compiled.
157  * @param \Drupal\paragraphs_editor\WidgetBinder\WidgetBinderDataCompilerState $state
158  * The current state of the compiler.
159  * @param \Drupal\paragraphs\ParagraphInterface $paragraph
160  * The paragraph to traverse.
161  */
162  protected function traverseParagraph(WidgetBinderData $data, WidgetBinderDataCompilerState $state, ParagraphInterface $paragraph) {
163  $this->applyGenerators('processParagraph', $data, $state, $paragraph);
164 
165  foreach ($paragraph->getFields() as $items) {
166  $field_definition = $items->getFieldDefinition();
167  if ($this->fieldValueManager->isParagraphsField($field_definition)) {
168  $is_editor_field = $this->fieldValueManager->isParagraphsEditorField($field_definition);
169  $this->applyGenerators('processField', $data, $state, $items, $is_editor_field);
170 
171  if (!$is_editor_field) {
172  foreach ($this->fieldValueManager->getReferencedEntities($items) as $child_paragraph) {
173  $this->traverseParagraph($data, $state, $child_paragraph);
174  }
175  }
176 
177  $this->applyGenerators('postprocessField', $data, $state, $items, $is_editor_field);
178  }
179  }
180 
181  $this->applyGenerators('postprocessParagraph', $data, $state, $paragraph);
182  }
183 
184  /**
185  * Runs the widget binder data generators.
186  *
187  * This function accepts a variable number of arguments. The first argument is
188  * always the name of the method to invoke on the generator. The rest of the
189  * arguments are passed through to the generator.
190  */
191  protected function applyGenerators() {
192  $args = func_get_args();
193  $method = array_shift($args);
194  foreach ($this->generators as $generator) {
195  call_user_func_array([$generator, $method], $args);
196  }
197  }
198 
199 }
traverseParagraph(WidgetBinderData $data, WidgetBinderDataCompilerState $state, ParagraphInterface $paragraph)
compile(CommandContextInterface $context, EditBufferItemInterface $item, $view_mode= 'full', $langcode=NULL)
processElement(array &$element, WidgetBinderDataCompilerState $state, $process_children=TRUE)
__construct(EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer, FieldValueManagerInterface $field_value_manager)