annotate core/modules/views_ui/src/ViewEditForm.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\views_ui;
Chris@0 4
Chris@0 5 use Drupal\Component\Utility\Html;
Chris@17 6 use Drupal\Component\Render\FormattableMarkup;
Chris@0 7 use Drupal\Core\Ajax\AjaxResponse;
Chris@0 8 use Drupal\Core\Ajax\HtmlCommand;
Chris@0 9 use Drupal\Core\Ajax\ReplaceCommand;
Chris@0 10 use Drupal\Core\Datetime\DateFormatterInterface;
Chris@0 11 use Drupal\Core\Form\FormStateInterface;
Chris@0 12 use Drupal\Core\Render\ElementInfoManagerInterface;
Chris@0 13 use Drupal\Core\Url;
Chris@14 14 use Drupal\Core\TempStore\SharedTempStoreFactory;
Chris@0 15 use Drupal\views\Views;
Chris@0 16 use Symfony\Component\DependencyInjection\ContainerInterface;
Chris@0 17 use Symfony\Component\HttpFoundation\RequestStack;
Chris@14 18 use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
Chris@0 19
Chris@0 20 /**
Chris@0 21 * Form controller for the Views edit form.
Chris@14 22 *
Chris@14 23 * @internal
Chris@0 24 */
Chris@0 25 class ViewEditForm extends ViewFormBase {
Chris@0 26
Chris@0 27 /**
Chris@0 28 * The views temp store.
Chris@0 29 *
Chris@14 30 * @var \Drupal\Core\TempStore\SharedTempStore
Chris@0 31 */
Chris@0 32 protected $tempStore;
Chris@0 33
Chris@0 34 /**
Chris@0 35 * The request object.
Chris@0 36 *
Chris@0 37 * @var \Symfony\Component\HttpFoundation\RequestStack
Chris@0 38 */
Chris@0 39 protected $requestStack;
Chris@0 40
Chris@0 41 /**
Chris@0 42 * The date formatter service.
Chris@0 43 *
Chris@0 44 * @var \Drupal\Core\Datetime\DateFormatterInterface
Chris@0 45 */
Chris@0 46 protected $dateFormatter;
Chris@0 47
Chris@0 48 /**
Chris@0 49 * The element info manager.
Chris@0 50 *
Chris@0 51 * @var \Drupal\Core\Render\ElementInfoManagerInterface
Chris@0 52 */
Chris@0 53 protected $elementInfo;
Chris@0 54
Chris@0 55 /**
Chris@0 56 * Constructs a new ViewEditForm object.
Chris@0 57 *
Chris@14 58 * @param \Drupal\Core\TempStore\SharedTempStoreFactory $temp_store_factory
Chris@0 59 * The factory for the temp store object.
Chris@0 60 * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
Chris@0 61 * The request stack object.
Chris@0 62 * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
Chris@0 63 * The date Formatter service.
Chris@0 64 * @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info
Chris@0 65 * The element info manager.
Chris@0 66 */
Chris@0 67 public function __construct(SharedTempStoreFactory $temp_store_factory, RequestStack $requestStack, DateFormatterInterface $date_formatter, ElementInfoManagerInterface $element_info) {
Chris@0 68 $this->tempStore = $temp_store_factory->get('views');
Chris@0 69 $this->requestStack = $requestStack;
Chris@0 70 $this->dateFormatter = $date_formatter;
Chris@0 71 $this->elementInfo = $element_info;
Chris@0 72 }
Chris@0 73
Chris@0 74 /**
Chris@0 75 * {@inheritdoc}
Chris@0 76 */
Chris@0 77 public static function create(ContainerInterface $container) {
Chris@0 78 return new static(
Chris@14 79 $container->get('tempstore.shared'),
Chris@0 80 $container->get('request_stack'),
Chris@0 81 $container->get('date.formatter'),
Chris@0 82 $container->get('element_info')
Chris@0 83 );
Chris@0 84 }
Chris@0 85
Chris@0 86 /**
Chris@0 87 * {@inheritdoc}
Chris@0 88 */
Chris@0 89 public function form(array $form, FormStateInterface $form_state) {
Chris@18 90 /** @var \Drupal\views_ui\ViewUI $view */
Chris@0 91 $view = $this->entity;
Chris@0 92 $display_id = $this->displayID;
Chris@0 93 // Do not allow the form to be cached, because $form_state->get('view') can become
Chris@0 94 // stale between page requests.
Chris@0 95 // See views_ui_ajax_get_form() for how this affects #ajax.
Chris@0 96 // @todo To remove this and allow the form to be cacheable:
Chris@0 97 // - Change $form_state->get('view') to $form_state->getTemporary()['view'].
Chris@0 98 // - Add a #process function to initialize $form_state->getTemporary()['view']
Chris@0 99 // on cached form submissions.
Chris@0 100 // - Use \Drupal\Core\Form\FormStateInterface::loadInclude().
Chris@0 101 $form_state->disableCache();
Chris@0 102
Chris@0 103 if ($display_id) {
Chris@0 104 if (!$view->getExecutable()->setDisplay($display_id)) {
Chris@0 105 $form['#markup'] = $this->t('Invalid display id @display', ['@display' => $display_id]);
Chris@0 106 return $form;
Chris@0 107 }
Chris@0 108 }
Chris@0 109
Chris@0 110 $form['#tree'] = TRUE;
Chris@0 111
Chris@0 112 $form['#attached']['library'][] = 'core/jquery.ui.tabs';
Chris@0 113 $form['#attached']['library'][] = 'core/jquery.ui.dialog';
Chris@0 114 $form['#attached']['library'][] = 'core/drupal.states';
Chris@0 115 $form['#attached']['library'][] = 'core/drupal.tabledrag';
Chris@0 116 $form['#attached']['library'][] = 'views_ui/views_ui.admin';
Chris@0 117 $form['#attached']['library'][] = 'views_ui/admin.styling';
Chris@0 118
Chris@0 119 $form += [
Chris@0 120 '#prefix' => '',
Chris@0 121 '#suffix' => '',
Chris@0 122 ];
Chris@0 123
Chris@0 124 $view_status = $view->status() ? 'enabled' : 'disabled';
Chris@0 125 $form['#prefix'] .= '<div class="views-edit-view views-admin ' . $view_status . ' clearfix">';
Chris@0 126 $form['#suffix'] = '</div>' . $form['#suffix'];
Chris@0 127
Chris@0 128 $form['#attributes']['class'] = ['form-edit'];
Chris@0 129
Chris@0 130 if ($view->isLocked()) {
Chris@0 131 $form['locked'] = [
Chris@0 132 '#type' => 'container',
Chris@0 133 '#attributes' => ['class' => ['view-locked', 'messages', 'messages--warning']],
Chris@0 134 '#weight' => -10,
Chris@18 135 'message' => [
Chris@18 136 '#type' => 'break_lock_link',
Chris@18 137 '#label' => $view->getEntityType()->getSingularLabel(),
Chris@18 138 '#lock' => $view->getLock(),
Chris@18 139 '#url' => $view->toUrl('break-lock-form'),
Chris@18 140 ],
Chris@0 141 ];
Chris@0 142 }
Chris@0 143 else {
Chris@0 144 $form['changed'] = [
Chris@0 145 '#type' => 'container',
Chris@0 146 '#attributes' => ['class' => ['view-changed', 'messages', 'messages--warning']],
Chris@0 147 '#children' => $this->t('You have unsaved changes.'),
Chris@0 148 '#weight' => -10,
Chris@0 149 ];
Chris@0 150 if (empty($view->changed)) {
Chris@0 151 $form['changed']['#attributes']['class'][] = 'js-hide';
Chris@0 152 }
Chris@0 153 }
Chris@0 154
Chris@0 155 $form['displays'] = [
Chris@0 156 '#prefix' => '<h1 class="unit-title clearfix">' . $this->t('Displays') . '</h1>',
Chris@0 157 '#type' => 'container',
Chris@0 158 '#attributes' => [
Chris@0 159 'class' => [
Chris@0 160 'views-displays',
Chris@0 161 ],
Chris@0 162 ],
Chris@0 163 ];
Chris@0 164
Chris@0 165 $form['displays']['top'] = $this->renderDisplayTop($view);
Chris@0 166
Chris@0 167 // The rest requires a display to be selected.
Chris@0 168 if ($display_id) {
Chris@0 169 $form_state->set('display_id', $display_id);
Chris@0 170
Chris@0 171 // The part of the page where editing will take place.
Chris@0 172 $form['displays']['settings'] = [
Chris@0 173 '#type' => 'container',
Chris@0 174 '#id' => 'edit-display-settings',
Chris@0 175 '#attributes' => [
Chris@0 176 'class' => ['edit-display-settings'],
Chris@0 177 ],
Chris@0 178 ];
Chris@0 179
Chris@0 180 // Add a text that the display is disabled.
Chris@0 181 if ($view->getExecutable()->displayHandlers->has($display_id)) {
Chris@0 182 if (!$view->getExecutable()->displayHandlers->get($display_id)->isEnabled()) {
Chris@0 183 $form['displays']['settings']['disabled']['#markup'] = $this->t('This display is disabled.');
Chris@0 184 }
Chris@0 185 }
Chris@0 186
Chris@0 187 // Add the edit display content
Chris@0 188 $tab_content = $this->getDisplayTab($view);
Chris@0 189 $tab_content['#theme_wrappers'] = ['container'];
Chris@0 190 $tab_content['#attributes'] = ['class' => ['views-display-tab']];
Chris@0 191 $tab_content['#id'] = 'views-tab-' . $display_id;
Chris@0 192 // Mark deleted displays as such.
Chris@0 193 $display = $view->get('display');
Chris@0 194 if (!empty($display[$display_id]['deleted'])) {
Chris@0 195 $tab_content['#attributes']['class'][] = 'views-display-deleted';
Chris@0 196 }
Chris@0 197 // Mark disabled displays as such.
Chris@0 198
Chris@0 199 if ($view->getExecutable()->displayHandlers->has($display_id) && !$view->getExecutable()->displayHandlers->get($display_id)->isEnabled()) {
Chris@0 200 $tab_content['#attributes']['class'][] = 'views-display-disabled';
Chris@0 201 }
Chris@0 202 $form['displays']['settings']['settings_content'] = [
Chris@0 203 '#type' => 'container',
Chris@0 204 'tab_content' => $tab_content,
Chris@0 205 ];
Chris@0 206 }
Chris@0 207
Chris@0 208 return $form;
Chris@0 209 }
Chris@0 210
Chris@0 211 /**
Chris@0 212 * {@inheritdoc}
Chris@0 213 */
Chris@0 214 protected function actions(array $form, FormStateInterface $form_state) {
Chris@0 215 $actions = parent::actions($form, $form_state);
Chris@0 216 unset($actions['delete']);
Chris@0 217
Chris@0 218 $actions['cancel'] = [
Chris@0 219 '#type' => 'submit',
Chris@0 220 '#value' => $this->t('Cancel'),
Chris@0 221 '#submit' => ['::cancel'],
Chris@0 222 '#limit_validation_errors' => [],
Chris@0 223 ];
Chris@0 224 if ($this->entity->isLocked()) {
Chris@0 225 $actions['submit']['#access'] = FALSE;
Chris@0 226 $actions['cancel']['#access'] = FALSE;
Chris@0 227 }
Chris@0 228 return $actions;
Chris@0 229 }
Chris@0 230
Chris@0 231 /**
Chris@0 232 * {@inheritdoc}
Chris@0 233 */
Chris@0 234 public function validateForm(array &$form, FormStateInterface $form_state) {
Chris@0 235 parent::validateForm($form, $form_state);
Chris@0 236
Chris@0 237 $view = $this->entity;
Chris@0 238 if ($view->isLocked()) {
Chris@0 239 $form_state->setErrorByName('', $this->t('Changes cannot be made to a locked view.'));
Chris@0 240 }
Chris@0 241 foreach ($view->getExecutable()->validate() as $display_errors) {
Chris@0 242 foreach ($display_errors as $error) {
Chris@0 243 $form_state->setErrorByName('', $error);
Chris@0 244 }
Chris@0 245 }
Chris@0 246 }
Chris@0 247
Chris@0 248 /**
Chris@0 249 * {@inheritdoc}
Chris@0 250 */
Chris@0 251 public function save(array $form, FormStateInterface $form_state) {
Chris@0 252 $view = $this->entity;
Chris@0 253 $executable = $view->getExecutable();
Chris@0 254 $executable->initDisplay();
Chris@0 255
Chris@0 256 // Go through and remove displayed scheduled for removal.
Chris@0 257 $displays = $view->get('display');
Chris@0 258 foreach ($displays as $id => $display) {
Chris@0 259 if (!empty($display['deleted'])) {
Chris@0 260 // Remove view display from view attachment under the attachments
Chris@0 261 // options.
Chris@0 262 $display_handler = $executable->displayHandlers->get($id);
Chris@0 263 if ($attachments = $display_handler->getAttachedDisplays()) {
Chris@0 264 foreach ($attachments as $attachment) {
Chris@0 265 $attached_options = $executable->displayHandlers->get($attachment)->getOption('displays');
Chris@0 266 unset($attached_options[$id]);
Chris@0 267 $executable->displayHandlers->get($attachment)->setOption('displays', $attached_options);
Chris@0 268 }
Chris@0 269 }
Chris@0 270 $executable->displayHandlers->remove($id);
Chris@0 271 unset($displays[$id]);
Chris@0 272 }
Chris@0 273 }
Chris@0 274
Chris@0 275 // Rename display ids if needed.
Chris@0 276 foreach ($executable->displayHandlers as $id => $display) {
Chris@0 277 if (!empty($display->display['new_id']) && $display->display['new_id'] !== $display->display['id'] && empty($display->display['deleted'])) {
Chris@0 278 $new_id = $display->display['new_id'];
Chris@0 279 $display->display['id'] = $new_id;
Chris@0 280 unset($display->display['new_id']);
Chris@0 281 $executable->displayHandlers->set($new_id, $display);
Chris@0 282
Chris@0 283 $displays[$new_id] = $displays[$id];
Chris@0 284 unset($displays[$id]);
Chris@0 285
Chris@0 286 // Redirect the user to the renamed display to be sure that the page itself exists and doesn't throw errors.
Chris@0 287 $form_state->setRedirect('entity.view.edit_display_form', [
Chris@0 288 'view' => $view->id(),
Chris@0 289 'display_id' => $new_id,
Chris@0 290 ]);
Chris@0 291 }
Chris@0 292 elseif (isset($display->display['new_id'])) {
Chris@0 293 unset($display->display['new_id']);
Chris@0 294 }
Chris@0 295 }
Chris@0 296 $view->set('display', $displays);
Chris@0 297
Chris@0 298 // @todo: Revisit this when https://www.drupal.org/node/1668866 is in.
Chris@0 299 $query = $this->requestStack->getCurrentRequest()->query;
Chris@0 300 $destination = $query->get('destination');
Chris@0 301
Chris@0 302 if (!empty($destination)) {
Chris@0 303 // Find out the first display which has a changed path and redirect to this url.
Chris@0 304 $old_view = Views::getView($view->id());
Chris@0 305 $old_view->initDisplay();
Chris@0 306 foreach ($old_view->displayHandlers as $id => $display) {
Chris@0 307 // Only check for displays with a path.
Chris@0 308 $old_path = $display->getOption('path');
Chris@0 309 if (empty($old_path)) {
Chris@0 310 continue;
Chris@0 311 }
Chris@0 312
Chris@0 313 if (($display->getPluginId() == 'page') && ($old_path == $destination) && ($old_path != $view->getExecutable()->displayHandlers->get($id)->getOption('path'))) {
Chris@0 314 $destination = $view->getExecutable()->displayHandlers->get($id)->getOption('path');
Chris@0 315 $query->remove('destination');
Chris@0 316 }
Chris@0 317 }
Chris@0 318 // @todo Use Url::fromPath() once https://www.drupal.org/node/2351379 is
Chris@0 319 // resolved.
Chris@0 320 $form_state->setRedirectUrl(Url::fromUri("base:$destination"));
Chris@0 321 }
Chris@0 322
Chris@0 323 $view->save();
Chris@0 324
Chris@17 325 $this->messenger()->addStatus($this->t('The view %name has been saved.', ['%name' => $view->label()]));
Chris@0 326
Chris@0 327 // Remove this view from cache so we can edit it properly.
Chris@0 328 $this->tempStore->delete($view->id());
Chris@0 329 }
Chris@0 330
Chris@0 331 /**
Chris@0 332 * Form submission handler for the 'cancel' action.
Chris@0 333 *
Chris@0 334 * @param array $form
Chris@0 335 * An associative array containing the structure of the form.
Chris@0 336 * @param \Drupal\Core\Form\FormStateInterface $form_state
Chris@0 337 * The current state of the form.
Chris@0 338 */
Chris@0 339 public function cancel(array $form, FormStateInterface $form_state) {
Chris@0 340 // Remove this view from cache so edits will be lost.
Chris@0 341 $view = $this->entity;
Chris@0 342 $this->tempStore->delete($view->id());
Chris@18 343 $form_state->setRedirectUrl($this->entity->toUrl('collection'));
Chris@0 344 }
Chris@0 345
Chris@0 346 /**
Chris@0 347 * Returns a renderable array representing the edit page for one display.
Chris@0 348 */
Chris@0 349 public function getDisplayTab($view) {
Chris@0 350 $build = [];
Chris@0 351 $display_id = $this->displayID;
Chris@0 352 $display = $view->getExecutable()->displayHandlers->get($display_id);
Chris@0 353 // If the plugin doesn't exist, display an error message instead of an edit
Chris@0 354 // page.
Chris@0 355 if (empty($display)) {
Chris@0 356 // @TODO: Improved UX for the case where a plugin is missing.
Chris@0 357 $build['#markup'] = $this->t("Error: Display @display refers to a plugin named '@plugin', but that plugin is not available.", ['@display' => $display->display['id'], '@plugin' => $display->display['display_plugin']]);
Chris@0 358 }
Chris@0 359 // Build the content of the edit page.
Chris@0 360 else {
Chris@0 361 $build['details'] = $this->getDisplayDetails($view, $display->display);
Chris@0 362 }
Chris@0 363 // In AJAX context, ViewUI::rebuildCurrentTab() returns this outside of form
Chris@0 364 // context, so hook_form_views_ui_edit_form_alter() is insufficient.
Chris@0 365 \Drupal::moduleHandler()->alter('views_ui_display_tab', $build, $view, $display_id);
Chris@0 366 return $build;
Chris@0 367 }
Chris@0 368
Chris@0 369 /**
Chris@0 370 * Helper function to get the display details section of the edit UI.
Chris@0 371 *
Chris@0 372 * @param $display
Chris@0 373 *
Chris@0 374 * @return array
Chris@0 375 * A renderable page build array.
Chris@0 376 */
Chris@0 377 public function getDisplayDetails($view, $display) {
Chris@0 378 $display_title = $this->getDisplayLabel($view, $display['id'], FALSE);
Chris@0 379 $build = [
Chris@0 380 '#theme_wrappers' => ['container'],
Chris@0 381 '#attributes' => ['id' => 'edit-display-settings-details'],
Chris@0 382 ];
Chris@0 383
Chris@0 384 $is_display_deleted = !empty($display['deleted']);
Chris@0 385 // The master display cannot be duplicated.
Chris@0 386 $is_default = $display['id'] == 'default';
Chris@0 387 // @todo: Figure out why getOption doesn't work here.
Chris@0 388 $is_enabled = $view->getExecutable()->displayHandlers->get($display['id'])->isEnabled();
Chris@0 389
Chris@0 390 if ($display['id'] != 'default') {
Chris@0 391 $build['top']['#theme_wrappers'] = ['container'];
Chris@0 392 $build['top']['#attributes']['id'] = 'edit-display-settings-top';
Chris@0 393 $build['top']['#attributes']['class'] = ['views-ui-display-tab-actions', 'edit-display-settings-top', 'views-ui-display-tab-bucket', 'clearfix'];
Chris@0 394
Chris@0 395 // The Delete, Duplicate and Undo Delete buttons.
Chris@0 396 $build['top']['actions'] = [
Chris@0 397 '#theme_wrappers' => ['dropbutton_wrapper'],
Chris@0 398 ];
Chris@0 399
Chris@0 400 // Because some of the 'links' are actually submit buttons, we have to
Chris@0 401 // manually wrap each item in <li> and the whole list in <ul>.
Chris@0 402 $build['top']['actions']['prefix']['#markup'] = '<ul class="dropbutton">';
Chris@0 403
Chris@0 404 if (!$is_display_deleted) {
Chris@0 405 if (!$is_enabled) {
Chris@0 406 $build['top']['actions']['enable'] = [
Chris@0 407 '#type' => 'submit',
Chris@0 408 '#value' => $this->t('Enable @display_title', ['@display_title' => $display_title]),
Chris@0 409 '#limit_validation_errors' => [],
Chris@0 410 '#submit' => ['::submitDisplayEnable', '::submitDelayDestination'],
Chris@0 411 '#prefix' => '<li class="enable">',
Chris@0 412 "#suffix" => '</li>',
Chris@0 413 ];
Chris@0 414 }
Chris@0 415 // Add a link to view the page unless the view is disabled or has no
Chris@0 416 // path.
Chris@0 417 elseif ($view->status() && $view->getExecutable()->displayHandlers->get($display['id'])->hasPath()) {
Chris@0 418 $path = $view->getExecutable()->displayHandlers->get($display['id'])->getPath();
Chris@14 419
Chris@0 420 if ($path && (strpos($path, '%') === FALSE)) {
Chris@14 421 // Wrap this in a try/catch as trying to generate links to some
Chris@14 422 // routes may throw a NotAcceptableHttpException if they do not
Chris@14 423 // respond to HTML, such as RESTExports.
Chris@14 424 try {
Chris@14 425 if (!parse_url($path, PHP_URL_SCHEME)) {
Chris@14 426 // @todo Views should expect and store a leading /. See:
Chris@14 427 // https://www.drupal.org/node/2423913
Chris@14 428 $url = Url::fromUserInput('/' . ltrim($path, '/'));
Chris@14 429 }
Chris@14 430 else {
Chris@14 431 $url = Url::fromUri("base:$path");
Chris@14 432 }
Chris@0 433 }
Chris@14 434 catch (NotAcceptableHttpException $e) {
Chris@14 435 $url = '/' . $path;
Chris@0 436 }
Chris@14 437
Chris@0 438 $build['top']['actions']['path'] = [
Chris@0 439 '#type' => 'link',
Chris@0 440 '#title' => $this->t('View @display_title', ['@display_title' => $display_title]),
Chris@0 441 '#options' => ['alt' => [$this->t("Go to the real page for this display")]],
Chris@0 442 '#url' => $url,
Chris@0 443 '#prefix' => '<li class="view">',
Chris@0 444 "#suffix" => '</li>',
Chris@0 445 ];
Chris@0 446 }
Chris@0 447 }
Chris@0 448 if (!$is_default) {
Chris@0 449 $build['top']['actions']['duplicate'] = [
Chris@0 450 '#type' => 'submit',
Chris@0 451 '#value' => $this->t('Duplicate @display_title', ['@display_title' => $display_title]),
Chris@0 452 '#limit_validation_errors' => [],
Chris@0 453 '#submit' => ['::submitDisplayDuplicate', '::submitDelayDestination'],
Chris@0 454 '#prefix' => '<li class="duplicate">',
Chris@0 455 "#suffix" => '</li>',
Chris@0 456 ];
Chris@0 457 }
Chris@0 458 // Always allow a display to be deleted.
Chris@0 459 $build['top']['actions']['delete'] = [
Chris@0 460 '#type' => 'submit',
Chris@0 461 '#value' => $this->t('Delete @display_title', ['@display_title' => $display_title]),
Chris@0 462 '#limit_validation_errors' => [],
Chris@0 463 '#submit' => ['::submitDisplayDelete', '::submitDelayDestination'],
Chris@0 464 '#prefix' => '<li class="delete">',
Chris@0 465 "#suffix" => '</li>',
Chris@0 466 ];
Chris@0 467
Chris@0 468 foreach (Views::fetchPluginNames('display', NULL, [$view->get('storage')->get('base_table')]) as $type => $label) {
Chris@0 469 if ($type == $display['display_plugin']) {
Chris@0 470 continue;
Chris@0 471 }
Chris@0 472
Chris@0 473 $build['top']['actions']['duplicate_as'][$type] = [
Chris@0 474 '#type' => 'submit',
Chris@0 475 '#value' => $this->t('Duplicate as @type', ['@type' => $label]),
Chris@0 476 '#limit_validation_errors' => [],
Chris@0 477 '#submit' => ['::submitDuplicateDisplayAsType', '::submitDelayDestination'],
Chris@0 478 '#prefix' => '<li class="duplicate">',
Chris@0 479 '#suffix' => '</li>',
Chris@0 480 ];
Chris@0 481 }
Chris@0 482 }
Chris@0 483 else {
Chris@0 484 $build['top']['actions']['undo_delete'] = [
Chris@0 485 '#type' => 'submit',
Chris@0 486 '#value' => $this->t('Undo delete of @display_title', ['@display_title' => $display_title]),
Chris@0 487 '#limit_validation_errors' => [],
Chris@0 488 '#submit' => ['::submitDisplayUndoDelete', '::submitDelayDestination'],
Chris@0 489 '#prefix' => '<li class="undo-delete">',
Chris@0 490 "#suffix" => '</li>',
Chris@0 491 ];
Chris@0 492 }
Chris@0 493 if ($is_enabled) {
Chris@0 494 $build['top']['actions']['disable'] = [
Chris@0 495 '#type' => 'submit',
Chris@0 496 '#value' => $this->t('Disable @display_title', ['@display_title' => $display_title]),
Chris@0 497 '#limit_validation_errors' => [],
Chris@0 498 '#submit' => ['::submitDisplayDisable', '::submitDelayDestination'],
Chris@0 499 '#prefix' => '<li class="disable">',
Chris@0 500 "#suffix" => '</li>',
Chris@0 501 ];
Chris@0 502 }
Chris@0 503 $build['top']['actions']['suffix']['#markup'] = '</ul>';
Chris@0 504
Chris@0 505 // The area above the three columns.
Chris@0 506 $build['top']['display_title'] = [
Chris@0 507 '#theme' => 'views_ui_display_tab_setting',
Chris@0 508 '#description' => $this->t('Display name'),
Chris@0 509 '#link' => $view->getExecutable()->displayHandlers->get($display['id'])->optionLink($display_title, 'display_title'),
Chris@0 510 ];
Chris@0 511 }
Chris@0 512
Chris@0 513 $build['columns'] = [];
Chris@0 514 $build['columns']['#theme_wrappers'] = ['container'];
Chris@0 515 $build['columns']['#attributes'] = ['id' => 'edit-display-settings-main', 'class' => ['clearfix', 'views-display-columns']];
Chris@0 516
Chris@0 517 $build['columns']['first']['#theme_wrappers'] = ['container'];
Chris@0 518 $build['columns']['first']['#attributes'] = ['class' => ['views-display-column', 'first']];
Chris@0 519
Chris@0 520 $build['columns']['second']['#theme_wrappers'] = ['container'];
Chris@0 521 $build['columns']['second']['#attributes'] = ['class' => ['views-display-column', 'second']];
Chris@0 522
Chris@0 523 $build['columns']['second']['settings'] = [];
Chris@0 524 $build['columns']['second']['header'] = [];
Chris@0 525 $build['columns']['second']['footer'] = [];
Chris@0 526 $build['columns']['second']['empty'] = [];
Chris@0 527 $build['columns']['second']['pager'] = [];
Chris@0 528
Chris@0 529 // The third column buckets are wrapped in details.
Chris@0 530 $build['columns']['third'] = [
Chris@0 531 '#type' => 'details',
Chris@0 532 '#title' => $this->t('Advanced'),
Chris@0 533 '#theme_wrappers' => ['details'],
Chris@0 534 '#attributes' => [
Chris@0 535 'class' => [
Chris@0 536 'views-display-column',
Chris@0 537 'third',
Chris@0 538 ],
Chris@0 539 ],
Chris@0 540 ];
Chris@0 541 // Collapse the details by default.
Chris@0 542 $build['columns']['third']['#open'] = \Drupal::config('views.settings')->get('ui.show.advanced_column');
Chris@0 543
Chris@0 544 // Each option (e.g. title, access, display as grid/table/list) fits into one
Chris@0 545 // of several "buckets," or boxes (Format, Fields, Sort, and so on).
Chris@0 546 $buckets = [];
Chris@0 547
Chris@0 548 // Fetch options from the display plugin, with a list of buckets they go into.
Chris@0 549 $options = [];
Chris@0 550 $view->getExecutable()->displayHandlers->get($display['id'])->optionsSummary($buckets, $options);
Chris@0 551
Chris@0 552 // Place each option into its bucket.
Chris@0 553 foreach ($options as $id => $option) {
Chris@0 554 // Each option self-identifies as belonging in a particular bucket.
Chris@0 555 $buckets[$option['category']]['build'][$id] = $this->buildOptionForm($view, $id, $option, $display);
Chris@0 556 }
Chris@0 557
Chris@0 558 // Place each bucket into the proper column.
Chris@0 559 foreach ($buckets as $id => $bucket) {
Chris@0 560 // Let buckets identify themselves as belonging in a column.
Chris@0 561 if (isset($bucket['column']) && isset($build['columns'][$bucket['column']])) {
Chris@0 562 $column = $bucket['column'];
Chris@0 563 }
Chris@0 564 // If a bucket doesn't pick one of our predefined columns to belong to, put
Chris@0 565 // it in the last one.
Chris@0 566 else {
Chris@0 567 $column = 'third';
Chris@0 568 }
Chris@0 569 if (isset($bucket['build']) && is_array($bucket['build'])) {
Chris@0 570 $build['columns'][$column][$id] = $bucket['build'];
Chris@0 571 $build['columns'][$column][$id]['#theme_wrappers'][] = 'views_ui_display_tab_bucket';
Chris@0 572 $build['columns'][$column][$id]['#title'] = !empty($bucket['title']) ? $bucket['title'] : '';
Chris@0 573 $build['columns'][$column][$id]['#name'] = $id;
Chris@0 574 }
Chris@0 575 }
Chris@0 576
Chris@0 577 $build['columns']['first']['fields'] = $this->getFormBucket($view, 'field', $display);
Chris@0 578 $build['columns']['first']['filters'] = $this->getFormBucket($view, 'filter', $display);
Chris@0 579 $build['columns']['first']['sorts'] = $this->getFormBucket($view, 'sort', $display);
Chris@0 580 $build['columns']['second']['header'] = $this->getFormBucket($view, 'header', $display);
Chris@0 581 $build['columns']['second']['footer'] = $this->getFormBucket($view, 'footer', $display);
Chris@0 582 $build['columns']['second']['empty'] = $this->getFormBucket($view, 'empty', $display);
Chris@0 583 $build['columns']['third']['arguments'] = $this->getFormBucket($view, 'argument', $display);
Chris@0 584 $build['columns']['third']['relationships'] = $this->getFormBucket($view, 'relationship', $display);
Chris@0 585
Chris@0 586 return $build;
Chris@0 587 }
Chris@0 588
Chris@0 589 /**
Chris@0 590 * Submit handler to add a restore a removed display to a view.
Chris@0 591 */
Chris@0 592 public function submitDisplayUndoDelete($form, FormStateInterface $form_state) {
Chris@0 593 $view = $this->entity;
Chris@0 594 // Create the new display
Chris@0 595 $id = $form_state->get('display_id');
Chris@0 596 $displays = $view->get('display');
Chris@0 597 $displays[$id]['deleted'] = FALSE;
Chris@0 598 $view->set('display', $displays);
Chris@0 599
Chris@0 600 // Store in cache
Chris@0 601 $view->cacheSet();
Chris@0 602
Chris@0 603 // Redirect to the top-level edit page.
Chris@0 604 $form_state->setRedirect('entity.view.edit_display_form', [
Chris@0 605 'view' => $view->id(),
Chris@0 606 'display_id' => $id,
Chris@0 607 ]);
Chris@0 608 }
Chris@0 609
Chris@0 610 /**
Chris@0 611 * Submit handler to enable a disabled display.
Chris@0 612 */
Chris@0 613 public function submitDisplayEnable($form, FormStateInterface $form_state) {
Chris@0 614 $view = $this->entity;
Chris@0 615 $id = $form_state->get('display_id');
Chris@0 616 // setOption doesn't work because this would might affect upper displays
Chris@0 617 $view->getExecutable()->displayHandlers->get($id)->setOption('enabled', TRUE);
Chris@0 618
Chris@0 619 // Store in cache
Chris@0 620 $view->cacheSet();
Chris@0 621
Chris@0 622 // Redirect to the top-level edit page.
Chris@0 623 $form_state->setRedirect('entity.view.edit_display_form', [
Chris@0 624 'view' => $view->id(),
Chris@0 625 'display_id' => $id,
Chris@0 626 ]);
Chris@0 627 }
Chris@0 628
Chris@0 629 /**
Chris@0 630 * Submit handler to disable display.
Chris@0 631 */
Chris@0 632 public function submitDisplayDisable($form, FormStateInterface $form_state) {
Chris@0 633 $view = $this->entity;
Chris@0 634 $id = $form_state->get('display_id');
Chris@0 635 $view->getExecutable()->displayHandlers->get($id)->setOption('enabled', FALSE);
Chris@0 636
Chris@0 637 // Store in cache
Chris@0 638 $view->cacheSet();
Chris@0 639
Chris@0 640 // Redirect to the top-level edit page.
Chris@0 641 $form_state->setRedirect('entity.view.edit_display_form', [
Chris@0 642 'view' => $view->id(),
Chris@0 643 'display_id' => $id,
Chris@0 644 ]);
Chris@0 645 }
Chris@0 646
Chris@0 647 /**
Chris@0 648 * Submit handler to delete a display from a view.
Chris@0 649 */
Chris@0 650 public function submitDisplayDelete($form, FormStateInterface $form_state) {
Chris@0 651 $view = $this->entity;
Chris@0 652 $display_id = $form_state->get('display_id');
Chris@0 653
Chris@0 654 // Mark the display for deletion.
Chris@0 655 $displays = $view->get('display');
Chris@0 656 $displays[$display_id]['deleted'] = TRUE;
Chris@0 657 $view->set('display', $displays);
Chris@0 658 $view->cacheSet();
Chris@0 659
Chris@0 660 // Redirect to the top-level edit page. The first remaining display will
Chris@0 661 // become the active display.
Chris@18 662 $form_state->setRedirectUrl($view->toUrl('edit-form'));
Chris@0 663 }
Chris@0 664
Chris@0 665 /**
Chris@0 666 * Regenerate the current tab for AJAX updates.
Chris@0 667 *
Chris@0 668 * @param \Drupal\views_ui\ViewUI $view
Chris@0 669 * The view to regenerate its tab.
Chris@0 670 * @param \Drupal\Core\Ajax\AjaxResponse $response
Chris@0 671 * The response object to add new commands to.
Chris@0 672 * @param string $display_id
Chris@0 673 * The display ID of the tab to regenerate.
Chris@0 674 */
Chris@0 675 public function rebuildCurrentTab(ViewUI $view, AjaxResponse $response, $display_id) {
Chris@0 676 $this->displayID = $display_id;
Chris@0 677 if (!$view->getExecutable()->setDisplay('default')) {
Chris@0 678 return;
Chris@0 679 }
Chris@0 680
Chris@0 681 // Regenerate the main display area.
Chris@0 682 $build = $this->getDisplayTab($view);
Chris@0 683 $response->addCommand(new HtmlCommand('#views-tab-' . $display_id, $build));
Chris@0 684
Chris@0 685 // Regenerate the top area so changes to display names and order will appear.
Chris@0 686 $build = $this->renderDisplayTop($view);
Chris@0 687 $response->addCommand(new ReplaceCommand('#views-display-top', $build));
Chris@0 688 }
Chris@0 689
Chris@0 690 /**
Chris@0 691 * Render the top of the display so it can be updated during ajax operations.
Chris@0 692 */
Chris@0 693 public function renderDisplayTop(ViewUI $view) {
Chris@0 694 $display_id = $this->displayID;
Chris@0 695 $element['#theme_wrappers'][] = 'views_ui_container';
Chris@0 696 $element['#attributes']['class'] = ['views-display-top', 'clearfix'];
Chris@0 697 $element['#attributes']['id'] = ['views-display-top'];
Chris@0 698
Chris@0 699 // Extra actions for the display
Chris@0 700 $element['extra_actions'] = [
Chris@0 701 '#type' => 'dropbutton',
Chris@0 702 '#attributes' => [
Chris@0 703 'id' => 'views-display-extra-actions',
Chris@0 704 ],
Chris@0 705 '#links' => [
Chris@0 706 'edit-details' => [
Chris@0 707 'title' => $this->t('Edit view name/description'),
Chris@0 708 'url' => Url::fromRoute('views_ui.form_edit_details', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display_id]),
Chris@0 709 'attributes' => ['class' => ['views-ajax-link']],
Chris@0 710 ],
Chris@0 711 'analyze' => [
Chris@0 712 'title' => $this->t('Analyze view'),
Chris@0 713 'url' => Url::fromRoute('views_ui.form_analyze', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display_id]),
Chris@0 714 'attributes' => ['class' => ['views-ajax-link']],
Chris@0 715 ],
Chris@0 716 'duplicate' => [
Chris@0 717 'title' => $this->t('Duplicate view'),
Chris@18 718 'url' => $view->toUrl('duplicate-form'),
Chris@0 719 ],
Chris@0 720 'reorder' => [
Chris@0 721 'title' => $this->t('Reorder displays'),
Chris@0 722 'url' => Url::fromRoute('views_ui.form_reorder_displays', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display_id]),
Chris@0 723 'attributes' => ['class' => ['views-ajax-link']],
Chris@0 724 ],
Chris@0 725 ],
Chris@0 726 ];
Chris@0 727
Chris@0 728 if ($view->access('delete')) {
Chris@0 729 $element['extra_actions']['#links']['delete'] = [
Chris@0 730 'title' => $this->t('Delete view'),
Chris@18 731 'url' => $view->toUrl('delete-form'),
Chris@0 732 ];
Chris@0 733 }
Chris@0 734
Chris@0 735 // Let other modules add additional links here.
Chris@0 736 \Drupal::moduleHandler()->alter('views_ui_display_top_links', $element['extra_actions']['#links'], $view, $display_id);
Chris@0 737
Chris@0 738 if (isset($view->type) && $view->type != $this->t('Default')) {
Chris@0 739 if ($view->type == $this->t('Overridden')) {
Chris@0 740 $element['extra_actions']['#links']['revert'] = [
Chris@0 741 'title' => $this->t('Revert view'),
Chris@0 742 'href' => "admin/structure/views/view/{$view->id()}/revert",
Chris@18 743 'query' => ['destination' => $view->toUrl('edit-form')->toString()],
Chris@0 744 ];
Chris@0 745 }
Chris@0 746 else {
Chris@0 747 $element['extra_actions']['#links']['delete'] = [
Chris@0 748 'title' => $this->t('Delete view'),
Chris@18 749 'url' => $view->toUrl('delete-form'),
Chris@0 750 ];
Chris@0 751 }
Chris@0 752 }
Chris@0 753
Chris@0 754 // Determine the displays available for editing.
Chris@0 755 if ($tabs = $this->getDisplayTabs($view)) {
Chris@0 756 if ($display_id) {
Chris@0 757 $tabs[$display_id]['#active'] = TRUE;
Chris@0 758 }
Chris@0 759 $tabs['#prefix'] = '<h2 class="visually-hidden">' . $this->t('Secondary tabs') . '</h2><ul id = "views-display-menu-tabs" class="tabs secondary">';
Chris@0 760 $tabs['#suffix'] = '</ul>';
Chris@0 761 $element['tabs'] = $tabs;
Chris@0 762 }
Chris@0 763
Chris@0 764 // Buttons for adding a new display.
Chris@0 765 foreach (Views::fetchPluginNames('display', NULL, [$view->get('base_table')]) as $type => $label) {
Chris@0 766 $element['add_display'][$type] = [
Chris@0 767 '#type' => 'submit',
Chris@0 768 '#value' => $this->t('Add @display', ['@display' => $label]),
Chris@0 769 '#limit_validation_errors' => [],
Chris@0 770 '#submit' => ['::submitDisplayAdd', '::submitDelayDestination'],
Chris@0 771 '#attributes' => ['class' => ['add-display']],
Chris@0 772 // Allow JavaScript to remove the 'Add ' prefix from the button label when
Chris@0 773 // placing the button in a "Add" dropdown menu.
Chris@0 774 '#process' => array_merge(['views_ui_form_button_was_clicked'], $this->elementInfo->getInfoProperty('submit', '#process', [])),
Chris@0 775 '#values' => [$this->t('Add @display', ['@display' => $label]), $label],
Chris@0 776 ];
Chris@0 777 }
Chris@0 778
Chris@0 779 return $element;
Chris@0 780 }
Chris@0 781
Chris@0 782 /**
Chris@0 783 * Submit handler for form buttons that do not complete a form workflow.
Chris@0 784 *
Chris@0 785 * The Edit View form is a multistep form workflow, but with state managed by
Chris@0 786 * the SharedTempStore rather than $form_state->setRebuild(). Without this
Chris@0 787 * submit handler, buttons that add or remove displays would redirect to the
Chris@0 788 * destination parameter (e.g., when the Edit View form is linked to from a
Chris@0 789 * contextual link). This handler can be added to buttons whose form submission
Chris@0 790 * should not yet redirect to the destination.
Chris@0 791 */
Chris@0 792 public function submitDelayDestination($form, FormStateInterface $form_state) {
Chris@0 793 $request = $this->requestStack->getCurrentRequest();
Chris@0 794 $destination = $request->query->get('destination');
Chris@0 795
Chris@0 796 $redirect = $form_state->getRedirect();
Chris@0 797 // If there is a destination, and redirects are not explicitly disabled, add
Chris@0 798 // the destination as a query string to the redirect and suppress it for the
Chris@0 799 // current request.
Chris@0 800 if (isset($destination) && $redirect !== FALSE) {
Chris@0 801 // Create a valid redirect if one does not exist already.
Chris@0 802 if (!($redirect instanceof Url)) {
Chris@0 803 $redirect = Url::createFromRequest($request);
Chris@0 804 }
Chris@0 805
Chris@0 806 // Add the current destination to the redirect unless one exists already.
Chris@0 807 $options = $redirect->getOptions();
Chris@0 808 if (!isset($options['query']['destination'])) {
Chris@0 809 $options['query']['destination'] = $destination;
Chris@0 810 $redirect->setOptions($options);
Chris@0 811 }
Chris@0 812
Chris@0 813 $form_state->setRedirectUrl($redirect);
Chris@0 814 $request->query->remove('destination');
Chris@0 815 }
Chris@0 816 }
Chris@0 817
Chris@0 818 /**
Chris@0 819 * Submit handler to duplicate a display for a view.
Chris@0 820 */
Chris@0 821 public function submitDisplayDuplicate($form, FormStateInterface $form_state) {
Chris@0 822 $view = $this->entity;
Chris@0 823 $display_id = $this->displayID;
Chris@0 824
Chris@0 825 // Create the new display.
Chris@0 826 $displays = $view->get('display');
Chris@0 827 $display = $view->getExecutable()->newDisplay($displays[$display_id]['display_plugin']);
Chris@0 828 $new_display_id = $display->display['id'];
Chris@0 829 $displays[$new_display_id] = $displays[$display_id];
Chris@0 830 $displays[$new_display_id]['id'] = $new_display_id;
Chris@0 831 $view->set('display', $displays);
Chris@0 832
Chris@0 833 // By setting the current display the changed marker will appear on the new
Chris@0 834 // display.
Chris@0 835 $view->getExecutable()->current_display = $new_display_id;
Chris@0 836 $view->cacheSet();
Chris@0 837
Chris@0 838 // Redirect to the new display's edit page.
Chris@0 839 $form_state->setRedirect('entity.view.edit_display_form', [
Chris@0 840 'view' => $view->id(),
Chris@0 841 'display_id' => $new_display_id,
Chris@0 842 ]);
Chris@0 843 }
Chris@0 844
Chris@0 845 /**
Chris@0 846 * Submit handler to add a display to a view.
Chris@0 847 */
Chris@0 848 public function submitDisplayAdd($form, FormStateInterface $form_state) {
Chris@0 849 $view = $this->entity;
Chris@0 850 // Create the new display.
Chris@0 851 $parents = $form_state->getTriggeringElement()['#parents'];
Chris@0 852 $display_type = array_pop($parents);
Chris@0 853 $display = $view->getExecutable()->newDisplay($display_type);
Chris@0 854 $display_id = $display->display['id'];
Chris@0 855 // A new display got added so the asterisks symbol should appear on the new
Chris@0 856 // display.
Chris@0 857 $view->getExecutable()->current_display = $display_id;
Chris@0 858 $view->cacheSet();
Chris@0 859
Chris@0 860 // Redirect to the new display's edit page.
Chris@0 861 $form_state->setRedirect('entity.view.edit_display_form', [
Chris@0 862 'view' => $view->id(),
Chris@0 863 'display_id' => $display_id,
Chris@0 864 ]);
Chris@0 865 }
Chris@0 866
Chris@0 867 /**
Chris@0 868 * Submit handler to Duplicate a display as another display type.
Chris@0 869 */
Chris@0 870 public function submitDuplicateDisplayAsType($form, FormStateInterface $form_state) {
Chris@0 871 /** @var \Drupal\views\ViewEntityInterface $view */
Chris@0 872 $view = $this->entity;
Chris@0 873 $display_id = $this->displayID;
Chris@0 874
Chris@0 875 // Create the new display.
Chris@0 876 $parents = $form_state->getTriggeringElement()['#parents'];
Chris@0 877 $display_type = array_pop($parents);
Chris@0 878
Chris@0 879 $new_display_id = $view->duplicateDisplayAsType($display_id, $display_type);
Chris@0 880
Chris@0 881 // By setting the current display the changed marker will appear on the new
Chris@0 882 // display.
Chris@0 883 $view->getExecutable()->current_display = $new_display_id;
Chris@0 884 $view->cacheSet();
Chris@0 885
Chris@0 886 // Redirect to the new display's edit page.
Chris@0 887 $form_state->setRedirect('entity.view.edit_display_form', [
Chris@0 888 'view' => $view->id(),
Chris@0 889 'display_id' => $new_display_id,
Chris@0 890 ]);
Chris@0 891 }
Chris@0 892
Chris@0 893 /**
Chris@0 894 * Build a renderable array representing one option on the edit form.
Chris@0 895 *
Chris@0 896 * This function might be more logical as a method on an object, if a suitable
Chris@0 897 * object emerges out of refactoring.
Chris@0 898 */
Chris@0 899 public function buildOptionForm(ViewUI $view, $id, $option, $display) {
Chris@0 900 $option_build = [];
Chris@0 901 $option_build['#theme'] = 'views_ui_display_tab_setting';
Chris@0 902
Chris@0 903 $option_build['#description'] = $option['title'];
Chris@0 904
Chris@0 905 $option_build['#link'] = $view->getExecutable()->displayHandlers->get($display['id'])->optionLink($option['value'], $id, '', empty($option['desc']) ? '' : $option['desc']);
Chris@0 906
Chris@0 907 $option_build['#links'] = [];
Chris@0 908 if (!empty($option['links']) && is_array($option['links'])) {
Chris@0 909 foreach ($option['links'] as $link_id => $link_value) {
Chris@0 910 $option_build['#settings_links'][] = $view->getExecutable()->displayHandlers->get($display['id'])->optionLink($option['setting'], $link_id, 'views-button-configure', $link_value);
Chris@0 911 }
Chris@0 912 }
Chris@0 913
Chris@0 914 if (!empty($view->getExecutable()->displayHandlers->get($display['id'])->options['defaults'][$id])) {
Chris@0 915 $display_id = 'default';
Chris@0 916 $option_build['#defaulted'] = TRUE;
Chris@0 917 }
Chris@0 918 else {
Chris@0 919 $display_id = $display['id'];
Chris@0 920 if (!$view->getExecutable()->displayHandlers->get($display['id'])->isDefaultDisplay()) {
Chris@0 921 if ($view->getExecutable()->displayHandlers->get($display['id'])->defaultableSections($id)) {
Chris@0 922 $option_build['#overridden'] = TRUE;
Chris@0 923 }
Chris@0 924 }
Chris@0 925 }
Chris@0 926 $option_build['#attributes']['class'][] = Html::cleanCssIdentifier($display_id . '-' . $id);
Chris@0 927 return $option_build;
Chris@0 928 }
Chris@0 929
Chris@0 930 /**
Chris@0 931 * Add information about a section to a display.
Chris@0 932 */
Chris@0 933 public function getFormBucket(ViewUI $view, $type, $display) {
Chris@0 934 $executable = $view->getExecutable();
Chris@0 935 $executable->setDisplay($display['id']);
Chris@0 936 $executable->initStyle();
Chris@0 937
Chris@0 938 $types = $executable->getHandlerTypes();
Chris@0 939
Chris@0 940 $build = [
Chris@0 941 '#theme_wrappers' => ['views_ui_display_tab_bucket'],
Chris@0 942 ];
Chris@0 943
Chris@0 944 $build['#overridden'] = FALSE;
Chris@0 945 $build['#defaulted'] = FALSE;
Chris@0 946
Chris@0 947 $build['#name'] = $type;
Chris@0 948 $build['#title'] = $types[$type]['title'];
Chris@0 949
Chris@0 950 $rearrange_url = Url::fromRoute('views_ui.form_rearrange', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display['id'], 'type' => $type]);
Chris@0 951 $class = 'icon compact rearrange';
Chris@0 952
Chris@0 953 // Different types now have different rearrange forms, so we use this switch
Chris@0 954 // to get the right one.
Chris@0 955 switch ($type) {
Chris@0 956 case 'filter':
Chris@0 957 // The rearrange form for filters contains the and/or UI, so override
Chris@0 958 // the used path.
Chris@0 959 $rearrange_url = Url::fromRoute('views_ui.form_rearrange_filter', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display['id']]);
Chris@0 960 // TODO: Add another class to have another symbol for filter rearrange.
Chris@0 961 $class = 'icon compact rearrange';
Chris@0 962 break;
Chris@0 963 case 'field':
Chris@0 964 // Fetch the style plugin info so we know whether to list fields or not.
Chris@0 965 $style_plugin = $executable->style_plugin;
Chris@0 966 $uses_fields = $style_plugin && $style_plugin->usesFields();
Chris@0 967 if (!$uses_fields) {
Chris@0 968 $build['fields'][] = [
Chris@0 969 '#markup' => $this->t('The selected style or row format does not use fields.'),
Chris@0 970 '#theme_wrappers' => ['views_ui_container'],
Chris@0 971 '#attributes' => ['class' => ['views-display-setting']],
Chris@0 972 ];
Chris@0 973 return $build;
Chris@0 974 }
Chris@0 975 break;
Chris@0 976 case 'header':
Chris@0 977 case 'footer':
Chris@0 978 case 'empty':
Chris@0 979 if (!$executable->display_handler->usesAreas()) {
Chris@0 980 $build[$type][] = [
Chris@0 981 '#markup' => $this->t('The selected display type does not use @type plugins', ['@type' => $type]),
Chris@0 982 '#theme_wrappers' => ['views_ui_container'],
Chris@0 983 '#attributes' => ['class' => ['views-display-setting']],
Chris@0 984 ];
Chris@0 985 return $build;
Chris@0 986 }
Chris@0 987 break;
Chris@0 988 }
Chris@0 989
Chris@0 990 // Create an array of actions to pass to links template.
Chris@0 991 $actions = [];
Chris@0 992 $count_handlers = count($executable->display_handler->getHandlers($type));
Chris@0 993
Chris@0 994 // Create the add text variable for the add action.
Chris@0 995 $add_text = $this->t('Add <span class="visually-hidden">@type</span>', ['@type' => $types[$type]['ltitle']]);
Chris@0 996
Chris@0 997 $actions['add'] = [
Chris@0 998 'title' => $add_text,
Chris@0 999 'url' => Url::fromRoute('views_ui.form_add_handler', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display['id'], 'type' => $type]),
Chris@0 1000 'attributes' => ['class' => ['icon compact add', 'views-ajax-link'], 'id' => 'views-add-' . $type],
Chris@0 1001 ];
Chris@0 1002 if ($count_handlers > 0) {
Chris@0 1003 // Create the rearrange text variable for the rearrange action.
Chris@0 1004 $rearrange_text = $type == 'filter' ? $this->t('And/Or Rearrange <span class="visually-hidden">filter criteria</span>') : $this->t('Rearrange <span class="visually-hidden">@type</span>', ['@type' => $types[$type]['ltitle']]);
Chris@0 1005
Chris@0 1006 $actions['rearrange'] = [
Chris@0 1007 'title' => $rearrange_text,
Chris@0 1008 'url' => $rearrange_url,
Chris@0 1009 'attributes' => ['class' => [$class, 'views-ajax-link'], 'id' => 'views-rearrange-' . $type],
Chris@0 1010 ];
Chris@0 1011 }
Chris@0 1012
Chris@0 1013 // Render the array of links
Chris@0 1014 $build['#actions'] = [
Chris@0 1015 '#type' => 'dropbutton',
Chris@0 1016 '#links' => $actions,
Chris@0 1017 '#attributes' => [
Chris@0 1018 'class' => ['views-ui-settings-bucket-operations'],
Chris@0 1019 ],
Chris@0 1020 ];
Chris@0 1021
Chris@0 1022 if (!$executable->display_handler->isDefaultDisplay()) {
Chris@0 1023 if (!$executable->display_handler->isDefaulted($types[$type]['plural'])) {
Chris@0 1024 $build['#overridden'] = TRUE;
Chris@0 1025 }
Chris@0 1026 else {
Chris@0 1027 $build['#defaulted'] = TRUE;
Chris@0 1028 }
Chris@0 1029 }
Chris@0 1030
Chris@0 1031 static $relationships = NULL;
Chris@0 1032 if (!isset($relationships)) {
Chris@0 1033 // Get relationship labels.
Chris@0 1034 $relationships = [];
Chris@0 1035 foreach ($executable->display_handler->getHandlers('relationship') as $id => $handler) {
Chris@0 1036 $relationships[$id] = $handler->adminLabel();
Chris@0 1037 }
Chris@0 1038 }
Chris@0 1039
Chris@0 1040 // Filters can now be grouped so we do a little bit extra:
Chris@0 1041 $groups = [];
Chris@0 1042 $grouping = FALSE;
Chris@0 1043 if ($type == 'filter') {
Chris@0 1044 $group_info = $executable->display_handler->getOption('filter_groups');
Chris@0 1045 // If there is only one group but it is using the "OR" filter, we still
Chris@0 1046 // treat it as a group for display purposes, since we want to display the
Chris@0 1047 // "OR" label next to items within the group.
Chris@0 1048 if (!empty($group_info['groups']) && (count($group_info['groups']) > 1 || current($group_info['groups']) == 'OR')) {
Chris@0 1049 $grouping = TRUE;
Chris@0 1050 $groups = [0 => []];
Chris@0 1051 }
Chris@0 1052 }
Chris@0 1053
Chris@0 1054 $build['fields'] = [];
Chris@0 1055
Chris@0 1056 foreach ($executable->display_handler->getOption($types[$type]['plural']) as $id => $field) {
Chris@0 1057 // Build the option link for this handler ("Node: ID = article").
Chris@0 1058 $build['fields'][$id] = [];
Chris@0 1059 $build['fields'][$id]['#theme'] = 'views_ui_display_tab_setting';
Chris@0 1060
Chris@0 1061 $handler = $executable->display_handler->getHandler($type, $id);
Chris@0 1062 if ($handler->broken()) {
Chris@0 1063 $build['fields'][$id]['#class'][] = 'broken';
Chris@0 1064 $field_name = $handler->adminLabel();
Chris@0 1065 $build['fields'][$id]['#link'] = $this->l($field_name, new Url('views_ui.form_handler', [
Chris@0 1066 'js' => 'nojs',
Chris@0 1067 'view' => $view->id(),
Chris@0 1068 'display_id' => $display['id'],
Chris@0 1069 'type' => $type,
Chris@0 1070 'id' => $id,
Chris@0 1071 ], ['attributes' => ['class' => ['views-ajax-link']]]));
Chris@0 1072 continue;
Chris@0 1073 }
Chris@0 1074
Chris@0 1075 $field_name = $handler->adminLabel(TRUE);
Chris@0 1076 if (!empty($field['relationship']) && !empty($relationships[$field['relationship']])) {
Chris@0 1077 $field_name = '(' . $relationships[$field['relationship']] . ') ' . $field_name;
Chris@0 1078 }
Chris@0 1079
Chris@0 1080 $description = $handler->adminSummary();
Chris@0 1081 $link_text = $field_name . (empty($description) ? '' : " ($description)");
Chris@0 1082 $link_attributes = ['class' => ['views-ajax-link']];
Chris@0 1083 if (!empty($field['exclude'])) {
Chris@0 1084 $link_attributes['class'][] = 'views-field-excluded';
Chris@0 1085 // Add a [hidden] marker, if the field is excluded.
Chris@0 1086 $link_text .= ' [' . $this->t('hidden') . ']';
Chris@0 1087 }
Chris@0 1088 $build['fields'][$id]['#link'] = $this->l($link_text, new Url('views_ui.form_handler', [
Chris@0 1089 'js' => 'nojs',
Chris@0 1090 'view' => $view->id(),
Chris@0 1091 'display_id' => $display['id'],
Chris@0 1092 'type' => $type,
Chris@0 1093 'id' => $id,
Chris@0 1094 ], ['attributes' => $link_attributes]));
Chris@0 1095 $build['fields'][$id]['#class'][] = Html::cleanCssIdentifier($display['id'] . '-' . $type . '-' . $id);
Chris@0 1096
Chris@0 1097 if ($executable->display_handler->useGroupBy() && $handler->usesGroupBy()) {
Chris@17 1098 $build['fields'][$id]['#settings_links'][] = $this->l(new FormattableMarkup('<span class="label">@text</span>', ['@text' => $this->t('Aggregation settings')]), new Url('views_ui.form_handler_group', [
Chris@0 1099 'js' => 'nojs',
Chris@0 1100 'view' => $view->id(),
Chris@0 1101 'display_id' => $display['id'],
Chris@0 1102 'type' => $type,
Chris@0 1103 'id' => $id,
Chris@0 1104 ], ['attributes' => ['class' => ['views-button-configure', 'views-ajax-link'], 'title' => $this->t('Aggregation settings')]]));
Chris@0 1105 }
Chris@0 1106
Chris@0 1107 if ($handler->hasExtraOptions()) {
Chris@17 1108 $build['fields'][$id]['#settings_links'][] = $this->l(new FormattableMarkup('<span class="label">@text</span>', ['@text' => $this->t('Settings')]), new Url('views_ui.form_handler_extra', [
Chris@0 1109 'js' => 'nojs',
Chris@0 1110 'view' => $view->id(),
Chris@0 1111 'display_id' => $display['id'],
Chris@0 1112 'type' => $type,
Chris@0 1113 'id' => $id,
Chris@0 1114 ], ['attributes' => ['class' => ['views-button-configure', 'views-ajax-link'], 'title' => $this->t('Settings')]]));
Chris@0 1115 }
Chris@0 1116
Chris@0 1117 if ($grouping) {
Chris@0 1118 $gid = $handler->options['group'];
Chris@0 1119
Chris@0 1120 // Show in default group if the group does not exist.
Chris@0 1121 if (empty($group_info['groups'][$gid])) {
Chris@0 1122 $gid = 0;
Chris@0 1123 }
Chris@0 1124 $groups[$gid][] = $id;
Chris@0 1125 }
Chris@0 1126 }
Chris@0 1127
Chris@0 1128 // If using grouping, re-order fields so that they show up properly in the list.
Chris@0 1129 if ($type == 'filter' && $grouping) {
Chris@0 1130 $store = $build['fields'];
Chris@0 1131 $build['fields'] = [];
Chris@0 1132 foreach ($groups as $gid => $contents) {
Chris@0 1133 // Display an operator between each group.
Chris@0 1134 if (!empty($build['fields'])) {
Chris@0 1135 $build['fields'][] = [
Chris@0 1136 '#theme' => 'views_ui_display_tab_setting',
Chris@0 1137 '#class' => ['views-group-text'],
Chris@0 1138 '#link' => ($group_info['operator'] == 'OR' ? $this->t('OR') : $this->t('AND')),
Chris@0 1139 ];
Chris@0 1140 }
Chris@0 1141 // Display an operator between each pair of filters within the group.
Chris@0 1142 $keys = array_keys($contents);
Chris@0 1143 $last = end($keys);
Chris@0 1144 foreach ($contents as $key => $pid) {
Chris@0 1145 if ($key != $last) {
Chris@0 1146 $operator = $group_info['groups'][$gid] == 'OR' ? $this->t('OR') : $this->t('AND');
Chris@17 1147 $store[$pid]['#link'] = new FormattableMarkup('@link <span>@operator</span>', ['@link' => $store[$pid]['#link'], '@operator' => $operator]);
Chris@0 1148 }
Chris@0 1149 $build['fields'][$pid] = $store[$pid];
Chris@0 1150 }
Chris@0 1151 }
Chris@0 1152 }
Chris@0 1153
Chris@0 1154 return $build;
Chris@0 1155 }
Chris@0 1156
Chris@0 1157 }