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 }
|