Projet

Général

Profil

Paste
Télécharger (202 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / views / includes / admin.inc @ 13c3c9b4

1 85ad3d82 Assos Assos
<?php
2
3
/**
4
 * @file
5
 * Provides the Views' administrative interface.
6
 */
7
8
/**
9
 * Create an array of Views admin CSS for adding or attaching.
10
 *
11
 * This returns an array of arrays. Each array represents a single
12
 * file. The array format is:
13
 * - file: The fully qualified name of the file to send to drupal_add_css
14
 * - options: An array of options to pass to drupal_add_css.
15
 */
16
function views_ui_get_admin_css() {
17
  $module_path = drupal_get_path('module', 'views_ui');
18
  $list = array();
19
  $list[$module_path . '/css/views-admin.css'] = array();
20
21
  $list[$module_path . '/css/ie/views-admin.ie7.css'] = array(
22
    'browsers' => array(
23
      'IE' => 'lte IE 7',
24
      '!IE' => FALSE
25
    ),
26
    'preprocess' => FALSE,
27
  );
28
29
  $list[$module_path . '/css/views-admin.theme.css'] = array();
30
31
  // Add in any theme specific CSS files we have
32
  $themes = list_themes();
33
  $theme_key = $GLOBALS['theme'];
34
  while ($theme_key) {
35
    // Try to find the admin css file for non-core themes.
36
    if (!in_array($theme_key, array('garland', 'seven', 'bartik'))) {
37
      $theme_path = drupal_get_path('theme', $theme_key);
38
      // First search in the css directory, then in the root folder of the theme.
39
      if (file_exists($theme_path . "/css/views-admin.$theme_key.css")) {
40
        $list[$theme_path . "/css/views-admin.$theme_key.css"] = array(
41
          'group' => CSS_THEME,
42
        );
43
      }
44
      else if (file_exists($theme_path . "/views-admin.$theme_key.css")) {
45
        $list[$theme_path . "/views-admin.$theme_key.css"] = array(
46
          'group' => CSS_THEME,
47
        );
48
      }
49
    }
50
    else {
51
      $list[$module_path . "/css/views-admin.$theme_key.css"] = array(
52
        'group' => CSS_THEME,
53
      );
54
    }
55
    $theme_key = isset($themes[$theme_key]->base_theme) ? $themes[$theme_key]->base_theme : '';
56
  }
57
  // Views contains style overrides for the following modules
58
  $module_list = array('contextual', 'advanced_help', 'ctools');
59
  foreach ($module_list as $module) {
60
    if (module_exists($module)) {
61
      $list[$module_path . '/css/views-admin.' . $module . '.css'] = array();
62
    }
63
  }
64
65
66
  return $list;
67
}
68
69
/**
70
 * Adds standard Views administration CSS to the current page.
71
 */
72
function views_ui_add_admin_css() {
73
  foreach (views_ui_get_admin_css() as $file => $options) {
74
    drupal_add_css($file, $options);
75
  }
76
}
77
78
/**
79
 * Check to see if the advanced help module is installed, and if not put up
80
 * a message.
81
 *
82
 * Only call this function if the user is already in a position for this to
83
 * be useful.
84
 */
85
function views_ui_check_advanced_help() {
86
  if (!variable_get('views_ui_show_advanced_help_warning', TRUE)) {
87
    return;
88
  }
89
90
  if (!module_exists('advanced_help')) {
91
    $filename = db_query_range("SELECT filename FROM {system} WHERE type = 'module' AND name = 'advanced_help'", 0, 1)
92
      ->fetchField();
93
    if ($filename && file_exists($filename)) {
94
      drupal_set_message(t('If you <a href="@modules">enable the advanced help module</a>, Views will provide more and better help. <a href="@hide">You can disable this message at the Views settings page.</a>', array('@modules' => url('admin/modules'),'@hide' => url('admin/structure/views/settings'))));
95
    }
96
    else {
97
      drupal_set_message(t('If you install the advanced help module from !href, Views will provide more and better help. <a href="@hide">You can disable this message at the Views settings page.</a>', array('!href' => l('http://drupal.org/project/advanced_help', 'http://drupal.org/project/advanced_help'), '@hide' => url('admin/structure/views/settings'))));
98
    }
99
  }
100
}
101
102
/**
103
 * Returns the results of the live preview.
104
 */
105
function views_ui_preview($view, $display_id, $args = array()) {
106
  // When this function is invoked as a page callback, each Views argument is
107
  // passed separately.
108
  if (!is_array($args)) {
109
    $args = array_slice(func_get_args(), 2);
110
  }
111
112
  // Save $_GET['q'] so it can be restored before returning from this function.
113
  $q = $_GET['q'];
114
115
  // Determine where the query and performance statistics should be output.
116
  $show_query = variable_get('views_ui_show_sql_query', FALSE);
117
  $show_info = variable_get('views_ui_show_preview_information', FALSE);
118
  $show_location = variable_get('views_ui_show_sql_query_where', 'above');
119
120
  $show_stats = variable_get('views_ui_show_performance_statistics', FALSE);
121
  if ($show_stats) {
122
    $show_stats = variable_get('views_ui_show_sql_query_where', 'above');
123
  }
124
125
  $combined = $show_query && $show_stats;
126
127
  $rows = array('query' => array(), 'statistics' => array());
128
  $output = '';
129
130
  $errors = $view->validate();
131
  if ($errors === TRUE) {
132
    $view->ajax = TRUE;
133
    $view->live_preview = TRUE;
134
    $view->views_ui_context = TRUE;
135
136
    // AJAX happens via $_POST but everything expects exposed data to
137
    // be in GET. Copy stuff but remove ajax-framework specific keys.
138
    // If we're clicking on links in a preview, though, we could actually
139
    // still have some in $_GET, so we use $_REQUEST to ensure we get it all.
140
    $exposed_input = $_REQUEST;
141
    foreach (array('view_name', 'view_display_id', 'view_args', 'view_path', 'view_dom_id', 'pager_element', 'view_base_path', 'ajax_html_ids', 'ajax_page_state', 'form_id', 'form_build_id', 'form_token') as $key) {
142
      if (isset($exposed_input[$key])) {
143
        unset($exposed_input[$key]);
144
      }
145
    }
146
147
    $view->set_exposed_input($exposed_input);
148
149
150
    if (!$view->set_display($display_id)) {
151
      return t('Invalid display id @display', array('@display' => $display_id));
152
    }
153
154
    $view->set_arguments($args);
155
156
    // Store the current view URL for later use:
157
    if ($view->display_handler->get_option('path')) {
158
      $path = $view->get_url();
159
    }
160
161
    // Make view links come back to preview.
162
    $view->override_path = 'admin/structure/views/nojs/preview/' . $view->name . '/' . $display_id;
163
164
    // Also override $_GET['q'] so we get the pager.
165
    $original_path = current_path();
166
    $_GET['q'] = $view->override_path;
167
    if ($args) {
168
      $_GET['q'] .= '/' . implode('/', $args);
169
    }
170
171
    // Suppress contextual links of entities within the result set during a
172
    // Preview.
173
    // @todo We'll want to add contextual links specific to editing the View, so
174
    //   the suppression may need to be moved deeper into the Preview pipeline.
175
    views_ui_contextual_links_suppress_push();
176
    $preview = $view->preview($display_id, $args);
177
    views_ui_contextual_links_suppress_pop();
178
179
    // Reset variables.
180
    unset($view->override_path);
181
    $_GET['q'] = $original_path;
182
183
    // Prepare the query information and statistics to show either above or
184
    // below the view preview.
185
    if ($show_info || $show_query || $show_stats) {
186
      // Get information from the preview for display.
187
      if (!empty($view->build_info['query'])) {
188
        if ($show_query) {
189
          $query = $view->build_info['query'];
190
          // Only the sql default class has a method getArguments.
191
          $quoted = array();
192
193
          if (get_class($view->query) == 'views_plugin_query_default') {
194
            $quoted = $query->getArguments();
195
            $connection = Database::getConnection();
196
            foreach ($quoted as $key => $val) {
197
              if (is_array($val)) {
198
                $quoted[$key] = implode(', ', array_map(array($connection, 'quote'), $val));
199
              }
200
              else {
201
                $quoted[$key] = $connection->quote($val);
202
              }
203
            }
204
          }
205
          $rows['query'][] = array('<strong>' . t('Query') . '</strong>', '<pre>' . check_plain(strtr($query, $quoted)) . '</pre>');
206
          if (!empty($view->additional_queries)) {
207
            $queries = '<strong>' . t('These queries were run during view rendering:') . '</strong>';
208
            foreach ($view->additional_queries as $query) {
209
              if ($queries) {
210
                $queries .= "\n";
211
              }
212
              $queries .= t('[@time ms]', array('@time' => intval($query[1] * 100000) / 100)) . ' ' . $query[0];
213
            }
214
215
            $rows['query'][] = array('<strong>' . t('Other queries') . '</strong>', '<pre>' . $queries . '</pre>');
216
          }
217
        }
218
        if ($show_info) {
219
          $rows['query'][] = array('<strong>' . t('Title') . '</strong>', filter_xss_admin($view->get_title()));
220
          if (isset($path)) {
221
            $path = l($path, $path);
222
          }
223
          else {
224
            $path = t('This display has no path.');
225
          }
226
          $rows['query'][] = array('<strong>' . t('Path') . '</strong>', $path);
227
        }
228
229
        if ($show_stats) {
230
          $rows['statistics'][] = array('<strong>' . t('Query build time') . '</strong>', t('@time ms', array('@time' => intval($view->build_time * 100000) / 100)));
231
          $rows['statistics'][] = array('<strong>' . t('Query execute time') . '</strong>', t('@time ms', array('@time' => intval($view->execute_time * 100000) / 100)));
232
          $rows['statistics'][] = array('<strong>' . t('View render time') . '</strong>', t('@time ms', array('@time' => intval($view->render_time * 100000) / 100)));
233
234
        }
235
        drupal_alter('views_preview_info', $rows, $view);
236
      }
237
      else {
238
        // No query was run. Display that information in place of either the
239
        // query or the performance statistics, whichever comes first.
240
        if ($combined || ($show_location === 'above')) {
241
          $rows['query'] = array(array('<strong>' . t('Query') . '</strong>', t('No query was run')));
242
        }
243
        else {
244
          $rows['statistics'] = array(array('<strong>' . t('Query') . '</strong>', t('No query was run')));
245
        }
246
      }
247
    }
248
  }
249
  else {
250
    foreach ($errors as $error) {
251
      drupal_set_message($error, 'error');
252
    }
253
    $preview = t('Unable to preview due to validation errors.');
254
  }
255
256
  // Assemble the preview, the query info, and the query statistics in the
257
  // requested order.
258
  if ($show_location === 'above') {
259
    if ($combined) {
260
      $output .= '<div class="views-query-info">' . theme('table', array('rows' => array_merge($rows['query'], $rows['statistics']))) . '</div>';
261
    }
262
    else {
263
      $output .= '<div class="views-query-info">' . theme('table', array('rows' => $rows['query'])) . '</div>';
264
    }
265
  }
266
  elseif ($show_stats === 'above') {
267
    $output .= '<div class="views-query-info">' . theme('table', array('rows' => $rows['statistics'])) . '</div>';
268
  }
269
270
  $output .= $preview;
271
272
  if ($show_location === 'below') {
273
    if ($combined) {
274
      $output .= '<div class="views-query-info">' . theme('table', array('rows' => array_merge($rows['query'], $rows['statistics']))) . '</div>';
275
    }
276
    else {
277
      $output .= '<div class="views-query-info">' . theme('table', array('rows' => $rows['query'])) . '</div>';
278
    }
279
  }
280
  elseif ($show_stats === 'below') {
281
    $output .= '<div class="views-query-info">' . theme('table', array('rows' => $rows['statistics'])) . '</div>';
282
  }
283
284
  $_GET['q'] = $q;
285
  return $output;
286
}
287
288
/**
289
 * Page callback to add a new view.
290
 */
291
function views_ui_add_page() {
292
  views_ui_add_admin_css();
293
  drupal_set_title(t('Add new view'));
294
  return drupal_get_form('views_ui_add_form');
295
}
296
297
/**
298
 * Form builder for the "add new view" page.
299
 */
300
function views_ui_add_form($form, &$form_state) {
301
  ctools_include('dependent');
302
  $form['#attached']['js'][] = drupal_get_path('module', 'views_ui') . '/js/views-admin.js';
303
  $form['#attributes']['class'] = array('views-admin');
304
305
  $form['human_name'] = array(
306
    '#type' => 'textfield',
307
    '#title' => t('View name'),
308
    '#required' => TRUE,
309
    '#size' => 32,
310
    '#default_value' => !empty($form_state['view']) ? $form_state['view']->human_name : '',
311
    '#maxlength' => 255,
312
  );
313
  $form['name'] = array(
314
    '#type' => 'machine_name',
315
    '#maxlength' => 128,
316
    '#machine_name' => array(
317
      'exists' => 'views_get_view',
318
      'source' => array('human_name'),
319
    ),
320
    '#description' => t('A unique machine-readable name for this View. It must only contain lowercase letters, numbers, and underscores.'),
321
  );
322
323
  $form['description_enable'] = array(
324
    '#type' => 'checkbox',
325
    '#title' => t('Description'),
326
  );
327
  $form['description'] = array(
328
    '#type' => 'textfield',
329
    '#title' => t('Provide description'),
330
    '#title_display' => 'invisible',
331
    '#size' => 64,
332
    '#default_value' => !empty($form_state['view']) ? $form_state['view']->description : '',
333
    '#dependency' => array(
334
      'edit-description-enable' => array(1),
335
    ),
336
  );
337
338
  // Create a wrapper for the entire dynamic portion of the form. Everything
339
  // that can be updated by AJAX goes somewhere inside here. For example, this
340
  // is needed by "Show" dropdown (below); it changes the base table of the
341
  // view and therefore potentially requires all options on the form to be
342
  // dynamically updated.
343
  $form['displays'] = array();
344
345
  // Create the part of the form that allows the user to select the basic
346
  // properties of what the view will display.
347
  $form['displays']['show'] = array(
348
    '#type' => 'fieldset',
349
    '#tree' => TRUE,
350
    '#attributes' => array('class' => array('container-inline')),
351
  );
352
353
  // Create the "Show" dropdown, which allows the base table of the view to be
354
  // selected.
355
  $wizard_plugins = views_ui_get_wizards();
356
  $options = array();
357
  foreach ($wizard_plugins as $key => $wizard) {
358
    $options[$key] = $wizard['title'];
359
  }
360
  $form['displays']['show']['wizard_key'] = array(
361
    '#type' => 'select',
362
    '#title' => t('Show'),
363
    '#options' => $options,
364
  );
365
  $show_form = &$form['displays']['show'];
366
  $show_form['wizard_key']['#default_value'] = views_ui_get_selected($form_state, array('show', 'wizard_key'), 'node', $show_form['wizard_key']);
367
  // Changing this dropdown updates the entire content of $form['displays'] via
368
  // AJAX.
369
  views_ui_add_ajax_trigger($show_form, 'wizard_key', array('displays'));
370
371
  // Build the rest of the form based on the currently selected wizard plugin.
372
  $wizard_key = $show_form['wizard_key']['#default_value'];
373
  $get_instance = $wizard_plugins[$wizard_key]['get_instance'];
374
  $wizard_instance = $get_instance($wizard_plugins[$wizard_key]);
375
  $form = $wizard_instance->build_form($form, $form_state);
376
377
  $form['save'] = array(
378
    '#type' => 'submit',
379
    '#value' => t('Save & exit'),
380
    '#validate' => array('views_ui_wizard_form_validate'),
381
    '#submit' => array('views_ui_add_form_save_submit'),
382
  );
383
  $form['continue'] = array(
384
    '#type' => 'submit',
385
    '#value' => t('Continue & edit'),
386
    '#validate' => array('views_ui_wizard_form_validate'),
387
    '#submit' => array('views_ui_add_form_store_edit_submit'),
388
    '#process' => array_merge(array('views_ui_default_button'), element_info_property('submit', '#process', array())),
389
  );
390
  $form['cancel'] = array(
391
    '#type' => 'submit',
392
    '#value' => t('Cancel'),
393
    '#submit' => array('views_ui_add_form_cancel_submit'),
394
    '#limit_validation_errors' => array(),
395
  );
396
397
  return $form;
398
}
399
400
/**
401
 * Helper form element validator: integer.
402
 *
403
 * The problem with this is that the function is private so it's not guaranteed
404
 * that it might not be renamed/changed. In the future field.module or something else
405
 * should provide a public validate function.
406
 *
407
 * @see _element_validate_integer_positive()
408
 */
409
function views_element_validate_integer($element, &$form_state) {
410
  $value = $element['#value'];
411 6eb8d15f Assos Assos
  if ($value !== '' && (!is_numeric($value) || intval($value) != $value || abs($value) != $value)) {
412 85ad3d82 Assos Assos
    form_error($element, t('%name must be a positive integer.', array('%name' => $element['#title'])));
413
  }
414
}
415
416
/**
417
 * Gets the current value of a #select element, from within a form constructor function.
418
 *
419
 * This function is intended for use in highly dynamic forms (in particular the
420
 * add view wizard) which are rebuilt in different ways depending on which
421
 * triggering element (AJAX or otherwise) was most recently fired. For example,
422
 * sometimes it is necessary to decide how to build one dynamic form element
423
 * based on the value of a different dynamic form element that may not have
424
 * even been present on the form the last time it was submitted. This function
425
 * takes care of resolving those conflicts and gives you the proper current
426
 * value of the requested #select element.
427
 *
428
 * By necessity, this function sometimes uses non-validated user input from
429
 * $form_state['input'] in making its determination. Although it performs some
430
 * minor validation of its own, it is not complete. The intention is that the
431
 * return value of this function should only be used to help decide how to
432
 * build the current form the next time it is reloaded, not to be saved as if
433
 * it had gone through the normal, final form validation process. Do NOT use
434
 * the results of this function for any other purpose besides deciding how to
435
 * build the next version of the form.
436
 *
437
 * @param $form_state
438
 *   The  standard associative array containing the current state of the form.
439
 * @param $parents
440
 *   An array of parent keys that point to the part of the submitted form
441
 *   values that are expected to contain the element's value (in the case where
442
 *   this form element was actually submitted). In a simple case (assuming
443
 *   #tree is TRUE throughout the form), if the select element is located in
444
 *   $form['wrapper']['select'], so that the submitted form values would
445
 *   normally be found in $form_state['values']['wrapper']['select'], you would
446
 *   pass array('wrapper', 'select') for this parameter.
447
 * @param $default_value
448
 *   The default value to return if the #select element does not currently have
449
 *   a proper value set based on the submitted input.
450
 * @param $element
451
 *   An array representing the current version of the #select element within
452
 *   the form.
453
 *
454
 * @return
455
 *   The current value of the #select element. A common use for this is to feed
456
 *   it back into $element['#default_value'] so that the form will be rendered
457
 *   with the correct value selected.
458
 */
459
function views_ui_get_selected($form_state, $parents, $default_value, $element) {
460
  // For now, don't trust this to work on anything but a #select element.
461
  if (!isset($element['#type']) || $element['#type'] != 'select' || !isset($element['#options'])) {
462
    return $default_value;
463
  }
464
465
  // If there is a user-submitted value for this element that matches one of
466
  // the currently available options attached to it, use that. We need to check
467
  // $form_state['input'] rather than $form_state['values'] here because the
468
  // triggering element often has the #limit_validation_errors property set to
469
  // prevent unwanted errors elsewhere on the form. This means that the
470
  // $form_state['values'] array won't be complete. We could make it complete
471
  // by adding each required part of the form to the #limit_validation_errors
472
  // property individually as the form is being built, but this is difficult to
473
  // do for a highly dynamic and extensible form. This method is much simpler.
474
  if (!empty($form_state['input'])) {
475
    $key_exists = NULL;
476
    $submitted = drupal_array_get_nested_value($form_state['input'], $parents, $key_exists);
477
    // Check that the user-submitted value is one of the allowed options before
478
    // returning it. This is not a substitute for actual form validation;
479
    // rather it is necessary because, for example, the same select element
480
    // might have #options A, B, and C under one set of conditions but #options
481
    // D, E, F under a different set of conditions. So the form submission
482
    // might have occurred with option A selected, but when the form is rebuilt
483
    // option A is no longer one of the choices. In that case, we don't want to
484
    // use the value that was submitted anymore but rather fall back to the
485
    // default value.
486
    if ($key_exists && in_array($submitted, array_keys($element['#options']))) {
487
      return $submitted;
488
    }
489
  }
490
491
  // Fall back on returning the default value if nothing was returned above.
492
  return $default_value;
493
}
494
495
/**
496
 * Converts a form element in the add view wizard to be AJAX-enabled.
497
 *
498
 * This function takes a form element and adds AJAX behaviors to it such that
499
 * changing it triggers another part of the form to update automatically. It
500
 * also adds a submit button to the form that appears next to the triggering
501
 * element and that duplicates its functionality for users who do not have
502
 * JavaScript enabled (the button is automatically hidden for users who do have
503
 * JavaScript).
504
 *
505
 * To use this function, call it directly from your form builder function
506
 * immediately after you have defined the form element that will serve as the
507
 * JavaScript trigger. Calling it elsewhere (such as in hook_form_alter()) may
508
 * mean that the non-JavaScript fallback button does not appear in the correct
509
 * place in the form.
510
 *
511
 * @param $wrapping_element
512
 *   The element whose child will server as the AJAX trigger. For example, if
513
 *   $form['some_wrapper']['triggering_element'] represents the element which
514
 *   will trigger the AJAX behavior, you would pass $form['some_wrapper'] for
515
 *   this parameter.
516
 * @param $trigger_key
517
 *   The key within the wrapping element that identifies which of its children
518
 *   serves as the AJAX trigger. In the above example, you would pass
519
 *   'triggering_element' for this parameter.
520
 * @param $refresh_parents
521
 *   An array of parent keys that point to the part of the form that will be
522
 *   refreshed by AJAX. For example, if triggering the AJAX behavior should
523
 *   cause $form['dynamic_content']['section'] to be refreshed, you would pass
524
 *   array('dynamic_content', 'section') for this parameter.
525
 */
526
function views_ui_add_ajax_trigger(&$wrapping_element, $trigger_key, $refresh_parents) {
527
  $seen_ids = &drupal_static(__FUNCTION__ . ':seen_ids', array());
528
  $seen_buttons = &drupal_static(__FUNCTION__ . ':seen_buttons', array());
529
530
  // Add the AJAX behavior to the triggering element.
531
  $triggering_element = &$wrapping_element[$trigger_key];
532
  $triggering_element['#ajax']['callback'] = 'views_ui_ajax_update_form';
533
  // We do not use drupal_html_id() to get an ID for the AJAX wrapper, because
534
  // it remembers IDs across AJAX requests (and won't reuse them), but in our
535
  // case we need to use the same ID from request to request so that the
536
  // wrapper can be recognized by the AJAX system and its content can be
537
  // dynamically updated. So instead, we will keep track of duplicate IDs
538
  // (within a single request) on our own, later in this function.
539
  $triggering_element['#ajax']['wrapper'] = 'edit-view-' . implode('-', $refresh_parents) . '-wrapper';
540
541
  // Add a submit button for users who do not have JavaScript enabled. It
542
  // should be displayed next to the triggering element on the form.
543
  $button_key = $trigger_key . '_trigger_update';
544
  $wrapping_element[$button_key] = array(
545
    '#type' => 'submit',
546
    // Hide this button when JavaScript is enabled.
547
    '#attributes' => array('class' => array('js-hide')),
548
    '#submit' => array('views_ui_nojs_submit'),
549
    // Add a process function to limit this button's validation errors to the
550
    // triggering element only. We have to do this in #process since until the
551
    // form API has added the #parents property to the triggering element for
552
    // us, we don't have any (easy) way to find out where its submitted values
553
    // will eventually appear in $form_state['values'].
554
    '#process' => array_merge(array('views_ui_add_limited_validation'), element_info_property('submit', '#process', array())),
555
    // Add an after-build function that inserts a wrapper around the region of
556
    // the form that needs to be refreshed by AJAX (so that the AJAX system can
557
    // detect and dynamically update it). This is done in #after_build because
558
    // it's a convenient place where we have automatic access to the complete
559
    // form array, but also to minimize the chance that the HTML we add will
560
    // get clobbered by code that runs after we have added it.
561
    '#after_build' => array_merge(element_info_property('submit', '#after_build', array()), array('views_ui_add_ajax_wrapper')),
562
  );
563
  // Copy #weight and #access from the triggering element to the button, so
564
  // that the two elements will be displayed together.
565
  foreach (array('#weight', '#access') as $property) {
566
    if (isset($triggering_element[$property])) {
567
      $wrapping_element[$button_key][$property] = $triggering_element[$property];
568
    }
569
  }
570
  // For easiest integration with the form API and the testing framework, we
571
  // always give the button a unique #value, rather than playing around with
572
  // #name.
573
  $button_title = !empty($triggering_element['#title']) ? $triggering_element['#title'] : $trigger_key;
574
  if (empty($seen_buttons[$button_title])) {
575
    $wrapping_element[$button_key]['#value'] = t('Update "@title" choice', array(
576
      '@title' => $button_title,
577
    ));
578
    $seen_buttons[$button_title] = 1;
579
  }
580
  else {
581
    $wrapping_element[$button_key]['#value'] = t('Update "@title" choice (@number)', array(
582
      '@title' => $button_title,
583
      '@number' => ++$seen_buttons[$button_title],
584
   ));
585
  }
586
587
  // Attach custom data to the triggering element and submit button, so we can
588
  // use it in both the process function and AJAX callback.
589
  $ajax_data = array(
590
    'wrapper' => $triggering_element['#ajax']['wrapper'],
591
    'trigger_key' => $trigger_key,
592
    'refresh_parents' => $refresh_parents,
593
    // Keep track of duplicate wrappers so we don't add the same wrapper to the
594
    // page more than once.
595
    'duplicate_wrapper' => !empty($seen_ids[$triggering_element['#ajax']['wrapper']]),
596
  );
597
  $seen_ids[$triggering_element['#ajax']['wrapper']] = TRUE;
598
  $triggering_element['#views_ui_ajax_data'] = $ajax_data;
599
  $wrapping_element[$button_key]['#views_ui_ajax_data'] = $ajax_data;
600
}
601
602
/**
603
 * Processes a non-JavaScript fallback submit button to limit its validation errors.
604
 */
605
function views_ui_add_limited_validation($element, &$form_state) {
606
  // Retrieve the AJAX triggering element so we can determine its parents. (We
607
  // know it's at the same level of the complete form array as the submit
608
  // button, so all we have to do to find it is swap out the submit button's
609
  // last array parent.)
610
  $array_parents = $element['#array_parents'];
611
  array_pop($array_parents);
612
  $array_parents[] = $element['#views_ui_ajax_data']['trigger_key'];
613
  $ajax_triggering_element = drupal_array_get_nested_value($form_state['complete form'], $array_parents);
614
615
  // Limit this button's validation to the AJAX triggering element, so it can
616
  // update the form for that change without requiring that the rest of the
617
  // form be filled out properly yet.
618
  $element['#limit_validation_errors'] = array($ajax_triggering_element['#parents']);
619
620
  // If we are in the process of a form submission and this is the button that
621
  // was clicked, the form API workflow in form_builder() will have already
622
  // copied it to $form_state['triggering_element'] before our #process
623
  // function is run. So we need to make the same modifications in $form_state
624
  // as we did to the element itself, to ensure that #limit_validation_errors
625
  // will actually be set in the correct place.
626
  if (!empty($form_state['triggering_element'])) {
627
    $clicked_button = &$form_state['triggering_element'];
628
    if ($clicked_button['#name'] == $element['#name'] && $clicked_button['#value'] == $element['#value']) {
629
      $clicked_button['#limit_validation_errors'] = $element['#limit_validation_errors'];
630
    }
631
  }
632
633
  return $element;
634
}
635
636
/**
637
 * After-build function that adds a wrapper to a form region (for AJAX refreshes).
638
 *
639
 * This function inserts a wrapper around the region of the form that needs to
640
 * be refreshed by AJAX, based on information stored in the corresponding
641
 * submit button form element.
642
 */
643
function views_ui_add_ajax_wrapper($element, &$form_state) {
644
  // Don't add the wrapper <div> if the same one was already inserted on this
645
  // form.
646
  if (empty($element['#views_ui_ajax_data']['duplicate_wrapper'])) {
647
    // Find the region of the complete form that needs to be refreshed by AJAX.
648
    // This was earlier stored in a property on the element.
649
    $complete_form = &$form_state['complete form'];
650
    $refresh_parents = $element['#views_ui_ajax_data']['refresh_parents'];
651
    $refresh_element = drupal_array_get_nested_value($complete_form, $refresh_parents);
652
653
    // The HTML ID that AJAX expects was also stored in a property on the
654
    // element, so use that information to insert the wrapper <div> here.
655
    $id = $element['#views_ui_ajax_data']['wrapper'];
656
    $refresh_element += array(
657
      '#prefix' => '',
658
      '#suffix' => '',
659
    );
660
    $refresh_element['#prefix'] = '<div id="' . $id . '" class="views-ui-ajax-wrapper">' . $refresh_element['#prefix'];
661
    $refresh_element['#suffix'] .= '</div>';
662
663
    // Copy the element that needs to be refreshed back into the form, with our
664
    // modifications to it.
665
    drupal_array_set_nested_value($complete_form, $refresh_parents, $refresh_element);
666
  }
667
668
  return $element;
669
}
670
671
/**
672
 * Updates a part of the add view form via AJAX.
673
 *
674
 * @return
675
 *   The part of the form that has changed.
676
 */
677
function views_ui_ajax_update_form($form, $form_state) {
678
  // The region that needs to be updated was stored in a property of the
679
  // triggering element by views_ui_add_ajax_trigger(), so all we have to do is
680
  // retrieve that here.
681
  return drupal_array_get_nested_value($form, $form_state['triggering_element']['#views_ui_ajax_data']['refresh_parents']);
682
}
683
684
/**
685
 * Non-Javascript fallback for updating the add view form.
686
 */
687
function views_ui_nojs_submit($form, &$form_state) {
688
  $form_state['rebuild'] = TRUE;
689
}
690
691
/**
692
 * Validate the add view form.
693
 */
694
function views_ui_wizard_form_validate($form, &$form_state) {
695
  $wizard = views_ui_get_wizard($form_state['values']['show']['wizard_key']);
696
  $form_state['wizard'] = $wizard;
697
  $get_instance = $wizard['get_instance'];
698
  $form_state['wizard_instance'] = $get_instance($wizard);
699
  $errors = $form_state['wizard_instance']->validate($form, $form_state);
700
  foreach ($errors as $name => $message) {
701
    form_set_error($name, $message);
702
  }
703
}
704
705
/**
706
 * Process the add view form, 'save'.
707
 */
708
function views_ui_add_form_save_submit($form, &$form_state) {
709
  try {
710
    $view = $form_state['wizard_instance']->create_view($form, $form_state);
711
  }
712
  catch (ViewsWizardException $e) {
713
    drupal_set_message($e->getMessage(), 'error');
714
    $form_state['redirect'] = 'admin/structure/views';
715
  }
716
  $view->save();
717
718
  $form_state['redirect'] = 'admin/structure/views';
719
  if (!empty($view->display['page'])) {
720
    $display = $view->display['page'];
721
    if ($display->handler->has_path()) {
722
      $one_path = $display->handler->get_option('path');
723
      if (strpos($one_path, '%') === FALSE) {
724
        $form_state['redirect'] = $one_path;  // PATH TO THE VIEW IF IT HAS ONE
725
        return;
726
      }
727
    }
728
  }
729
  drupal_set_message(t('Your view was saved. You may edit it from the list below.'));
730
}
731
732
/**
733
 * Process the add view form, 'continue'.
734
 */
735
function views_ui_add_form_store_edit_submit($form, &$form_state) {
736
  try {
737
    $view = $form_state['wizard_instance']->create_view($form, $form_state);
738
  }
739
  catch (ViewsWizardException $e) {
740
    drupal_set_message($e->getMessage(), 'error');
741
    $form_state['redirect'] = 'admin/structure/views';
742
  }
743
  // Just cache it temporarily to edit it.
744
  views_ui_cache_set($view);
745
746
  // If there is a destination query, ensure we still redirect the user to the
747
  // edit view page, and then redirect the user to the destination.
748
  $destination = array();
749
  if (isset($_GET['destination'])) {
750
    $destination = drupal_get_destination();
751
    unset($_GET['destination']);
752
  }
753
  $form_state['redirect'] = array('admin/structure/views/view/' . $view->name, array('query' => $destination));
754
}
755
756
/**
757
 * Cancel the add view form.
758
 */
759
function views_ui_add_form_cancel_submit($form, &$form_state) {
760
  $form_state['redirect'] = 'admin/structure/views';
761
}
762
763
/**
764
 * Form element validation handler for a taxonomy autocomplete field.
765
 *
766
 * This allows a taxonomy autocomplete field to be validated outside the
767
 * standard Field API workflow, without passing in a complete field widget.
768
 * Instead, all that is required is that $element['#field_name'] contain the
769
 * name of the taxonomy autocomplete field that is being validated.
770
 *
771
 * This function is currently not used for validation directly, although it
772
 * could be. Instead, it is only used to store the term IDs and vocabulary name
773
 * in the element value, based on the tags that the user typed in.
774
 *
775
 * @see taxonomy_autocomplete_validate()
776
 */
777
function views_ui_taxonomy_autocomplete_validate($element, &$form_state) {
778
  $value = array();
779
  if ($tags = $element['#value']) {
780
    // Get the machine names of the vocabularies we will search, keyed by the
781
    // vocabulary IDs.
782
    $field = field_info_field($element['#field_name']);
783
    $vocabularies = array();
784
    if (!empty($field['settings']['allowed_values'])) {
785
      foreach ($field['settings']['allowed_values'] as $tree) {
786
        if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) {
787
          $vocabularies[$vocabulary->vid] = $tree['vocabulary'];
788
        }
789
      }
790
    }
791
    // Store the term ID of each (valid) tag that the user typed.
792
    $typed_terms = drupal_explode_tags($tags);
793
    foreach ($typed_terms as $typed_term) {
794
      if ($terms = taxonomy_term_load_multiple(array(), array('name' => trim($typed_term), 'vid' => array_keys($vocabularies)))) {
795
        $term = array_pop($terms);
796
        $value['tids'][] = $term->tid;
797
      }
798
    }
799
    // Store the term IDs along with the name of the vocabulary. Currently
800
    // Views (as well as the Field UI) assumes that there will only be one
801
    // vocabulary, although technically the API allows there to be more than
802
    // one.
803
    if (!empty($value['tids'])) {
804
      $value['tids'] = array_unique($value['tids']);
805
      $value['vocabulary'] = array_pop($vocabularies);
806
    }
807
  }
808
  form_set_value($element, $value, $form_state);
809
}
810
811
/**
812
 * Theme function; returns basic administrative information about a view.
813
 *
814
 * TODO: template + preprocess
815
 */
816
function theme_views_ui_view_info($variables) {
817
  $view = $variables['view'];
818
  $title = $view->get_human_name();
819
820
  $displays = _views_ui_get_displays_list($view);
821
  $displays = empty($displays) ? t('None') : format_plural(count($displays), 'Display', 'Displays') . ': ' . '<em>' . implode(', ', $displays) . '</em>';
822
823
  switch ($view->type) {
824
    case t('Default'):
825
    default:
826
      $type = t('In code');
827
      break;
828
829
    case t('Normal'):
830
      $type = t('In database');
831
      break;
832
833
    case t('Overridden'):
834
      $type = t('Database overriding code');
835
  }
836
837
  $output = '';
838
  $output .= '<div class="views-ui-view-title">' . check_plain($title) . "</div>\n";
839
  $output .= '<div class="views-ui-view-displays">' . $displays . "</div>\n";
840
  $output .= '<div class="views-ui-view-storage">' . $type . "</div>\n";
841
  $output .= '<div class="views-ui-view-base">' . t('Type') . ': ' . check_plain($variables['base']). "</div>\n";
842
  return $output;
843
}
844
845
/**
846
 * Page to delete a view.
847
 */
848
function views_ui_break_lock_confirm($form, &$form_state, $view) {
849
  $form_state['view'] = &$view;
850
  $form = array();
851
852
  if (empty($view->locked)) {
853
    $form['message']['#markup'] = t('There is no lock on view %name to break.', array('%name' => $view->name));
854
    return $form;
855
  }
856
857
  $cancel = 'admin/structure/views/view/' . $view->name . '/edit';
858
859
  $account = user_load($view->locked->uid);
860
  return confirm_form($form,
861
                  t('Are you sure you want to break the lock on view %name?',
862
                  array('%name' => $view->name)),
863
                  $cancel,
864
                  t('By breaking this lock, any unsaved changes made by !user will be lost!', array('!user' => theme('username', array('account' => $account)))),
865
                  t('Break lock'),
866
                  t('Cancel'));
867
}
868
869
/**
870
 * Submit handler to break_lock a view.
871
 */
872
function views_ui_break_lock_confirm_submit(&$form, &$form_state) {
873
  ctools_object_cache_clear_all('view', $form_state['view']->name);
874
  $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit';
875
  drupal_set_message(t('The lock has been broken and you may now edit this view.'));
876
}
877
878
/**
879
 * Helper function to return the used display_id for the edit page
880
 *
881
 * This function handles access to the display.
882
 */
883
function views_ui_edit_page_display($view, $display_id) {
884
  // Determine the displays available for editing.
885
  if ($tabs = views_ui_edit_page_display_tabs($view, $display_id)) {
886
    // If a display isn't specified, use the first one.
887
    if (empty($display_id)) {
888
      foreach ($tabs as $id => $tab) {
889
        if (!isset($tab['#access']) || $tab['#access']) {
890
          $display_id = $id;
891
          break;
892
        }
893
      }
894
    }
895
    // If a display is specified, but we don't have access to it, return
896
    // an access denied page.
897
    if ($display_id && (!isset($tabs[$display_id]) || (isset($tabs[$display_id]['#access']) && !$tabs[$display_id]['#access']))) {
898
      return MENU_ACCESS_DENIED;
899
    }
900
901
    return $display_id;
902
  }
903
  elseif ($display_id) {
904
    return MENU_ACCESS_DENIED;
905
  }
906
  else {
907
    $display_id = NULL;
908
  }
909
910
  return $display_id;
911
}
912
913
/**
914
 * Page callback for the Edit View page.
915
 */
916
function views_ui_edit_page($view, $display_id = NULL) {
917
  $display_id = views_ui_edit_page_display($view, $display_id);
918
  if (!in_array($display_id, array(MENU_ACCESS_DENIED, MENU_NOT_FOUND))) {
919
    $build = array();
920
    $build['edit_form'] = drupal_get_form('views_ui_edit_form', $view, $display_id);
921
    $build['preview'] = views_ui_build_preview($view, $display_id, FALSE);
922
  }
923
  else {
924
    $build = $display_id;
925
  }
926
927
  return $build;
928
}
929
930
function views_ui_build_preview($view, $display_id, $render = TRUE) {
931
  if (isset($_POST['ajax_html_ids'])) {
932
    unset($_POST['ajax_html_ids']);
933
  }
934
935
  $build = array(
936
    '#theme_wrappers' => array('container'),
937
    '#attributes' => array('id' => 'views-preview-wrapper', 'class' => 'views-admin clearfix'),
938
  );
939
940
  $form_state = array('build_info' => array('args' => array($view, $display_id)));
941
  $build['controls'] = drupal_build_form('views_ui_preview_form', $form_state);
942
943
  $args = array();
944
  if (!empty($form_state['values']['view_args'])) {
945
    $args = explode('/', $form_state['values']['view_args']);
946
  }
947
948
  $build['preview'] = array(
949
    '#theme_wrappers' => array('container'),
950
    '#attributes' => array('id' => 'views-live-preview'),
951
    '#markup' => $render ? views_ui_preview($view->clone_view(), $display_id, $args) : '',
952
  );
953
954
  return $build;
955
}
956
957
/**
958
 * Form builder callback for editing a View.
959
 *
960
 * @todo Remove as many #prefix/#suffix lines as possible. Use #theme_wrappers
961
 *   instead.
962
 *
963
 * @todo Rename to views_ui_edit_view_form(). See that function for the "old"
964
 *   version.
965
 *
966
 * @see views_ui_ajax_get_form()
967
 */
968
function views_ui_edit_form($form, &$form_state, $view, $display_id = NULL) {
969
  // Do not allow the form to be cached, because $form_state['view'] can become
970
  // stale between page requests.
971
  // See views_ui_ajax_get_form() for how this affects #ajax.
972
  // @todo To remove this and allow the form to be cacheable:
973
  //   - Change $form_state['view'] to $form_state['temporary']['view'].
974
  //   - Add a #process function to initialize $form_state['temporary']['view']
975
  //     on cached form submissions.
976
  //   - Update ctools_include() to support cached forms, or else use
977
  //     form_load_include().
978
  $form_state['no_cache'] = TRUE;
979
980
  if ($display_id) {
981
    if (!$view->set_display($display_id)) {
982
      $form['#markup'] = t('Invalid display id @display', array('@display' => $display_id));
983
      return $form;
984
    }
985
986
    $view->fix_missing_relationships();
987
  }
988
989
  ctools_include('dependent');
990
  $form['#attached']['js'][] = ctools_attach_js('dependent');
991
  $form['#attached']['js'][] = ctools_attach_js('collapsible-div');
992
993
  $form['#tree'] = TRUE;
994
  // @todo When more functionality is added to this form, cloning here may be
995
  //   too soon. But some of what we do with $view later in this function
996
  //   results in making it unserializable due to PDO limitations.
997
  $form_state['view'] = clone($view);
998
999
  $form['#attached']['library'][] = array('system', 'ui.tabs');
1000
  $form['#attached']['library'][] = array('system', 'ui.dialog');
1001
  $form['#attached']['library'][] = array('system', 'drupal.ajax');
1002
  $form['#attached']['library'][] = array('system', 'jquery.form');
1003
  // TODO: This should be getting added to the page when an ajax popup calls
1004
  // for it, instead of having to add it manually here.
1005
  $form['#attached']['js'][] = 'misc/tabledrag.js';
1006
1007
  $form['#attached']['css'] = views_ui_get_admin_css();
1008
  $module_path = drupal_get_path('module', 'views_ui');
1009
1010
  $form['#attached']['js'][] = $module_path . '/js/views-admin.js';
1011
  $form['#attached']['js'][] = array(
1012
    'data' => array('views' => array('ajax' => array(
1013
      'id' => '#views-ajax-body',
1014
      'title' => '#views-ajax-title',
1015
      'popup' => '#views-ajax-popup',
1016
      'defaultForm' => views_ui_get_default_ajax_message(),
1017
    ))),
1018
    'type' => 'setting',
1019
  );
1020
1021
  $form += array(
1022
    '#prefix' => '',
1023
    '#suffix' => '',
1024
  );
1025
  $form['#prefix'] = $form['#prefix'] . '<div class="views-edit-view views-admin clearfix">';
1026
  $form['#suffix'] = '</div>' . $form['#suffix'];
1027
1028
  $form['#attributes']['class'] = array('form-edit');
1029
1030
  if (isset($view->locked) && is_object($view->locked)) {
1031
    $form['locked'] = array(
1032
      '#theme_wrappers' => array('container'),
1033
      '#attributes' => array('class' => array('view-locked', 'messages', 'warning')),
1034
      '#markup' => 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="!break">break this lock</a>.', array('!user' => theme('username', array('account' => user_load($view->locked->uid))), '!age' => format_interval(REQUEST_TIME - $view->locked->updated), '!break' => url('admin/structure/views/view/' . $view->name . '/break-lock'))),
1035
    );
1036
  }
1037
  if (isset($view->vid) && $view->vid == 'new') {
1038
    $message = t('* All changes are stored temporarily. Click Save to make your changes permanent. Click Cancel to discard the view.');
1039
  }
1040
  else {
1041
    $message = t('* All changes are stored temporarily. Click Save to make your changes permanent. Click Cancel to discard your changes.');
1042
  }
1043
1044
  $form['changed'] = array(
1045
    '#theme_wrappers' => array('container'),
1046
    '#attributes' => array('class' => array('view-changed', 'messages', 'warning')),
1047
    '#markup' => $message,
1048
  );
1049
  if (empty($view->changed)) {
1050
    $form['changed']['#attributes']['class'][] = 'js-hide';
1051
  }
1052
1053
  $form['help_text'] = array(
1054
    '#prefix' => '<div>',
1055
    '#suffix' => '</div>',
1056
    '#markup' => t('Modify the display(s) of your view below or add new displays.'),
1057
  );
1058
1059
  $form['actions'] = array(
1060
    '#type' => 'actions',
1061
    '#weight' => 0,
1062
  );
1063
1064
  if (empty($view->changed)) {
1065
    $form['actions']['#attributes'] = array(
1066
      'class' => array(
1067
        'js-hide',
1068
      ),
1069
    );
1070
  }
1071
1072
  $form['actions']['save'] = array(
1073
    '#type' => 'submit',
1074
    '#value' => t('Save'),
1075
    // Taken from the "old" UI. @TODO: Review and rename.
1076
    '#validate' => array('views_ui_edit_view_form_validate'),
1077
    '#submit' => array('views_ui_edit_view_form_submit'),
1078
  );
1079
  $form['actions']['cancel'] = array(
1080
    '#type' => 'submit',
1081
    '#value' => t('Cancel'),
1082
    '#submit' => array('views_ui_edit_view_form_cancel'),
1083
  );
1084
1085
  $form['displays'] = array(
1086
    '#prefix' => '<h1 class="unit-title clearfix">' . t('Displays') . '</h1>' . "\n" . '<div class="views-displays">',
1087
    '#suffix' => '</div>',
1088
  );
1089
1090
  $form['displays']['top'] = views_ui_render_display_top($view, $display_id);
1091
1092
  // The rest requires a display to be selected.
1093
  if ($display_id) {
1094
    $form_state['display_id'] = $display_id;
1095
1096
    // The part of the page where editing will take place.
1097
    // This element is the ctools collapsible-div container for the display edit elements.
1098
    $form['displays']['settings'] = array(
1099
      '#theme_wrappers' => array('container'),
1100
      '#attributes' => array(
1101
        'class' => array(
1102
          'views-display-settings',
1103
          'box-margin',
1104
          'ctools-collapsible-container',
1105
        ),
1106
      ),
1107
      '#id' => 'edit-display-settings',
1108
    );
1109
    $display_title = views_ui_get_display_label($view, $display_id, FALSE);
1110
    // Add a handle for the ctools collapsible-div. The handle is the title of the display
1111
    $form['displays']['settings']['tab_title']['#markup'] = '<h2 id="edit-display-settings-title" class="ctools-collapsible-handle">' . t('@display_title details', array('@display_title' => ucwords($display_title))) . '</h2>';
1112
    // Add a text that the display is disabled.
1113
    if (!empty($view->display[$display_id]->handler)) {
1114
      $enabled = $view->display[$display_id]->handler->get_option('enabled');
1115
      if (empty($enabled)) {
1116
        $form['displays']['settings']['disabled']['#markup'] = t('This display is disabled.');
1117
      }
1118
    }
1119
    // The ctools collapsible-div content
1120
    $form['displays']['settings']['settings_content']= array(
1121
      '#theme_wrappers' => array('container'),
1122
      '#id' => 'edit-display-settings-content',
1123
      '#attributes' => array(
1124
        'class' => array(
1125
          'ctools-collapsible-content',
1126
        ),
1127
      ),
1128
    );
1129
    // Add the edit display content
1130
    $form['displays']['settings']['settings_content']['tab_content'] = views_ui_get_display_tab($view, $display_id);
1131
    $form['displays']['settings']['settings_content']['tab_content']['#theme_wrappers'] = array('container');
1132
    $form['displays']['settings']['settings_content']['tab_content']['#attributes'] = array('class' => array('views-display-tab'));
1133
    $form['displays']['settings']['settings_content']['tab_content']['#id'] = 'views-tab-' . $display_id;
1134
    // Mark deleted displays as such.
1135
    if (!empty($view->display[$display_id]->deleted)) {
1136
      $form['displays']['settings']['settings_content']['tab_content']['#attributes']['class'][] = 'views-display-deleted';
1137
    }
1138
    // Mark disabled displays as such.
1139
    if (empty($enabled)) {
1140
      $form['displays']['settings']['settings_content']['tab_content']['#attributes']['class'][] = 'views-display-disabled';
1141
    }
1142
1143
    // The content of the popup dialog.
1144
    $form['ajax-area'] = array(
1145
      '#theme_wrappers' => array('container'),
1146
      '#id' => 'views-ajax-popup',
1147
    );
1148
    $form['ajax-area']['ajax-title'] = array(
1149
      '#markup' => '<h2 id="views-ajax-title"></h2>',
1150
    );
1151
    $form['ajax-area']['ajax-body'] = array(
1152
      '#theme_wrappers' => array('container'),
1153
      '#id' => 'views-ajax-body',
1154
      '#markup' => views_ui_get_default_ajax_message(),
1155
    );
1156
  }
1157
1158
  // If relationships had to be fixed, we want to get that into the cache
1159
  // so that edits work properly, and to try to get the user to save it
1160
  // so that it's not using weird fixed up relationships.
1161
  if (!empty($view->relationships_changed) && empty($_POST)) {
1162
    drupal_set_message(t('This view has been automatically updated to fix missing relationships. While this View should continue to work, you should verify that the automatic updates are correct and save this view.'));
1163
    views_ui_cache_set($view);
1164
  }
1165
  return $form;
1166
}
1167
1168
/**
1169
 * Provide the preview formulas and the preview output, too.
1170
 */
1171
function views_ui_preview_form($form, &$form_state, $view, $display_id = 'default') {
1172
  $form_state['no_cache'] = TRUE;
1173
  $form_state['view'] = $view;
1174
1175
  $form['#attributes'] = array('class' => array('clearfix',));
1176
1177
  // Add a checkbox controlling whether or not this display auto-previews.
1178
  $form['live_preview'] = array(
1179
    '#type' => 'checkbox',
1180
    '#id' => 'edit-displays-live-preview',
1181
    '#title' => t('Auto preview'),
1182
    '#default_value' => variable_get('views_ui_always_live_preview', TRUE),
1183
  );
1184
1185
  // Add the arguments textfield
1186
  $form['view_args'] = array(
1187
    '#type' => 'textfield',
1188
    '#title' => t('Preview with contextual filters:'),
1189
    '#description' => t('Separate contextual filter values with a "/". For example, %example.', array('%example' => '40/12/10')),
1190
    '#id' => 'preview-args',
1191
//      '#attributes' => array('class' => array('ctools-auto-submit')),
1192
  );
1193
1194
  // Add the preview button
1195
  $form['button'] = array(
1196
    '#type' => 'submit',
1197
    '#value' => t('Update preview'),
1198
    '#attributes' => array('class' => array('arguments-preview', 'ctools-auto-submit-click')),
1199
    '#pre_render' => array('ctools_dependent_pre_render'),
1200
    '#prefix' => '<div id="preview-submit-wrapper">',
1201
    '#suffix' => '</div>',
1202
    '#id' => 'preview-submit',
1203
    '#submit' => array('views_ui_edit_form_submit_preview'),
1204
    '#ajax' => array(
1205
      'path' => 'admin/structure/views/view/' . $view->name . '/preview/' . $display_id . '/ajax',
1206
      'wrapper' => 'views-preview-wrapper',
1207
      'event' => 'click',
1208
      'progress' => array('type' => 'throbber'),
1209
      'method' => 'replace',
1210
    ),
1211
    // Make ENTER in arguments textfield (and other controls) submit the form
1212
    // as this button, not the Save button.
1213
    // @todo This only works for JS users. To make this work for nojs users,
1214
    //   we may need to split Preview into a separate form.
1215
    '#process' => array_merge(array('views_ui_default_button'), element_info_property('submit', '#process', array())),
1216
  );
1217
  $form['#action'] = url('admin/structure/views/view/' . $view->name .'/preview/' . $display_id);
1218
1219
  return $form;
1220
}
1221
1222
/**
1223
 * Render the top of the display so it can be updated during ajax operations.
1224
 */
1225
function views_ui_render_display_top($view, $display_id) {
1226
  $element['#theme_wrappers'] = array('views_container');
1227
  $element['#attributes']['class'] = array('views-display-top', 'clearfix');
1228
  $element['#attributes']['id'] = array('views-display-top');
1229
1230
  // Extra actions for the display
1231
  $element['extra_actions'] = array(
1232
    '#theme' => 'links__ctools_dropbutton',
1233
    '#attributes' => array(
1234
        'id' => 'views-display-extra-actions',
1235
        'class' => array(
1236
          'horizontal', 'right', 'links', 'actions',
1237
        ),
1238
      ),
1239
    '#links' => array(
1240
      'edit-details' => array(
1241
        'title' => t('edit view name/description'),
1242
        'href' => "admin/structure/views/nojs/edit-details/$view->name",
1243
        'attributes' => array('class' => array('views-ajax-link')),
1244
      ),
1245
      'analyze' => array(
1246
        'title' => t('analyze view'),
1247
        'href' => "admin/structure/views/nojs/analyze/$view->name/$display_id",
1248
        'attributes' => array('class' => array('views-ajax-link')),
1249
      ),
1250
      'clone' => array(
1251
        'title' => t('clone view'),
1252
        'href' => "admin/structure/views/view/$view->name/clone",
1253
      ),
1254
      'export' => array(
1255
        'title' => t('export view'),
1256
        'href' => "admin/structure/views/view/$view->name/export",
1257
      ),
1258
      'reorder' => array(
1259
        'title' => t('reorder displays'),
1260
        'href' => "admin/structure/views/nojs/reorder-displays/$view->name/$display_id",
1261
        'attributes' => array('class' => array('views-ajax-link')),
1262
      ),
1263
    ),
1264
  );
1265
1266
  // Let other modules add additional links here.
1267
  drupal_alter('views_ui_display_top_links', $element['extra_actions']['#links'], $view, $display_id);
1268
1269
  if (isset($view->type) && $view->type != t('Default')) {
1270
    if ($view->type == t('Overridden')) {
1271
      $element['extra_actions']['#links']['revert'] = array(
1272
        'title' => t('revert view'),
1273
        'href' => "admin/structure/views/view/$view->name/revert",
1274
        'query' => array('destination' => "admin/structure/views/view/$view->name"),
1275
      );
1276
    }
1277
    else {
1278
      $element['extra_actions']['#links']['delete'] = array(
1279
        'title' => t('delete view'),
1280
        'href' => "admin/structure/views/view/$view->name/delete",
1281
      );
1282
    }
1283
  }
1284
1285
  // Determine the displays available for editing.
1286
  if ($tabs = views_ui_edit_page_display_tabs($view, $display_id)) {
1287
    if ($display_id) {
1288
      $tabs[$display_id]['#active'] = TRUE;
1289
    }
1290
    $tabs['#prefix'] = '<h2 class="element-invisible">' . t('Secondary tabs') . '</h2><ul id = "views-display-menu-tabs" class="tabs secondary">';
1291
    $tabs['#suffix'] = '</ul>';
1292
    $element['tabs'] = $tabs;
1293
  }
1294
1295
  // Buttons for adding a new display.
1296
  foreach (views_fetch_plugin_names('display', NULL, array($view->base_table)) as $type => $label) {
1297
    $element['add_display'][$type] = array(
1298
      '#type' => 'submit',
1299
      '#value' => t('Add !display', array('!display' => $label)),
1300
      '#limit_validation_errors' => array(),
1301
      '#submit' => array('views_ui_edit_form_submit_add_display', 'views_ui_edit_form_submit_delay_destination'),
1302
      '#attributes' => array('class' => array('add-display')),
1303
      // Allow JavaScript to remove the 'Add ' prefix from the button label when
1304
      // placing the button in a "Add" dropdown menu.
1305
      '#process' => array_merge(array('views_ui_form_button_was_clicked'), element_info_property('submit', '#process', array())),
1306
      '#values' => array(t('Add !display', array('!display' => $label)), $label),
1307
    );
1308
  }
1309
1310
  return $element;
1311
}
1312
1313
function views_ui_get_default_ajax_message() {
1314
  return '<div class="message">' . t("Click on an item to edit that item's details.") . '</div>';
1315
}
1316
1317
/**
1318
 * Submit handler to add a display to a view.
1319
 */
1320
function views_ui_edit_form_submit_add_display($form, &$form_state) {
1321
  $view = $form_state['view'];
1322
1323
  // Create the new display.
1324
  $parents = $form_state['triggering_element']['#parents'];
1325
  $display_type = array_pop($parents);
1326
  $display_id = $view->add_display($display_type);
1327
  views_ui_cache_set($view);
1328
1329
  // Redirect to the new display's edit page.
1330
  $form_state['redirect'] = 'admin/structure/views/view/' . $view->name . '/edit/' . $display_id;
1331
}
1332
1333
/**
1334
 * Submit handler to duplicate a display for a view.
1335
 */
1336
function views_ui_edit_form_submit_duplicate_display($form, &$form_state) {
1337
  $view = $form_state['view'];
1338
  $display_id = $form_state['display_id'];
1339
1340
  // Create the new display.
1341
  $display = $view->display[$display_id];
1342
  $new_display_id = $view->add_display($display->display_plugin);
1343
  $view->display[$new_display_id] = clone $display;
1344
  $view->display[$new_display_id]->id = $new_display_id;
1345
1346
  // By setting the current display the changed marker will appear on the new
1347
  // display.
1348
  $view->current_display = $new_display_id;
1349
  views_ui_cache_set($view);
1350
1351
  // Redirect to the new display's edit page.
1352
  $form_state['redirect'] = 'admin/structure/views/view/' . $view->name . '/edit/' . $new_display_id;
1353
}
1354
1355
/**
1356
 * Submit handler to delete a display from a view.
1357
 */
1358
function views_ui_edit_form_submit_delete_display($form, &$form_state) {
1359
  $view = $form_state['view'];
1360
  $display_id = $form_state['display_id'];
1361
1362
  // Mark the display for deletion.
1363
  $view->display[$display_id]->deleted = TRUE;
1364
  views_ui_cache_set($view);
1365
1366
  // Redirect to the top-level edit page. The first remaining display will
1367
  // become the active display.
1368
  $form_state['redirect'] = 'admin/structure/views/view/' . $view->name;
1369
}
1370
1371
/**
1372
 * Submit handler to add a restore a removed display to a view.
1373
 */
1374
function views_ui_edit_form_submit_undo_delete_display($form, &$form_state) {
1375
  // Create the new display
1376
  $id = $form_state['display_id'];
1377
  $form_state['view']->display[$id]->deleted = FALSE;
1378
1379
  // Store in cache
1380
  views_ui_cache_set($form_state['view']);
1381
1382
  // Redirect to the top-level edit page.
1383
  $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit/' . $id;
1384
}
1385
1386
/**
1387
 * Submit handler to enable a disabled display.
1388
 */
1389
function views_ui_edit_form_submit_enable_display($form, &$form_state) {
1390
  $id = $form_state['display_id'];
1391
  // set_option doesn't work because this would might affect upper displays
1392
  $form_state['view']->display[$id]->handler->set_option('enabled', TRUE);
1393
1394
  // Store in cache
1395
  views_ui_cache_set($form_state['view']);
1396
1397
  // Redirect to the top-level edit page.
1398
  $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit/' . $id;
1399
}
1400
1401
/**
1402
 * Submit handler to disable display.
1403
 */
1404
function views_ui_edit_form_submit_disable_display($form, &$form_state) {
1405
  $id = $form_state['display_id'];
1406
  $form_state['view']->display[$id]->handler->set_option('enabled', FALSE);
1407
1408
  // Store in cache
1409
  views_ui_cache_set($form_state['view']);
1410
1411
  // Redirect to the top-level edit page.
1412
  $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit/' . $id;
1413
}
1414
1415
/**
1416
 * Submit handler when Preview button is clicked.
1417
 */
1418
function views_ui_edit_form_submit_preview($form, &$form_state) {
1419
  // Rebuild the form with a pristine $view object.
1420
  $form_state['build_info']['args'][0] = views_ui_cache_load($form_state['view']->name);
1421
  $form_state['show_preview'] = TRUE;
1422
  $form_state['rebuild'] = TRUE;
1423
}
1424
1425
/**
1426
 * Submit handler for form buttons that do not complete a form workflow.
1427
 *
1428
 * The Edit View form is a multistep form workflow, but with state managed by
1429
 * the CTools object cache rather than $form_state['rebuild']. Without this
1430
 * submit handler, buttons that add or remove displays would redirect to the
1431
 * destination parameter (e.g., when the Edit View form is linked to from a
1432
 * contextual link). This handler can be added to buttons whose form submission
1433
 * should not yet redirect to the destination.
1434
 */
1435
function views_ui_edit_form_submit_delay_destination($form, &$form_state) {
1436
  if (isset($_GET['destination']) && $form_state['redirect'] !== FALSE) {
1437
    if (!isset($form_state['redirect'])) {
1438
      $form_state['redirect'] = $_GET['q'];
1439
    }
1440
    if (is_string($form_state['redirect'])) {
1441
      $form_state['redirect'] = array($form_state['redirect']);
1442
    }
1443
    $options = isset($form_state['redirect'][1]) ? $form_state['redirect'][1] : array();
1444
    if (!isset($options['query']['destination'])) {
1445
      $options['query']['destination'] = $_GET['destination'];
1446
    }
1447
    $form_state['redirect'][1] = $options;
1448
    unset($_GET['destination']);
1449
  }
1450
}
1451
1452
/**
1453
 * Adds tabs for navigating across Displays when editing a View.
1454
 *
1455
 * This function can be called from hook_menu_local_tasks_alter() to implement
1456
 * these tabs as secondary local tasks, or it can be called from elsewhere if
1457
 * having them as secondary local tasks isn't desired. The caller is responsible
1458
 * for setting the active tab's #active property to TRUE.
1459
 *
1460
 * @param view $view
1461
 *    The view which will be edited.
1462
 * @param $display_id
1463
 *    The display_id which is edited on the current request.
1464
 */
1465
function views_ui_edit_page_display_tabs($view, $display_id = NULL) {
1466
  $tabs = array();
1467
1468
  // Create a tab for each display.
1469
  foreach ($view->display as $id => $display) {
1470
    $tabs[$id] = array(
1471
      '#theme' => 'menu_local_task',
1472
      '#link' => array(
1473
        'title' => views_ui_get_display_label($view, $id),
1474
        'href' => 'admin/structure/views/view/' . $view->name . '/edit/' . $id,
1475
        'localized_options' => array(),
1476
      ),
1477
    );
1478
    if (!empty($display->deleted)) {
1479
      $tabs[$id]['#link']['localized_options']['attributes']['class'][] = 'views-display-deleted-link';
1480
    }
1481
    if (isset($display->display_options['enabled']) && !$display->display_options['enabled']) {
1482
      $tabs[$id]['#link']['localized_options']['attributes']['class'][] = 'views-display-disabled-link';
1483
    }
1484
  }
1485
1486
  // If the default display isn't supposed to be shown, don't display its tab, unless it's the only display.
1487
  if ((!views_ui_show_default_display($view) && $display_id != 'default') && count($tabs) > 1) {
1488
    $tabs['default']['#access'] = FALSE;
1489
  }
1490
1491
  // Mark the display tab as red to show validation errors.
1492
  $view->validate();
1493
  foreach ($view->display as $id => $display) {
1494
    if (!empty($view->display_errors[$id])) {
1495
      // Always show the tab.
1496
      $tabs[$id]['#access'] = TRUE;
1497
      // Add a class to mark the error and a title to make a hover tip.
1498
      $tabs[$id]['#link']['localized_options']['attributes']['class'][] = 'error';
1499
      $tabs[$id]['#link']['localized_options']['attributes']['title'] = t('This display has one or more validation errors; please review it.');
1500
    }
1501
  }
1502
1503
  return $tabs;
1504
}
1505
1506
/**
1507
 * Controls whether or not the default display should have its own tab on edit.
1508
 */
1509
function views_ui_show_default_display($view) {
1510
  // Always show the default display for advanced users who prefer that mode.
1511
  $advanced_mode = variable_get('views_ui_show_master_display', FALSE);
1512
  // For other users, show the default display only if there are no others, and
1513
  // hide it if there's at least one "real" display.
1514
  $additional_displays = (count($view->display) == 1);
1515
1516
  return $advanced_mode || $additional_displays;
1517
}
1518
1519
/**
1520
 * Returns a renderable array representing the edit page for one display.
1521
 */
1522
function views_ui_get_display_tab($view, $display_id) {
1523
  $build = array();
1524
  $display = $view->display[$display_id];
1525
  // If the plugin doesn't exist, display an error message instead of an edit
1526
  // page.
1527
  if (empty($display->handler)) {
1528
    $title = isset($display->display_title) ? $display->display_title : t('Invalid');
1529
    // @TODO: Improved UX for the case where a plugin is missing.
1530
    $build['#markup'] = t("Error: Display @display refers to a plugin named '@plugin', but that plugin is not available.", array('@display' => $display->id, '@plugin' => $display->display_plugin));
1531
  }
1532
  // Build the content of the edit page.
1533
  else {
1534
    $build['details'] = views_ui_get_display_tab_details($view, $display);
1535
  }
1536
  // In AJAX context, views_ui_regenerate_tab() returns this outside of form
1537
  // context, so hook_form_views_ui_edit_form_alter() is insufficient.
1538
  drupal_alter('views_ui_display_tab', $build, $view, $display_id);
1539
  return $build;
1540
}
1541
1542
/**
1543
 * Helper function to get the display details section of the edit UI.
1544
 *
1545
 * @param $view
1546
 * @param $display
1547
 *
1548
 * @return array
1549
 *   A renderable page build array.
1550
 */
1551
function views_ui_get_display_tab_details($view, $display) {
1552
  $display_title = views_ui_get_display_label($view, $display->id, FALSE);
1553
  $build = array(
1554
    '#theme_wrappers' => array('container'),
1555
    '#attributes' => array('id' => 'edit-display-settings-details',),
1556
  );
1557
1558
  $plugin = views_fetch_plugin_data('display', $view->display[$display->id]->display_plugin);
1559
  // The following is for display purposes only. We need to determine if there is more than one button and wrap
1560
  // the buttons in a .ctools-dropbutton class if more than one is present.  Otherwise, we'll just wrap the
1561
  // actions in the .ctools-button class.
1562
  $isDisplayDeleted = !empty($display->deleted);
1563
  $isDeletable = empty($plugin['no remove']);
1564
  // The master display cannot be cloned.
1565
  $isDefault = $display->id == 'default';
1566
  // @todo: Figure out why get_option doesn't work here.
1567
  $isEnabled = $display->handler->get_option('enabled');
1568
1569
  if (!$isDisplayDeleted && $isDeletable && !$isDefault) {
1570
    $prefix = '<div class="ctools-no-js ctools-button ctools-dropbutton"><div class="ctools-link"><a href="#" class="ctools-twisty ctools-text">open</a></div><div class="ctools-content"><ul class="horizontal right actions">';
1571
    $suffix = '</ul></div></div>';
1572
    $itemElement = 'li';
1573
  }
1574
  else {
1575
    $prefix = '<div class="ctools-button"><div class="ctools-content"><ul class="horizontal right actions">';
1576
    $suffix = '</ul></div></div>';
1577
    $itemElement = 'li';
1578
  }
1579
1580
  if ($display->id != 'default') {
1581
    $build['top']['#theme_wrappers'] = array('container');
1582
    $build['top']['#attributes']['id'] = 'edit-display-settings-top';
1583
    $build['top']['#attributes']['class'] = array('views-ui-display-tab-actions', 'views-ui-display-tab-bucket', 'clearfix');
1584
1585
    // The Delete, Duplicate and Undo Delete buttons.
1586
    $build['top']['actions'] = array(
1587
      '#prefix' => $prefix,
1588
      '#suffix' => $suffix,
1589
    );
1590
1591
    if (!$isDisplayDeleted) {
1592
      if (!$isEnabled) {
1593
        $build['top']['actions']['enable'] = array(
1594
          '#type' => 'submit',
1595
          '#value' => t('enable @display_title', array('@display_title' => $display_title)),
1596
          '#limit_validation_errors' => array(),
1597
          '#submit' => array('views_ui_edit_form_submit_enable_display', 'views_ui_edit_form_submit_delay_destination'),
1598
          '#prefix' => '<' . $itemElement . ' class="enable">',
1599
          "#suffix" => '</' . $itemElement . '>',
1600
        );
1601
      }
1602
      // Add a link to view the page.
1603
      elseif ($display->handler->has_path()) {
1604
        $path = $display->handler->get_path();
1605
        if (strpos($path, '%') === FALSE) {
1606
          $build['top']['actions']['path'] = array(
1607
            '#type' => 'link',
1608
            '#title' => t('view @display', array('@display' => $display->display_title)),
1609
            '#options' => array('alt' => array(t("Go to the real page for this display"))),
1610
            '#href' => $path,
1611
            '#prefix' => '<' . $itemElement . ' class="view">',
1612
            "#suffix" => '</' . $itemElement . '>',
1613
          );
1614
        }
1615
      }
1616
      if (!$isDefault) {
1617
        $build['top']['actions']['duplicate'] = array(
1618
          '#type' => 'submit',
1619
          '#value' => t('clone @display_title', array('@display_title' => $display_title)),
1620
          '#limit_validation_errors' => array(),
1621
          '#submit' => array('views_ui_edit_form_submit_duplicate_display', 'views_ui_edit_form_submit_delay_destination'),
1622
          '#prefix' => '<' . $itemElement . ' class="duplicate">',
1623
          "#suffix" => '</' . $itemElement . '>',
1624
        );
1625
      }
1626
      if ($isDeletable) {
1627
        $build['top']['actions']['delete'] = array(
1628
          '#type' => 'submit',
1629
          '#value' => t('delete @display_title', array('@display_title' => $display_title)),
1630
          '#limit_validation_errors' => array(),
1631
          '#submit' => array('views_ui_edit_form_submit_delete_display', 'views_ui_edit_form_submit_delay_destination'),
1632
          '#prefix' => '<' . $itemElement . ' class="delete">',
1633
          "#suffix" => '</' . $itemElement . '>',
1634
        );
1635
      }
1636
      if ($isEnabled) {
1637
        $build['top']['actions']['disable'] = array(
1638
          '#type' => 'submit',
1639
          '#value' => t('disable @display_title', array('@display_title' => $display_title)),
1640
          '#limit_validation_errors' => array(),
1641
          '#submit' => array('views_ui_edit_form_submit_disable_display', 'views_ui_edit_form_submit_delay_destination'),
1642
          '#prefix' => '<' . $itemElement . ' class="disable">',
1643
          "#suffix" => '</' . $itemElement . '>',
1644
        );
1645
      }
1646
    }
1647
    else {
1648
      $build['top']['actions']['undo_delete'] = array(
1649
        '#type' => 'submit',
1650
        '#value' => t('undo delete of @display_title', array('@display_title' => $display_title)),
1651
        '#limit_validation_errors' => array(),
1652
        '#submit' => array('views_ui_edit_form_submit_undo_delete_display', 'views_ui_edit_form_submit_delay_destination'),
1653
        '#prefix' => '<' . $itemElement . ' class="undo-delete">',
1654
        "#suffix" => '</' . $itemElement . '>',
1655
      );
1656
    }
1657
1658
    // The area above the three columns.
1659
    $build['top']['display_title'] = array(
1660
      '#theme' => 'views_ui_display_tab_setting',
1661
      '#description' => t('Display name'),
1662
      '#link' => $display->handler->option_link(check_plain($display_title), 'display_title'),
1663
    );
1664
  }
1665
1666
  $build['columns'] = array();
1667
  $build['columns']['#theme_wrappers'] = array('container');
1668
  $build['columns']['#attributes'] = array('id' => 'edit-display-settings-main', 'class' => array('clearfix', 'views-display-columns'),);
1669
1670
  $build['columns']['first']['#theme_wrappers'] = array('container');
1671
  $build['columns']['first']['#attributes'] = array('class' => array('views-display-column', 'first'));
1672
1673
  $build['columns']['second']['#theme_wrappers'] = array('container');
1674
  $build['columns']['second']['#attributes'] = array('class' => array('views-display-column', 'second'));
1675
1676
  $build['columns']['second']['settings'] = array();
1677
  $build['columns']['second']['header'] = array();
1678
  $build['columns']['second']['footer'] = array();
1679
  $build['columns']['second']['pager'] = array();
1680
1681
  // The third column buckets are wrapped in a CTools collapsible div
1682
  $build['columns']['third']['#theme_wrappers'] = array('container');
1683
  $build['columns']['third']['#attributes'] = array('class' => array('views-display-column', 'third', 'ctools-collapsible-container', 'ctools-collapsible-remember'));
1684
  // Specify an id that won't change after AJAX requests, so ctools can keep
1685
  // track of the user's preferred collapsible state. Use the same id across
1686
  // different displays of the same view, so changing displays doesn't
1687
  // recollapse the column.
1688
  $build['columns']['third']['#attributes']['id'] = 'views-ui-advanced-column-' . $view->name;
1689
  // Collapse the div by default.
1690
  if (!variable_get('views_ui_show_advanced_column', FALSE)) {
1691
    $build['columns']['third']['#attributes']['class'][] = 'ctools-collapsed';
1692
  }
1693
  $build['columns']['third']['advanced'] = array('#markup' => '<h3 class="ctools-collapsible-handle"><a href="">' . t('Advanced') . '</a></h3>',);
1694
  $build['columns']['third']['collapse']['#theme_wrappers'] = array('container');
1695
  $build['columns']['third']['collapse']['#attributes'] = array('class' => array('ctools-collapsible-content',),);
1696
1697
  // Each option (e.g. title, access, display as grid/table/list) fits into one
1698
  // of several "buckets," or boxes (Format, Fields, Sort, and so on).
1699
  $buckets = array();
1700
1701
  // Fetch options from the display plugin, with a list of buckets they go into.
1702
  $options = array();
1703
  $display->handler->options_summary($buckets, $options);
1704
1705
  // Place each option into its bucket.
1706
  foreach ($options as $id => $option) {
1707
    // Each option self-identifies as belonging in a particular bucket.
1708
    $buckets[$option['category']]['build'][$id] = views_ui_edit_form_get_build_from_option($id, $option, $view, $display);
1709
  }
1710
1711
  // Place each bucket into the proper column.
1712
  foreach ($buckets as $id => $bucket) {
1713
    // Let buckets identify themselves as belonging in a column.
1714
    if (isset($bucket['column']) && isset($build['columns'][$bucket['column']])) {
1715
      $column = $bucket['column'];
1716
    }
1717
    // If a bucket doesn't pick one of our predefined columns to belong to, put
1718
    // it in the last one.
1719
    else {
1720
      $column = 'third';
1721
    }
1722
    if (isset($bucket['build']) && is_array($bucket['build'])) {
1723
      // The third column is a CTools collapsible div, so
1724
      // the structure of the form is a little different for this column
1725
      if ($column === 'third') {
1726
        $build['columns'][$column]['collapse'][$id] = $bucket['build'];
1727
        $build['columns'][$column]['collapse'][$id]['#theme_wrappers'][] = 'views_ui_display_tab_bucket';
1728
        $build['columns'][$column]['collapse'][$id]['#title'] = !empty($bucket['title']) ? $bucket['title'] : '';
1729
        $build['columns'][$column]['collapse'][$id]['#name'] = !empty($bucket['title']) ? $bucket['title'] : $id;
1730
      }
1731
      else {
1732
        $build['columns'][$column][$id] = $bucket['build'];
1733
        $build['columns'][$column][$id]['#theme_wrappers'][] = 'views_ui_display_tab_bucket';
1734
        $build['columns'][$column][$id]['#title'] = !empty($bucket['title']) ? $bucket['title'] : '';
1735
        $build['columns'][$column][$id]['#name'] = !empty($bucket['title']) ? $bucket['title'] : $id;
1736
      }
1737
    }
1738
  }
1739
1740
  $build['columns']['first']['fields'] = views_ui_edit_form_get_bucket('field', $view, $display);
1741
  $build['columns']['first']['filters'] = views_ui_edit_form_get_bucket('filter', $view, $display);
1742
  $build['columns']['first']['sorts'] = views_ui_edit_form_get_bucket('sort', $view, $display);
1743
  $build['columns']['second']['header'] = views_ui_edit_form_get_bucket('header', $view, $display);
1744
  $build['columns']['second']['footer'] = views_ui_edit_form_get_bucket('footer', $view, $display);
1745
  $build['columns']['third']['collapse']['arguments'] = views_ui_edit_form_get_bucket('argument', $view, $display);
1746
  $build['columns']['third']['collapse']['relationships'] = views_ui_edit_form_get_bucket('relationship', $view, $display);
1747
  $build['columns']['third']['collapse']['empty'] = views_ui_edit_form_get_bucket('empty', $view, $display);
1748
1749
  return $build;
1750
}
1751
1752
/**
1753
 * Build a renderable array representing one option on the edit form.
1754
 *
1755
 * This function might be more logical as a method on an object, if a suitable
1756
 * object emerges out of refactoring.
1757
 */
1758
function views_ui_edit_form_get_build_from_option($id, $option, $view, $display) {
1759
  $option_build = array();
1760
  $option_build['#theme'] = 'views_ui_display_tab_setting';
1761
1762
  $option_build['#description'] = $option['title'];
1763
1764
  $option_build['#link'] = $display->handler->option_link($option['value'], $id, '', empty($option['desc']) ? '' : $option['desc']);
1765
1766
  $option_build['#links'] = array();
1767
  if (!empty($option['links']) && is_array($option['links'])) {
1768
    foreach ($option['links'] as $link_id => $link_value) {
1769
      $option_build['#settings_links'][] = $display->handler->option_link($option['setting'], $link_id, 'views-button-configure', $link_value);
1770
    }
1771
  }
1772
1773
  if (!empty($display->handler->options['defaults'][$id])) {
1774
    $display_id = 'default';
1775
    $option_build['#defaulted'] = TRUE;
1776
  }
1777
  else {
1778
    $display_id = $display->id;
1779
    if (!$display->handler->is_default_display()) {
1780
      if ($display->handler->defaultable_sections($id)) {
1781
        $option_build['#overridden'] = TRUE;
1782
      }
1783
    }
1784
  }
1785
  $option_build['#attributes']['class'][] = drupal_clean_css_identifier($display_id . '-' . $id);
1786
  if (!empty($view->changed_sections[$display_id . '-' . $id])) {
1787
    $option_build['#changed'] = TRUE;
1788
  }
1789
  return $option_build;
1790
}
1791
1792
function template_preprocess_views_ui_display_tab_setting(&$variables) {
1793
  static $zebra = 0;
1794
  $variables['zebra'] = ($zebra % 2 === 0 ? 'odd' : 'even');
1795
  $zebra++;
1796
1797
  // Put the main link to the left side
1798
  array_unshift($variables['settings_links'], $variables['link']);
1799
  $variables['settings_links'] = implode('<span class="label">&nbsp;|&nbsp;</span>', $variables['settings_links']);
1800
1801
  // Add classes associated with this display tab to the overall list.
1802
  $variables['classes_array'] = array_merge($variables['classes_array'], $variables['class']);
1803
1804
  if (!empty($variables['defaulted'])) {
1805
    $variables['classes_array'][] = 'defaulted';
1806
  }
1807
  if (!empty($variables['overridden'])) {
1808
    $variables['classes_array'][] = 'overridden';
1809
    $variables['attributes_array']['title'][] = t('Overridden');
1810
  }
1811
1812
  // Append a colon to the description, if requested.
1813
  if ($variables['description'] && $variables['description_separator']) {
1814
    $variables['description'] .= t(':');
1815
  }
1816
}
1817
1818
function template_preprocess_views_ui_display_tab_bucket(&$variables) {
1819
  $element = $variables['element'];
1820
1821
  $variables['item_help_icon'] = '';
1822
  if (!empty($element['#item_help_icon'])) {
1823
    $variables['item_help_icon'] = render($element['#item_help_icon']);
1824
  }
1825
  if (!empty($element['#name'])) {
1826
    $variables['classes_array'][] = drupal_clean_css_identifier(strtolower($element['#name']));
1827
  }
1828
  if (!empty($element['#overridden'])) {
1829
    $variables['classes_array'][] = 'overridden';
1830
    $variables['attributes_array']['title'][] = t('Overridden');
1831
  }
1832
1833
  $variables['content'] = $element['#children'];
1834
  $variables['title'] = $element['#title'];
1835
  $variables['actions'] = !empty($element['#actions']) ? $element['#actions'] : '';
1836
}
1837
1838
function template_preprocess_views_ui_display_tab_column(&$variables) {
1839
  $element = $variables['element'];
1840
1841
  $variables['content'] = $element['#children'];
1842
  $variables['column'] = $element['#column'];
1843
}
1844
1845
/**
1846
 * Move form elements into fieldsets for presentation purposes.
1847
 *
1848
 * Many views forms use #tree = TRUE to keep their values in a hierarchy for
1849
 * easier storage. Moving the form elements into fieldsets during form building
1850
 * would break up that hierarchy. Therefore, we wait until the pre_render stage,
1851
 * where any changes we make affect presentation only and aren't reflected in
1852
 * $form_state['values'].
1853
 */
1854
function views_ui_pre_render_add_fieldset_markup($form) {
1855
  foreach (element_children($form) as $key) {
1856
    $element = $form[$key];
1857
    // In our form builder functions, we added an arbitrary #fieldset property
1858
    // to any element that belongs in a fieldset. If this form element has that
1859
    // property, move it into its fieldset.
1860
    if (isset($element['#fieldset']) && isset($form[$element['#fieldset']])) {
1861
      $form[$element['#fieldset']][$key] = $element;
1862
      // Remove the original element this duplicates.
1863
      unset($form[$key]);
1864
    }
1865
  }
1866
1867
  return $form;
1868
}
1869
1870
/**
1871
 * Flattens the structure of an element containing the #flatten property.
1872
 *
1873
 * If a form element has #flatten = TRUE, then all of it's children
1874
 * get moved to the same level as the element itself.
1875
 * So $form['to_be_flattened'][$key] becomes $form[$key], and
1876
 * $form['to_be_flattened'] gets unset.
1877
 */
1878
function views_ui_pre_render_flatten_data($form) {
1879
  foreach (element_children($form) as $key) {
1880
    $element = $form[$key];
1881
    if (!empty($element['#flatten'])) {
1882
      foreach (element_children($element) as $child_key) {
1883
        $form[$child_key] = $form[$key][$child_key];
1884
      }
1885
      // All done, remove the now-empty parent.
1886
      unset($form[$key]);
1887
    }
1888
  }
1889
1890
  return $form;
1891
}
1892
1893
/**
1894
 * Moves argument options into their place.
1895
 *
1896
 * When configuring the default argument behavior, almost each of the radio
1897
 * buttons has its own fieldset shown bellow it when the radio button is
1898
 * clicked. That fieldset is created through a custom form process callback.
1899
 * Each element that has #argument_option defined and pointing to a default
1900
 * behavior gets moved to the appropriate fieldset.
1901
 * So if #argument_option is specified as 'default', the element is moved
1902
 * to the 'default_options' fieldset.
1903
 */
1904
function views_ui_pre_render_move_argument_options($form) {
1905
  foreach (element_children($form) as $key) {
1906
    $element = $form[$key];
1907
    if (!empty($element['#argument_option'])) {
1908
      $container_name = $element['#argument_option'] . '_options';
1909
      if (isset($form['no_argument']['default_action'][$container_name])) {
1910
        $form['no_argument']['default_action'][$container_name][$key] = $element;
1911
      }
1912
      // Remove the original element this duplicates.
1913
      unset($form[$key]);
1914
    }
1915
  }
1916
  return $form;
1917
}
1918
1919
/**
1920
 * Custom form radios process function.
1921
 *
1922
 * Roll out a single radios element to a list of radios,
1923
 * using the options array as index.
1924
 * While doing that, create a container element underneath each option, which
1925
 * contains the settings related to that option.
1926
 *
1927
 * @see form_process_radios()
1928
 */
1929
function views_ui_process_container_radios($element) {
1930
  if (count($element['#options']) > 0) {
1931
    foreach ($element['#options'] as $key => $choice) {
1932
      $element += array($key => array());
1933
      // Generate the parents as the autogenerator does, so we will have a
1934
      // unique id for each radio button.
1935
      $parents_for_id = array_merge($element['#parents'], array($key));
1936
1937
      $element[$key] += array(
1938
        '#type' => 'radio',
1939
        '#title' => $choice,
1940
        // The key is sanitized in drupal_attributes() during output from the
1941
        // theme function.
1942
        '#return_value' => $key,
1943
        '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : NULL,
1944
        '#attributes' => $element['#attributes'],
1945
        '#parents' => $element['#parents'],
1946
        '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)),
1947
        '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
1948
      );
1949
      $element[$key . '_options'] = array(
1950
        '#type' => 'container',
1951
        '#attributes' => array('class' => array('views-admin-dependent')),
1952
      );
1953
    }
1954
  }
1955
  return $element;
1956
}
1957
1958
/**
1959
 * Import a view from cut & paste.
1960
 */
1961
function views_ui_import_page($form, &$form_state) {
1962
  $form['name'] = array(
1963
    '#type' => 'textfield',
1964
    '#title' => t('View name'),
1965
    '#description' => t('Enter the name to use for this view if it is different from the source view. Leave blank to use the name of the view.'),
1966
  );
1967
1968
  $form['name_override'] = array(
1969
    '#type' => 'checkbox',
1970
    '#title' => t('Replace an existing view if one exists with the same name'),
1971
  );
1972
1973
  $form['bypass_validation'] = array(
1974
    '#type' => 'checkbox',
1975
    '#title' => t('Bypass view validation'),
1976
    '#description' => t('Bypass the validation of plugins and handlers when importing this view.'),
1977
  );
1978
1979
  $form['view'] = array(
1980
    '#type' => 'textarea',
1981
    '#title' => t('Paste view code here'),
1982
    '#required' => TRUE,
1983
  );
1984
1985
  $form['submit'] = array(
1986
    '#type' => 'submit',
1987
    '#value' => t('Import'),
1988
    '#submit' => array('views_ui_import_submit'),
1989
    '#validate' => array('views_ui_import_validate'),
1990
  );
1991
  return $form;
1992
}
1993
1994
/**
1995
 * Validate handler to import a view.
1996
 */
1997
function views_ui_import_validate($form, &$form_state) {
1998
  $view = '';
1999
  views_include('view');
2000
  // Be forgiving if someone pastes views code that starts with '<?php'.
2001
  if (substr($form_state['values']['view'], 0, 5) == '<?php') {
2002
    $form_state['values']['view'] = substr($form_state['values']['view'], 5);
2003
  }
2004
  ob_start();
2005
  eval($form_state['values']['view']);
2006
  ob_end_clean();
2007
2008
  if (!is_object($view)) {
2009
    return form_error($form['view'], t('Unable to interpret view code.'));
2010
  }
2011
2012
  if (empty($view->api_version) || $view->api_version < 2) {
2013
    form_error($form['view'], t('That view is not compatible with this version of Views.
2014
      If you have a view from views1 you have to go to a drupal6 installation and import it there.'));
2015
  }
2016
  elseif (version_compare($view->api_version, views_api_version(), '>')) {
2017
    form_error($form['view'], t('That view is created for the version @import_version of views, but you only have @api_version', array(
2018
      '@import_version' => $view->api_version,
2019
      '@api_version' => views_api_version())));
2020
  }
2021
2022
  // View name must be alphanumeric or underscores, no other punctuation.
2023
  if (!empty($form_state['values']['name']) && preg_match('/[^a-zA-Z0-9_]/', $form_state['values']['name'])) {
2024
    form_error($form['name'], t('View name must be alphanumeric or underscores only.'));
2025
  }
2026
2027
  if ($form_state['values']['name']) {
2028
    $view->name = $form_state['values']['name'];
2029
  }
2030
2031
  $test = views_get_view($view->name);
2032
  if (!$form_state['values']['name_override']) {
2033
    if ($test && $test->type != t('Default')) {
2034
      form_set_error('', t('A view by that name already exists; please choose a different name'));
2035
    }
2036
  }
2037
  else {
2038
    if ($test->vid) {
2039
      $view->vid = $test->vid;
2040
    }
2041
  }
2042
2043
  // Make sure base table gets set properly if it got moved.
2044
  $view->update();
2045
2046
  $view->init_display();
2047
2048
  $broken = FALSE;
2049
2050
  // Bypass the validation of view pluigns/handlers if option is checked.
2051
  if (!$form_state['values']['bypass_validation']) {
2052
    // Make sure that all plugins and handlers needed by this view actually exist.
2053
    foreach ($view->display as $id => $display) {
2054
      if (empty($display->handler) || !empty($display->handler->broken)) {
2055
        drupal_set_message(t('Display plugin @plugin is not available.', array('@plugin' => $display->display_plugin)), 'error');
2056
        $broken = TRUE;
2057
        continue;
2058
      }
2059
2060
      $plugin = views_get_plugin('style', $display->handler->get_option('style_plugin'));
2061
      if (!$plugin) {
2062
        drupal_set_message(t('Style plugin @plugin is not available.', array('@plugin' => $display->handler->get_option('style_plugin'))), 'error');
2063
        $broken = TRUE;
2064
      }
2065
      elseif ($plugin->uses_row_plugin()) {
2066
        $plugin = views_get_plugin('row', $display->handler->get_option('row_plugin'));
2067
        if (!$plugin) {
2068
          drupal_set_message(t('Row plugin @plugin is not available.', array('@plugin' => $display->handler->get_option('row_plugin'))), 'error');
2069
          $broken = TRUE;
2070
        }
2071
      }
2072
2073
      foreach (views_object_types() as $type => $info) {
2074
        $handlers = $display->handler->get_handlers($type);
2075
        if ($handlers) {
2076
          foreach ($handlers as $id => $handler) {
2077
            if ($handler->broken()) {
2078
              drupal_set_message(t('@type handler @table.@field is not available.', array(
2079
                '@type' => $info['stitle'],
2080
                '@table' => $handler->table,
2081
                '@field' => $handler->field,
2082
              )), 'error');
2083
              $broken = TRUE;
2084
            }
2085
          }
2086
        }
2087
      }
2088
    }
2089
  }
2090
2091
  if ($broken) {
2092
    form_set_error('', t('Unable to import view.'));
2093
  }
2094
2095
  $form_state['view'] = &$view;
2096
}
2097
2098
/**
2099
 * Submit handler for view import.
2100
 */
2101
function views_ui_import_submit($form, &$form_state) {
2102
  // Store in cache and then go to edit.
2103
  views_ui_cache_set($form_state['view']);
2104
  $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit';
2105
}
2106
2107
/**
2108
 * Validate that a view is complete and whole.
2109
 */
2110
function views_ui_edit_view_form_validate($form, &$form_state) {
2111
  // Do not validate cancel or delete or revert.
2112
  if (empty($form_state['clicked_button']['#value']) || $form_state['clicked_button']['#value'] != t('Save')) {
2113
    return;
2114
  }
2115
2116
  $errors = $form_state['view']->validate();
2117
  if ($errors !== TRUE) {
2118
    foreach ($errors as $error) {
2119
      form_set_error('', $error);
2120
    }
2121
  }
2122
}
2123
2124
/**
2125
 * Submit handler for the edit view form.
2126
 */
2127
function views_ui_edit_view_form_submit($form, &$form_state) {
2128
  // Go through and remove displayed scheduled for removal.
2129
  foreach ($form_state['view']->display as $id => $display) {
2130
    if (!empty($display->deleted)) {
2131
      unset($form_state['view']->display[$id]);
2132
    }
2133
  }
2134
  // Rename display ids if needed.
2135
  foreach ($form_state['view']->display as $id => $display) {
2136
    if (!empty($display->new_id)) {
2137
      $form_state['view']->display[$id]->id = $display->new_id;
2138
      // Redirect the user to the renamed display to be sure that the page itself exists and doesn't throw errors.
2139
      $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit/' . $display->new_id;
2140
    }
2141
  }
2142
2143
  // Direct the user to the right url, if the path of the display has changed.
2144
  if (!empty($_GET['destination'])) {
2145
    $destination = $_GET['destination'];
2146
    // Find out the first display which has a changed path and redirect to this url.
2147
    $old_view = views_get_view($form_state['view']->name);
2148
    foreach ($old_view->display as $id => $display) {
2149
      // Only check for displays with a path.
2150
      if (!isset($display->display_options['path'])) {
2151
        continue;
2152
      }
2153
      $old_path = $display->display_options['path'];
2154
      if (($display->display_plugin == 'page') && ($old_path == $destination) && ($old_path != $form_state['view']->display[$id]->display_options['path'])) {
2155
        $destination = $form_state['view']->display[$id]->display_options['path'];
2156
        unset($_GET['destination']);
2157
      }
2158
    }
2159
    $form_state['redirect'] = $destination;
2160
  }
2161
2162
  $form_state['view']->save();
2163
  drupal_set_message(t('The view %name has been saved.', array('%name' => $form_state['view']->get_human_name())));
2164
2165
  // Remove this view from cache so we can edit it properly.
2166
  ctools_object_cache_clear('view', $form_state['view']->name);
2167
}
2168
2169
/**
2170
 * Submit handler for the edit view form.
2171
 */
2172
function views_ui_edit_view_form_cancel($form, &$form_state) {
2173
  // Remove this view from cache so edits will be lost.
2174
  ctools_object_cache_clear('view', $form_state['view']->name);
2175
  if (empty($form['view']->vid)) {
2176
    // I seem to have to drupal_goto here because I can't get fapi to
2177
    // honor the redirect target. Not sure what I screwed up here.
2178
    drupal_goto('admin/structure/views');
2179
  }
2180
}
2181
2182
function views_ui_edit_view_form_delete($form, &$form_state) {
2183
  unset($_REQUEST['destination']);
2184
  // Redirect to the delete confirm page
2185
  $form_state['redirect'] = array('admin/structure/views/view/' . $form_state['view']->name . '/delete', array('query' => drupal_get_destination() + array('cancel' => 'admin/structure/views/view/' . $form_state['view']->name . '/edit')));
2186
}
2187
2188
/**
2189
 * Add information about a section to a display.
2190
 */
2191
function views_ui_edit_form_get_bucket($type, $view, $display) {
2192
  $build = array(
2193
    '#theme_wrappers' => array('views_ui_display_tab_bucket'),
2194
  );
2195
  $types = views_object_types();
2196
2197
  $build['#overridden'] = FALSE;
2198
  $build['#defaulted'] = FALSE;
2199
  if (module_exists('advanced_help')) {
2200
    $build['#item_help_icon'] = array(
2201
      '#theme' => 'advanced_help_topic',
2202
      '#module' => 'views',
2203
      '#topic' => $type,
2204
    );
2205
  }
2206
2207
  $build['#name'] = $build['#title'] = $types[$type]['title'];
2208
2209
  // Different types now have different rearrange forms, so we use this switch
2210
  // to get the right one.
2211
  switch ($type) {
2212
    case 'filter':
2213
      $rearrange_url = "admin/structure/views/nojs/rearrange-$type/$view->name/$display->id/$type";
2214
      $rearrange_text = t('And/Or, Rearrange');
2215
      // TODO: Add another class to have another symbol for filter rearrange.
2216
      $class = 'icon compact rearrange';
2217
      break;
2218
    case 'field':
2219
      // Fetch the style plugin info so we know whether to list fields or not.
2220
      $style_plugin = $display->handler->get_plugin();
2221
      $uses_fields = $style_plugin && $style_plugin->uses_fields();
2222
      if (!$uses_fields) {
2223
        $build['fields'][] = array(
2224
          '#markup' => t('The selected style or row format does not utilize fields.'),
2225
          '#theme_wrappers' => array('views_container'),
2226
          '#attributes' => array('class' => array('views-display-setting')),
2227
        );
2228
        return $build;
2229
      }
2230
2231
    default:
2232
      $rearrange_url = "admin/structure/views/nojs/rearrange/$view->name/$display->id/$type";
2233
      $rearrange_text = t('Rearrange');
2234
      $class = 'icon compact rearrange';
2235
  }
2236
2237
  // Create an array of actions to pass to theme_links
2238
  $actions = array();
2239
  $count_handlers = count($display->handler->get_handlers($type));
2240
  $actions['add'] = array(
2241
    'title' => t('Add'),
2242
    'href' => "admin/structure/views/nojs/add-item/$view->name/$display->id/$type",
2243
    'attributes'=> array('class' => array('icon compact add', 'views-ajax-link'), 'title' => t('Add'), 'id' => 'views-add-' . $type),
2244
    'html' => TRUE,
2245
  );
2246
  if ($count_handlers > 0) {
2247
    $actions['rearrange'] = array(
2248
      'title' => $rearrange_text,
2249
      'href' => $rearrange_url,
2250
      'attributes' => array('class' => array($class, 'views-ajax-link'), 'title' => t('Rearrange'), 'id' => 'views-rearrange-' . $type),
2251
      'html' => TRUE,
2252
    );
2253
  }
2254
2255
  // Render the array of links
2256
  $build['#actions'] = theme('links__ctools_dropbutton',
2257
    array(
2258
      'links' => $actions,
2259
      'attributes' => array(
2260
        'class' => array('inline', 'links', 'actions', 'horizontal', 'right')
2261
      ),
2262
      'class' => array('views-ui-settings-bucket-operations'),
2263
    )
2264
  );
2265
2266
  if (!$display->handler->is_default_display()) {
2267
    if (!$display->handler->is_defaulted($types[$type]['plural'])) {
2268
      $build['#overridden'] = TRUE;
2269
    }
2270
    else {
2271
      $build['#defaulted'] = TRUE;
2272
    }
2273
  }
2274
2275
  // If there's an options form for the bucket, link to it.
2276
  if (!empty($types[$type]['options'])) {
2277
    $build['#title'] = l($build['#title'], "admin/structure/views/nojs/config-type/$view->name/$display->id/$type", array('attributes' => array('class' => array('views-ajax-link'), 'id' => 'views-title-' . $type)));
2278
  }
2279
2280
  static $relationships = NULL;
2281
  if (!isset($relationships)) {
2282
    // Get relationship labels
2283
    $relationships = array();
2284
    // @todo: get_handlers()
2285
    $handlers = $display->handler->get_option('relationships');
2286
    if ($handlers) {
2287
      foreach ($handlers as $id => $info) {
2288
        $handler = $display->handler->get_handler('relationship', $id);
2289
        $relationships[$id] = $handler->label();
2290
      }
2291
    }
2292
  }
2293
2294
  // Filters can now be grouped so we do a little bit extra:
2295
  $groups = array();
2296
  $grouping = FALSE;
2297
  if ($type == 'filter') {
2298
    $group_info = $view->display_handler->get_option('filter_groups');
2299
    // If there is only one group but it is using the "OR" filter, we still
2300
    // treat it as a group for display purposes, since we want to display the
2301
    // "OR" label next to items within the group.
2302
    if (!empty($group_info['groups']) && (count($group_info['groups']) > 1 || current($group_info['groups']) == 'OR')) {
2303
      $grouping = TRUE;
2304
      $groups = array(0 => array());
2305
    }
2306
  }
2307
2308
  $build['fields'] = array();
2309
2310
  foreach ($display->handler->get_option($types[$type]['plural']) as $id => $field) {
2311
    // Build the option link for this handler ("Node: ID = article").
2312
    $build['fields'][$id] = array();
2313
    $build['fields'][$id]['#theme'] = 'views_ui_display_tab_setting';
2314
2315
    $handler = $display->handler->get_handler($type, $id);
2316
    if (empty($handler)) {
2317
      $build['fields'][$id]['#class'][] = 'broken';
2318
      $field_name = t('Broken/missing handler: @table > @field', array('@table' => $field['table'], '@field' => $field['field']));
2319
      $build['fields'][$id]['#link'] = l($field_name, "admin/structure/views/nojs/config-item/$view->name/$display->id/$type/$id", array('attributes' => array('class' => array('views-ajax-link')), 'html' => TRUE));
2320
      continue;
2321
    }
2322
2323
    $field_name = check_plain($handler->ui_name(TRUE));
2324
    if (!empty($field['relationship']) && !empty($relationships[$field['relationship']])) {
2325
      $field_name = '(' . $relationships[$field['relationship']] . ') ' . $field_name;
2326
    }
2327
2328
    $description = filter_xss_admin($handler->admin_summary());
2329
    $link_text = $field_name . (empty($description) ? '' : " ($description)");
2330
    $link_attributes = array('class' => array('views-ajax-link'));
2331
    if (!empty($field['exclude'])) {
2332
      $link_attributes['class'][] = 'views-field-excluded';
2333
    }
2334
    $build['fields'][$id]['#link'] = l($link_text, "admin/structure/views/nojs/config-item/$view->name/$display->id/$type/$id", array('attributes' => $link_attributes, 'html' => TRUE));
2335
    $build['fields'][$id]['#class'][] = drupal_clean_css_identifier($display->id . '-' . $type . '-' . $id);
2336
    if (!empty($view->changed_sections[$display->id . '-' . $type . '-' . $id])) {
2337
      // @TODO: #changed is no longer being used?
2338
      $build['fields'][$id]['#changed'] = TRUE;
2339
    }
2340
2341
    if ($display->handler->use_group_by() && $handler->use_group_by()) {
2342
      $build['fields'][$id]['#settings_links'][] = l('<span class="label">' . t('Aggregation settings') . '</span>', "admin/structure/views/nojs/config-item-group/$view->name/$display->id/$type/$id", array('attributes' => array('class' => 'views-button-configure views-ajax-link', 'title' => t('Aggregation settings')), 'html' => true));
2343
    }
2344
2345
    if ($handler->has_extra_options()) {
2346
      $build['fields'][$id]['#settings_links'][] = l('<span class="label">' . t('Settings') . '</span>', "admin/structure/views/nojs/config-item-extra/$view->name/$display->id/$type/$id", array('attributes' => array('class' => array('views-button-configure', 'views-ajax-link'), 'title' => t('Settings')), 'html' => true));
2347
    }
2348
2349
    if ($grouping) {
2350
      $gid = $handler->options['group'];
2351
2352
      // Show in default group if the group does not exist.
2353
      if (empty($group_info['groups'][$gid])) {
2354
        $gid = 0;
2355
      }
2356
      $groups[$gid][] = $id;
2357
    }
2358
  }
2359
2360
  // If using grouping, re-order fields so that they show up properly in the list.
2361
  if ($type == 'filter' && $grouping) {
2362
    $store = $build['fields'];
2363
    $build['fields'] = array();
2364
    foreach ($groups as $gid => $contents) {
2365
      // Display an operator between each group.
2366
      if (!empty($build['fields'])) {
2367
        $build['fields'][] = array(
2368
          '#theme' => 'views_ui_display_tab_setting',
2369
          '#class' => array('views-group-text'),
2370
          '#link' => ($group_info['operator'] == 'OR' ? t('OR') : t('AND')),
2371
        );
2372
      }
2373
      // Display an operator between each pair of filters within the group.
2374
      $keys = array_keys($contents);
2375
      $last = end($keys);
2376
      foreach ($contents as $key => $pid) {
2377
        if ($key != $last) {
2378
          $store[$pid]['#link'] .= '&nbsp;&nbsp;' . ($group_info['groups'][$gid] == 'OR' ? t('OR') : t('AND'));
2379
        }
2380
        $build['fields'][$pid] = $store[$pid];
2381
      }
2382
    }
2383
  }
2384
2385
  return $build;
2386
}
2387
2388
/**
2389
 * Regenerate the current tab for AJAX updates.
2390
 */
2391
function views_ui_regenerate_tab(&$view, &$output, $display_id) {
2392
  if (!$view->set_display('default')) {
2393
    return;
2394
  }
2395
2396
  // Regenerate the main display area.
2397
  $build = views_ui_get_display_tab($view, $display_id);
2398
  views_ui_add_microweights($build);
2399
  $output[] = ajax_command_html('#views-tab-' . $display_id, drupal_render($build));
2400
2401
  // Regenerate the top area so changes to display names and order will appear.
2402
  $build = views_ui_render_display_top($view, $display_id);
2403
  views_ui_add_microweights($build);
2404
  $output[] = ajax_command_replace('#views-display-top', drupal_render($build));
2405
}
2406
2407
/**
2408
 * Recursively adds microweights to a render array, similar to what form_builder() does for forms.
2409
 *
2410
 * @todo Submit a core patch to fix drupal_render() to do this, so that all
2411
 *   render arrays automatically preserve array insertion order, as forms do.
2412
 */
2413
function views_ui_add_microweights(&$build) {
2414
  $count = 0;
2415
  foreach (element_children($build) as $key) {
2416
    if (!isset($build[$key]['#weight'])) {
2417
      $build[$key]['#weight'] = $count/1000;
2418
    }
2419
    views_ui_add_microweights($build[$key]);
2420
    $count++;
2421
  }
2422
}
2423
2424
/**
2425
 * Provide a standard set of Apply/Cancel/OK buttons for the forms. Also provide
2426
 * a hidden op operator because the forms plugin doesn't seem to properly
2427
 * provide which button was clicked.
2428
 *
2429
 * TODO: Is the hidden op operator still here somewhere, or is that part of the
2430
 * docblock outdated?
2431
 */
2432
function views_ui_standard_form_buttons(&$form, &$form_state, $form_id, $name = NULL, $third = NULL, $submit = NULL) {
2433
  $form['buttons'] = array(
2434
    '#prefix' => '<div class="clearfix"><div class="form-buttons">',
2435
    '#suffix' => '</div></div>',
2436
  );
2437
2438
  if (empty($name)) {
2439
    $name = t('Apply');
2440
    $view = $form_state['view'];
2441
    if (!empty($view->stack) && count($view->stack) > 1) {
2442
      $name = t('Apply and continue');
2443
    }
2444
    $names = array(t('Apply'), t('Apply and continue'));
2445
  }
2446
2447
  // Forms that are purely informational set an ok_button flag, so we know not
2448
  // to create an "Apply" button for them.
2449
  if (empty($form_state['ok_button'])) {
2450
    $form['buttons']['submit'] = array(
2451
      '#type' => 'submit',
2452
      '#value' => $name,
2453
      // The regular submit handler ($form_id . '_submit') does not apply if
2454
      // we're updating the default display. It does apply if we're updating
2455
      // the current display. Since we have no way of knowing at this point
2456
      // which display the user wants to update, views_ui_standard_submit will
2457
      // take care of running the regular submit handler as appropriate.
2458
      '#submit' => array('views_ui_standard_submit'),
2459
    );
2460
    // Form API button click detection requires the button's #value to be the
2461
    // same between the form build of the initial page request, and the initial
2462
    // form build of the request processing the form submission. Ideally, the
2463
    // button's #value shouldn't change until the form rebuild step. However,
2464
    // views_ui_ajax_form() implements a different multistep form workflow than
2465
    // the Form API does, and adjusts $view->stack prior to form processing, so
2466
    // we compensate by extending button click detection code to support any of
2467
    // the possible button labels.
2468
    if (isset($names)) {
2469
      $form['buttons']['submit']['#values'] = $names;
2470
      $form['buttons']['submit']['#process'] = array_merge(array('views_ui_form_button_was_clicked'), element_info_property($form['buttons']['submit']['#type'], '#process', array()));
2471
    }
2472
    // If a validation handler exists for the form, assign it to this button.
2473
    if (function_exists($form_id . '_validate')) {
2474
      $form['buttons']['submit']['#validate'][] = $form_id . '_validate';
2475
    }
2476
  }
2477
2478
  // Create a "Cancel" button. For purely informational forms, label it "OK".
2479
  $cancel_submit = function_exists($form_id . '_cancel') ? $form_id . '_cancel' : 'views_ui_standard_cancel';
2480
  $form['buttons']['cancel'] = array(
2481
    '#type' => 'submit',
2482
    '#value' => empty($form_state['ok_button']) ? t('Cancel') : t('Ok'),
2483
    '#submit' => array($cancel_submit),
2484
    '#validate' => array(),
2485
  );
2486
2487
  // Some forms specify a third button, with a name and submit handler.
2488
  if ($third) {
2489
    if (empty($submit)) {
2490
      $submit = 'third';
2491
    }
2492
    $third_submit = function_exists($form_id . '_' . $submit) ? $form_id . '_' . $submit : 'views_ui_standard_cancel';
2493
2494
    $form['buttons'][$submit] = array(
2495
      '#type' => 'submit',
2496
      '#value' => $third,
2497
      '#validate' => array(),
2498
      '#submit' => array($third_submit),
2499
    );
2500
  }
2501
2502
  // Compatibility, to be removed later: // TODO: When is "later"?
2503
  // We used to set these items on the form, but now we want them on the $form_state:
2504
  if (isset($form['#title'])) {
2505
    $form_state['title'] = $form['#title'];
2506
  }
2507
  if (isset($form['#help_topic'])) {
2508
    $form_state['help_topic'] = $form['#help_topic'];
2509
  }
2510
  if (isset($form['#help_module'])) {
2511
    $form_state['help_module'] = $form['#help_module'];
2512
  }
2513
  if (isset($form['#url'])) {
2514
    $form_state['url'] = $form['#url'];
2515
  }
2516
  if (isset($form['#section'])) {
2517
    $form_state['#section'] = $form['#section'];
2518
  }
2519
  // Finally, we never want these cached -- our object cache does that for us.
2520
  $form['#no_cache'] = TRUE;
2521
2522
  // If this isn't an ajaxy form, then we want to set the title.
2523
  if (!empty($form['#title'])) {
2524
    drupal_set_title($form['#title']);
2525
  }
2526
}
2527
2528
/**
2529
 * Basic submit handler applicable to all 'standard' forms.
2530
 *
2531
 * This submit handler determines whether the user wants the submitted changes
2532
 * to apply to the default display or to the current display, and dispatches
2533
 * control appropriately.
2534
 */
2535
function views_ui_standard_submit($form, &$form_state) {
2536
  // Determine whether the values the user entered are intended to apply to
2537
  // the current display or the default display.
2538
2539
  list($was_defaulted, $is_defaulted, $revert) = views_ui_standard_override_values($form, $form_state);
2540
2541
  // Mark the changed section of the view as changed.
2542
  // TODO: Document why we are doing this, and see if we still need it.
2543
  if (!empty($form['#section'])) {
2544
    $form_state['view']->changed_sections[$form['#section']] = TRUE;
2545
  }
2546
2547
  // Based on the user's choice in the display dropdown, determine which display
2548
  // these changes apply to.
2549
  if ($revert) {
2550
    // If it's revert just change the override and return.
2551
    $display = &$form_state['view']->display[$form_state['display_id']];
2552
    $display->handler->options_override($form, $form_state);
2553
2554
    // Don't execute the normal submit handling but still store the changed view into cache.
2555
    views_ui_cache_set($form_state['view']);
2556
    return;
2557
  }
2558
  elseif ($was_defaulted === $is_defaulted) {
2559
    // We're not changing which display these form values apply to.
2560
    // Run the regular submit handler for this form.
2561
  }
2562
  elseif ($was_defaulted && !$is_defaulted) {
2563
    // We were using the default display's values, but we're now overriding
2564
    // the default display and saving values specific to this display.
2565
    $display = &$form_state['view']->display[$form_state['display_id']];
2566
    // options_override toggles the override of this section.
2567
    $display->handler->options_override($form, $form_state);
2568
    $display->handler->options_submit($form, $form_state);
2569
  }
2570
  elseif (!$was_defaulted && $is_defaulted) {
2571
    // We used to have an override for this display, but the user now wants
2572
    // to go back to the default display.
2573
    // Overwrite the default display with the current form values, and make
2574
    // the current display use the new default values.
2575
    $display = &$form_state['view']->display[$form_state['display_id']];
2576
    // options_override toggles the override of this section.
2577
    $display->handler->options_override($form, $form_state);
2578
    $display->handler->options_submit($form, $form_state);
2579
  }
2580
2581
  $submit_handler = $form['#form_id'] . '_submit';
2582
  if (function_exists($submit_handler)) {
2583
    $submit_handler($form, $form_state);
2584
  }
2585
}
2586
2587
/**
2588
 * Return the was_defaulted, is_defaulted and revert state of a form.
2589
 */
2590
function views_ui_standard_override_values($form, $form_state) {
2591
  // Make sure the dropdown exists in the first place.
2592
  if (isset($form_state['values']['override']['dropdown'])) {
2593
    // #default_value is used to determine whether it was the default value or not.
2594
    // So the available options are: $display, 'default' and 'default_revert', not 'defaults'.
2595
    $was_defaulted = ($form['override']['dropdown']['#default_value'] === 'defaults');
2596
    $is_defaulted = ($form_state['values']['override']['dropdown'] === 'default');
2597
    $revert = ($form_state['values']['override']['dropdown'] === 'default_revert');
2598
2599
    if ($was_defaulted !== $is_defaulted && isset($form['#section'])) {
2600
      // We're changing which display these values apply to.
2601
      // Update the #section so it knows what to mark changed.
2602
      $form['#section'] = str_replace('default-', $form_state['display_id'] . '-', $form['#section']);
2603
    }
2604
  }
2605
  else {
2606
    // The user didn't get the dropdown for overriding the default display.
2607
    $was_defaulted = FALSE;
2608
    $is_defaulted = FALSE;
2609
    $revert = FALSE;
2610
  }
2611
2612
  return array($was_defaulted, $is_defaulted, $revert);
2613
}
2614
2615
/**
2616
 * Submit handler for cancel button
2617
 */
2618
function views_ui_standard_cancel($form, &$form_state) {
2619
  if (!empty($form_state['view']->changed) && isset($form_state['view']->form_cache)) {
2620
    unset($form_state['view']->form_cache);
2621
    views_ui_cache_set($form_state['view']);
2622
  }
2623
2624
  $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit';
2625
}
2626
2627
/**
2628
 * Add a <select> dropdown for a given section, allowing the user to
2629
 * change whether this info is stored on the default display or on
2630
 * the current display.
2631
 */
2632
function views_ui_standard_display_dropdown(&$form, &$form_state, $section) {
2633
  $view = &$form_state['view'];
2634
  $display_id = $form_state['display_id'];
2635
  $displays = $view->display;
2636
  $current_display = $view->display[$display_id];
2637
2638
  // Add the "2 of 3" progress indicator.
2639
  // @TODO: Move this to a separate function if it's needed on any forms that
2640
  // don't have the display dropdown.
2641
  if ($form_progress = views_ui_get_form_progress($view)) {
2642
    $form['progress']['#markup'] = '<div id="views-progress-indicator">' . t('@current of @total', array('@current' => $form_progress['current'], '@total' => $form_progress['total'])) . '</div>';
2643
    $form['progress']['#weight'] = -1001;
2644
  }
2645
2646
  if ($current_display->handler->is_default_display()) {
2647
    return;
2648
  }
2649
2650
  // Determine whether any other displays have overrides for this section.
2651
  $section_overrides = FALSE;
2652
  $section_defaulted = $current_display->handler->is_defaulted($section);
2653
  foreach ($displays as $id => $display) {
2654
    if ($id === 'default' || $id === $display_id) {
2655
      continue;
2656
    }
2657
    if ($display->handler && !$display->handler->is_defaulted($section)) {
2658
      $section_overrides = TRUE;
2659
    }
2660
  }
2661
2662
  $display_dropdown['default'] = ($section_overrides ? t('All displays (except overridden)') : t('All displays'));
2663
  $display_dropdown[$display_id] = t('This @display_type (override)', array('@display_type' => $current_display->display_plugin));
2664
  // Only display the revert option if we are in a overridden section.
2665
  if (!$section_defaulted) {
2666
    $display_dropdown['default_revert'] = t('Revert to default');
2667
  }
2668
2669
  $form['override'] = array(
2670
    '#prefix' => '<div class="views-override clearfix container-inline">',
2671
    '#suffix' => '</div>',
2672
    '#weight' => -1000,
2673
    '#tree' => TRUE,
2674
  );
2675
  $form['override']['dropdown'] = array(
2676
    '#type' => 'select',
2677
    '#title' => t('For'), // @TODO: Translators may need more context than this.
2678
    '#options' => $display_dropdown,
2679
  );
2680
  if ($current_display->handler->is_defaulted($section)) {
2681
    $form['override']['dropdown']['#default_value'] = 'defaults';
2682
  }
2683
  else {
2684
    $form['override']['dropdown']['#default_value'] = $display_id;
2685
  }
2686
2687
}
2688
2689
/**
2690
 * Get the user's current progress through the form stack.
2691
 *
2692
 * @param $view
2693
 *   The current view.
2694
 *
2695
 * @return
2696
 *   FALSE if the user is not currently in a multiple-form stack. Otherwise,
2697
 *   an associative array with the following keys:
2698
 *   - current: The number of the current form on the stack.
2699
 *   - total: The total number of forms originally on the stack.
2700
 */
2701
function views_ui_get_form_progress($view) {
2702
  $progress = FALSE;
2703
  if (!empty($view->stack)) {
2704
    $stack = $view->stack;
2705
    // The forms on the stack have integer keys that don't change as the forms
2706
    // are completed, so we can see which ones are still left.
2707
    $keys = array_keys($view->stack);
2708
    // Add 1 to the array keys for the benefit of humans, who start counting
2709
    // from 1 and not 0.
2710
    $current = reset($keys) + 1;
2711
    $total = end($keys) + 1;
2712
    if ($total > 1) {
2713
      $progress = array();
2714
      $progress['current'] = $current;
2715
      $progress['total'] = $total;
2716
    }
2717
  }
2718
  return $progress;
2719
}
2720
2721
2722
// --------------------------------------------------------------------------
2723
// Various subforms for editing the pieces of a view.
2724
2725
function views_ui_ajax_forms($key = NULL) {
2726
  $forms = array(
2727
    'display' => array(
2728
      'form_id' => 'views_ui_edit_display_form',
2729
      'args' => array('section'),
2730
    ),
2731
    'remove-display' => array(
2732
      'form_id' => 'views_ui_remove_display_form',
2733
      'args' => array(),
2734
    ),
2735
    'config-type' => array(
2736
      'form_id' => 'views_ui_config_type_form',
2737
      'args' => array('type'),
2738
    ),
2739
    'rearrange' => array(
2740
      'form_id' => 'views_ui_rearrange_form',
2741
      'args' => array('type'),
2742
    ),
2743
    'rearrange-filter' => array(
2744
      'form_id' => 'views_ui_rearrange_filter_form',
2745
      'args' => array('type'),
2746
    ),
2747
    'reorder-displays' => array(
2748
      'form_id' => 'views_ui_reorder_displays_form',
2749
      'args' => array(),
2750
    ),
2751
    'add-item' => array(
2752
      'form_id' => 'views_ui_add_item_form',
2753
      'args' => array('type'),
2754
    ),
2755
    'config-item' => array(
2756
      'form_id' => 'views_ui_config_item_form',
2757
      'args' => array('type', 'id'),
2758
    ),
2759
    'config-item-extra' => array(
2760
      'form_id' => 'views_ui_config_item_extra_form',
2761
      'args' => array('type', 'id'),
2762
    ),
2763
    'config-item-group' => array(
2764
      'form_id' => 'views_ui_config_item_group_form',
2765
      'args' => array('type', 'id'),
2766
    ),
2767
    'config-style' => array(
2768
      'form_id' => 'views_ui_config_style_form',
2769
      'args' => array('type', 'id'),
2770
    ),
2771
    'edit-details' => array(
2772
      'form_id' => 'views_ui_edit_details_form',
2773
      'args' => array(),
2774
    ),
2775
    'analyze' => array(
2776
      'form_id' => 'views_ui_analyze_view_form',
2777
      'args' => array(),
2778
    ),
2779
  );
2780
2781
  if ($key) {
2782
    return !empty($forms[$key]) ? $forms[$key] : NULL;
2783
  }
2784
2785
  return $forms;
2786
}
2787
2788
/**
2789
 * Build a form identifier that we can use to see if one form
2790
 * is the same as another. Since the arguments differ slightly
2791
 * we do a lot of spiffy concatenation here.
2792
 */
2793
function views_ui_build_identifier($key, $view, $display_id, $args) {
2794
  $form = views_ui_ajax_forms($key);
2795
  // Automatically remove the single-form cache if it exists and
2796
  // does not match the key.
2797
  $identifier = implode('-', array($key, $view->name, $display_id));
2798
2799
  foreach ($form['args'] as $id) {
2800
    $arg = (!empty($args)) ? array_shift($args) : NULL;
2801
    $identifier .= '-' . $arg;
2802
  }
2803
  return $identifier;
2804
}
2805
2806
/**
2807
 * Build up a $form_state object suitable for use with drupal_build_form
2808
 * based on known information about a form.
2809
 */
2810
function views_ui_build_form_state($js, $key, &$view, $display_id, $args) {
2811
  $form = views_ui_ajax_forms($key);
2812
  // Build up form state
2813
  $form_state = array(
2814
    'form_key' => $key,
2815
    'form_id' => $form['form_id'],
2816
    'view' => &$view,
2817
    'ajax' => $js,
2818
    'display_id' => $display_id,
2819
    'no_redirect' => TRUE,
2820
  );
2821
2822
  foreach ($form['args'] as $id) {
2823
    $form_state[$id] = (!empty($args)) ? array_shift($args) : NULL;
2824
  }
2825
2826
  return $form_state;
2827
}
2828
2829
/**
2830
 * Create the URL for one of our standard AJAX forms based upon known
2831
 * information about the form.
2832
 */
2833
function views_ui_build_form_url($form_state) {
2834
  $form = views_ui_ajax_forms($form_state['form_key']);
2835
  $ajax = empty($form_state['ajax']) ? 'nojs' : 'ajax';
2836
  $name = $form_state['view']->name;
2837
  $url = "admin/structure/views/$ajax/$form_state[form_key]/$name/$form_state[display_id]";
2838
  foreach ($form['args'] as $arg) {
2839
    $url .= '/' . $form_state[$arg];
2840
  }
2841
  return $url;
2842
}
2843
2844
/**
2845
 * Add another form to the stack; clicking 'apply' will go to this form
2846
 * rather than closing the ajax popup.
2847
 */
2848
function views_ui_add_form_to_stack($key, &$view, $display_id, $args, $top = FALSE, $rebuild_keys = FALSE) {
2849
  if (empty($view->stack)) {
2850
    $view->stack = array();
2851
  }
2852
2853
  $stack = array(views_ui_build_identifier($key, $view, $display_id, $args), $key, &$view, $display_id, $args);
2854
  // If we're being asked to add this form to the bottom of the stack, no
2855
  // special logic is required. Our work is equally easy if we were asked to add
2856
  // to the top of the stack, but there's nothing in it yet.
2857
  if (!$top || empty($view->stack)) {
2858
    $view->stack[] = $stack;
2859
  }
2860
  // If we're adding to the top of an existing stack, we have to maintain the
2861
  // existing integer keys, so they can be used for the "2 of 3" progress
2862
  // indicator (which will now read "2 of 4").
2863
  else {
2864
    $keys = array_keys($view->stack);
2865
    $first = current($keys);
2866
    $last = end($keys);
2867
    for ($i = $last; $i >= $first; $i--) {
2868
      if (!isset($view->stack[$i])) {
2869
        continue;
2870
      }
2871
      // Move form number $i to the next position in the stack.
2872
      $view->stack[$i + 1] = $view->stack[$i];
2873
      unset($view->stack[$i]);
2874
    }
2875
    // Now that the previously $first slot is free, move the new form into it.
2876
    $view->stack[$first] = $stack;
2877
    ksort($view->stack);
2878
2879
    // Start the keys from 0 again, if requested.
2880
    if ($rebuild_keys) {
2881
      $view->stack = array_values($view->stack);
2882
    }
2883
  }
2884
}
2885
2886
/**
2887
 * Generic entry point to handle forms.
2888
 *
2889
 * We do this for consistency and to make it easy to chain forms
2890
 * together.
2891
 */
2892
function views_ui_ajax_form($js, $key, $view, $display_id = '') {
2893
  // Reset the cache of IDs. Drupal rather aggressively prevents id duplication
2894
  // but this causes it to remember IDs that are no longer even being used.
2895
  if (isset($_POST['ajax_html_ids'])) {
2896
    unset($_POST['ajax_html_ids']);
2897
  }
2898
2899
  $form = views_ui_ajax_forms($key);
2900
  if (empty($form)) {
2901
    return MENU_NOT_FOUND;
2902
  }
2903
2904
  views_include('ajax');
2905
  $args = func_get_args();
2906
  // Remove the known args
2907
  array_splice($args, 0, 4);
2908
2909
  $form_state = views_ui_build_form_state($js, $key, $view, $display_id, $args);
2910
  // check to see if this is the top form of the stack. If it is, pop
2911
  // it off; if it isn't, the user clicked somewhere else and the stack is
2912
  // now irrelevant.
2913
  if (!empty($view->stack)) {
2914
    $identifier = views_ui_build_identifier($key, $view, $display_id, $args);
2915
    // Retrieve the first form from the stack without changing the integer keys,
2916
    // as they're being used for the "2 of 3" progress indicator.
2917
    reset($view->stack);
2918
    list($key, $top) = each($view->stack);
2919
    unset($view->stack[$key]);
2920
2921
    if (array_shift($top) != $identifier) {
2922
      $view->stack = array();
2923
    }
2924
  }
2925
2926
  // Automatically remove the form cache if it is set and the key does
2927
  // not match. This way navigating away from the form without hitting
2928
  // update will work.
2929
  if (isset($view->form_cache) && $view->form_cache['key'] != $key) {
2930
    unset($view->form_cache);
2931
  }
2932
2933
  // With the below logic, we may end up rendering a form twice (or two forms
2934
  // each sharing the same element ids), potentially resulting in
2935
  // drupal_add_js() being called twice to add the same setting. drupal_get_js()
2936
  // is ok with that, but until ajax_render() is (http://drupal.org/node/208611),
2937
  // reset the drupal_add_js() static before rendering the second time.
2938
  $drupal_add_js_original = drupal_add_js();
2939
  $drupal_add_js = &drupal_static('drupal_add_js');
2940
  $output = views_ajax_form_wrapper($form_state['form_id'], $form_state);
2941
  if ($form_state['submitted'] && empty($form_state['rerender'])) {
2942
    // Sometimes we need to re-generate the form for multi-step type operations.
2943
    $object = NULL;
2944
    if (!empty($view->stack)) {
2945
      $drupal_add_js = $drupal_add_js_original;
2946
      $stack = $view->stack;
2947
      $top = array_shift($stack);
2948
      $top[0] = $js;
2949
      $form_state = call_user_func_array('views_ui_build_form_state', $top);
2950
      $form_state['input'] = array();
2951
      $form_state['url'] = url(views_ui_build_form_url($form_state));
2952
      if (!$js) {
2953
        return drupal_goto(views_ui_build_form_url($form_state));
2954
      }
2955
      $output = views_ajax_form_wrapper($form_state['form_id'], $form_state);
2956
    }
2957
    elseif (!$js) {
2958
      // if nothing on the stack, non-js forms just go back to the main view editor.
2959
      return drupal_goto("admin/structure/views/view/$view->name/edit");
2960
    }
2961
    else {
2962
      $output = array();
2963
      $output[] = views_ajax_command_dismiss_form();
2964 13c3c9b4 Assos Assos
      $output[] = views_ajax_command_show_buttons(!empty($view->changed));
2965 85ad3d82 Assos Assos
      $output[] = views_ajax_command_trigger_preview();
2966
      if (!empty($form_state['#page_title'])) {
2967
        $output[] = views_ajax_command_replace_title($form_state['#page_title']);
2968
      }
2969
    }
2970
    // If this form was for view-wide changes, there's no need to regenerate
2971
    // the display section of the form.
2972
    if ($display_id !== '') {
2973
      views_ui_regenerate_tab($view, $output, $display_id);
2974
    }
2975
  }
2976
2977
  return $js ? array('#type' => 'ajax', '#commands' => $output) : $output;
2978
}
2979
2980
/**
2981
 * Submit handler to add a restore a removed display to a view.
2982
 */
2983
function views_ui_remove_display_form_restore($form, &$form_state) {
2984
  // Create the new display
2985
  $id = $form_state['display_id'];
2986
  $form_state['view']->display[$id]->deleted = FALSE;
2987
2988
  // Store in cache
2989
  views_ui_cache_set($form_state['view']);
2990
}
2991
2992
/**
2993
 * Form constructor callback to display analysis information on a view
2994
 */
2995
function views_ui_analyze_view_form($form, &$form_state) {
2996
  $view = &$form_state['view'];
2997
2998
  $form['#title'] = t('View analysis');
2999
  $form['#section'] = 'analyze';
3000
3001
  views_include('analyze');
3002
  $messages = views_analyze_view($view);
3003
3004
  $form['analysis'] = array(
3005
    '#prefix' => '<div class="form-item">',
3006
    '#suffix' => '</div>',
3007
    '#markup' => views_analyze_format_result($view, $messages),
3008
  );
3009
3010
  // Inform the standard button function that we want an OK button.
3011
  $form_state['ok_button'] = TRUE;
3012
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_analyze_view_form');
3013
  return $form;
3014
}
3015
3016
/**
3017
 * Submit handler for views_ui_analyze_view_form
3018
 */
3019
function views_ui_analyze_view_form_submit($form, &$form_state) {
3020
  $form_state['redirect'] = 'admin/structure/views/view/' . $form_state['view']->name . '/edit';
3021
}
3022
3023
/**
3024
 * Form constructor callback to reorder displays on a view
3025
 */
3026
function views_ui_reorder_displays_form($form, &$form_state) {
3027
  $view = &$form_state['view'];
3028
  $display_id = $form_state['display_id'];
3029
3030
  $form['view'] = array('#type' => 'value', '#value' => $view);
3031
3032
  $form['#tree'] = TRUE;
3033
3034
  $last_display = end($view->display);
3035
3036
  foreach ($view->display as $display) {
3037
    $form[$display->id] = array(
3038
      'title'  => array('#markup' => check_plain($display->display_title)),
3039
      'weight' => array(
3040
        '#type' => 'weight',
3041
        '#value' => $display->position,
3042
        '#delta' => $last_display->position,
3043
        '#title' => t('Weight for @display', array('@display' => $display->display_title)),
3044
        '#title_display' => 'invisible',
3045
      ),
3046
      '#tree' => TRUE,
3047
      '#display' => $display,
3048
      'removed' => array(
3049
        '#type' => 'checkbox',
3050
        '#id' => 'display-removed-' . $display->id,
3051
        '#attributes' => array('class' => array('views-remove-checkbox')),
3052
        '#default_value' => isset($display->deleted),
3053
      ),
3054
    );
3055
3056
    if (isset($display->deleted) && $display->deleted) {
3057
      $form[$display->id]['deleted'] = array('#type' => 'value', '#value' => TRUE);
3058
    }
3059
    if ($display->id === 'default') {
3060
      unset($form[$display->id]['weight']);
3061
      unset($form[$display->id]['removed']);
3062
    }
3063
3064
  }
3065
3066
  $form['#title'] = t('Displays Reorder');
3067
  $form['#section'] = 'reorder';
3068
3069
  // Add javascript settings that will be added via $.extend for tabledragging
3070
  $form['#js']['tableDrag']['reorder-displays']['weight'][0] = array(
3071
    'target' => 'weight',
3072
    'source' => NULL,
3073
    'relationship' => 'sibling',
3074
    'action' => 'order',
3075
    'hidden' => TRUE,
3076
    'limit' => 0,
3077
  );
3078
3079
  $form['#action'] = url('admin/structure/views/nojs/reorder-displays/' . $view->name . '/' . $display_id);
3080
3081
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_reorder_displays_form');
3082
3083
  return $form;
3084
}
3085
3086
/**
3087
 * Display position sorting function
3088
 */
3089
function _views_position_sort($display1, $display2) {
3090
  if ($display1->position != $display2->position) {
3091
    return $display1->position < $display2->position ? -1 : 1;
3092
  }
3093
3094
  return 0;
3095
}
3096
3097
/**
3098
 * Submit handler for rearranging display form
3099
 */
3100
function views_ui_reorder_displays_form_submit($form, &$form_state) {
3101
  foreach($form_state['input'] as $display => $info) {
3102
    // add each value that is a field with a weight to our list, but only if
3103
    // it has had its 'removed' checkbox checked.
3104
    if (is_array($info) && isset($info['weight']) && empty($info['removed'])) {
3105
      $order[$display] = $info['weight'];
3106
    }
3107
  }
3108
3109
  // Sort the order array
3110
  asort($order);
3111
3112
  // Fixing up positions
3113
  $position = 2;
3114
3115
  foreach(array_keys($order) as $display) {
3116
    $order[$display] = $position++;
3117
  }
3118
3119
  // Setting up position and removing deleted displays
3120
  $displays = $form_state['view']->display;
3121
  foreach($displays as $display_id => $display) {
3122
    // Don't touch the default !!!
3123
    if ($display_id === 'default') {
3124
      continue;
3125
    }
3126
    if (isset($order[$display_id])) {
3127
      $form_state['view']->display[$display_id]->position = $order[$display_id];
3128
    }
3129
    else {
3130
      $form_state['view']->display[$display_id]->deleted = TRUE;
3131
    }
3132
  }
3133
3134
  // Sorting back the display array as the position is not enough
3135
  uasort($form_state['view']->display, '_views_position_sort');
3136
3137
  // Store in cache
3138
  views_ui_cache_set($form_state['view']);
3139
  $form_state['redirect'] = array('admin/structure/views/view/' . $form_state['view']->name . '/edit', array('fragment' => 'views-tab-default'));
3140
}
3141
3142
/**
3143
 * Turn the reorder form into a proper table
3144
 */
3145
function theme_views_ui_reorder_displays_form($vars) {
3146
  $form = $vars['form'];
3147
  $rows = array();
3148
  foreach (element_children($form) as $key) {
3149
    if (isset($form[$key]['#display'])) {
3150
      $display = &$form[$key];
3151
3152
      $row = array();
3153
      $row[] = drupal_render($display['title']);
3154
      $form[$key]['weight']['#attributes']['class'] = array('weight');
3155
      $row[] = drupal_render($form[$key]['weight']);
3156
      if (isset($display['removed'])) {
3157
        $row[] = drupal_render($form[$key]['removed']) .
3158
          l('<span>' . t('Remove') . '</span>',
3159
            'javascript:void()',
3160
            array(
3161
              'attributes' => array(
3162
                'id' => 'display-remove-link-' . $key,
3163
                'class' => array('views-button-remove display-remove-link'),
3164
                'alt' => t('Remove this display'),
3165
                'title' => t('Remove this display')),
3166
              'html' => TRUE));
3167
      }
3168
      else {
3169
        $row[] = '';
3170
      }
3171
      $class = array();
3172
      $styles = array();
3173
      if (isset($form[$key]['weight']['#type'])) {
3174
        $class[] = 'draggable';
3175
      }
3176
      if (isset($form[$key]['deleted']['#value']) && $form[$key]['deleted']['#value']) {
3177
        $styles[] = 'display: none;';
3178
      }
3179
      $rows[] = array('data' => $row, 'class' => $class, 'id' => 'display-row-' . $key, 'style' => $styles);
3180
    }
3181
  }
3182
3183
  $header = array(t('Display'), t('Weight'), t('Remove'));
3184
  $output = '';
3185
  drupal_add_tabledrag('reorder-displays', 'order', 'sibling', 'weight');
3186
3187
  $output = drupal_render($form['override']);
3188
  $output .= '<div class="scroll">';
3189
  $output .= theme('table',
3190
    array('header' => $header,
3191
    'rows' => $rows,
3192
    'attributes' => array('id' => 'reorder-displays'),
3193
  ));
3194
  $output .= '</div>';
3195
  $output .= drupal_render_children($form);
3196
3197
  return $output;
3198
}
3199
3200
/**
3201
 * Form builder to edit details of a view.
3202
 */
3203
function views_ui_edit_details_form($form, &$form_state) {
3204
  $view = &$form_state['view'];
3205
3206
  $form['#title'] = t('View name and description');
3207
  $form['#section'] = 'details';
3208
3209
  $form['details'] = array(
3210
    '#theme_wrappers' => array('container'),
3211
    '#attributes' => array('class' => array('scroll')),
3212
  );
3213
  $form['details']['human_name'] = array(
3214
    '#type' => 'textfield',
3215
    '#title' => t('Human-readable name'),
3216
    '#description' => t('A descriptive human-readable name for this view. Spaces are allowed'),
3217
    '#default_value' => $view->get_human_name(),
3218
  );
3219
  $form['details']['tag'] = array(
3220
    '#type' => 'textfield',
3221
    '#title' => t('View tag'),
3222
    '#description' => t('Optionally, enter a comma delimited list of tags for this view to use in filtering and sorting views on the administrative page.'),
3223
    '#default_value' => $view->tag,
3224
    '#autocomplete_path' => 'admin/views/ajax/autocomplete/tag',
3225
  );
3226
  $form['details']['description'] = array(
3227
    '#type' => 'textfield',
3228
    '#title' => t('View description'),
3229
    '#description' => t('This description will appear on the Views administrative UI to tell you what the view is about.'),
3230
    '#default_value' => $view->description,
3231
  );
3232
3233
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_edit_details_form');
3234
  return $form;
3235
}
3236
3237
/**
3238
 * Submit handler for views_ui_edit_details_form.
3239
 */
3240
function views_ui_edit_details_form_submit($form, &$form_state) {
3241
  $view = $form_state['view'];
3242
  foreach ($form_state['values'] as $key => $value) {
3243
    // Only save values onto the view if they're actual view properties
3244
    // (as opposed to 'op' or 'form_build_id').
3245
    if (isset($form['details'][$key])) {
3246
      $view->$key = $value;
3247
    }
3248
  }
3249
  $form_state['#page_title'] = views_ui_edit_page_title($view);
3250
  views_ui_cache_set($view);
3251
}
3252
3253
/**
3254
 * Form constructor callback to edit display of a view
3255
 */
3256
function views_ui_edit_display_form($form, &$form_state) {
3257
  $view = &$form_state['view'];
3258
  $display_id = $form_state['display_id'];
3259
  $section = $form_state['section'];
3260
3261
  if (!$view->set_display($display_id)) {
3262
    views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
3263
  }
3264
  $display = &$view->display[$display_id];
3265
3266
  // Get form from the handler.
3267
  $form['options'] = array(
3268
    '#theme_wrappers' => array('container'),
3269
    '#attributes' => array('class' => array('scroll')),
3270
  );
3271
  $display->handler->options_form($form['options'], $form_state);
3272
3273
  // The handler options form sets $form['#title'], which we need on the entire
3274
  // $form instead of just the ['options'] section.
3275
  $form['#title'] = $form['options']['#title'];
3276
  unset($form['options']['#title']);
3277
3278
  // Move the override dropdown out of the scrollable section of the form.
3279
  if (isset($form['options']['override'])) {
3280
    $form['override'] = $form['options']['override'];
3281
    unset($form['options']['override']);
3282
  }
3283
3284
  $name = NULL;
3285
  if (isset($form_state['update_name'])) {
3286
    $name = $form_state['update_name'];
3287
  }
3288
3289
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_edit_display_form', $name);
3290
  return $form;
3291
}
3292
3293
/**
3294
 * Validate handler for views_ui_edit_display_form
3295
 */
3296
function views_ui_edit_display_form_validate($form, &$form_state) {
3297
  $display = &$form_state['view']->display[$form_state['display_id']];
3298
  $display->handler->options_validate($form['options'], $form_state);
3299
3300
  if (form_get_errors()) {
3301
    $form_state['rerender'] = TRUE;
3302
  }
3303
}
3304
3305
/**
3306
 * Submit handler for views_ui_edit_display_form
3307
 */
3308
function views_ui_edit_display_form_submit($form, &$form_state) {
3309
  $display = &$form_state['view']->display[$form_state['display_id']];
3310
  $display->handler->options_submit($form, $form_state);
3311
3312
  views_ui_cache_set($form_state['view']);
3313
}
3314
3315
/**
3316
 * Override handler for views_ui_edit_display_form
3317
 *
3318
 * @TODO: Not currently used. Remove unless we implement an override toggle.
3319
 */
3320
function views_ui_edit_display_form_override($form, &$form_state) {
3321
  $display = &$form_state['view']->display[$form_state['display_id']];
3322
  $display->handler->options_override($form, $form_state);
3323
3324
  views_ui_cache_set($form_state['view']);
3325
  $form_state['rerender'] = TRUE;
3326
  $form_state['rebuild'] = TRUE;
3327
}
3328
3329
/**
3330
 * Form to config items in the views UI.
3331
 */
3332
function views_ui_config_type_form($form, &$form_state) {
3333
  $view = &$form_state['view'];
3334
  $display_id = $form_state['display_id'];
3335
  $type = $form_state['type'];
3336
3337
  $types = views_object_types();
3338
  if (!$view->set_display($display_id)) {
3339
    views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
3340
  }
3341
  $display = &$view->display[$display_id];
3342
  $form['#title'] = t('Configure @type', array('@type' => $types[$type]['ltitle']));
3343
  $form['#section'] = $display_id . 'config-item';
3344
3345
  if ($display->handler->defaultable_sections($types[$type]['plural'])) {
3346
    $form_state['section'] = $types[$type]['plural'];
3347
    views_ui_standard_display_dropdown($form, $form_state, $form_state['section']);
3348
  }
3349
3350
  if (!empty($types[$type]['options']) && function_exists($types[$type]['options'])) {
3351
    $options = $type . '_options';
3352
    $form[$options] = array('#tree' => TRUE);
3353
    $types[$type]['options']($form, $form_state);
3354
  }
3355
3356
  $name = NULL;
3357
  if (isset($form_state['update_name'])) {
3358
    $name = $form_state['update_name'];
3359
  }
3360
3361
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_type_form', $name);
3362
  return $form;
3363
}
3364
3365
/**
3366
 * Submit handler for type configuration form
3367
 */
3368
function views_ui_config_type_form_submit($form, &$form_state) {
3369
  $types = views_object_types();
3370
  $display = &$form_state['view']->display[$form_state['display_id']];
3371
3372
  // Store in cache
3373
  views_ui_cache_set($form_state['view']);
3374
}
3375
3376
/**
3377
 * Form to rearrange items in the views UI.
3378
 */
3379
function views_ui_rearrange_form($form, &$form_state) {
3380
  $view = &$form_state['view'];
3381
  $display_id = $form_state['display_id'];
3382
  $type = $form_state['type'];
3383
3384
  $types = views_object_types();
3385
  if (!$view->set_display($display_id)) {
3386
    views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
3387
  }
3388
  $display = &$view->display[$display_id];
3389
  $form['#title'] = t('Rearrange @type', array('@type' => $types[$type]['ltitle']));
3390
  $form['#section'] = $display_id . 'rearrange-item';
3391
3392
  if ($display->handler->defaultable_sections($types[$type]['plural'])) {
3393
    $form_state['section'] = $types[$type]['plural'];
3394
    views_ui_standard_display_dropdown($form, $form_state, $form_state['section']);
3395
  }
3396
3397
  $count = 0;
3398
3399
  // Get relationship labels
3400
  $relationships = array();
3401
  foreach ($display->handler->get_handlers('relationship') as $id => $handler) {
3402
    $relationships[$id] = $handler->label();
3403
    $handlers = $display->handler->get_option('relationships');
3404
    if ($handlers) {
3405
      foreach ($handlers as $id => $info) {
3406
        $handler = $display->handler->get_handler('relationship', $id);
3407
        $relationships[$id] = $handler->label();
3408
      }
3409
    }
3410
  }
3411
3412
  // Filters can now be grouped so we do a little bit extra:
3413
  $groups = array();
3414
  $grouping = FALSE;
3415
  if ($type == 'filter') {
3416
    $group_info = $view->display_handler->get_option('filter_groups');
3417
    if (!empty($group_info['groups']) && count($group_info['groups']) > 1) {
3418
      $grouping = TRUE;
3419
      $groups = array(0 => array());
3420
    }
3421
  }
3422
3423
  foreach ($display->handler->get_option($types[$type]['plural']) as $id => $field) {
3424
    $form['fields'][$id] = array('#tree' => TRUE);
3425
    $form['fields'][$id]['weight'] = array(
3426
      '#type' => 'textfield',
3427
      '#default_value' => ++$count,
3428
    );
3429
    $handler = $display->handler->get_handler($type, $id);
3430
    if ($handler) {
3431
      $name = $handler->ui_name() . ' ' . $handler->admin_summary();
3432
      if (!empty($field['relationship']) && !empty($relationships[$field['relationship']])) {
3433
        $name = '(' . $relationships[$field['relationship']] . ') ' . $name;
3434
      }
3435
3436
      $form['fields'][$id]['name'] = array(
3437
        '#markup' => $name,
3438
      );
3439
    }
3440
    else {
3441
      $form['fields'][$id]['name'] = array('#markup' => t('Broken field @id', array('@id' => $id)));
3442
    }
3443
    $form['fields'][$id]['removed'] = array(
3444
      '#type' => 'checkbox',
3445
      '#id' => 'views-removed-' . $id,
3446
      '#attributes' => array('class' => array('views-remove-checkbox')),
3447
      '#default_value' => 0,
3448
    );
3449
  }
3450
3451
  // Add javascript settings that will be added via $.extend for tabledragging
3452
  $form['#js']['tableDrag']['arrange']['weight'][0] = array(
3453
    'target' => 'weight',
3454
    'source' => NULL,
3455
    'relationship' => 'sibling',
3456
    'action' => 'order',
3457
    'hidden' => TRUE,
3458
    'limit' => 0,
3459
  );
3460
3461
  $name = NULL;
3462
  if (isset($form_state['update_name'])) {
3463
    $name = $form_state['update_name'];
3464
  }
3465
3466
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_rearrange_form');
3467
  return $form;
3468
}
3469
3470
/**
3471
 * Turn the rearrange form into a proper table
3472
 */
3473
function theme_views_ui_rearrange_form($variables) {
3474
  $form = $variables['form'];
3475
3476
  $rows = array();
3477
  foreach (element_children($form['fields']) as $id) {
3478
    if (isset($form['fields'][$id]['name'])) {
3479
      $row = array();
3480
      $row[] = drupal_render($form['fields'][$id]['name']);
3481
      $form['fields'][$id]['weight']['#attributes']['class'] = array('weight');
3482
      $row[] = drupal_render($form['fields'][$id]['weight']);
3483
      $row[] = drupal_render($form['fields'][$id]['removed']) . l('<span>' . t('Remove') . '</span>', 'javascript:void()', array('attributes' => array('id' => 'views-remove-link-' . $id, 'class' => array('views-hidden', 'views-button-remove', 'views-remove-link'), 'alt' => t('Remove this item'), 'title' => t('Remove this item')), 'html' => TRUE));
3484
      $rows[] = array('data' => $row, 'class' => array('draggable'), 'id' => 'views-row-' . $id);
3485
    }
3486
  }
3487
  if (empty($rows)) {
3488
    $rows[] = array(array('data' => t('No fields available.'), 'colspan' => '2'));
3489
  }
3490
3491
  $header = array('', t('Weight'), t('Remove'));
3492
  $output = drupal_render($form['override']);
3493
  $output .= '<div class="scroll">';
3494
  $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'arrange')));
3495
  $output .= '</div>';
3496
  $output .= drupal_render_children($form);
3497
  drupal_add_tabledrag('arrange', 'order', 'sibling', 'weight');
3498
3499
  return $output;
3500
}
3501
3502
/**
3503
 * Theme the expose filter form.
3504
 */
3505
function theme_views_ui_expose_filter_form($variables) {
3506
  $form = $variables['form'];
3507
  $more = drupal_render($form['more']);
3508
3509
  $output = drupal_render($form['form_description']);
3510
  $output .= drupal_render($form['expose_button']);
3511
  $output .= drupal_render($form['group_button']);
3512
  if (isset($form['required'])) {
3513
    $output .= drupal_render($form['required']);
3514
  }
3515
  $output .= drupal_render($form['label']);
3516
  $output .= drupal_render($form['description']);
3517
3518
  $output .= drupal_render($form['operator']);
3519
  $output .= drupal_render($form['value']);
3520
3521
  if (isset($form['use_operator'])) {
3522
    $output .= '<div class="views-left-40">';
3523
    $output .= drupal_render($form['use_operator']);
3524
    $output .= '</div>';
3525
  }
3526
3527
  // Only output the right column markup if there's a left column to begin with
3528
  if (!empty($form['operator']['#type'])) {
3529
    $output .= '<div class="views-right-60">';
3530
    $output .= drupal_render_children($form);
3531
    $output .= '</div>';
3532
  }
3533
  else {
3534
    $output .= drupal_render_children($form);
3535
  }
3536
3537
  $output .= $more;
3538
3539
  return $output;
3540
}
3541
3542
 /**
3543
 * Theme the build group filter form.
3544
 */
3545
function theme_views_ui_build_group_filter_form($variables) {
3546
  $form = $variables['form'];
3547
  $more = drupal_render($form['more']);
3548
3549
  $output = drupal_render($form['form_description']);
3550
  $output .= drupal_render($form['expose_button']);
3551
  $output .= drupal_render($form['group_button']);
3552
  if (isset($form['required'])) {
3553
    $output .= drupal_render($form['required']);
3554
  }
3555
3556
  $output .= drupal_render($form['operator']);
3557
  $output .= drupal_render($form['value']);
3558
3559
  $output .= '<div class="views-left-40">';
3560
  $output .= drupal_render($form['optional']);
3561
  $output .= drupal_render($form['remember']);
3562
  $output .= '</div>';
3563
3564
  $output .= '<div class="views-right-60">';
3565
  $output .= drupal_render($form['widget']);
3566
  $output .= drupal_render($form['label']);
3567
  $output .= drupal_render($form['description']);
3568
  $output .= '</div>';
3569
3570
3571
  $header = array(
3572
    t('Default'),
3573
    t('Weight'),
3574
    t('Label'),
3575
    t('Operator'),
3576
    t('Value'),
3577
    t('Operations'),
3578
  );
3579
3580
  $form['default_group'] = form_process_radios($form['default_group']);
3581
  $form['default_group_multiple'] = form_process_checkboxes($form['default_group_multiple']);
3582
  $form['default_group']['All']['#title'] = '';
3583
3584
  drupal_render($form['default_group_multiple']['All']); // Don't render
3585
  $rows[] = array(
3586
    drupal_render($form['default_group']['All']),
3587
    '',
3588
    array(
3589
      'data' => variable_get('views_exposed_filter_any_label', 'new_any') == 'old_any' ? t('&lt;Any&gt;') : t('- Any -'),
3590
      'colspan' => 4,
3591
      'class' => array('class' => 'any-default-radios-row'),
3592
    ),
3593
  );
3594
3595
  foreach (element_children($form['group_items']) as $group_id) {
3596
    $form['group_items'][$group_id]['value']['#title'] = '';
3597
    $data = array(
3598
      'default' => drupal_render($form['default_group'][$group_id]) . drupal_render($form['default_group_multiple'][$group_id]),
3599
      'weight' => drupal_render($form['group_items'][$group_id]['weight']),
3600
      'title' => drupal_render($form['group_items'][$group_id]['title']),
3601
      'operator' => drupal_render($form['group_items'][$group_id]['operator']),
3602
      'value' => drupal_render($form['group_items'][$group_id]['value']),
3603
      'remove' => drupal_render($form['group_items'][$group_id]['remove']) . l('<span>' . t('Remove') . '</span>', 'javascript:void()', array('attributes' => array('id' => 'views-remove-link-' . $group_id, 'class' => array('views-hidden', 'views-button-remove', 'views-groups-remove-link', 'views-remove-link'), 'alt' => t('Remove this item'), 'title' => t('Remove this item')), 'html' => true)),
3604
    );
3605
    $rows[] = array('data' => $data, 'id' => 'views-row-' . $group_id, 'class' => array('draggable'));
3606
  }
3607
  $table = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => array('views-filter-groups'), 'id' => 'views-filter-groups'))) . drupal_render($form['add_group']);
3608
  drupal_add_tabledrag('views-filter-groups', 'order', 'sibling', 'weight');
3609
  $render_form = drupal_render_children($form);
3610
  return $output . $render_form . $table . $more;
3611
}
3612
3613
3614
/**
3615
 * Submit handler for rearranging form
3616
 */
3617
function views_ui_rearrange_form_submit($form, &$form_state) {
3618
  $types = views_object_types();
3619
  $display = &$form_state['view']->display[$form_state['display_id']];
3620
3621
  $old_fields = $display->handler->get_option($types[$form_state['type']]['plural']);
3622
  $new_fields = $order = array();
3623
3624
  // Make an array with the weights
3625
  foreach ($form_state['values'] as $field => $info) {
3626
    // add each value that is a field with a weight to our list, but only if
3627
    // it has had its 'removed' checkbox checked.
3628
    if (is_array($info) && isset($info['weight']) && empty($info['removed'])) {
3629
      $order[$field] = $info['weight'];
3630
    }
3631
  }
3632
3633
  // Sort the array
3634
  asort($order);
3635
3636
  // Create a new list of fields in the new order.
3637
  foreach (array_keys($order) as $field) {
3638
    $new_fields[$field] = $old_fields[$field];
3639
  }
3640
  $display->handler->set_option($types[$form_state['type']]['plural'], $new_fields);
3641
3642
  // Store in cache
3643
  views_ui_cache_set($form_state['view']);
3644
}
3645
3646
/**
3647
 * Form to rearrange items in the views UI.
3648
 */
3649
function views_ui_rearrange_filter_form($form, &$form_state) {
3650
  $view = &$form_state['view'];
3651
  $display_id = $form_state['display_id'];
3652
  $type = $form_state['type'];
3653
3654
  $types = views_object_types();
3655
  if (!$view->set_display($display_id)) {
3656
    views_ajax_render(t('Invalid display id @display', array('@display' => $display_id)));
3657
  }
3658
  $display = &$view->display[$display_id];
3659
  $form['#title'] = check_plain($display->display_title) . ': ';
3660
  $form['#title'] .= t('Rearrange @type', array('@type' => $types[$type]['ltitle']));
3661
  $form['#section'] = $display_id . 'rearrange-item';
3662
3663
  if ($display->handler->defaultable_sections($types[$type]['plural'])) {
3664
    $form_state['section'] = $types[$type]['plural'];
3665
    views_ui_standard_display_dropdown($form, $form_state, $form_state['section']);
3666
  }
3667
3668
  if (!empty($view->form_cache)) {
3669
    $groups = $view->form_cache['groups'];
3670
    $handlers = $view->form_cache['handlers'];
3671
  }
3672
  else {
3673
    $groups = $display->handler->get_option('filter_groups');
3674
    $handlers = $display->handler->get_option($types[$type]['plural']);
3675
  }
3676
  $count = 0;
3677
3678
  // Get relationship labels
3679
  $relationships = array();
3680
  foreach ($display->handler->get_handlers('relationship') as $id => $handler) {
3681
    $relationships[$id] = $handler->label();
3682
  }
3683
3684
  $group_options = array();
3685
3686
  /**
3687
   * Filter groups is an array that contains:
3688
   * array(
3689
   *   'operator' => 'and' || 'or',
3690
   *   'groups' => array(
3691
   *     $group_id => 'and' || 'or',
3692
   *   ),
3693
   * );
3694
   */
3695
3696
  $grouping = count(array_keys($groups['groups'])) > 1;
3697
3698
  $form['filter_groups']['#tree'] = TRUE;
3699
  $form['filter_groups']['operator'] = array(
3700
    '#type' => 'select',
3701
    '#options' => array (
3702
      'AND' => t('And'),
3703
      'OR' => t('Or'),
3704
    ),
3705
    '#default_value' => $groups['operator'],
3706
    '#attributes' => array(
3707
      'class' => array('warning-on-change'),
3708
    ),
3709
    '#title' => t('Operator to use on all groups'),
3710
    '#description' => t('Either "group 0 AND group 1 AND group 2" or "group 0 OR group 1 OR group 2", etc'),
3711
    '#access' => $grouping,
3712
  );
3713
3714
  $form['remove_groups']['#tree'] = TRUE;
3715
3716
  foreach ($groups['groups'] as $id => $group) {
3717
    $form['filter_groups']['groups'][$id] = array(
3718
      '#title' => t('Operator'),
3719
      '#type' => 'select',
3720
      '#options' => array(
3721
        'AND' => t('And'),
3722
        'OR' => t('Or'),
3723
      ),
3724
      '#default_value' => $group,
3725
      '#attributes' => array(
3726
        'class' => array('warning-on-change'),
3727
      ),
3728
    );
3729
3730
    $form['remove_groups'][$id] = array(); // to prevent a notice
3731
    if ($id != 1) {
3732
      $form['remove_groups'][$id] = array(
3733
        '#type' => 'submit',
3734
        '#value' => t('Remove group @group', array('@group' => $id)),
3735
        '#id' => "views-remove-group-$id",
3736
        '#attributes' => array(
3737
          'class' => array('views-remove-group'),
3738
        ),
3739
        '#group' => $id,
3740
      );
3741
    }
3742
    $group_options[$id] = $id == 1 ? t('Default group') : t('Group @group', array('@group' => $id));
3743
    $form['#group_renders'][$id] = array();
3744
  }
3745
3746
  $form['#group_options'] = $group_options;
3747
  $form['#groups'] = $groups;
3748
  // We don't use get_handlers() because we want items without handlers to
3749
  // appear and show up as 'broken' so that the user can see them.
3750
  $form['filters'] = array('#tree' => TRUE);
3751
  foreach ($handlers as $id => $field) {
3752
    // If the group does not exist, move the filters to the default group.
3753
    if (empty($field['group']) || empty($groups['groups'][$field['group']])) {
3754
      $field['group'] = 1;
3755
    }
3756
3757
    $handler = $display->handler->get_handler($type, $id);
3758
    if ($grouping && $handler && !$handler->can_group()) {
3759
      $field['group'] = 'ungroupable';
3760
    }
3761
3762
    // If not grouping and the handler is set ungroupable, move it back to
3763
    // the default group to prevent weird errors from having it be in its
3764
    // own group:
3765
    if (!$grouping && $field['group'] == 'ungroupable') {
3766
      $field['group'] = 1;
3767
    }
3768
3769
    // Place this item into the proper group for rendering.
3770
    $form['#group_renders'][$field['group']][] = $id;
3771
3772
    $form['filters'][$id]['weight'] = array(
3773
      '#type' => 'textfield',
3774
      '#default_value' => ++$count,
3775
      '#size' => 8,
3776
    );
3777
    $form['filters'][$id]['group'] = array(
3778
      '#type' => 'select',
3779
      '#options' => $group_options,
3780
      '#default_value' => $field['group'],
3781
      '#attributes' => array(
3782
        'class' => array('views-region-select', 'views-region-' . $id),
3783
      ),
3784
      '#access' => $field['group'] !== 'ungroupable',
3785
    );
3786
3787
    if ($handler) {
3788
      $name = $handler->ui_name() . ' ' . $handler->admin_summary();
3789
      if (!empty($field['relationship']) && !empty($relationships[$field['relationship']])) {
3790
        $name = '(' . $relationships[$field['relationship']] . ') ' . $name;
3791
      }
3792
3793
      $form['filters'][$id]['name'] = array(
3794
        '#markup' => $name,
3795
      );
3796
    }
3797
    else {
3798
      $form['filters'][$id]['name'] = array('#markup' => t('Broken field @id', array('@id' => $id)));
3799
    }
3800
    $form['filters'][$id]['removed'] = array(
3801
      '#type' => 'checkbox',
3802
      '#id' => 'views-removed-' . $id,
3803
      '#attributes' => array('class' => array('views-remove-checkbox')),
3804
      '#default_value' => 0,
3805
    );
3806
  }
3807
3808
  if (isset($form_state['update_name'])) {
3809
    $name = $form_state['update_name'];
3810
  }
3811
3812
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_rearrange_filter_form');
3813
  $form['buttons']['add_group'] = array(
3814
    '#type' => 'submit',
3815
    '#value' => t('Create new filter group'),
3816
    '#id' => 'views-add-group',
3817
    '#group' => 'add',
3818
  );
3819
3820
  return $form;
3821
}
3822
3823
/**
3824
 * Turn the rearrange form into a proper table
3825
 */
3826
function theme_views_ui_rearrange_filter_form(&$vars) {
3827
  $form = $vars['form'];
3828
  $rows = $ungroupable_rows = array();
3829
  // Enable grouping only if > 1 group.
3830
  $grouping = count(array_keys($form['#group_options'])) > 1;
3831
3832
  foreach ($form['#group_renders'] as $group_id => $contents) {
3833
    // Header row for the group.
3834
    if ($group_id !== 'ungroupable') {
3835
      // Set up tabledrag so that it changes the group dropdown when rows are
3836
      // dragged between groups.
3837
      drupal_add_tabledrag('views-rearrange-filters', 'match', 'sibling', 'views-group-select', 'views-group-select-' . $group_id);
3838
3839
      // Title row, spanning all columns.
3840
      $row = array();
3841
      // Add a cell to the first row, containing the group operator.
3842
      $row[] = array('class' => array('group', 'group-operator', 'container-inline'), 'data' => drupal_render($form['filter_groups']['groups'][$group_id]), 'rowspan' => max(array(2, count($contents) + 1)));
3843
      // Title.
3844
      $row[] = array('class' => array('group', 'group-title'), 'data' => '<span>' . $form['#group_options'][$group_id] . '</span>', 'colspan' => 4);
3845
      $rows[] = array('class' => array('views-group-title'), 'data' => $row, 'id' => 'views-group-title-' . $group_id);
3846
3847
      // Row which will only appear if the group has nothing in it.
3848
      $row = array();
3849
      $class = 'group-' . (count($contents) ? 'populated' : 'empty');
3850
      $instructions = '<span>' . t('No filters have been added.') . '</span> <span class="js-only">' . t('Drag to add filters.') . '</span>';
3851
      // When JavaScript is enabled, the button for removing the group (if it's
3852
      // present) should be hidden, since it will be replaced by a link on the
3853
      // client side.
3854
      if (!empty($form['remove_groups'][$group_id]['#type']) && $form['remove_groups'][$group_id]['#type'] == 'submit') {
3855
        $form['remove_groups'][$group_id]['#attributes']['class'][] = 'js-hide';
3856
      }
3857
      $row[] = array('colspan' => 5, 'data' => $instructions . drupal_render($form['remove_groups'][$group_id]));
3858
      $rows[] = array('class' => array("group-message", "group-$group_id-message", $class), 'data' => $row, 'id' => 'views-group-' . $group_id);
3859
    }
3860
3861
    foreach ($contents as $id) {
3862
      if (isset($form['filters'][$id]['name'])) {
3863
        $row = array();
3864
        $row[] = drupal_render($form['filters'][$id]['name']);
3865
        $form['filters'][$id]['weight']['#attributes']['class'] = array('weight');
3866
        $row[] = drupal_render($form['filters'][$id]['weight']);
3867
        $form['filters'][$id]['group']['#attributes']['class'] = array('views-group-select views-group-select-' . $group_id);
3868
        $row[] = drupal_render($form['filters'][$id]['group']);
3869
        $form['filters'][$id]['removed']['#attributes']['class'][] = 'js-hide';
3870
        $row[] = drupal_render($form['filters'][$id]['removed']) . l('<span>' . t('Remove') . '</span>', 'javascript:void()', array('attributes' => array('id' => 'views-remove-link-' . $id, 'class' => array('views-hidden', 'views-button-remove', 'views-groups-remove-link', 'views-remove-link'), 'alt' => t('Remove this item'), 'title' => t('Remove this item')), 'html' => true));
3871
3872
        $row = array('data' => $row, 'class' => array('draggable'), 'id' => 'views-row-' . $id);
3873
        if ($group_id !== 'ungroupable') {
3874
          $rows[] = $row;
3875
        }
3876
        else {
3877
          $ungroupable_rows[] = $row;
3878
        }
3879
      }
3880
    }
3881
  }
3882
  if (empty($rows)) {
3883
    $rows[] = array(array('data' => t('No fields available.'), 'colspan' => '2'));
3884
  }
3885
3886
  $output = drupal_render($form['override']);
3887
  $output .= '<div class="scroll">';
3888
  if ($grouping) {
3889
    $output .= drupal_render($form['filter_groups']['operator']);
3890
  }
3891
  else {
3892
    $form['filter_groups']['groups'][0]['#title'] = t('Operator');
3893
    $output .= drupal_render($form['filter_groups']['groups'][0]);
3894
  }
3895
3896
  if (!empty($ungroupable_rows)) {
3897
    drupal_add_tabledrag('views-rearrange-filters-ungroupable', 'order', 'sibling', 'weight');
3898
    $header = array(t('Ungroupable filters'), t('Weight'), array('class' => array('views-hide-label'), 'data' => t('Group')), array('class' => array('views-hide-label'), 'data' => t('Remove')));
3899
    $output .= theme('table', array('header' => $header, 'rows' => $ungroupable_rows, 'attributes' => array('id' => 'views-rearrange-filters-ungroupable', 'class' => array('arrange'))));
3900
  }
3901
3902
  // Set up tabledrag so that the weights are changed when rows are dragged.
3903
  drupal_add_tabledrag('views-rearrange-filters', 'order', 'sibling', 'weight');
3904
  $output .= theme('table', array('rows' => $rows, 'attributes' => array('id' => 'views-rearrange-filters', 'class' => array('arrange'))));
3905
  $output .= '</div>';
3906
3907
  // When JavaScript is enabled, the button for adding a new group should be
3908
  // hidden, since it will be replaced by a link on the client side.
3909
  $form['buttons']['add_group']['#attributes']['class'][] = 'js-hide';
3910
3911
  // Render the rest of the form and return.
3912
  $output .= drupal_render_children($form);
3913
  return $output;
3914
}
3915
3916
/**
3917
 * Submit handler for rearranging form
3918
 */
3919
function views_ui_rearrange_filter_form_submit($form, &$form_state) {
3920
  $types = views_object_types();
3921
  $display = &$form_state['view']->display[$form_state['display_id']];
3922
  $remember_groups = array();
3923
3924
  if (!empty($form_state['view']->form_cache)) {
3925
    $old_fields = $form_state['view']->form_cache['handlers'];
3926
  }
3927
  else {
3928
    $old_fields = $display->handler->get_option($types[$form_state['type']]['plural']);
3929
  }
3930
  $count = 0;
3931
3932
  $groups = $form_state['values']['filter_groups'];
3933
  // Whatever button was clicked, re-calculate field information.
3934
  $new_fields = $order = array();
3935
3936
  // Make an array with the weights
3937
  foreach ($form_state['values']['filters'] as $field => $info) {
3938
    // add each value that is a field with a weight to our list, but only if
3939
    // it has had its 'removed' checkbox checked.
3940
    if (is_array($info) && empty($info['removed'])) {
3941
      if (isset($info['weight'])) {
3942
        $order[$field] = $info['weight'];
3943
      }
3944
3945
      if (isset($info['group'])) {
3946
        $old_fields[$field]['group'] = $info['group'];
3947
        $remember_groups[$info['group']][] = $field;
3948
      }
3949
    }
3950
  }
3951
3952
  // Sort the array
3953
  asort($order);
3954
3955
  // Create a new list of fields in the new order.
3956
  foreach (array_keys($order) as $field) {
3957
    $new_fields[$field] = $old_fields[$field];
3958
  }
3959
3960
  // If the #group property is set on the clicked button, that means we are
3961
  // either adding or removing a group, not actually updating the filters.
3962
  if (!empty($form_state['clicked_button']['#group'])) {
3963
    if ($form_state['clicked_button']['#group'] == 'add') {
3964
      // Add a new group
3965
      $groups['groups'][] = 'AND';
3966
    }
3967
    else {
3968
      // Renumber groups above the removed one down.
3969
      foreach (array_keys($groups['groups']) as $group_id) {
3970
        if ($group_id >= $form_state['clicked_button']['#group']) {
3971
          $old_group = $group_id + 1;
3972
          if (isset($groups['groups'][$old_group])) {
3973
            $groups['groups'][$group_id] = $groups['groups'][$old_group];
3974
            if (isset($remember_groups[$old_group])) {
3975
              foreach ($remember_groups[$old_group] as $id) {
3976
                $new_fields[$id]['group'] = $group_id;
3977
              }
3978
            }
3979
          }
3980
          else {
3981
            // If this is the last one, just unset it.
3982
            unset($groups['groups'][$group_id]);
3983
          }
3984
        }
3985
      }
3986
    }
3987
    // Update our cache with values so that cancel still works the way
3988
    // people expect.
3989
    $form_state['view']->form_cache = array(
3990
      'key' => 'rearrange-filter',
3991
      'groups' => $groups,
3992
      'handlers' => $new_fields,
3993
    );
3994
3995
    // Return to this form except on actual Update.
3996
    views_ui_add_form_to_stack('rearrange-filter', $form_state['view'], $form_state['display_id'], array($form_state['type']));
3997
  }
3998
  else {
3999
    // The actual update button was clicked. Remove the empty groups, and
4000
    // renumber them sequentially.
4001
    ksort($remember_groups);
4002
    $groups['groups'] = views_array_key_plus(array_values(array_intersect_key($groups['groups'], $remember_groups)));
4003
    // Change the 'group' key on each field to match. Here, $mapping is an
4004
    // array whose keys are the old group numbers and whose values are the new
4005
    // (sequentially numbered) ones.
4006
    $mapping = array_flip(views_array_key_plus(array_keys($remember_groups)));
4007
    foreach ($new_fields as &$new_field) {
4008
      $new_field['group'] = $mapping[$new_field['group']];
4009
    }
4010
4011
    // Write the changed handler values.
4012
    $display->handler->set_option($types[$form_state['type']]['plural'], $new_fields);
4013
    $display->handler->set_option('filter_groups', $groups);
4014
    if (isset($form_state['view']->form_cache)) {
4015
      unset($form_state['view']->form_cache);
4016
    }
4017
  }
4018
4019
  // Store in cache.
4020
  views_ui_cache_set($form_state['view']);
4021
}
4022
4023
/**
4024
 * Form to add_item items in the views UI.
4025
 */
4026
function views_ui_add_item_form($form, &$form_state) {
4027
  $view = &$form_state['view'];
4028
  $display_id = $form_state['display_id'];
4029
  $type = $form_state['type'];
4030
4031
  $form = array(
4032
    'options' => array(
4033
      '#theme_wrappers' => array('container'),
4034
      '#attributes' => array('class' => array('scroll')),
4035
    ),
4036
  );
4037
4038
  ctools_add_js('dependent');
4039
4040
  if (!$view->set_display($display_id)) {
4041
    views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
4042
  }
4043
  $display = &$view->display[$display_id];
4044
4045
  $types = views_object_types();
4046
  $ltitle = $types[$type]['ltitle'];
4047
  $section = $types[$type]['plural'];
4048
4049
  if (!empty($types[$type]['type'])) {
4050
    $type = $types[$type]['type'];
4051
  }
4052
4053
  $form['#title'] = t('Add @type', array('@type' => $ltitle));
4054
  $form['#section'] = $display_id . 'add-item';
4055
4056
4057
  // Add the display override dropdown.
4058
  views_ui_standard_display_dropdown($form, $form_state, $section);
4059
4060
  // Figure out all the base tables allowed based upon what the relationships provide.
4061
  $base_tables = $view->get_base_tables();
4062
  $options = views_fetch_fields(array_keys($base_tables), $type, $display->handler->use_group_by());
4063
4064
  if (!empty($options)) {
4065
    $form['options']['controls'] = array(
4066
      '#theme_wrappers' => array('container'),
4067
      '#id' => 'views-filterable-options-controls',
4068
      '#attributes' => array('class' => array('container-inline')),
4069
    );
4070
    $form['options']['controls']['options_search'] = array(
4071
      '#type' => 'textfield',
4072
      '#title' => t('Search'),
4073
    );
4074
4075
    $groups = array('all' => t('- All -'));
4076
    $form['options']['controls']['group'] = array(
4077
      '#type' => 'select',
4078
      '#title' => t('Filter'),
4079
      '#options' => array(),
4080
      '#attributes' => array('class' => array('ctools-master-dependent')),
4081
    );
4082
4083
    $form['options']['name'] = array(
4084
      '#prefix' => '<div class="views-radio-box form-checkboxes views-filterable-options">',
4085
      '#suffix' => '</div>',
4086
      '#tree' => TRUE,
4087
      '#default_value' => 'all',
4088
    );
4089
4090
    // Group options first to simplify the DOM objects that Views
4091
    // dependent JS will act upon.
4092
    $grouped_options = array();
4093
    foreach ($options as $key => $option) {
4094
      $group = preg_replace('/[^a-z0-9]/', '-', strtolower($option['group']));
4095
      $groups[$group] = $option['group'];
4096
      $grouped_options[$group][$key] = $option;
4097
      if (!empty($option['aliases']) && is_array($option['aliases'])) {
4098
        foreach ($option['aliases'] as $id => $alias) {
4099
          if (empty($alias['base']) || !empty($base_tables[$alias['base']])) {
4100
            $copy = $option;
4101
            $copy['group'] = $alias['group'];
4102
            $copy['title'] = $alias['title'];
4103
            if (isset($alias['help'])) {
4104
              $copy['help'] = $alias['help'];
4105
            }
4106
4107
            $group = preg_replace('/[^a-z0-9]/', '-', strtolower($copy['group']));
4108
            $groups[$group] = $copy['group'];
4109
            $grouped_options[$group][$key . '$' . $id] = $copy;
4110
          }
4111
        }
4112
      }
4113
    }
4114
4115
    foreach ($grouped_options as $group => $group_options) {
4116
      $form['options']['name'][$group . '_start']['#markup'] = '<div class="ctools-dependent-all ctools-dependent-' . $group . '">';
4117
      $zebra = 0;
4118
      foreach ($group_options as $key => $option) {
4119
        $zebra_class = ($zebra % 2) ? 'odd' : 'even';
4120
        $form['options']['name'][$key] = array(
4121
          '#type' => 'checkbox',
4122
          '#title' => t('!group: !field', array('!group' => check_plain($option['group']), '!field' => check_plain($option['title']))),
4123
          '#description' => filter_xss_admin($option['help']),
4124
          '#return_value' => $key,
4125
          '#prefix' => "<div class='$zebra_class filterable-option'>",
4126
          '#suffix' => '</div>',
4127
        );
4128
        $zebra++;
4129
      }
4130
      $form['options']['name'][$group . '_end']['#markup'] = '</div>';
4131
    }
4132
4133
    $form['options']['controls']['group']['#options'] = $groups;
4134
  }
4135
  else {
4136
    $form['options']['markup'] = array(
4137
      '#markup' => '<div class="form-item">' . t('There are no @types available to add.', array('@types' =>  $ltitle)) . '</div>',
4138
    );
4139
  }
4140
  // Add a div to show the selected items
4141
  $form['selected'] = array(
4142
    '#type' => 'item',
4143
    '#markup' => '<div class="views-selected-options"></div>',
4144
    '#title' => t('Selected') . ':',
4145
    '#theme_wrappers' => array('form_element', 'views_container'),
4146
    '#attributes' => array('class' => array('container-inline', 'views-add-form-selected')),
4147
  );
4148
  ctools_include('dependent');
4149
  views_ui_standard_form_buttons($form, $form_state, 'views_ui_add_item_form', t('Add and configure @types', array('@types' => $ltitle)));
4150
4151
  // Remove the default submit function.
4152
  $form['buttons']['submit']['#submit'] = array_diff($form['buttons']['submit']['#submit'], array('views_ui_standard_submit'));
4153
  $form['buttons']['submit']['#submit'][] = 'views_ui_add_item_form_submit';
4154
4155
  return $form;
4156
}
4157
4158
/**
4159
 * Submit handler for adding new item(s) to a view.
4160
 */
4161
function views_ui_add_item_form_submit($form, &$form_state) {
4162
  $type = $form_state['type'];
4163
  $types = views_object_types();
4164
  $section = $types[$type]['plural'];
4165
4166
  // Handle the override select.
4167
  list($was_defaulted, $is_defaulted) = views_ui_standard_override_values($form, $form_state);
4168
  if ($was_defaulted && !$is_defaulted) {
4169
    // We were using the default display's values, but we're now overriding
4170
    // the default display and saving values specific to this display.
4171
    $display = &$form_state['view']->display[$form_state['display_id']];
4172
    // set_override toggles the override of this section.
4173
    $display->handler->set_override($section);
4174
  }
4175
  elseif (!$was_defaulted && $is_defaulted) {
4176
    // We used to have an override for this display, but the user now wants
4177
    // to go back to the default display.
4178
    // Overwrite the default display with the current form values, and make
4179
    // the current display use the new default values.
4180
    $display = &$form_state['view']->display[$form_state['display_id']];
4181
    // options_override toggles the override of this section.
4182
    $display->handler->set_override($section);
4183
  }
4184
4185
  if (!empty($form_state['values']['name']) && is_array($form_state['values']['name'])) {
4186
    // Loop through each of the items that were checked and add them to the view.
4187
    foreach (array_keys(array_filter($form_state['values']['name'])) as $field) {
4188
      list($table, $field) = explode('.', $field, 2);
4189
4190
      if ($cut = strpos($field, '$')) {
4191
        $field = substr($field, 0, $cut);
4192
      }
4193
      $id = $form_state['view']->add_item($form_state['display_id'], $type, $table, $field);
4194
4195
      // check to see if we have group by settings
4196
      $key = $type;
4197
      // Footer,header and empty text have a different internal handler type(area).
4198
      if (isset($types[$type]['type'])) {
4199
        $key = $types[$type]['type'];
4200
      }
4201
      $handler = views_get_handler($table, $field, $key);
4202
      if ($form_state['view']->display_handler->use_group_by() && $handler->use_group_by()) {
4203
        views_ui_add_form_to_stack('config-item-group', $form_state['view'], $form_state['display_id'], array($type, $id));
4204
      }
4205
4206
      // check to see if this type has settings, if so add the settings form first
4207
      if ($handler && $handler->has_extra_options()) {
4208
        views_ui_add_form_to_stack('config-item-extra', $form_state['view'], $form_state['display_id'], array($type, $id));
4209
      }
4210
      // Then add the form to the stack
4211
      views_ui_add_form_to_stack('config-item', $form_state['view'], $form_state['display_id'], array($type, $id));
4212
    }
4213
  }
4214
4215
  if (isset($form_state['view']->form_cache)) {
4216
    unset($form_state['view']->form_cache);
4217
  }
4218
4219
  // Store in cache
4220
  views_ui_cache_set($form_state['view']);
4221
}
4222
4223
/**
4224
 * Override handler for views_ui_edit_display_form
4225
 */
4226
function views_ui_config_item_form_build_group($form, &$form_state) {
4227
  $item = &$form_state['handler']->options;
4228
  // flip. If the filter was a group, set back to a standard filter.
4229
  $item['is_grouped'] = empty($item['is_grouped']);
4230
4231
  // If necessary, set new defaults:
4232
  if ($item['is_grouped']) {
4233
    $form_state['handler']->build_group_options();
4234
  }
4235
4236
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
4237
4238
  views_ui_add_form_to_stack($form_state['form_key'], $form_state['view'], $form_state['display_id'], array($form_state['type'], $form_state['id']), TRUE, TRUE);
4239
4240
  views_ui_cache_set($form_state['view']);
4241
  $form_state['rerender'] = TRUE;
4242
  $form_state['rebuild'] = TRUE;
4243
  $form_state['force_build_group_options'] = TRUE;
4244
}
4245
4246
/**
4247
 * Add a new group to the exposed filter groups.
4248
 */
4249
function views_ui_config_item_form_add_group($form, &$form_state) {
4250
  $item =& $form_state['handler']->options;
4251
4252
  // Add a new row.
4253
  $item['group_info']['group_items'][] = array();
4254
4255
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
4256
4257
  views_ui_cache_set($form_state['view']);
4258
  $form_state['rerender'] = TRUE;
4259
  $form_state['rebuild'] = TRUE;
4260
  $form_state['force_build_group_options'] = TRUE;
4261
}
4262
4263
/**
4264
 * Form to config_item items in the views UI.
4265
 */
4266
function views_ui_config_item_form($form, &$form_state) {
4267
  $view = &$form_state['view'];
4268
  $display_id = $form_state['display_id'];
4269
  $type = $form_state['type'];
4270
  $id = $form_state['id'];
4271
4272
  $form = array(
4273
    'options' => array(
4274
      '#tree' => TRUE,
4275
      '#theme_wrappers' => array('container'),
4276
      '#attributes' => array('class' => array('scroll')),
4277
    ),
4278
  );
4279
  if (!$view->set_display($display_id)) {
4280
    views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
4281
  }
4282
  $item = $view->get_item($display_id, $type, $id);
4283
4284
  if ($item) {
4285
    $handler = $view->display_handler->get_handler($type, $id);
4286
    if (empty($handler)) {
4287
      $form['markup'] = array('#markup' => t("Error: handler for @table > @field doesn't exist!", array('@table' => $item['table'], '@field' => $item['field'])));
4288
    }
4289
    else {
4290
      $types = views_object_types();
4291
4292
      // If this item can come from the default display, show a dropdown
4293
      // that lets the user choose which display the changes should apply to.
4294
      if ($view->display_handler->defaultable_sections($types[$type]['plural'])) {
4295
        $form_state['section'] = $types[$type]['plural'];
4296
        views_ui_standard_display_dropdown($form, $form_state, $form_state['section']);
4297
      }
4298
4299
      // A whole bunch of code to figure out what relationships are valid for
4300
      // this item.
4301
      $relationships = $view->display_handler->get_option('relationships');
4302
      $relationship_options = array();
4303
4304
      foreach ($relationships as $relationship) {
4305
        // relationships can't link back to self. But also, due to ordering,
4306
        // relationships can only link to prior relationships.
4307
        if ($type == 'relationship' && $id == $relationship['id']) {
4308
          break;
4309
        }
4310
        $relationship_handler = views_get_handler($relationship['table'], $relationship['field'], 'relationship');
4311
        // ignore invalid/broken relationships.
4312
        if (empty($relationship_handler)) {
4313
          continue;
4314
        }
4315
4316
        // If this relationship is valid for this type, add it to the list.
4317
        $data = views_fetch_data($relationship['table']);
4318
        $base = $data[$relationship['field']]['relationship']['base'];
4319
        $base_fields = views_fetch_fields($base, $form_state['type'], $view->display_handler->use_group_by());
4320
        if (isset($base_fields[$item['table'] . '.' . $item['field']])) {
4321
          $relationship_handler->init($view, $relationship);
4322
          $relationship_options[$relationship['id']] = $relationship_handler->label();
4323
        }
4324
      }
4325
4326
      if (!empty($relationship_options)) {
4327
        // Make sure the existing relationship is even valid. If not, force
4328
        // it to none.
4329
        $base_fields = views_fetch_fields($view->base_table, $form_state['type'], $view->display_handler->use_group_by());
4330
        if (isset($base_fields[$item['table'] . '.' . $item['field']])) {
4331
          $relationship_options = array_merge(array('none' => t('Do not use a relationship')), $relationship_options);
4332
        }
4333
        $rel = empty($item['relationship']) ? 'none' : $item['relationship'];
4334
        if (empty($relationship_options[$rel])) {
4335
          // Pick the first relationship.
4336
          $rel = key($relationship_options);
4337
          // We want this relationship option to get saved even if the user
4338
          // skips submitting the form.
4339
          $view->set_item_option($display_id, $type, $id, 'relationship', $rel);
4340
          $temp_view = $view->clone_view();
4341
          views_ui_cache_set($temp_view);
4342
        }
4343
4344
        $form['options']['relationship'] = array(
4345
          '#type' => 'select',
4346
          '#title' => t('Relationship'),
4347
          '#options' => $relationship_options,
4348
          '#default_value' => $rel,
4349
          '#weight' => -500,
4350
        );
4351
      }
4352
      else {
4353
        $form['options']['relationship'] = array(
4354
          '#type' => 'value',
4355
          '#value' => 'none',
4356
        );
4357
      }
4358
4359
      $form['#title'] = t('Configure @type: @item', array('@type' => $types[$type]['lstitle'], '@item' => $handler->ui_name()));
4360
4361
      if (!empty($handler->definition['help'])) {
4362
        $form['options']['form_description'] = array(
4363
          '#markup' => $handler->definition['help'],
4364
          '#theme_wrappers' => array('container'),
4365
          '#attributes' => array('class' => array('form-item description')),
4366
          '#weight' => -1000,
4367
        );
4368
      }
4369
4370
      $form['#section'] = $display_id . '-' . $type . '-' . $id;
4371
4372
      // Get form from the handler.
4373
      $handler->options_form($form['options'], $form_state);
4374
      $form_state['handler'] = &$handler;
4375
    }
4376
4377
    $name = NULL;
4378
    if (isset($form_state['update_name'])) {
4379
      $name = $form_state['update_name'];
4380
    }
4381
4382
    views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_item_form', $name, t('Remove'), 'remove');
4383
    // Only validate the override values, because this values are required for
4384
    // the override selection.
4385
    $form['buttons']['remove']['#limit_validation_errors'] = array(array('override'));
4386
  }
4387
4388
  return $form;
4389
}
4390
4391
/**
4392
 * Submit handler for configing new item(s) to a view.
4393
 */
4394
function views_ui_config_item_form_validate($form, &$form_state) {
4395
  $form_state['handler']->options_validate($form['options'], $form_state);
4396
4397
  if (form_get_errors()) {
4398
    $form_state['rerender'] = TRUE;
4399
  }
4400
}
4401
4402
/**
4403
 * A submit handler that is used for storing temporary items when using
4404
 * multi-step changes, such as ajax requests.
4405
 */
4406
function views_ui_config_item_form_submit_temporary($form, &$form_state) {
4407
  // Run it through the handler's submit function.
4408
  $form_state['handler']->options_submit($form['options'], $form_state);
4409
  $item = $form_state['handler']->options;
4410
  $types = views_object_types();
4411
4412
  // For footer/header $handler_type is area but $type is footer/header.
4413
  // For all other handle types it's the same.
4414
  $handler_type = $type = $form_state['type'];
4415
  if (!empty($types[$type]['type'])) {
4416
    $handler_type = $types[$type]['type'];
4417
  }
4418
4419
  $override = NULL;
4420
  if ($form_state['view']->display_handler->use_group_by() && !empty($item['group_type'])) {
4421
    if (empty($form_state['view']->query)) {
4422
      $form_state['view']->init_query();
4423
    }
4424
    $aggregate = $form_state['view']->query->get_aggregation_info();
4425
    if (!empty($aggregate[$item['group_type']]['handler'][$type])) {
4426
      $override = $aggregate[$item['group_type']]['handler'][$type];
4427
    }
4428
  }
4429
4430
  // Create a new handler and unpack the options from the form onto it. We
4431
  // can use that for storage.
4432
  $handler = views_get_handler($item['table'], $item['field'], $handler_type, $override);
4433
  $handler->init($form_state['view'], $item);
4434
4435
4436
  // Add the incoming options to existing options because items using
4437
  // the extra form may not have everything in the form here.
4438
  $options = $form_state['values']['options'] + $form_state['handler']->options;
4439
4440
  // This unpacks only options that are in the definition, ensuring random
4441
  // extra stuff on the form is not sent through.
4442
  $handler->unpack_options($handler->options, $options, NULL, FALSE);
4443
4444
  // Store the item back on the view
4445
  $form_state['view']->temporary_options[$type][$form_state['id']] = $handler->options;
4446
4447
  // @todo: Figure out whether views_ui_ajax_form is perhaps the better place to fix the issue.
4448
  // views_ui_ajax_form() drops the current form from the stack, even if it's an #ajax.
4449
  // So add the item back to the top of the stack.
4450
  views_ui_add_form_to_stack($form_state['form_key'], $form_state['view'], $form_state['display_id'], array($type, $item['id']), TRUE);
4451
4452
  $form_state['rerender'] = TRUE;
4453
  $form_state['rebuild'] = TRUE;
4454
  // Write to cache
4455
  views_ui_cache_set($form_state['view']);
4456
}
4457
4458
/**
4459
 * Submit handler for configing new item(s) to a view.
4460
 */
4461
function views_ui_config_item_form_submit($form, &$form_state) {
4462
  // Run it through the handler's submit function.
4463
  $form_state['handler']->options_submit($form['options'], $form_state);
4464
  $item = $form_state['handler']->options;
4465
  $types = views_object_types();
4466
4467
  // For footer/header $handler_type is area but $type is footer/header.
4468
  // For all other handle types it's the same.
4469
  $handler_type = $type = $form_state['type'];
4470
  if (!empty($types[$type]['type'])) {
4471
    $handler_type = $types[$type]['type'];
4472
  }
4473
4474
  $override = NULL;
4475
  if ($form_state['view']->display_handler->use_group_by() && !empty($item['group_type'])) {
4476
    if (empty($form_state['view']->query)) {
4477
      $form_state['view']->init_query();
4478
    }
4479
    $aggregate = $form_state['view']->query->get_aggregation_info();
4480
    if (!empty($aggregate[$item['group_type']]['handler'][$type])) {
4481
      $override = $aggregate[$item['group_type']]['handler'][$type];
4482
    }
4483
  }
4484
4485
  // Create a new handler and unpack the options from the form onto it. We
4486
  // can use that for storage.
4487
  $handler = views_get_handler($item['table'], $item['field'], $handler_type, $override);
4488
  $handler->init($form_state['view'], $item);
4489
4490
4491
  // Add the incoming options to existing options because items using
4492
  // the extra form may not have everything in the form here.
4493
  $options = $form_state['values']['options'] + $form_state['handler']->options;
4494
4495
  // This unpacks only options that are in the definition, ensuring random
4496
  // extra stuff on the form is not sent through.
4497
  $handler->unpack_options($handler->options, $options, NULL, FALSE);
4498
4499
  // Store the item back on the view
4500
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $handler->options);
4501
4502
  // Ensure any temporary options are removed.
4503
  if (isset($form_state['view']->temporary_options[$type][$form_state['id']])) {
4504
    unset($form_state['view']->temporary_options[$type][$form_state['id']]);
4505
  }
4506
4507
  // Write to cache
4508
  views_ui_cache_set($form_state['view']);
4509
}
4510
4511
/**
4512
 * Form to config_item items in the views UI.
4513
 */
4514
function views_ui_config_item_group_form($type, &$form_state) {
4515
  $view = &$form_state['view'];
4516
  $display_id = $form_state['display_id'];
4517
  $type = $form_state['type'];
4518
  $id = $form_state['id'];
4519
4520
  $form = array(
4521
    'options' => array(
4522
      '#tree' => TRUE,
4523
      '#theme_wrappers' => array('container'),
4524
      '#attributes' => array('class' => array('scroll')),
4525
    ),
4526
  );
4527
  if (!$view->set_display($display_id)) {
4528
    views_ajax_render(t('Invalid display id @display', array('@display' => $display_id)));
4529
  }
4530
4531
  $view->init_query();
4532
4533
  $item = $view->get_item($display_id, $type, $id);
4534
4535
  if ($item) {
4536
    $handler = $view->display_handler->get_handler($type, $id);
4537
    if (empty($handler)) {
4538
      $form['markup'] = array('#markup' => t("Error: handler for @table > @field doesn't exist!", array('@table' => $item['table'], '@field' => $item['field'])));
4539
    }
4540
    else {
4541
      $handler->init($view, $item);
4542
      $types = views_object_types();
4543
4544
      $form['#title'] = t('Configure group settings for @type %item', array('@type' => $types[$type]['lstitle'], '%item' => $handler->ui_name()));
4545
4546
      $handler->groupby_form($form['options'], $form_state);
4547
      $form_state['handler'] = &$handler;
4548
    }
4549
4550
    views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_item_group_form');
4551
  }
4552
  return $form;
4553
}
4554
4555
/**
4556
 * Submit handler for configing group settings on a view.
4557
 */
4558
function views_ui_config_item_group_form_submit($form, &$form_state) {
4559
  $item =& $form_state['handler']->options;
4560
  $type = $form_state['type'];
4561
  $id = $form_state['id'];
4562
4563
  $handler = views_get_handler($item['table'], $item['field'], $type);
4564
  $handler->init($form_state['view'], $item);
4565
4566
  $handler->groupby_form_submit($form, $form_state);
4567
4568
  // Store the item back on the view
4569
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
4570
4571
  // Write to cache
4572
  views_ui_cache_set($form_state['view']);
4573
}
4574
4575
/**
4576
 * Submit handler for removing an item from a view
4577
 */
4578
function views_ui_config_item_form_remove($form, &$form_state) {
4579
  // Store the item back on the view
4580
  list($was_defaulted, $is_defaulted) = views_ui_standard_override_values($form, $form_state);
4581
  // If the display selection was changed toggle the override value.
4582
  if ($was_defaulted != $is_defaulted) {
4583
    $display =& $form_state['view']->display[$form_state['display_id']];
4584
    $display->handler->options_override($form, $form_state);
4585
  }
4586
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], NULL);
4587
4588
  // Write to cache
4589
  views_ui_cache_set($form_state['view']);
4590
}
4591
4592
/**
4593
 * Override handler for views_ui_edit_display_form
4594
 */
4595
function views_ui_config_item_form_expose($form, &$form_state) {
4596
  $item = &$form_state['handler']->options;
4597
  // flip
4598
  $item['exposed'] = empty($item['exposed']);
4599
4600
  // If necessary, set new defaults:
4601
  if ($item['exposed']) {
4602
    $form_state['handler']->expose_options();
4603
  }
4604
4605
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
4606
4607
  views_ui_add_form_to_stack($form_state['form_key'], $form_state['view'], $form_state['display_id'], array($form_state['type'], $form_state['id']), TRUE, TRUE);
4608
4609
  views_ui_cache_set($form_state['view']);
4610
  $form_state['rerender'] = TRUE;
4611
  $form_state['rebuild'] = TRUE;
4612
  $form_state['force_expose_options'] = TRUE;
4613
}
4614
4615
/**
4616
 * Form to config_item items in the views UI.
4617
 */
4618
function views_ui_config_item_extra_form($form, &$form_state) {
4619
  $view = &$form_state['view'];
4620
  $display_id = $form_state['display_id'];
4621
  $type = $form_state['type'];
4622
  $id = $form_state['id'];
4623
4624
  $form = array(
4625
    'options' => array(
4626
      '#tree' => TRUE,
4627
      '#theme_wrappers' => array('container'),
4628
      '#attributes' => array('class' => array('scroll')),
4629
    ),
4630
  );
4631
  if (!$view->set_display($display_id)) {
4632
    views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
4633
  }
4634
  $item = $view->get_item($display_id, $type, $id);
4635
4636
  if ($item) {
4637
    $handler = $view->display_handler->get_handler($type, $id);
4638
    if (empty($handler)) {
4639
      $form['markup'] = array('#markup' => t("Error: handler for @table > @field doesn't exist!", array('@table' => $item['table'], '@field' => $item['field'])));
4640
    }
4641
    else {
4642
      $handler->init($view, $item);
4643
      $types = views_object_types();
4644
4645
      $form['#title'] = t('Configure extra settings for @type %item', array('@type' => $types[$type]['lstitle'], '%item' => $handler->ui_name()));
4646
4647
      $form['#section'] = $display_id . '-' . $type . '-' . $id;
4648
4649
      // Get form from the handler.
4650
      $handler->extra_options_form($form['options'], $form_state);
4651
      $form_state['handler'] = &$handler;
4652
    }
4653
4654
    views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_item_extra_form');
4655
  }
4656
  return $form;
4657
}
4658
4659
/**
4660
 * Validation handler for configing new item(s) to a view.
4661
 */
4662
function views_ui_config_item_extra_form_validate($form, &$form_state) {
4663
  $form_state['handler']->extra_options_validate($form['options'], $form_state);
4664
}
4665
4666
/**
4667
 * Submit handler for configing new item(s) to a view.
4668
 */
4669
function views_ui_config_item_extra_form_submit($form, &$form_state) {
4670
  // Run it through the handler's submit function.
4671
  $form_state['handler']->extra_options_submit($form['options'], $form_state);
4672
  $item = $form_state['handler']->options;
4673
4674
  // Store the data we're given.
4675
  foreach ($form_state['values']['options'] as $key => $value) {
4676
    $item[$key] = $value;
4677
  }
4678
4679
  // Store the item back on the view
4680
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
4681
4682
  // Write to cache
4683
  views_ui_cache_set($form_state['view']);
4684
}
4685
4686
/**
4687
 * Form to config_style items in the views UI.
4688
 */
4689
function views_ui_config_style_form($form, &$form_state) {
4690
  $view = &$form_state['view'];
4691
  $display_id = $form_state['display_id'];
4692
  $type = $form_state['type'];
4693
  $id = $form_state['id'];
4694
4695
  $form = array(
4696
    'options' => array(
4697
      '#tree' => TRUE,
4698
      '#theme_wrappers' => array('container'),
4699
      '#attributes' => array('class' => array('scroll')),
4700
    ),
4701
  );
4702
  if (!$view->set_display($display_id)) {
4703
    views_ajax_error(t('Invalid display id @display', array('@display' => $display_id)));
4704
  }
4705
  $item = $view->get_item($display_id, $type, $id);
4706
4707
  if ($item) {
4708
    $handler = views_get_handler($item['table'], $item['field'], $type);
4709
    if (empty($handler)) {
4710
      $form['markup'] = array('#markup' => t("Error: handler for @table > @field doesn't exist!", array('@table' => $item['table'], '@field' => $item['field'])));
4711
    }
4712
    else {
4713
      $handler->init($view, $item);
4714
      $types = views_object_types();
4715
4716
      $form['#title'] = t('Configure summary style for @type %item', array('@type' => $types[$type]['lstitle'], '%item' => $handler->ui_name()));
4717
4718
      $form['#section'] = $display_id . '-' . $type . '-style-options';
4719
4720
      $plugin = views_get_plugin('style', $handler->options['style_plugin']);
4721
      if ($plugin) {
4722
        $form['style_options'] = array(
4723
          '#tree' => TRUE,
4724
        );
4725
        $plugin->init($view, $view->display[$display_id], $handler->options['style_options']);
4726
4727
        $plugin->options_form($form['style_options'], $form_state);
4728
      }
4729
4730
      $form_state['handler'] = &$handler;
4731
    }
4732
4733
    views_ui_standard_form_buttons($form, $form_state, 'views_ui_config_style_form');
4734
  }
4735
  return $form;
4736
}
4737
4738
/**
4739
 * Submit handler for configing new item(s) to a view.
4740
 */
4741
function views_ui_config_style_form_submit($form, &$form_state) {
4742
  // Run it through the handler's submit function.
4743
  $form_state['handler']->options_submit($form['style_options'], $form_state);
4744
  $item = $form_state['handler']->options;
4745
4746
  // Store the data we're given.
4747
  $item['style_options'] = $form_state['values']['style_options'];
4748
4749
  // Store the item back on the view
4750
  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
4751
4752
  // Write to cache
4753
  views_ui_cache_set($form_state['view']);
4754
}
4755
4756
/**
4757
 * Get a list of roles in the system.
4758
 */
4759
function views_ui_get_roles() {
4760
  static $roles = NULL;
4761
  if (!isset($roles)) {
4762
    $roles = array();
4763
    $result = db_query("SELECT r.rid, r.name FROM {role} r ORDER BY r.name");
4764
    foreach ($result as $obj) {
4765
      $roles[$obj->rid] = $obj->name;
4766
    }
4767
  }
4768
4769
  return $roles;
4770
}
4771
4772
/**
4773
 * Form builder for the admin display defaults page.
4774
 */
4775
function views_ui_admin_settings_basic() {
4776
  $form = array();
4777
  $form['#attached']['css'] = views_ui_get_admin_css();
4778
4779
  $options = array();
4780
  foreach (list_themes() as $name => $theme) {
4781
    if ($theme->status) {
4782
      $options[$name] = $theme->info['name'];
4783
    }
4784
  }
4785
4786
  // This is not currently a fieldset but we may want it to be later,
4787
  // so this will make it easier to change if we do.
4788
  $form['basic'] = array();
4789
4790
  $form['basic']['views_ui_show_listing_filters'] = array(
4791
    '#type' => 'checkbox',
4792
    '#title' => t('Show filters on the list of views'),
4793
    '#default_value' => variable_get('views_ui_show_listing_filters', FALSE),
4794
  );
4795
  $form['basic']['views_ui_show_advanced_help_warning'] = array(
4796
    '#type' => 'checkbox',
4797
    '#title' => t('Show advanced help warning'),
4798
    '#default_value' => variable_get('views_ui_show_advanced_help_warning', TRUE),
4799
  );
4800
4801
  $form['basic']['views_ui_show_master_display'] = array(
4802
    '#type' => 'checkbox',
4803
    '#title' => t('Always show the master display'),
4804
    '#description' => t('Advanced users of views may choose to see the master (i.e. default) display.'),
4805
    '#default_value' => variable_get('views_ui_show_master_display', FALSE),
4806
  );
4807
4808
  $form['basic']['views_ui_show_advanced_column'] = array(
4809
    '#type' => 'checkbox',
4810
    '#title' => t('Always show advanced display settings'),
4811
    '#description' => t('Default to showing advanced display settings, such as relationships and contextual filters.'),
4812
    '#default_value' => variable_get('views_ui_show_advanced_column', FALSE),
4813
  );
4814
4815
  $form['basic']['views_ui_display_embed'] = array(
4816
    '#type' => 'checkbox',
4817
    '#title' => t('Show the embed display in the ui.'),
4818
    '#description' => t('Allow advanced user to use the embed view display. The plugin itself works if it\'s not visible in the ui'),
4819
    '#default_value' => variable_get('views_ui_display_embed', FALSE),
4820
  );
4821
4822
  $form['basic']['views_ui_custom_theme'] = array(
4823
    '#type' => 'select',
4824
    '#title' => t('Custom admin theme for the Views UI'),
4825
    '#options' => array('_default' => t('- Use default -')) + $options,
4826
    '#default_value' => variable_get('views_ui_custom_theme', '_default'),
4827
    '#description' => t('In some cases you might want to select a different admin theme for the Views UI.')
4828
  );
4829
4830
  $form['basic']['views_exposed_filter_any_label'] = array(
4831
    '#type' => 'select',
4832
    '#title' => t('Label for "Any" value on non-required single-select exposed filters'),
4833
    '#options' => array('old_any' => '<Any>', 'new_any' => t('- Any -')),
4834
    '#default_value' => variable_get('views_exposed_filter_any_label', 'new_any'),
4835
  );
4836
4837
  $form['live_preview'] = array(
4838
    '#type' => 'fieldset',
4839
    '#title' => t('Live preview settings'),
4840
  );
4841
4842
  $form['live_preview']['views_ui_always_live_preview'] = array(
4843
    '#type' => 'checkbox',
4844
    '#title' => t('Automatically update preview on changes'),
4845
    '#default_value' => variable_get('views_ui_always_live_preview', TRUE),
4846
  );
4847
4848
//  $form['live_preview']['views_ui_always_live_preview_button'] = array(
4849
//    '#type' => 'checkbox',
4850
//    '#title' => t('Always show the preview button, even when the automatically update option is checked'),
4851
//    '#default_value' => variable_get('views_ui_always_live_preview_button', FALSE),
4852
//  );
4853
4854
  $form['live_preview']['views_ui_show_preview_information'] = array(
4855
    '#type' => 'checkbox',
4856
    '#title' => t('Show information and statistics about the view during live preview'),
4857
    '#default_value' => variable_get('views_ui_show_preview_information', TRUE),
4858
  );
4859
4860
  $form['live_preview']['views_ui_show_sql_query_where'] = array(
4861
    '#type' => 'radios',
4862
    '#options' => array(
4863
      'above' => t('Above the preview'),
4864
      'below' => t('Below the preview'),
4865
    ),
4866
    '#id' => 'edit-show-sql',
4867
    '#default_value' => variable_get('views_ui_show_sql_query_where', 'above'),
4868
    '#dependency' => array('edit-views-ui-show-preview-information' => array(TRUE)),
4869
    '#prefix' => '<div id="edit-show-sql-wrapper" class="views-dependent">',
4870
    '#suffix' => '</div>',
4871
  );
4872
4873
  $form['live_preview']['views_ui_show_sql_query'] = array(
4874
    '#type' => 'checkbox',
4875
    '#title' => t('Show the SQL query'),
4876
    '#default_value' => variable_get('views_ui_show_sql_query', FALSE),
4877
    '#dependency' => array('edit-views-ui-show-preview-information' => array(TRUE)),
4878
  );
4879
  $form['live_preview']['views_ui_show_performance_statistics'] = array(
4880
    '#type' => 'checkbox',
4881
    '#title' => t('Show performance statistics'),
4882
    '#default_value' => variable_get('views_ui_show_performance_statistics', FALSE),
4883
    '#dependency' => array('edit-views-ui-show-preview-information' => array(TRUE)),
4884
  );
4885
4886
  $form['live_preview']['views_show_additional_queries'] = array(
4887
    '#type' => 'checkbox',
4888
    '#title' => t('Show other queries run during render during live preview'),
4889
    '#description' => t("Drupal has the potential to run many queries while a view is being rendered. Checking this box will display every query run during view render as part of the live preview."),
4890
    '#default_value' => variable_get('views_show_additional_queries', FALSE),
4891
    '#dependency' => array('edit-views-ui-show-preview-information' => array(TRUE)),
4892
  );
4893
4894
//  $form['live_preview']['views_ui_show_performance_statistics_where'] = array(
4895
4896
  return system_settings_form($form);
4897
}
4898
4899
/**
4900
 * Form builder for the advanced admin settings page.
4901
 */
4902
function views_ui_admin_settings_advanced() {
4903
  $form = array();
4904
  $form['#attached']['css'] = views_ui_get_admin_css();
4905
4906
  $form['cache'] = array(
4907
    '#type' => 'fieldset',
4908
    '#title' => t('Caching'),
4909
  );
4910
4911
  $form['cache']['views_skip_cache'] = array(
4912
    '#type' => 'checkbox',
4913
    '#title' => t('Disable views data caching'),
4914
    '#description' => t("Views caches data about tables, modules and views available, to increase performance. By checking this box, Views will skip this cache and always rebuild this data when needed. This can have a serious performance impact on your site."),
4915
    '#default_value' => variable_get('views_skip_cache', FALSE),
4916
  );
4917
4918
  $form['cache']['clear_cache'] = array(
4919
    '#type' => 'submit',
4920
    '#value' => t("Clear Views' cache"),
4921
    '#submit' => array('views_ui_tools_clear_cache'),
4922
  );
4923
4924
  $form['debug'] = array(
4925
    '#type' => 'fieldset',
4926
    '#title' => t('Debugging'),
4927
  );
4928
4929
  $form['debug']['views_sql_signature'] = array(
4930
    '#type' => 'checkbox',
4931
    '#title' => t('Add Views signature to all SQL queries'),
4932
    '#description' => t("All Views-generated queries will include the name of the views and display 'view-name:display-name' as a string  at the end of the SELECT clause. This makes identifying Views queries in database server logs simpler, but should only be used when troubleshooting."),
4933
4934
    '#default_value' => variable_get('views_sql_signature', FALSE),
4935
  );
4936
4937
  $form['debug']['views_no_javascript'] = array(
4938
    '#type' => 'checkbox',
4939
    '#title' => t('Disable JavaScript with Views'),
4940
    '#description' => t("If you are having problems with the JavaScript, you can disable it here. The Views UI should degrade and still be usable without javascript; it's just not as good."),
4941
    '#default_value' => variable_get('views_no_javascript', FALSE),
4942
  );
4943
4944
  $form['debug']['views_devel_output'] = array(
4945
    '#type' => 'checkbox',
4946
    '#title' => t('Enable views performance statistics/debug messages via the Devel module'),
4947
    '#description' => t("Check this to enable some Views query and performance statistics/debug messages <em>if Devel is installed</em>."),
4948
    '#default_value' => variable_get('views_devel_output', FALSE),
4949
  );
4950
4951
  $form['locale'] = array(
4952
    '#type' => 'fieldset',
4953
    '#title' => t('Localization'),
4954
  );
4955
4956
  $form['locale']['views_localization_plugin'] =  array(
4957
    '#type' => 'radios',
4958
    '#title' => t('Translation method'),
4959
    '#options' => views_fetch_plugin_names('localization', NULL, array()),
4960
    '#default_value' => views_get_localization_plugin(),
4961
    '#description' => t('Select a translation method to use for Views data like header, footer, and empty text.'),
4962
  );
4963
4964
  $regions = array();
4965
  $regions['watchdog'] = t('Watchdog');
4966
  if (module_exists('devel')) {
4967
    $regions['message'] = t('Devel message(dpm)');
4968
    $regions['drupal_debug'] = t('Devel logging (tmp://drupal_debug.txt)');
4969
  }
4970
4971
  $form['debug']['views_devel_region'] = array(
4972
    '#type' => 'select',
4973
    '#title' => t('Page region to output performance statistics/debug messages'),
4974
    '#default_value' => variable_get('views_devel_region', 'footer'),
4975
    '#options' => $regions,
4976
    '#dependency' => array('edit-views-devel-output' => array(1)),
4977
  );
4978
4979
  $options = views_fetch_plugin_names('display_extender');
4980
  if (!empty($options)) {
4981
    $form['extenders'] = array(
4982
      '#type' => 'fieldset',
4983
    );
4984
    ;
4985
    $form['extenders']['views_display_extenders'] = array(
4986
      '#title' => t('Display extenders'),
4987
      '#default_value' => views_get_enabled_display_extenders(),
4988
      '#options' => $options,
4989
      '#type' => 'checkboxes',
4990
      '#description' => t('Select extensions of the views interface.')
4991
    );
4992
  }
4993
4994
  return system_settings_form($form);
4995
}
4996
4997
/**
4998
 * Submit hook to clear the views cache.
4999
 */
5000
function views_ui_tools_clear_cache() {
5001
  views_invalidate_cache();
5002
  drupal_set_message(t('The cache has been cleared.'));
5003
}
5004
5005
/**
5006
 * Submit hook to clear Drupal's theme registry (thereby triggering
5007
 * a templates rescan).
5008
 */
5009
function views_ui_config_item_form_rescan($form, &$form_state) {
5010
  drupal_theme_rebuild();
5011
5012
  // The 'Theme: Information' page is about to be shown again. That page
5013
  // analyzes the output of theme_get_registry(). However, this latter
5014
  // function uses an internal cache (which was initialized before we
5015
  // called drupal_theme_rebuild()) so it won't reflect the
5016
  // current state of our theme registry. The only way to clear that cache
5017
  // is to re-initialize the theme system:
5018
  unset($GLOBALS['theme']);
5019
  drupal_theme_initialize();
5020
5021
  $form_state['rerender'] = TRUE;
5022
  $form_state['rebuild'] = TRUE;
5023
}
5024
5025
/**
5026
 * Override handler for views_ui_edit_display_form
5027
 */
5028
function views_ui_edit_display_form_change_theme($form, &$form_state) {
5029
  // This is just a temporary variable.
5030
  $form_state['view']->theme = $form_state['values']['theme'];
5031
5032
  views_ui_cache_set($form_state['view']);
5033
  $form_state['rerender'] = TRUE;
5034
  $form_state['rebuild'] = TRUE;
5035
}
5036
5037
/**
5038
 * Page callback for views tag autocomplete
5039
 */
5040
function views_ui_autocomplete_tag($string = '') {
5041
  $matches = array();
5042
  // get matches from default views:
5043
  views_include('view');
5044
  $views = views_get_all_views();
5045
  foreach ($views as $view) {
5046
    if (!empty($view->tag) && strpos($view->tag, $string) === 0) {
5047
      $matches[$view->tag] = check_plain($view->tag);
5048
      if (count($matches) >= 10) {
5049
        break;
5050
      }
5051
    }
5052
  }
5053
5054
  drupal_json_output($matches);
5055
}
5056
5057
// ------------------------------------------------------------------
5058
// Get information from the Views data
5059
5060
function _views_weight_sort($a, $b) {
5061
  if ($a['weight'] != $b['weight']) {
5062
    return $a['weight'] < $b['weight'] ? -1 : 1;
5063
  }
5064
  if ($a['title'] != $b['title']) {
5065
    return $a['title'] < $b['title'] ? -1 : 1;
5066
  }
5067
5068
  return 0;
5069
}
5070
5071
/**
5072
 * Fetch a list of all base tables available
5073
 *
5074
 * @return
5075
 *   A keyed array of in the form of 'base_table' => 'Description'.
5076
 */
5077
function views_fetch_base_tables() {
5078
  static $base_tables = array();
5079
  if (empty($base_tables)) {
5080
    $weights = array();
5081
    $tables = array();
5082
    $data = views_fetch_data();
5083
    foreach ($data as $table => $info) {
5084
      if (!empty($info['table']['base'])) {
5085
        $tables[$table] = array(
5086
          'title' => $info['table']['base']['title'],
5087
          'description' => !empty($info['table']['base']['help']) ? $info['table']['base']['help'] : '',
5088
          'weight' => !empty($info['table']['base']['weight']) ? $info['table']['base']['weight'] : 0,
5089
        );
5090
      }
5091
    }
5092
    uasort($tables, '_views_weight_sort');
5093
    $base_tables = $tables;
5094
  }
5095
5096
  return $base_tables;
5097
}
5098
5099
function _views_sort_types($a, $b) {
5100
  $a_group = drupal_strtolower($a['group']);
5101
  $b_group = drupal_strtolower($b['group']);
5102
  if ($a_group != $b_group) {
5103
    return $a_group < $b_group ? -1 : 1;
5104
  }
5105
5106
  $a_title = drupal_strtolower($a['title']);
5107
  $b_title = drupal_strtolower($b['title']);
5108
  if ($a_title != $b_title) {
5109
    return $a_title < $b_title ? -1 : 1;
5110
  }
5111
5112
  return 0;
5113
}
5114
5115
/**
5116
 * Fetch a list of all fields available for a given base type.
5117
 *
5118
 * @param (array|string) $base
5119
 *   A list or a single base_table, for example node.
5120
 * @param string $type
5121
 *   The handler type, for example field or filter.
5122
 * @param bool $grouping
5123
 *   Should the result grouping by its 'group' label.
5124
 *
5125
 * @return array
5126
 *   A keyed array of in the form of 'base_table' => 'Description'.
5127
 */
5128
function views_fetch_fields($base, $type, $grouping = FALSE) {
5129
  static $fields = array();
5130
  if (empty($fields)) {
5131
    $data = views_fetch_data();
5132
    $start = microtime(TRUE);
5133
    // This constructs this ginormous multi dimensional array to
5134
    // collect the important data about fields. In the end,
5135
    // the structure looks a bit like this (using nid as an example)
5136
    // $strings['nid']['filter']['title'] = 'string'.
5137
    //
5138
    // This is constructed this way because the above referenced strings
5139
    // can appear in different places in the actual data structure so that
5140
    // the data doesn't have to be repeated a lot. This essentially lets
5141
    // each field have a cheap kind of inheritance.
5142
5143
    foreach ($data as $table => $table_data) {
5144
      $bases = array();
5145
      $strings = array();
5146
      $skip_bases = array();
5147
      foreach ($table_data as $field => $info) {
5148
        // Collect table data from this table
5149
        if ($field == 'table') {
5150
          // calculate what tables this table can join to.
5151
          if (!empty($info['join'])) {
5152
            $bases = array_keys($info['join']);
5153
          }
5154
          // And it obviously joins to itself.
5155
          $bases[] = $table;
5156
          continue;
5157
        }
5158
        foreach (array('field', 'sort', 'filter', 'argument', 'relationship', 'area') as $key) {
5159
          if (!empty($info[$key])) {
5160
            if ($grouping && !empty($info[$key]['no group by'])) {
5161
              continue;
5162
            }
5163
            if (!empty($info[$key]['skip base'])) {
5164
              foreach ((array) $info[$key]['skip base'] as $base_name) {
5165
                $skip_bases[$field][$key][$base_name] = TRUE;
5166
              }
5167
            }
5168
            elseif (!empty($info['skip base'])) {
5169
              foreach ((array) $info['skip base'] as $base_name) {
5170
                $skip_bases[$field][$key][$base_name] = TRUE;
5171
              }
5172
            }
5173
            // Don't show old fields. The real field will be added right.
5174
            if (isset($info[$key]['moved to'])) {
5175
              continue;
5176
            }
5177
            foreach (array('title', 'group', 'help', 'base', 'aliases') as $string) {
5178
              // First, try the lowest possible level
5179
              if (!empty($info[$key][$string])) {
5180
                $strings[$field][$key][$string] = $info[$key][$string];
5181
              }
5182
              // Then try the field level
5183
              elseif (!empty($info[$string])) {
5184
                $strings[$field][$key][$string] = $info[$string];
5185
              }
5186
              // Finally, try the table level
5187
              elseif (!empty($table_data['table'][$string])) {
5188
                $strings[$field][$key][$string] = $table_data['table'][$string];
5189
              }
5190
              else {
5191
                if ($string != 'base' && $string != 'base') {
5192
                  $strings[$field][$key][$string] = t("Error: missing @component", array('@component' => $string));
5193
                }
5194
              }
5195
            }
5196
          }
5197
        }
5198
      }
5199
      foreach ($bases as $base_name) {
5200
        foreach ($strings as $field => $field_strings) {
5201
          foreach ($field_strings as $type_name => $type_strings) {
5202
            if (empty($skip_bases[$field][$type_name][$base_name])) {
5203
              $fields[$base_name][$type_name]["$table.$field"] = $type_strings;
5204
            }
5205
          }
5206
        }
5207
      }
5208
    }
5209
//    vsm('Views UI data build time: ' . (views_microtime() - $start) * 1000 . ' ms');
5210
  }
5211
5212
  // If we have an array of base tables available, go through them
5213
  // all and add them together. Duplicate keys will be lost and that's
5214
  // Just Fine.
5215
  if (is_array($base)) {
5216
    $strings = array();
5217
    foreach ($base as $base_table) {
5218
      if (isset($fields[$base_table][$type])) {
5219
        $strings += $fields[$base_table][$type];
5220
      }
5221
    }
5222
    uasort($strings, '_views_sort_types');
5223
    return $strings;
5224
  }
5225
5226
  if (isset($fields[$base][$type])) {
5227
    uasort($fields[$base][$type], '_views_sort_types');
5228
    return $fields[$base][$type];
5229
  }
5230
  return array();
5231
}
5232
5233
5234
/**
5235
 * Theme the form for the table style plugin
5236
 */
5237
function theme_views_ui_style_plugin_table($variables) {
5238
  $form = $variables['form'];
5239
5240
  $output = drupal_render($form['description_markup']);
5241
5242
  $header = array(
5243
    t('Field'),
5244
    t('Column'),
5245
    t('Align'),
5246
    t('Separator'),
5247
    array(
5248
      'data' => t('Sortable'),
5249
      'align' => 'center',
5250
    ),
5251
    array(
5252
      'data' => t('Default order'),
5253
      'align' => 'center',
5254
    ),
5255
    array(
5256
      'data' => t('Default sort'),
5257
      'align' => 'center',
5258
    ),
5259
    array(
5260
      'data' => t('Hide empty column'),
5261
      'align' => 'center',
5262
    ),
5263
  );
5264
  $rows = array();
5265
  foreach (element_children($form['columns']) as $id) {
5266
    $row = array();
5267
    $row[] = check_plain(drupal_render($form['info'][$id]['name']));
5268
    $row[] = drupal_render($form['columns'][$id]);
5269
    $row[] = drupal_render($form['info'][$id]['align']);
5270
    $row[] = drupal_render($form['info'][$id]['separator']);
5271
    if (!empty($form['info'][$id]['sortable'])) {
5272
      $row[] = array(
5273
        'data' => drupal_render($form['info'][$id]['sortable']),
5274
        'align' => 'center',
5275
      );
5276
      $row[] = array(
5277
        'data' => drupal_render($form['info'][$id]['default_sort_order']),
5278
        'align' => 'center',
5279
      );
5280
      $row[] = array(
5281
        'data' => drupal_render($form['default'][$id]),
5282
        'align' => 'center',
5283
      );
5284
    }
5285
    else {
5286
      $row[] = '';
5287
      $row[] = '';
5288
      $row[] = '';
5289
    }
5290
    $row[] = array(
5291
      'data' => drupal_render($form['info'][$id]['empty_column']),
5292
      'align' => 'center',
5293
    );
5294
    $rows[] = $row;
5295
  }
5296
5297
  // Add the special 'None' row.
5298
  $rows[] = array(t('None'), '', '', '', '', '', array('align' => 'center', 'data' => drupal_render($form['default'][-1])), '');
5299
5300
  $output .= theme('table', array('header' => $header, 'rows' => $rows));
5301
  $output .= drupal_render_children($form);
5302
  return $output;
5303
}
5304
5305
/**
5306
 * Placeholder function for overriding $display->display_title.
5307
 *
5308
 * @todo Remove this function once editing the display title is possible.
5309
 */
5310
function views_ui_get_display_label($view, $display_id, $check_changed = TRUE) {
5311
  $title = $display_id == 'default' ? t('Master') : $view->display[$display_id]->display_title;
5312
  $title = views_ui_truncate($title, 25);
5313
5314
  if ($check_changed && !empty($view->changed_display[$display_id])) {
5315
    $changed = '*';
5316
    $title = $title . $changed;
5317
  }
5318
5319
  return $title;
5320
}
5321
5322
function views_ui_add_template_page() {
5323
  $templates = views_get_all_templates();
5324
5325
  if (empty($templates)) {
5326
    return t('There are no templates available.');
5327
  }
5328
5329
  $header = array(
5330
    t('Name'),
5331
    t('Description'),
5332
    t('Operation'),
5333
  );
5334
5335
  $rows = array();
5336
  foreach ($templates as $name => $template) {
5337
    $rows[] = array(
5338
      array('data' => check_plain($template->get_human_name())),
5339
      array('data' => check_plain($template->description)),
5340
      array('data' => l('add', 'admin/structure/views/template/' . $template->name . '/add')),
5341
    );
5342
  }
5343
5344
  $output = theme('table', array('header' => $header, 'rows' => $rows));
5345
  return $output;
5346
}
5347
5348
/**
5349
 * #process callback for a button; determines if a button is the form's triggering element.
5350
 *
5351
 * The Form API has logic to determine the form's triggering element based on
5352
 * the data in $_POST. However, it only checks buttons based on a single #value
5353
 * per button. This function may be added to a button's #process callbacks to
5354
 * extend button click detection to support multiple #values per button. If the
5355
 * data in $_POST matches any value in the button's #values array, then the
5356
 * button is detected as having been clicked. This can be used when the value
5357
 * (label) of the same logical button may be different based on context (e.g.,
5358
 * "Apply" vs. "Apply and continue").
5359
 *
5360
 * @see _form_builder_handle_input_element()
5361
 * @see _form_button_was_clicked()
5362
 */
5363
function views_ui_form_button_was_clicked($element, &$form_state) {
5364
  $process_input = empty($element['#disabled']) && ($form_state['programmed'] || ($form_state['process_input'] && (!isset($element['#access']) || $element['#access'])));
5365
  if ($process_input && !isset($form_state['triggering_element']) && isset($element['#button_type']) && isset($form_state['input'][$element['#name']]) && isset($element['#values']) && in_array($form_state['input'][$element['#name']], $element['#values'], TRUE)) {
5366
    $form_state['triggering_element'] = $element;
5367
  }
5368
  return $element;
5369
}
5370
5371
/**
5372
 * #process callback for a button; makes implicit form submissions trigger as this button.
5373
 *
5374
 * @see Drupal.behaviors.viewsImplicitFormSubmission
5375
 */
5376
function views_ui_default_button($element, &$form_state, $form) {
5377
  $setting['viewsImplicitFormSubmission'][$form['#id']]['defaultButton'] = $element['#id'];
5378
  $element['#attached']['js'][] = array('type' => 'setting', 'data' => $setting);
5379
  return $element;
5380
}
5381
5382
/**
5383
 * List all instances of fields on any views.
5384
 *
5385
 * Therefore it builds up a table of each field which is used in any view.
5386
 *
5387
 * @see field_ui_fields_list()
5388
 */
5389
function views_ui_field_list() {
5390
  $views = views_get_all_views();
5391
5392
  // Fetch all fieldapi fields which are used in views
5393
  // Therefore search in all views, displays and handler-types.
5394
  $fields = array();
5395
  foreach ($views as $view) {
5396
    foreach ($view->display as $display_id => $display) {
5397
      if ($view->set_display($display_id)) {
5398
        foreach (views_object_types() as $type => $info) {
5399
          foreach ($view->get_items($type, $display_id) as $item) {
5400
            $data = views_fetch_data($item['table']);
5401
            if (isset($data[$item['field']]) && isset($data[$item['field']][$type])
5402
              && $data = $data[$item['field']][$type]) {
5403
              // The final check that we have a fieldapi field now.
5404
              if (isset($data['field_name'])) {
5405
                $fields[$data['field_name']][$view->name] = $view->name;
5406
              }
5407
            }
5408
          }
5409
        }
5410
      }
5411
    }
5412
  }
5413
5414
  $header = array(t('Field name'), t('Used in'));
5415
  $rows = array();
5416
  foreach ($fields as $field_name => $views) {
5417
5418
    $rows[$field_name]['data'][0] = check_plain($field_name);
5419
    foreach ($views as $view) {
5420
      $rows[$field_name]['data'][1][] = l($view, "admin/structure/views/view/$view");
5421
    }
5422
    $rows[$field_name]['data'][1] = implode(', ', $rows[$field_name]['data'][1]);
5423
  }
5424
5425
  // Sort rows by field name.
5426
  ksort($rows);
5427
  $output = array(
5428
    '#theme' => 'table',
5429
    '#header' => $header,
5430
    '#rows' => $rows,
5431
    '#empty' => t('No fields have been used in views yet.'),
5432
  );
5433
5434
  return $output;
5435
}
5436
5437
/**
5438
 * Lists all plugins and what enabled Views use them.
5439
 */
5440
function views_ui_plugin_list() {
5441
  $rows = views_plugin_list();
5442
  foreach ($rows as &$row) {
5443
    // Link each view name to the view itself.
5444
    foreach ($row['views'] as $row_name => $view) {
5445
      $row['views'][$row_name] = l($view, "admin/structure/views/view/$view");
5446
    }
5447
    $row['views'] = implode(', ', $row['views']);
5448
  }
5449
5450
  // Sort rows by field name.
5451
  ksort($rows);
5452
  return array(
5453
    '#theme' => 'table',
5454
    '#header' => array(t('Type'), t('Name'), t('Provided by'), t('Used in')),
5455
    '#rows' => $rows,
5456
    '#empty' => t('There are no enabled views.'),
5457
  );
5458
}