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