ViewsFormBase.php 8.59 KB
Newer Older
1 2 3 4 5 6 7 8 9
<?php

/**
 * @file
 * Contains \Drupal\views_ui\Form\Ajax\ViewsFormBase.
 */

namespace Drupal\views_ui\Form\Ajax;

10
use Drupal\Component\Utility\Html;
11
use Drupal\Core\Ajax\OpenModalDialogCommand;
12
use Drupal\Core\Form\FormBase;
13 14
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
15 16
use Drupal\views_ui\ViewUI;
use Drupal\views\ViewEntityInterface;
17 18
use Drupal\views\Ajax;
use Drupal\Core\Ajax\AjaxResponse;
19
use Drupal\Core\Ajax\CloseModalDialogCommand;
20 21 22 23 24
use Symfony\Component\HttpFoundation\RedirectResponse;

/**
 * Provides a base class for Views UI AJAX forms.
 */
25
abstract class ViewsFormBase extends FormBase implements ViewsFormInterface {
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65

  /**
   * The ID of the item this form is manipulating.
   *
   * @var string
   */
  protected $id;

  /**
   * The type of item this form is manipulating.
   *
   * @var string
   */
  protected $type;

  /**
   * Sets the ID for this form.
   *
   * @param string $id
   *   The ID of the item this form is manipulating.
   */
  protected function setID($id) {
    if ($id) {
      $this->id = $id;
    }
  }

  /**
   * Sets the type for this form.
   *
   * @param string $type
   *   The type of the item this form is manipulating.
   */
  protected function setType($type) {
    if ($type) {
      $this->type = $type;
    }
  }

  /**
66
   * {@inheritdoc}
67
   */
68
  public function getFormState(ViewEntityInterface $view, $display_id, $js) {
69 70
    // $js may already have been converted to a Boolean.
    $ajax = is_string($js) ? $js === 'ajax' : $js;
71 72 73 74 75 76 77 78 79 80
    return (new FormState())
      ->set('form_id', $this->getFormId())
      ->set('form_key', $this->getFormKey())
      ->set('ajax', $ajax)
      ->set('display_id', $display_id)
      ->set('view', $view)
      ->set('type', $this->type)
      ->set('id', $this->id)
      ->disableRedirect()
      ->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 89
    $view = $form_state->get('view');
    $key = $form_state->get('form_key');
90 91

    // @todo Remove the need for this.
92
    \Drupal::moduleHandler()->loadInclude('views_ui', 'inc', 'admin');
93 94 95 96

    // Reset the cache of IDs. Drupal rather aggressively prevents ID
    // duplication but this causes it to remember IDs that are no longer even
    // being used.
97
    Html::resetSeenIds();
98 99 100 101 102

    // check to see if this is the top form of the stack. If it is, pop
    // it off; if it isn't, the user clicked somewhere else and the stack is
    // now irrelevant.
    if (!empty($view->stack)) {
103
      $identifier = implode('-', array_filter([$key, $view->id(), $display_id, $form_state->get('type'), $form_state->get('id')]));
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
      // Retrieve the first form from the stack without changing the integer keys,
      // as they're being used for the "2 of 3" progress indicator.
      reset($view->stack);
      list($key, $top) = each($view->stack);
      unset($view->stack[$key]);

      if (array_shift($top) != $identifier) {
        $view->stack = array();
      }
    }

    // Automatically remove the form cache if it is set and the key does
    // not match. This way navigating away from the form without hitting
    // update will work.
    if (isset($view->form_cache) && $view->form_cache['key'] != $key) {
      unset($view->form_cache);
    }

122
    $form_class = get_class($form_state->getFormObject());
123
    $response = $this->ajaxFormWrapper($form_class, $form_state);
124 125

    // If the form has not been submitted, or was not set for rerendering, stop.
126
    if (!$form_state->isSubmitted() || $form_state->get('rerender')) {
127 128 129 130 131 132 133 134 135 136
      return $response;
    }

    // Sometimes we need to re-generate the form for multi-step type operations.
    if (!empty($view->stack)) {
      $stack = $view->stack;
      $top = array_shift($stack);

      // Build the new form state for the next form in the stack.
      $reflection = new \ReflectionClass($view::$forms[$top[1]]);
137
      /** @var $form_state \Drupal\Core\Form\FormStateInterface */
138 139
      $form_state = $reflection->newInstanceArgs(array_slice($top, 3, 2))->getFormState($view, $top[2], $form_state->get('ajax'));
      $form_class = get_class($form_state->getFormObject());
140

141
      $form_state->setUserInput(array());
142
      $form_url = views_ui_build_form_url($form_state);
143
      if (!$form_state->get('ajax')) {
144
        return new RedirectResponse($form_url->setAbsolute()->toString());
145
      }
146
      $form_state->set('url', $form_url);
147
      $response = $this->ajaxFormWrapper($form_class, $form_state);
148
    }
149
    elseif (!$form_state->get('ajax')) {
150
      // if nothing on the stack, non-js forms just go back to the main view editor.
151
      $display_id = $form_state->get('display_id');
152
      return new RedirectResponse($this->url('entity.view.edit_display_form', ['view' => $view->id(), 'display_id' => $display_id], ['absolute' => TRUE]));
153 154 155
    }
    else {
      $response = new AjaxResponse();
156
      $response->addCommand(new CloseModalDialogCommand());
157
      $response->addCommand(new Ajax\ShowButtonsCommand(!empty($view->changed)));
158
      $response->addCommand(new Ajax\TriggerPreviewCommand());
159
      if ($page_title = $form_state->get('page_title')) {
160
        $response->addCommand(new Ajax\ReplaceTitleCommand($page_title));
161 162 163 164 165
      }
    }
    // If this form was for view-wide changes, there's no need to regenerate
    // the display section of the form.
    if ($display_id !== '') {
166
      \Drupal::entityManager()->getFormObject('view', 'edit')->rebuildCurrentTab($view, $response, $display_id);
167 168 169 170 171
    }

    return $response;
  }

172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
  /**
   * Wrapper for handling AJAX forms.
   *
   * Wrapper around \Drupal\Core\Form\FormBuilderInterface::buildForm() to
   * handle some AJAX stuff automatically.
   * This makes some assumptions about the client.
   *
   * @param \Drupal\Core\Form\FormInterface|string $form_class
   *   The value must be one of the following:
   *   - The name of a class that implements \Drupal\Core\Form\FormInterface.
   *   - An instance of a class that implements \Drupal\Core\Form\FormInterface.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
   *
   * @return \Drupal\Core\Ajax\AjaxResponse|string|array
   *   Returns one of three possible values:
   *   - A \Drupal\Core\Ajax\AjaxResponse object.
   *   - The rendered form, as a string.
   *   - A render array with the title in #title and the rendered form in the
   *   #markup array.
   */
  protected function ajaxFormWrapper($form_class, FormStateInterface &$form_state) {
194 195 196
    /** @var \Drupal\Core\Render\RendererInterface $renderer */
    $renderer = \Drupal::service('renderer');

197 198 199 200 201 202 203 204 205 206 207
    // This won't override settings already in.
    if (!$form_state->has('rerender')) {
      $form_state->set('rerender', FALSE);
    }
    $ajax = $form_state->get('ajax');
    // Do not overwrite if the redirect has been disabled.
    if (!$form_state->isRedirectDisabled()) {
      $form_state->disableRedirect($ajax);
    }
    $form_state->disableCache();

208
    $form = \Drupal::formBuilder()->buildForm($form_class, $form_state);
209
    $output = $renderer->renderRoot($form);
210 211 212 213 214 215 216 217 218 219
    drupal_process_attached($form);

    // These forms have the title built in, so set the title here:
    $title = $form_state->get('title') ?: '';

    if ($ajax && (!$form_state->isExecuted() || $form_state->get('rerender'))) {
      // If the form didn't execute and we're using ajax, build up an
      // Ajax command list to execute.
      $response = new AjaxResponse();

220 221 222 223 224
      // Attach the library necessary for using the OpenModalDialogCommand and
      // set the attachments for this Ajax response.
      $form['#attached']['library'][] = 'core/drupal.dialog.ajax';
      $response->setAttachments($form['#attached']);

225
      $display = '';
226 227
      $status_messages = array('#type' => 'status_messages');
      if ($messages = $renderer->renderRoot($status_messages)) {
228 229 230 231 232 233
        $display = '<div class="views-messages">' . $messages . '</div>';
      }
      $display .= $output;

      $options = array(
        'dialogClass' => 'views-ui-dialog',
234
        'width' => '75%',
235 236 237 238 239 240 241 242 243 244 245 246 247 248
      );

      $response->addCommand(new OpenModalDialogCommand($title, $display, $options));

      if ($section = $form_state->get('#section')) {
        $response->addCommand(new Ajax\HighlightCommand('.' . Html::cleanCssIdentifier($section)));
      }

      return $response;
    }

    return $title ? ['#title' => $title, '#markup' => $output] : $output;
  }

249
  /**
250
   * {@inheritdoc}
251
   */
252
  public function validateForm(array &$form, FormStateInterface $form_state) {
253 254 255
  }

  /**
256
   * {@inheritdoc}
257
   */
258
  public function submitForm(array &$form, FormStateInterface $form_state) {
259 260 261
  }

}