views_ui.module 15.5 KB
Newer Older
merlinofchaos's avatar
merlinofchaos committed
1
2
3
4
5
6
7
<?php

/**
 * @file
 * Provide structure for the administrative interface to Views.
 */

8
use Drupal\views\ViewExecutable;
9
use Drupal\views\ViewStorageInterface;
10
use Drupal\views_ui\ViewUI;
11
use Drupal\views\Analyzer;
12
13
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;
14

merlinofchaos's avatar
merlinofchaos committed
15
16
17
18
19
20
21
/**
 * Implements hook_menu().
 */
function views_ui_menu() {
  $items = array();

  // Top-level Views module pages (not tied to a particular View).
22
23
24
  $items['admin/structure/views'] = array(
    'title' => 'Views',
    'description' => 'Manage customized lists of content.',
25
    'route_name' => 'views_ui.list',
26
  );
27
28
29
30

  $items['admin/structure/views/list'] = array(
    'title' => 'List',
    'type' => MENU_DEFAULT_LOCAL_TASK,
31
32
33
34
35
36
  );

  $items['admin/structure/views/add'] = array(
    'title' => 'Add new view',
    'route_name' => 'views_ui.add',
    'type' => MENU_LOCAL_ACTION,
37
  );
38

merlinofchaos's avatar
merlinofchaos committed
39
40
  $items['admin/structure/views/settings'] = array(
    'title' => 'Settings',
41
    'route_name' => 'views_ui.settings.basic',
merlinofchaos's avatar
merlinofchaos committed
42
    'type' => MENU_LOCAL_TASK,
43
  );
merlinofchaos's avatar
merlinofchaos committed
44
45
46
  $items['admin/structure/views/settings/basic'] = array(
    'title' => 'Basic',
    'type' => MENU_DEFAULT_LOCAL_TASK,
47
  );
merlinofchaos's avatar
merlinofchaos committed
48
49
  $items['admin/structure/views/settings/advanced'] = array(
    'title' => 'Advanced',
50
    'route_name' => 'views_ui.settings.advanced',
merlinofchaos's avatar
merlinofchaos committed
51
    'type' => MENU_LOCAL_TASK,
52
  );
merlinofchaos's avatar
merlinofchaos committed
53
54
55

  // The primary Edit View page. Secondary tabs for each Display are added in
  // views_ui_menu_local_tasks_alter().
56
  $items['admin/structure/views/view/%'] = array(
57
    'route_name' => 'views_ui.edit',
58
59
  );
  $items['admin/structure/views/view/%/edit'] = array(
merlinofchaos's avatar
merlinofchaos committed
60
61
62
    'title' => 'Edit view',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
63
64
  );
  $items['admin/structure/views/view/%/preview/%'] = array(
65
    'route_name' => 'views_ui.preview',
merlinofchaos's avatar
merlinofchaos committed
66
67
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
    'type' => MENU_VISIBLE_IN_BREADCRUMB,
68
  );
merlinofchaos's avatar
merlinofchaos committed
69
70
71

  // Additional pages for acting on a View.

72
  $items['admin/structure/views/view/%/break-lock'] = array(
merlinofchaos's avatar
merlinofchaos committed
73
    'title' => 'Break lock',
74
    'route_name' => 'views_ui.breakLock',
merlinofchaos's avatar
merlinofchaos committed
75
    'type' => MENU_VISIBLE_IN_BREADCRUMB,
76
  );
merlinofchaos's avatar
merlinofchaos committed
77
78

  // NoJS/AJAX callbacks that can use the default Views AJAX form system.
79
80
81
82
  $items['admin/structure/views/nojs/%/%'] = array(
    'type' => MENU_VISIBLE_IN_BREADCRUMB,
  );

merlinofchaos's avatar
merlinofchaos committed
83
  // A page in the Reports section to show usage of fields in all views
84
85
  $items['admin/reports/fields/views-fields'] = array(
    'title' => 'Used in views',
merlinofchaos's avatar
merlinofchaos committed
86
    'description' => 'Overview of fields used in all views.',
87
    'route_name' => 'views_ui.reports.fields',
88
    'type' => MENU_LOCAL_TASK,
89
  );
merlinofchaos's avatar
merlinofchaos committed
90

91
92
93
94
  // A page in the Reports section to show usage of plugins in all views.
  $items['admin/reports/views-plugins'] = array(
    'title' => 'Views plugins',
    'description' => 'Overview of plugins used in all views.',
95
    'route_name' => 'views_ui.reports.plugins',
96
  );
97

merlinofchaos's avatar
merlinofchaos committed
98
99
100
101
102
103
104
105
106
107
108
109
  return $items;
}

/**
 * Implements hook_theme().
 */
function views_ui_theme() {
  return array(
    // edit a view
    'views_ui_display_tab_setting' => array(
      'variables' => array('description' => '', 'link' => '', 'settings_links' => array(), 'overridden' => FALSE, 'defaulted' => FALSE, 'description_separator' => TRUE, 'class' => array()),
      'template' => 'views-ui-display-tab-setting',
110
      'file' => 'views_ui.theme.inc',
merlinofchaos's avatar
merlinofchaos committed
111
112
113
114
    ),
    'views_ui_display_tab_bucket' => array(
      'render element' => 'element',
      'template' => 'views-ui-display-tab-bucket',
115
      'file' => 'views_ui.theme.inc',
merlinofchaos's avatar
merlinofchaos committed
116
117
118
    ),
    'views_ui_rearrange_filter_form' => array(
      'render element' => 'form',
119
      'file' => 'views_ui.theme.inc',
merlinofchaos's avatar
merlinofchaos committed
120
121
122
    ),
    'views_ui_expose_filter_form' => array(
      'render element' => 'form',
123
      'file' => 'views_ui.theme.inc',
merlinofchaos's avatar
merlinofchaos committed
124
125
126
127
128
    ),

    // list views
    'views_ui_view_info' => array(
      'variables' => array('view' => NULL, 'base' => NULL),
129
      'file' => 'views_ui.theme.inc',
merlinofchaos's avatar
merlinofchaos committed
130
131
    ),

132
133
134
    // Group of filters.
    'views_ui_build_group_filter_form' => array(
      'render element' => 'form',
135
      'file' => 'views_ui.theme.inc',
136
137
    ),

138
    // Reordering displays.
merlinofchaos's avatar
merlinofchaos committed
139
140
    'views_ui_reorder_displays_form' => array(
      'render element' => 'form',
141
      'file' => 'views_ui.theme.inc',
merlinofchaos's avatar
merlinofchaos committed
142
143
144
145
146
    ),

    // On behalf of a plugin
    'views_ui_style_plugin_table' => array(
      'render element' => 'form',
147
      'file' => 'views_ui.theme.inc',
merlinofchaos's avatar
merlinofchaos committed
148
149
150
151
152
    ),

    // When previewing a view.
    'views_ui_view_preview_section' => array(
      'variables' => array('view' => NULL, 'section' => NULL, 'content' => NULL, 'links' => ''),
153
      'file' => 'views_ui.theme.inc',
merlinofchaos's avatar
merlinofchaos committed
154
155
156
157
    ),

    // Generic container wrapper, to use instead of theme_container when an id
    // is not desired.
158
    'views_ui_container' => array(
merlinofchaos's avatar
merlinofchaos committed
159
      'render element' => 'element',
160
      'file' => 'views_ui.theme.inc',
merlinofchaos's avatar
merlinofchaos committed
161
162
163
164
    ),
  );
}

165
166
167
168
169
170
171
172
173
174
175
176
177
/**
 * Implements hook_permission().
 */
function views_ui_permission() {
  return array(
    'administer views' => array(
      'title' => t('Administer views'),
      'description' => t('Access the views administration pages.'),
      'restrict access' => TRUE,
    ),
  );
}

178
179
180
181
182
183
/**
 * Implements hook_library_info().
 */
function views_ui_library_info() {
  $libraries = array();

184
185
  $path = drupal_get_path('module', 'views_ui') . '/js/';

186
187
188
189
  $libraries['views_ui.admin'] = array(
    'title' => 'Views UI ADMIN',
    'version' => VERSION,
    'js' => array(
190
191
      $path . 'ajax.js' => array('group' => JS_DEFAULT),
      $path . 'views-admin.js' => array('group' => JS_DEFAULT),
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
    ),
    'dependencies' => array(
      array('system', 'jquery'),
      array('system', 'drupal'),
      array('system', 'drupalSettings'),
      array('system', 'jquery.once'),
      array('system', 'jquery.form'),
      array('system', 'drupal.ajax'),
      array('views', 'views.ajax'),
    ),
  );

  return $libraries;
}

merlinofchaos's avatar
merlinofchaos committed
207
208
209
210
/**
 * Specialized cache function to add a flag to our view, include an appropriate
 * include, and cache more easily.
 */
211
function views_ui_cache_set(ViewUI $view) {
212
  if (isset($view->locked) && is_object($view->locked) && $view->locked->owner != $GLOBALS['user']->uid) {
merlinofchaos's avatar
merlinofchaos committed
213
214
215
    drupal_set_message(t('Changes cannot be made to a locked view.'), 'error');
    return;
  }
216

merlinofchaos's avatar
merlinofchaos committed
217
218
  $view->changed = TRUE; // let any future object know that this view has changed.

219
220
  $executable = $view->get('executable');
  if (isset($executable->current_display)) {
merlinofchaos's avatar
merlinofchaos committed
221
    // Add the knowledge of the changed display, too.
222
223
    $view->changed_display[$executable->current_display] = TRUE;
    unset($executable->current_display);
merlinofchaos's avatar
merlinofchaos committed
224
225
226
  }

  // Unset handlers; we don't want to write these into the cache
227
228
229
  unset($executable->display_handler);
  unset($executable->default_display);
  $executable->query = NULL;
230
  unset($executable->displayHandlers);
231
  drupal_container()->get('user.tempstore')->get('views')->set($view->id(), $view);
merlinofchaos's avatar
merlinofchaos committed
232
233
234
235
236
237
238
}

/**
 * Theme preprocess for views-view.tpl.php.
 */
function views_ui_preprocess_views_view(&$vars) {
  $view = $vars['view'];
239
  if (!empty($view->live_preview) && Drupal::moduleHandler()->moduleExists('contextual')) {
merlinofchaos's avatar
merlinofchaos committed
240
241
242
243
244
245
246
247
    $view->hide_admin_links = TRUE;
    foreach (array('title', 'header', 'exposed', 'rows', 'pager', 'more', 'footer', 'empty', 'attachment_after', 'attachment_before') as $section) {
      if (!empty($vars[$section])) {
        $vars[$section] = array(
          '#theme' => 'views_ui_view_preview_section',
          '#view' => $view,
          '#section' => $section,
          '#content' => is_array($vars[$section]) ? drupal_render($vars[$section]) : $vars[$section],
248
          '#theme_wrappers' => array('views_ui_container'),
249
          '#attributes' => array('class' => 'contextual-region'),
merlinofchaos's avatar
merlinofchaos committed
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
        );
        $vars[$section] = drupal_render($vars[$section]);
      }
    }
  }
}

/**
 * Returns contextual links for each handler of a certain section.
 *
 * @TODO
 *   Bring in relationships
 *   Refactor this function to use much stuff of views_ui_edit_form_get_bucket.
 *
 * @param $title
 *   Add a bolded title of this section.
 */
267
268
269
function views_ui_view_preview_section_handler_links(ViewExecutable $view, $type, $title = FALSE) {
  $display = $view->display_handler->display;
  $handlers = $view->display_handler->getHandlers($type);
merlinofchaos's avatar
merlinofchaos committed
270
271
  $links = array();

272
  $types = ViewExecutable::viewsHandlerTypes();
merlinofchaos's avatar
merlinofchaos committed
273
274
275
276
277
278
279
  if ($title) {
    $links[$type . '-title'] = array(
      'title' => $types[$type]['title'],
    );
  }

  foreach ($handlers as $id => $handler) {
280
    $field_name = $handler->adminLabel(TRUE);
merlinofchaos's avatar
merlinofchaos committed
281
282
    $links[$type . '-edit-' . $id] = array(
      'title' => t('Edit @section', array('@section' => $field_name)),
283
      'href' => "admin/structure/views/nojs/config-item/{$view->storage->id()}/{$display['id']}/$type/$id",
merlinofchaos's avatar
merlinofchaos committed
284
285
286
287
288
      'attributes' => array('class' => array('views-ajax-link')),
    );
  }
  $links[$type . '-add'] = array(
    'title' => t('Add new'),
289
    'href' => "admin/structure/views/nojs/add-item/{$view->storage->id()}/{$display['id']}/$type",
merlinofchaos's avatar
merlinofchaos committed
290
291
292
293
294
295
296
297
298
    'attributes' => array('class' => array('views-ajax-link')),
  );

  return $links;
}

/**
 * Returns a link to editing a certain display setting.
 */
299
function views_ui_view_preview_section_display_category_links(ViewExecutable $view, $type, $title) {
merlinofchaos's avatar
merlinofchaos committed
300
301
302
303
  $display = $view->display_handler->display;
  $links = array(
    $type . '-edit' => array(
      'title' => t('Edit @section', array('@section' => $title)),
304
      'href' => "admin/structure/views/nojs/display/{$view->storage->id()}/{$display['id']}/$type",
merlinofchaos's avatar
merlinofchaos committed
305
306
307
308
309
310
311
312
313
314
      'attributes' => array('class' => array('views-ajax-link')),
    ),
  );

  return $links;
}

/**
 * Returns all contextual links for the main content part of the view.
 */
315
316
function views_ui_view_preview_section_rows_links(ViewExecutable $view) {
  $display = $view->display_handler->display;
merlinofchaos's avatar
merlinofchaos committed
317
318
319
320
321
322
323
324
325
326
327
  $links = array();
  $links = array_merge($links, views_ui_view_preview_section_handler_links($view, 'filter', TRUE));
  $links = array_merge($links, views_ui_view_preview_section_handler_links($view, 'field', TRUE));
  $links = array_merge($links, views_ui_view_preview_section_handler_links($view, 'sort', TRUE));
  $links = array_merge($links, views_ui_view_preview_section_handler_links($view, 'argument', TRUE));
  $links = array_merge($links, views_ui_view_preview_section_handler_links($view, 'relationship', TRUE));

  return $links;
}

/**
328
 * Implements hook_views_plugins_display_alter().
merlinofchaos's avatar
merlinofchaos committed
329
 */
330
function views_ui_views_plugins_display_alter(&$plugins) {
merlinofchaos's avatar
merlinofchaos committed
331
  // Attach contextual links to each display plugin. The links will point to
332
  // paths underneath "admin/structure/views/view/{$view->id()}" (i.e., paths
merlinofchaos's avatar
merlinofchaos committed
333
  // for editing and performing other contextual actions on the view).
334
  foreach ($plugins as &$display) {
merlinofchaos's avatar
merlinofchaos committed
335
336
    $display['contextual links']['views_ui'] = array(
      'parent path' => 'admin/structure/views/view',
337
      'argument properties' => array('id'),
merlinofchaos's avatar
merlinofchaos committed
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
    );
  }
}

/**
 * Implements hook_contextual_links_view_alter().
 */
function views_ui_contextual_links_view_alter(&$element, $items) {
  // Remove contextual links from being rendered, when so desired, such as
  // within a View preview.
  if (views_ui_contextual_links_suppress()) {
    $element['#links'] = array();
  }
  // Append the display ID to the Views UI edit links, so that clicking on the
  // contextual link takes you directly to the correct display tab on the edit
  // screen.
  elseif (!empty($element['#links']['views-ui-edit']) && !empty($element['#element']['#views_contextual_links_info']['views_ui']['view_display_id'])) {
    $display_id = $element['#element']['#views_contextual_links_info']['views_ui']['view_display_id'];
    $element['#links']['views-ui-edit']['href'] .= '/' . $display_id;
  }
}

/**
 * Sets a static variable for controlling whether contextual links are rendered.
 *
 * @see views_ui_contextual_links_view_alter()
 */
function views_ui_contextual_links_suppress($set = NULL) {
  $suppress = &drupal_static(__FUNCTION__);
  if (isset($set)) {
    $suppress = $set;
  }
  return $suppress;
}

/**
 * Increments the views_ui_contextual_links_suppress() static variable.
 *
 * When this function is added to the #pre_render of an element, and
 * 'views_ui_contextual_links_suppress_pop' is added to the #post_render of the
 * same element, then all contextual links within the element and its
 * descendants are suppressed from being rendered. This is used, for example,
 * during a View preview, when it is not desired for nodes in the Views result
 * to have contextual links.
 *
 * @see views_ui_contextual_links_suppress_pop()
 */
function views_ui_contextual_links_suppress_push() {
  views_ui_contextual_links_suppress(((int) views_ui_contextual_links_suppress())+1);
}

/**
 * Decrements the views_ui_contextual_links_suppress() static variable.
 *
 * @see views_ui_contextual_links_suppress_push()
 */
function views_ui_contextual_links_suppress_pop() {
  views_ui_contextual_links_suppress(((int) views_ui_contextual_links_suppress())-1);
}

/**
 * This is part of a patch to address a jQueryUI bug.  The bug is responsible
 * for the inability to scroll a page when a modal dialog is active. If the content
 * of the dialog extends beyond the bottom of the viewport, the user is only able
 * to scroll with a mousewheel or up/down keyboard keys.
 *
 * @see http://bugs.jqueryui.com/ticket/4671
 * @see https://bugs.webkit.org/show_bug.cgi?id=19033
 * @see /js/jquery.ui.dialog.patch.js
 * @see /js/jquery.ui.dialog.min.js
 *
 * The javascript patch overwrites the $.ui.dialog.overlay.events object to remove
 * the mousedown, mouseup and click events from the list of events that are bound
 * in $.ui.dialog.overlay.create.
 */

function views_ui_library_alter(&$libraries, $module) {
415
416
417
  if ($module == 'system' && isset($libraries['jquery.ui.dialog'])) {
    if (version_compare($libraries['jquery.ui.dialog']['version'], '1.7.2', '>=')) {
      $libraries['jquery.ui.dialog']['js'][drupal_get_path('module', 'views') . '/js/jquery.ui.dialog.patch.js'] = array();
merlinofchaos's avatar
merlinofchaos committed
418
419
420
421
    }
  }
}

422
423
424
425
426
427
428
/**
 * Implements hook_views_analyze().
 *
 * This is the basic views analysis that checks for very minimal problems.
 * There are other analysis tools in core specific sections, such as
 * node.views.inc as well.
 */
429
function views_ui_views_analyze(ViewExecutable $view) {
430
431
432
433
434
435
436
437
438
439
440
441
442
  $ret = array();
  // Check for something other than the default display:
  if (count($view->displayHandlers) < 2) {
    $ret[] = Analyzer::formatMessage(t('This view has only a default display and therefore will not be placed anywhere on your site; perhaps you want to add a page or a block display.'), 'warning');
  }
  // You can give a page display the same path as an alias existing in the
  // system, so the alias will not work anymore. Report this to the user,
  // because he probably wanted something else.
  foreach ($view->displayHandlers as $display) {
    if (empty($display)) {
      continue;
    }
    if ($display->hasPath() && $path = $display->getOption('path')) {
443
      $normal_path = drupal_container()->get('path.alias_manager.cached')->getSystemPath($path);
444
      if ($path != $normal_path) {
445
        $ret[] = Analyzer::formatMessage(t('You have configured display %display with a path which is an path alias as well. This might lead to unwanted effects so better use an internal path.', array('%display' => $display->display['display_title'])), 'warning');
446
447
448
449
450
451
452
      }
    }
  }

  return $ret;
}

merlinofchaos's avatar
merlinofchaos committed
453
454
455
456
457
458
459
460
461
462
463
464
465
/**
 * Truncate strings to a set length and provide a ... if they truncated.
 *
 * This is often used in the UI to ensure long strings fit.
 */
function views_ui_truncate($string, $length) {
  if (drupal_strlen($string) > $length) {
    $string = drupal_substr($string, 0, $length);
    $string .= '...';
  }

  return $string;
}
466
467
468
469

/**
 * Magic load function. Wrapper to load a view.
 */
470
function views_ui_load($name) {
471
472
  return views_get_view($name);
}