Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / web / core / modules / views_ui / src / Form / Ajax / ViewsFormBase.php
1 <?php
2
3 namespace Drupal\views_ui\Form\Ajax;
4
5 use Drupal\Component\Utility\Html;
6 use Drupal\Core\Ajax\AjaxResponse;
7 use Drupal\Core\Ajax\CloseModalDialogCommand;
8 use Drupal\Core\Ajax\OpenModalDialogCommand;
9 use Drupal\Core\Form\FormBase;
10 use Drupal\Core\Form\FormState;
11 use Drupal\Core\Form\FormStateInterface;
12 use Drupal\Core\Render\BubbleableMetadata;
13 use Drupal\Core\Render\RenderContext;
14 use Drupal\views\Ajax\HighlightCommand;
15 use Drupal\views\Ajax\ReplaceTitleCommand;
16 use Drupal\views\Ajax\ShowButtonsCommand;
17 use Drupal\views\Ajax\TriggerPreviewCommand;
18 use Drupal\views\ViewEntityInterface;
19 use Drupal\views_ui\Ajax\SetFormCommand;
20 use Symfony\Component\HttpFoundation\RedirectResponse;
21
22 /**
23  * Provides a base class for Views UI AJAX forms.
24  */
25 abstract class ViewsFormBase extends FormBase implements ViewsFormInterface {
26
27   /**
28    * The ID of the item this form is manipulating.
29    *
30    * @var string
31    */
32   protected $id;
33
34   /**
35    * The type of item this form is manipulating.
36    *
37    * @var string
38    */
39   protected $type;
40
41   /**
42    * Sets the ID for this form.
43    *
44    * @param string $id
45    *   The ID of the item this form is manipulating.
46    */
47   protected function setID($id) {
48     if ($id) {
49       $this->id = $id;
50     }
51   }
52
53   /**
54    * Sets the type for this form.
55    *
56    * @param string $type
57    *   The type of the item this form is manipulating.
58    */
59   protected function setType($type) {
60     if ($type) {
61       $this->type = $type;
62     }
63   }
64
65   /**
66    * {@inheritdoc}
67    */
68   public function getFormState(ViewEntityInterface $view, $display_id, $js) {
69     // $js may already have been converted to a Boolean.
70     $ajax = is_string($js) ? $js === 'ajax' : $js;
71     return (new FormState())
72       ->set('form_id', $this->getFormId())
73       ->set('form_key', $this->getFormKey())
74       ->set('ajax', $ajax)
75       ->set('display_id', $display_id)
76       ->set('view', $view)
77       ->set('type', $this->type)
78       ->set('id', $this->id)
79       ->disableRedirect()
80       ->addBuildInfo('callback_object', $this);
81   }
82
83   /**
84    * {@inheritdoc}
85    */
86   public function getForm(ViewEntityInterface $view, $display_id, $js) {
87     $form_state = $this->getFormState($view, $display_id, $js);
88     $view = $form_state->get('view');
89     $key = $form_state->get('form_key');
90
91     // @todo Remove the need for this.
92     \Drupal::moduleHandler()->loadInclude('views_ui', 'inc', 'admin');
93
94     // Reset the cache of IDs. Drupal rather aggressively prevents ID
95     // duplication but this causes it to remember IDs that are no longer even
96     // being used.
97     Html::resetSeenIds();
98
99     // check to see if this is the top form of the stack. If it is, pop
100     // it off; if it isn't, the user clicked somewhere else and the stack is
101     // now irrelevant.
102     if (!empty($view->stack)) {
103       $identifier = implode('-', array_filter([$key, $view->id(), $display_id, $form_state->get('type'), $form_state->get('id')]));
104       // Retrieve the first form from the stack without changing the integer keys,
105       // as they're being used for the "2 of 3" progress indicator.
106       reset($view->stack);
107       $key = key($view->stack);
108       $top = current($view->stack);
109       next($view->stack);
110       unset($view->stack[$key]);
111
112       if (array_shift($top) != $identifier) {
113         $view->stack = [];
114       }
115     }
116
117     // Automatically remove the form cache if it is set and the key does
118     // not match. This way navigating away from the form without hitting
119     // update will work.
120     if (isset($view->form_cache) && $view->form_cache['key'] != $key) {
121       unset($view->form_cache);
122     }
123
124     $form_class = get_class($form_state->getFormObject());
125     $response = $this->ajaxFormWrapper($form_class, $form_state);
126
127     // If the form has not been submitted, or was not set for rerendering, stop.
128     if (!$form_state->isSubmitted() || $form_state->get('rerender')) {
129       return $response;
130     }
131
132     // Sometimes we need to re-generate the form for multi-step type operations.
133     if (!empty($view->stack)) {
134       $stack = $view->stack;
135       $top = array_shift($stack);
136
137       // Build the new form state for the next form in the stack.
138       $reflection = new \ReflectionClass($view::$forms[$top[1]]);
139       /** @var $form_state \Drupal\Core\Form\FormStateInterface */
140       $form_state = $reflection->newInstanceArgs(array_slice($top, 3, 2))->getFormState($view, $top[2], $form_state->get('ajax'));
141       $form_class = get_class($form_state->getFormObject());
142
143       $form_state->setUserInput([]);
144       $form_url = views_ui_build_form_url($form_state);
145       if (!$form_state->get('ajax')) {
146         return new RedirectResponse($form_url->setAbsolute()->toString());
147       }
148       $form_state->set('url', $form_url);
149       $response = $this->ajaxFormWrapper($form_class, $form_state);
150     }
151     elseif (!$form_state->get('ajax')) {
152       // if nothing on the stack, non-js forms just go back to the main view editor.
153       $display_id = $form_state->get('display_id');
154       return new RedirectResponse($this->url('entity.view.edit_display_form', ['view' => $view->id(), 'display_id' => $display_id], ['absolute' => TRUE]));
155     }
156     else {
157       $response = new AjaxResponse();
158       $response->addCommand(new CloseModalDialogCommand());
159       $response->addCommand(new ShowButtonsCommand(!empty($view->changed)));
160       $response->addCommand(new TriggerPreviewCommand());
161       if ($page_title = $form_state->get('page_title')) {
162         $response->addCommand(new ReplaceTitleCommand($page_title));
163       }
164     }
165     // If this form was for view-wide changes, there's no need to regenerate
166     // the display section of the form.
167     if ($display_id !== '') {
168       \Drupal::entityManager()->getFormObject('view', 'edit')->rebuildCurrentTab($view, $response, $display_id);
169     }
170
171     return $response;
172   }
173
174   /**
175    * Wrapper for handling AJAX forms.
176    *
177    * Wrapper around \Drupal\Core\Form\FormBuilderInterface::buildForm() to
178    * handle some AJAX stuff automatically.
179    * This makes some assumptions about the client.
180    *
181    * @param \Drupal\Core\Form\FormInterface|string $form_class
182    *   The value must be one of the following:
183    *   - The name of a class that implements \Drupal\Core\Form\FormInterface.
184    *   - An instance of a class that implements \Drupal\Core\Form\FormInterface.
185    * @param \Drupal\Core\Form\FormStateInterface $form_state
186    *   The current state of the form.
187    *
188    * @return \Drupal\Core\Ajax\AjaxResponse|string|array
189    *   Returns one of three possible values:
190    *   - A \Drupal\Core\Ajax\AjaxResponse object.
191    *   - The rendered form, as a string.
192    *   - A render array with the title in #title and the rendered form in the
193    *   #markup array.
194    */
195   protected function ajaxFormWrapper($form_class, FormStateInterface &$form_state) {
196     /** @var \Drupal\Core\Render\RendererInterface $renderer */
197     $renderer = \Drupal::service('renderer');
198
199     // This won't override settings already in.
200     if (!$form_state->has('rerender')) {
201       $form_state->set('rerender', FALSE);
202     }
203     $ajax = $form_state->get('ajax');
204     // Do not overwrite if the redirect has been disabled.
205     if (!$form_state->isRedirectDisabled()) {
206       $form_state->disableRedirect($ajax);
207     }
208     $form_state->disableCache();
209
210     // Builds the form in a render context in order to ensure that cacheable
211     // metadata is bubbled up.
212     $render_context = new RenderContext();
213     $callable = function () use ($form_class, &$form_state) {
214       return \Drupal::formBuilder()->buildForm($form_class, $form_state);
215     };
216     $form = $renderer->executeInRenderContext($render_context, $callable);
217
218     if (!$render_context->isEmpty()) {
219       BubbleableMetadata::createFromRenderArray($form)
220         ->merge($render_context->pop())
221         ->applyTo($form);
222     }
223     $output = $renderer->renderRoot($form);
224
225     // These forms have the title built in, so set the title here:
226     $title = $form_state->get('title') ?: '';
227
228     if ($ajax && (!$form_state->isExecuted() || $form_state->get('rerender'))) {
229       // If the form didn't execute and we're using ajax, build up an
230       // Ajax command list to execute.
231       $response = new AjaxResponse();
232
233       // Attach the library necessary for using the OpenModalDialogCommand and
234       // set the attachments for this Ajax response.
235       $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
236       $response->setAttachments($form['#attached']);
237
238       $display = '';
239       $status_messages = ['#type' => 'status_messages'];
240       if ($messages = $renderer->renderRoot($status_messages)) {
241         $display = '<div class="views-messages">' . $messages . '</div>';
242       }
243       $display .= $output;
244
245       $options = [
246         'dialogClass' => 'views-ui-dialog js-views-ui-dialog',
247         'width' => '75%',
248       ];
249
250       $response->addCommand(new OpenModalDialogCommand($title, $display, $options));
251
252       // Views provides its own custom handling of AJAX form submissions.
253       // Usually this happens at the same path, but custom paths may be
254       // specified in $form_state.
255       $form_url = $form_state->has('url') ? $form_state->get('url')->toString() : $this->url('<current>');
256       $response->addCommand(new SetFormCommand($form_url));
257
258       if ($section = $form_state->get('#section')) {
259         $response->addCommand(new HighlightCommand('.' . Html::cleanCssIdentifier($section)));
260       }
261
262       return $response;
263     }
264
265     return $title ? ['#title' => $title, '#markup' => $output] : $output;
266   }
267
268   /**
269    * {@inheritdoc}
270    */
271   public function validateForm(array &$form, FormStateInterface $form_state) {
272   }
273
274   /**
275    * {@inheritdoc}
276    */
277   public function submitForm(array &$form, FormStateInterface $form_state) {
278   }
279
280 }